Index: src/uk/me/parabola/mkgmap/build/MapArea.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapArea.java	(revision 3742)
+++ src/uk/me/parabola/mkgmap/build/MapArea.java	(working copy)
@@ -23,7 +23,8 @@
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
 
 import uk.me.parabola.imgfmt.Utils;
-import uk.me.parabola.util.Java2DConverter;
+// import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.util.ShapeSplitter;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
@@ -658,10 +659,6 @@
 	/**
 	 * Spit the polygon into areas
 	 *
-	 * Using .intersect() here is expensive. The code should be changed to
-	 * use a simple rectangle clipping algorithm as in, say, 
-	 * util/ShapeSplitter.java
-	 *
 	 * @param areas The available areas to choose from.
 	 * @param e The map element.
 	 * @param used flag vector to say area has been added to.
@@ -668,6 +665,11 @@
 	 */
 	private void splitIntoAreas(MapArea[] areas, MapShape e, boolean[] used)
 	{
+		if (areas.length == 1) { // this happens quite a lot
+			used[0] = true;
+			areas[0].addShape(e);
+			return;
+		}
 		// quick check if bbox of shape lies fully inside one of the areas
 		Area shapeBounds = e.getBounds();
 
@@ -675,10 +677,19 @@
 		// tricky problems as it might not really be fully within the area.
 		// so: pretend the shape is a touch bigger. Will get the optimisation most of the time
 		// and in the boundary cases will fall into the precise code.
-		shapeBounds = new Area(shapeBounds.getMinLat()-2,
-				       shapeBounds.getMinLong()-2,
-				       shapeBounds.getMaxLat()+2,
-				       shapeBounds.getMaxLong()+2);
+		int xtra = 2;
+		// However, if the shape is significantly larger than the error margin (ie most of it
+		// should be in this area) I don't see any problem in letting it expand a little bit out
+		// of the area.
+		// This avoids very small polygons being left in the adjacent areas, which ShapeMergeFilter
+		// notices with an debug message and then output filters probably chuck away.
+		if (Math.min(shapeBounds.getWidth(), shapeBounds.getHeight()) > 8)
+			xtra = -2; // pretend shape is smaller
+
+		shapeBounds = new Area(shapeBounds.getMinLat()-xtra,
+				       shapeBounds.getMinLong()-xtra,
+				       shapeBounds.getMaxLat()+xtra,
+				       shapeBounds.getMaxLong()+xtra);
 		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
 			if (areas[areaIndex].getBounds().contains(shapeBounds)) {
 				used[areaIndex] = true;
@@ -688,6 +699,7 @@
 		}
 		// Shape crosses area(s), we have to split it
 
+/* old, redundant code
 		// Convert to a awt area
 		List<Coord> coords = e.getPoints();
 		java.awt.geom.Area area = Java2DConverter.createArea(coords);
@@ -698,14 +710,54 @@
 			Coord co = coords.get(i);
 			shapeHashMap.put(Utils.coord2Long(co), co);
 		}
+*/
 		if (areasHashMap == null)
 			areasHashMap = new Long2ObjectOpenHashMap<>();
 
+		if (areas.length == 2) { // just divide along the line between the two areas
+			int dividingLine = 0;
+			boolean isLongitude = false;
+			boolean commonLine = true;
+			if (areas[0].getBounds().getMaxLat() == areas[1].getBounds().getMinLat()) {
+				dividingLine = areas[0].getBounds().getMaxLat();
+				isLongitude = false;
+			} else if (areas[0].getBounds().getMaxLong() == areas[1].getBounds().getMinLong()) {
+				dividingLine = areas[0].getBounds().getMaxLong();
+				isLongitude = true;
+			} else {
+				commonLine = false;
+				log.warn("Split into 2 expects shared edge between the areas");
+			}
+			if (commonLine) {
+				List<List<Coord>> lessList = new ArrayList<>(), moreList = new ArrayList<>();
+				ShapeSplitter.splitShape(e.getPoints(), dividingLine << Coord.DELTA_SHIFT, isLongitude, lessList, moreList, areasHashMap);
+				for (List<Coord> subShape : lessList) {
+					MapShape s = e.copy();
+					s.setPoints(subShape);
+					s.setClipped(true);
+					areas[0].addShape(s);
+					used[0] = true;
+				}
+				for (List<Coord> subShape : moreList) {
+					MapShape s = e.copy();
+					s.setPoints(subShape);
+					s.setClipped(true);
+					areas[1].addShape(s);
+					used[1] = true;
+				}
+				return;
+			}
+		}
+
 		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
+/* old, redundant code
 			java.awt.geom.Area clipper = Java2DConverter.createBoundsArea(areas[areaIndex].getBounds());
 			clipper.intersect(area);
 			List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(clipper);
+*/
+			List<List<Coord>> subShapePoints = ShapeSplitter.clipToBounds(e.getPoints(), areas[areaIndex].getBounds(), areasHashMap);
 			for (List<Coord> subShape : subShapePoints) {
+/* old, redundant code
 				// Use original or share newly created coords on clipped edge.
 				// NB: .intersect()/areaToShapes can output flattened shapes,
 				// normally triangles, in any orientation; check we haven't got one by calc area.
@@ -734,6 +786,12 @@
 							areasHashMap.put(hashVal, co);
 					}
 				}
+				if (signedAreaSize == 0) {
+					log.warn("splitIntoAreas creates zero area shape. id", e.getOsmid(),
+						 "type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
+						 "points, at", subShape.get(0).toOSMURL());
+					continue;
+				}
 				if (Math.abs(signedAreaSize) < ShapeMergeFilter.SINGLE_POINT_AREA
 						&& areas[areaIndex].areaResolution != 24) {
 					if (log.isInfoEnabled()) {
@@ -743,13 +801,7 @@
 					}
 					continue;
 				}
-
-				if (signedAreaSize == 0) {
-					log.warn("splitIntoAreas creates single point shape. id", e.getOsmid(),
-						 "type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
-						 "points, at", subShape.get(0).toOSMURL());
-					continue;
-				}
+*/
 				MapShape s = e.copy();
 				s.setPoints(subShape);
 				s.setClipped(true);
@@ -759,7 +811,6 @@
 		}
 	}
 
-
 	/**
 	 * @return true if this area contains any data
 	 */
Index: src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(revision 3742)
+++ src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(working copy)
@@ -61,7 +61,8 @@
 			count++;
 			if (shape.getPoints().get(0) != shape.getPoints().get(shape.getPoints().size()-1)){
 				// should not happen here
-				log.error("shape is not closed with identical points", shape.getOsmid());
+				log.error("shape is not closed with identical points", shape.getOsmid(),
+					  shape.getPoints().get(0).toOSMURL());
 				mergedShapes.add(shape);
 				continue;
 			}
@@ -73,7 +74,8 @@
 				log.error("ignoring shape with id", sh.id, "and type",
 						GType.formatType(shape.getType()), "at resolution", resolution + ", it", 
 						(shape.wasClipped() ?   "was clipped to" : "has"), 
-						shape.getPoints().size(), "points and has an empty area ");
+						shape.getPoints().size(), "points and has an empty area",
+						shape.getPoints().get(0).toOSMURL());
 				continue;
 			}
 			if (sameTypeList.isEmpty()){
Index: src/uk/me/parabola/util/ShapeSplitter.java
===================================================================
--- src/uk/me/parabola/util/ShapeSplitter.java	(revision 3742)
+++ src/uk/me/parabola/util/ShapeSplitter.java	(working copy)
@@ -18,6 +18,14 @@
 import java.awt.geom.Rectangle2D;
 import java.util.Arrays;
 
+// RWB new bits
+import java.util.ArrayList;
+import java.util.List;
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.imgfmt.app.Area;
+import uk.me.parabola.imgfmt.app.Coord;
+
 import uk.me.parabola.log.Logger;
 
 public class ShapeSplitter {
@@ -269,4 +277,347 @@
 		return pointsToPath2D(outputList, countVals);
 	}
 
-}
+/* Dec16/Jan17. Ticker Berkin. New implementation for splitting shapes. 
+
+Eventually can be used instead of some of the above and maybe in following code:
+
+done	mkgmap/build/MapArea.java
+	mkgmap/filters/PolygonSplitterBase.java
+	mkgmap/filters/ShapeMergeFilter.java
+	mkgmap/general/AreaClipper.java
+	mkgmap/general/PolygonClipper.java
+	mkgmap/reader/osm/MultiPolygonRelation.java
+	mkgmap/reader/osm/SeaGenerator.java
+	mkgmap/reader/osm/boundary/BoundaryConverter.java
+	mkgmap/reader/osm/boundary/BoundaryCoverageUtil.java
+	mkgmap/reader/osm/boundary/BoundaryDiff.java
+	mkgmap/reader/osm/boundary/BoundaryElement.java
+	mkgmap/reader/osm/boundary/BoundaryFile2Gpx.java
+	mkgmap/reader/osm/boundary/BoundaryQuadTree.java
+	mkgmap/reader/osm/boundary/BoundaryRelation.java
+	mkgmap/reader/osm/boundary/BoundarySaver.java
+	mkgmap/reader/osm/boundary/BoundaryUtil.java
+	mkgmap/sea/optional/PrecompSeaGenerator.java
+	mkgmap/sea/optional/PrecompSeaMerger.java
+	util/ElementQuadTreeNode.java
+	util/Java2DConverter.java
+	util/QuadTreeNode.java
+*/
+
+    /**
+     * closes a shape and appends to list.
+     *
+     * If the shape starts and ends at the same point on the dividing line then
+     * there is no need to close it. Also check for and chuck a spike, which happens
+     * if there is a single point just across the dividing line and the two intersecting
+     * points ended up being the same.
+     *
+     * @param points	the shape to process.
+     * @param origList	list of shapes to which we append new shapes.
+     */
+    private static void closeAppend(List<Coord> points, List<List<Coord>> origList, boolean onDividingLine) {
+	Coord firstCoord = points.get(0);
+	int lastPointInx = points.size()-1;
+	if (firstCoord.highPrecEquals(points.get(lastPointInx))) { // start and end at same point on line
+	    if (lastPointInx == 2) // just a single point across the line
+		return;
+	    // There should be no need to close the line, but am finding, for shapes that never crossed the
+	    // dividing line, quite a few that, after splitShapes has rotating the shape by 1, have first and last
+	    // points highPrecEquals but they are different objects.
+	    // This means that the original first and last were the same object, but the second and last were highPrecEquals!
+	    // If left like this, it might be flagged by ShapeMergeFilter.
+	    // NB: if no coordPool, likely to be different closing object anyway
+	    if (firstCoord != points.get(lastPointInx)) {
+		if (onDividingLine)
+		    log.error("high prec/diff obj", firstCoord, lastPointInx, onDividingLine, firstCoord.toOSMURL());
+		else
+		    points.set(lastPointInx, firstCoord); // quietly replace with first point
+	    }
+	} else
+	    points.add(firstCoord); // close it
+	origList.add(points);
+    } // closeAppend
+
+    /**
+     * Service routine for processLineList. Processes a nested list of holes within a shape or
+     * list of shapes within a hole.
+     *
+     * Recurses to check for and handle the opposite of what has been called to process.
+     *
+     * @param startInx	starting point in list.
+     * @param endEnclosed	point where starting line ends on dividing line.
+     * @param addHolesToThis	if not null, then called from a shape and subtract holes from it
+     *				otherwise new shapes within a hole.
+     * @param lineInfo		array of lines.
+     * @param origList	list of shapes to which we append new shapes.
+     */
+    private static int doLines(int startInx, int endEnclosed, MergeCloseHelper addHolesToThis,
+			       MergeCloseHelper[] lineInfo, List<List<Coord>> origList) {
+	int inx = startInx;
+	while (inx < lineInfo.length) {
+	    MergeCloseHelper thisLine = lineInfo[inx];
+	    if (thisLine.highPoint > endEnclosed) // only do enclosed items
+		break;
+	    // process any enclosed lines
+	    inx = doLines(inx+1, thisLine.highPoint, addHolesToThis == null ? thisLine : null, lineInfo, origList);
+	    if (addHolesToThis == null) // handling list of shapes
+		closeAppend(thisLine.points, origList, true);
+	    else  // handling list of holes
+		addHolesToThis.addHole(thisLine);
+	}
+	return inx;
+    } // doLines
+
+    /**
+     * Service routine for splitShape. Takes list of lines and appends distinct shapes
+     * @param newList	list of lines that start and end on the dividing line (or orig startPoint)
+     * @param origList	list of shapes to which we append new shapes formed from above
+     * @param isLongitude	true if dividing on a line of longitude, false if latitude
+     */
+    private static void processLineList(List<List<Coord>> newList, List<List<Coord>> origList, boolean isLongitude) {
+	if (origList == null) // never wanted this side
+	    return;
+        List<Coord> firstPoly = newList.get(0);
+	if (newList.size() == 1) { // single shape that never crossed line
+	    if (!firstPoly.isEmpty()) // all on this side
+		closeAppend(firstPoly, origList, false);
+	    return;
+	}
+	// look at last elem in list of lists
+        List<Coord> lastPoly = newList.get(newList.size()-1);
+	if (lastPoly.isEmpty()) // will be empty if did not return to this side
+	    newList.remove(newList.size()-1);
+	else { // ended up on this side and must have crossed the line
+	    // so first points are a continuation of last
+	    lastPoly.addAll(firstPoly);
+	    newList.remove(0);
+	    firstPoly = newList.get(0);
+	}
+	if (newList.size() == 1) { // simple poly that crossed once and back
+	    closeAppend(firstPoly, origList, true);
+	    return;
+	}
+	// Above were the simple cases - probably 99% of calls.
+
+	// Now imagine something like a snake, with a coiled bit, zig-zag bit and
+	// a flat area that has been flash shape hacked out of it. Then cut a line
+	// through these complex shapes and now need to describe the shapes remaining
+	// on one side of the cut. I think the following does this!
+	// splitShape has generated a list of lines that start and end on the dividing line.
+	// These lines don't cross. Order them by their lowest point on the divider, but note which
+	// direction they go. The first (and last) line must define a shape. Starting with this
+	// shape; the next line up, if it is within this shape, must define a hole and
+	// so is added to the list of points for the shape. For the hole, recurse to
+	// handle any shapes enclosed. Repeat until we reach the end of the enclosing
+	// space.
+
+	final int numLines = newList.size();
+        // make ordered list of more info about the lines that start/end on the dividing line
+	MergeCloseHelper[] lineInfo = new MergeCloseHelper[numLines];
+	for (int inx = 0; inx < numLines; ++inx)
+	    lineInfo[inx] = new MergeCloseHelper(newList.get(inx), isLongitude);
+	Arrays.sort(lineInfo);
+
+	log.debug("complex ShapeSplit", numLines, "at", firstPoly.get(0).toOSMURL());
+	for (MergeCloseHelper el : lineInfo)
+	    log.debug(el.lowPoint, el.highPoint, el.direction);
+
+	int dummy = doLines(0, Integer.MAX_VALUE, null, lineInfo, origList);
+	assert dummy == lineInfo.length;
+    } // processLineList
+
+
+    /**
+     * Helper class for splitShape. Holds information about line.
+     * Sorts array of itself according to lowest point on dividing line.
+     */
+    private static class MergeCloseHelper implements Comparable<MergeCloseHelper> {
+
+	List<Coord> points;
+	int direction;
+	int lowPoint, highPoint;
+
+	MergeCloseHelper(List<Coord> points, boolean isLongitude) {
+	    this.points = points;
+	    Coord aCoord = points.get(0);
+	    int firstPoint = isLongitude ? aCoord.getHighPrecLat() : aCoord.getHighPrecLon();
+	    aCoord = points.get(points.size()-1);
+	    int lastPoint = isLongitude ? aCoord.getHighPrecLat() : aCoord.getHighPrecLon();
+	    this.direction = Integer.signum(lastPoint - firstPoint);
+	    if (this.direction > 0) {
+		this.lowPoint = firstPoint;
+		this.highPoint = lastPoint;
+	    } else {
+		this.lowPoint = lastPoint;
+		this.highPoint = firstPoint;
+	    }
+	} // MergeCloseHelper
+
+	public int compareTo(MergeCloseHelper other) {
+	    int cmp = this.lowPoint - other.lowPoint;
+	    if (cmp != 0)
+		return cmp;
+	    // for same lowPoint, sort highPoint other way around to enclose as much as possible
+	    cmp = other.highPoint - this.highPoint;
+	    if (cmp != 0)
+		return cmp;
+	    // pathalogical case where when have same start & end
+	    log.error("Lines hit divider at same points", this.lowPoint, this.highPoint, this.points.get(0).toOSMURL());
+	    // %%% should return one with larger area as being less,
+	    // but I don't think anyone will define an area like this and expect it to work
+	    // so, for moment:
+	    return this.direction - other.direction;
+	} // compareTo
+
+	void addHole(MergeCloseHelper other) {
+	    if (other.direction == 0 && other.points.size() == 3)
+		return; // spike into this area. cf. closeAppend()
+	    // shapes must have opposite directions.
+	    if (this.direction == 0 && other.direction == 0)
+		log.error("Direction of shape and hole indeterminate", this.points.get(0).toOSMURL());
+	    else if (this.direction != 0 && other.direction != 0 && this.direction == other.direction)
+		log.error("Direction of shape and hole conflict", this.points.get(0).toOSMURL());
+	    else if (this.direction < 0 || other.direction > 0) {
+		this.points.addAll(other.points);
+	        if (this.direction == 0)
+		    this.direction = -1;
+	    } else { // add at start
+		other.points.addAll(this.points);
+		this.points = other.points;
+	        if (this.direction == 0)
+		    this.direction = +1;
+	    }
+	} // addHole
+
+    } // MergeCloseHelper
+
+    /**
+     * split a shape with a line
+     * @param coords	the shape. Must be closed.
+     * @param dividingLine	the line in high precision.
+     * @param isLongitude	true if above is line of longitude, false if latitude.
+     * @param lessList	list of shapes to which we append new shapes on lower/left side of line.
+     * @param moreList	list of shapes to which we append new shapes on upper/right side of line.
+     * @param coordPool	if not null, hashmap for created coordinates. Will all be on the line.
+     */
+    public static void splitShape(List<Coord> coords, int dividingLine, boolean isLongitude,
+				  List<List<Coord>> lessList, List<List<Coord>> moreList,
+				  Long2ObjectOpenHashMap<Coord> coordPool) {
+
+	List<List<Coord>> newLess = null, newMore = null;
+	List<Coord> lessPoly = null, morePoly = null;
+	if (lessList != null) {
+	    newLess = new ArrayList<>();
+	    lessPoly = new ArrayList<>();
+	    newLess.add(lessPoly);
+	}
+	if (moreList != null) {
+	    newMore = new ArrayList<>();
+	    morePoly = new ArrayList<>();
+	    newMore.add(morePoly);
+	}
+	Coord trailCoord = null;
+	int trailPosn = 0, trailRel = 0;
+
+	for (Coord leadCoord : coords) {
+	    int leadPosn = isLongitude ? leadCoord.getHighPrecLon() : leadCoord.getHighPrecLat();
+	    int leadRel = Integer.signum(leadPosn - dividingLine);
+	    if (trailCoord != null) { // use first point as trailing (poly is closed)
+
+		Coord lineCoord = null;
+		if (trailRel == 0) // trailing point on line
+		    lineCoord = trailCoord;
+		else if (leadRel == 0)  // leading point on line
+		    lineCoord = leadCoord;
+		else if (trailRel != leadRel) { // crosses line; make intersecting coord
+		    int newOther = isLongitude ? trailCoord.getHighPrecLat() : trailCoord.getHighPrecLon();
+		    int leadOther = isLongitude ? leadCoord.getHighPrecLat() :  leadCoord.getHighPrecLon();
+		    if (newOther != leadOther)
+			newOther += (dividingLine - trailPosn) * (leadOther - newOther) / (leadPosn - trailPosn);
+		    lineCoord = Coord.makeHighPrecCoord(isLongitude ? newOther : dividingLine, isLongitude ? dividingLine : newOther);
+		}
+		if (lineCoord != null && coordPool != null) {
+		    // Add new coords to pool. Also add existing ones if on the dividing line because there is slight
+		    // chance that the intersection will coincide with an existing point and ShapeMergeFilter expects
+		    // the opening/closing point to be the same object. If we see the original point first,
+		    // all is good, but if other way around, it will replace an original point with the created one.
+		    long hashVal = Utils.coord2Long(lineCoord);
+		    Coord replCoord = coordPool.get(hashVal);
+		    if (replCoord == null)
+			coordPool.put(hashVal, lineCoord);
+		    else
+			lineCoord = replCoord;
+		}
+
+		if (lessList != null) {
+		    if (leadRel < 0) { // this point required
+			if (trailRel >= 0) // previous not on this side, add line point
+			    lessPoly.add(lineCoord);
+			lessPoly.add(leadCoord);
+		    } else if (trailRel < 0) { // if this not reqd and prev was, add line point and start new shape
+			lessPoly.add(lineCoord);
+			lessPoly = new ArrayList<>();
+			newLess.add(lessPoly);
+		    }
+		}
+
+		// identical to above except other way around
+		if (moreList != null) {
+		    if (leadRel > 0) { // this point required
+			if (trailRel <= 0) // previous not on this side, add line point
+			    morePoly.add(lineCoord);
+			morePoly.add(leadCoord);
+		    } else if (trailRel > 0) { // if this not reqd and prev was, add line point and start new shape
+			morePoly.add(lineCoord);
+			morePoly = new ArrayList<>();
+			newMore.add(morePoly);
+		    }
+		}
+
+	    } // if not first Coord
+	    trailCoord = leadCoord;
+	    trailPosn = leadPosn;
+	    trailRel = leadRel;
+	} // for leadCoord
+        processLineList(newLess, lessList, isLongitude);
+        processLineList(newMore, moreList, isLongitude);
+    } // splitShape
+
+
+    /**
+     * clip a shape with a rectangle
+     *
+     * Use above splitShape for each side; just keeping the 1/2 we want each time.
+     *
+     * @param coords	the shape.
+     * @param bounds	the clipping area.
+     * @param coordPool	if not null, hashmap for created coordinates. Will all be on the edge.
+     * @return	list of shapes.
+     */
+    public static List<List<Coord>> clipToBounds(List<Coord> coords, Area bounds, Long2ObjectOpenHashMap<Coord> coordPool) {
+	List<List<Coord>> newListA = new ArrayList<>();
+	int dividingLine = bounds.getMinLat() << Coord.DELTA_SHIFT;
+	splitShape(coords, dividingLine, false, null, newListA, coordPool);
+	if (newListA.isEmpty())
+	    return newListA;
+	List<List<Coord>> newListB = new ArrayList<>();
+	dividingLine = bounds.getMinLong() << Coord.DELTA_SHIFT;
+	for (List<Coord> aShape : newListA)
+	    splitShape(aShape, dividingLine, true, null, newListB, coordPool);
+	if (newListB.isEmpty())
+	    return newListB;
+	newListA.clear();
+	dividingLine = bounds.getMaxLat() << Coord.DELTA_SHIFT;
+	for (List<Coord> aShape : newListB)
+	    splitShape(aShape, dividingLine, false, newListA, null, coordPool);
+	if (newListA.isEmpty())
+	    return newListA;
+	newListB.clear();
+	dividingLine = bounds.getMaxLong() << Coord.DELTA_SHIFT;
+	for (List<Coord> aShape : newListA)
+	    splitShape(aShape, dividingLine, true, newListB, null, coordPool);
+	return newListB;
+    } // clipToBounds
+
+
+} // ShapeSplitter
