Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java	(revision 4745)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java	(working copy)
@@ -108,6 +108,7 @@
 import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
 import uk.me.parabola.util.Configurable;
 import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.util.GpxCreator;
 import uk.me.parabola.util.Java2DConverter;
 
 /**
@@ -924,7 +925,7 @@
 		}
 
 		if (mergeShapes && !isOverviewComponent) {
-			ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea);
+			ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea, -1);
 			shapes = shapeMergeFilter.merge(shapes);
 		}
 
@@ -1570,38 +1571,30 @@
 			removeTooSmallHoles(copy.getPoints(), minSize);
 			copies.add(copy);
 		}
-		ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea);
+		ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea, minSize);
 		List<MapShape> merged = shapeMergeFilter.merge(copies);
 		mapSource.getShapes().addAll(merged);
 	}
 
 	public static void removeTooSmallHoles(List<Coord> points , int minSize) {
-		// possibly rotate shape so that start point is on the boundary -> not part of a hole 
-		int minLatHp = Integer.MAX_VALUE;
-		int rotate = -1;
-		for (int i = 0; i < points.size(); i++) {
-			int latHp = points.get(i).getHighPrecLat();
-			if (latHp < minLatHp) {
-				minLatHp = latHp;
-				rotate = i;
-			}
-		}
-		if (rotate > 0) {
-			points.remove(points.size() - 1);
-			Collections.rotate(points, -rotate);
-			points.add(points.get(0));
-		}
+		if (minSize <= 0)
+			return;
+
 		Area bbox = Area.getBBox(points);
-		
 		points.forEach(Coord::resetHighwayCount);
 		points.forEach(Coord::incHighwayCount);
-		
+		long numSpecial = points.stream().filter(p -> p.getHighwayCount() > 1).count();
+		if (numSpecial <= 2)
+			return; 
+//		if (numSpecial > 4) {
+//			GpxCreator.createGpx("e:/ld/o0", points); // multiple holes or intersection
+//		}
 		for (int i = 0; i < points.size(); i++) {
 			Coord p0 = points.get(i);
 			if (p0.getHighwayCount() <= 1)
 				continue;
 			if (i == points.size() - 1)
-				return;
+				return; // done for now, additional call may remove more holes
 			for (int j = points.size() - 2; j > i; j--) {
 				if (p0 == points.get(j)) {
 					List<Coord> hole = points.subList(i, j + 1);
@@ -1609,18 +1602,34 @@
 					boolean outer = false;
 					if (bboxHole.equals(bbox)) {
 						// we travelled along the outer shape, the rest must be the hole
-						hole = points.subList(j, points.size());
+						hole = new ArrayList<>(points.subList(j, points.size()));
+						hole.addAll(points.subList(0, i+1));
 						bboxHole = Area.getBBox(hole);
+//						GpxCreator.createGpx("e:/ld/h", hole);
 						outer = true;
 					}
 					if (bboxHole.getMaxDimension() < minSize) {
-						if (!outer)
-							points.subList(i, j).clear(); 
-						else
-							points = points.subList(i, j);
-						p0.decHighwayCount();
-						if (p0.getHighwayCount() == 1) {
-							points.remove(i);
+						if (!outer) {
+							points.subList(i, j).clear();
+							p0.decHighwayCount();
+							if (p0.getHighwayCount() == 1) {
+								points.remove(i);
+							}
+						} else {
+//							GpxCreator.createGpx("e:/ld/o1", points);
+							points.subList(j, points.size()).clear();
+							points.subList(0, i+1).clear();
+//							GpxCreator.createGpx("e:/ld/o2", points);
+							if(points.get(0) != points.get(points.size()-1)) {
+								points.add(points.get(0));
+								points.get(0).incHighwayCount();
+							} else {
+								long dd = 4;
+							}
+//							GpxCreator.createGpx("e:/ld/o3", points);
+							if (points.size() <= 3 || points.get(0) != points.get(points.size()-1))
+								throw new MapFailedException("Error while removing hole");
+							i = -1;
 						}
 					}
 					break;
Index: src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java	(revision 4745)
+++ src/uk/me/parabola/mkgmap/filters/PolygonSplitterFilter.java	(working copy)
@@ -22,6 +22,7 @@
 import uk.me.parabola.mkgmap.general.MapElement;
 import uk.me.parabola.mkgmap.general.MapShape;
 import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.util.GpxCreator;
 import uk.me.parabola.util.ShapeSplitter;
 
 import java.util.ArrayList;
@@ -89,6 +90,22 @@
 			}
 			List<MapShape> outputs = new ArrayList<>();
 			split(shape, outputs); // split in half
+			
+//			double eps = 0.0001;
+//			long testVal = Math.abs(ShapeMergeFilter.calcAreaSizeTestVal(shape.getPoints()));
+//			long sumSplit = outputs.stream()
+//					.mapToLong(s -> Math.abs(ShapeMergeFilter.calcAreaSizeTestVal(s.getPoints()))).sum();
+//			double ratio = (double) testVal / sumSplit;
+//			
+//			if (ratio < 1 - eps || ratio > 1 + eps) {
+//				log.error("splitting shape", GType.formatType(shape.getType()), "at shift", shift);
+//				GpxCreator.createGpx("e:/ld/o", shape.getPoints());
+//				for (int i = 0; i < outputs.size(); i++) {
+//					GpxCreator.createGpx("e:/ld/s_"+i, outputs.get(i).getPoints());
+//				}
+//				log.error("split failed?, ratio:", ratio);
+//			}
+			
 			for (MapShape s : outputs) {
 				doFilter(s, next); // recurse as components could still be too big
 			}
Index: src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(revision 4745)
+++ src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(working copy)
@@ -20,14 +20,17 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import it.unimi.dsi.fastutil.ints.IntArrayList;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.build.MapBuilder;
 import uk.me.parabola.mkgmap.general.MapShape;
 import uk.me.parabola.mkgmap.osmstyle.WrongAngleFixer;
 import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.util.GpxCreator;
 import uk.me.parabola.util.Java2DConverter;
 
 
@@ -43,6 +46,7 @@
 	private static final ShapeHelper DUP_SHAPE = new ShapeHelper(new ArrayList<>(0)); 
 	private final boolean orderByDecreasingArea;
 	private final int maxPoints;
+	private final int minSize;
 
 	/**
 	 * Create the shape filter with the given attributes. It will ignore shapes
@@ -51,15 +55,23 @@
 	 * @param resolution            the resolution
 	 * @param orderByDecreasingArea if true, only certain shapes are merged
 	 */
-	public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
+	public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea, int minSize) {
 		this.resolution = resolution;
 		this.orderByDecreasingArea = orderByDecreasingArea;
 		if (resolution < 24)
-			maxPoints = 16 * 1024 * 1024; // let ShapeSplitter split if too complex
+			maxPoints = PolygonSplitterFilter.MAX_POINT_IN_ELEMENT * (1 << (24-resolution));
 		else 
 			maxPoints = PolygonSplitterFilter.MAX_POINT_IN_ELEMENT;
+		this.minSize = minSize;
 	}
 
+	public ShapeMergeFilter(int resolution, int maxPoints) {
+		this.resolution = resolution;
+		this.orderByDecreasingArea = false;
+		this.maxPoints = maxPoints;
+		this.minSize = -1;
+	}
+
 	/**
 	 * Merge shapes that are similar and have identical points.
 	 * @param shapes list of shapes
@@ -116,9 +128,9 @@
 		
 		final int partSize = 8192;
 		// sorting is meant to reduce the self intersections created by merging
-		similar.sort((o1,o2) -> o1.getBounds().getCenter().getLatitude() - o2.getBounds().getCenter().getLatitude());
+		similar.sort((o1,o2) -> o1.getBounds().getCenter().compareTo(o2.getBounds().getCenter()));
 		List<ShapeHelper> list = new ArrayList<>();
-		MapShape s1 = similar.get(0);
+		MapShape pattern = similar.get(0);
 		for (MapShape ms : similar) {
 			ShapeHelper sh = new ShapeHelper(ms.getPoints());
 			sh.id = ms.getOsmid();
@@ -125,12 +137,13 @@
 			list.add(sh);
 		}
 		
+		// if list is very large use partitions to reduce BitSet sizes in called methods 
 		while (list.size() > partSize) {
 			int oldSize = list.size();
 			List<ShapeHelper> nextList = new ArrayList<>();
 			for (int part = 0; part < list.size(); part += partSize) {
 				List<ShapeHelper> lower = new ArrayList<>(list.subList(part, Math.min(part+partSize, list.size())));
-				tryMerge(s1, lower);
+				tryMerge(pattern, lower);
 				nextList.addAll(lower);
 			}
 			list.clear();
@@ -138,9 +151,9 @@
 			if(list.size() == oldSize)
 				break;
 		}
-		tryMerge(s1, list);
+		tryMerge(pattern, list);
 		for (ShapeHelper sh : list) {
-			MapShape newShape = s1.copy();
+			MapShape newShape = pattern.copy();
 
 			assert sh.getPoints().get(0) == sh.getPoints().get(sh.getPoints().size() - 1);
 			if (sh.id == 0) {
@@ -148,6 +161,12 @@
 				List<Coord> optimizedPoints = WrongAngleFixer.fixAnglesInShape(sh.getPoints());
 				if (optimizedPoints.isEmpty())
 					continue;
+				while (minSize > 0) {
+					int before = optimizedPoints.size();
+					MapBuilder.removeTooSmallHoles(optimizedPoints, minSize);
+					if (optimizedPoints.size() == before)
+						break;
+				}
 				newShape.setPoints(optimizedPoints);
 				newShape.setOsmid(FakeIdGenerator.makeFakeId());
 			} else {
@@ -157,6 +176,7 @@
 			mergedShapes.add(newShape);
 		}
 	}
+	final Coord testp = new Coord(68.2873521, 15.3423361); //TODO: remove
 
 	/**
 	 * Merge ShapeHelpers. Calls itself recursively.
@@ -167,72 +187,39 @@
 	private void tryMerge(MapShape pattern, List<ShapeHelper> similarShapes) {
 		if (similarShapes.size() <= 1)
 			return;
-
+		int numCandidates = similarShapes.size();
+		List<BitSet> candidates = createMatrix(similarShapes);
+		List<ShapeHelper> next = new ArrayList<>();
+		boolean merged = false;
+		BitSet done = new BitSet();
+		BitSet delayed = new BitSet();
+		
 		List<ShapeHelper> noMerge = new ArrayList<>();
-		BitSet toMerge = new BitSet(similarShapes.size());
-		
-		// abuse highway count to find identical points in different shapes
-		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::resetHighwayCount));
-		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::incHighwayCount));
-		// decrement counter for duplicated start/end node
-		similarShapes.forEach(sh -> sh.getPoints().get(0).decHighwayCount());
-		
-		// points with count > 1 are probably shared by different shapes, collect the shapes
-		IdentityHashMap<Coord, BitSet> coord2Shape = new IdentityHashMap<>();
-		BitSet[] candidates = new BitSet[similarShapes.size()];
-		
-		for (int i = 0; i < similarShapes.size(); i++) {
-			ShapeHelper sh0 = similarShapes.get(i);
-			List<Coord> sharedPoints = new ArrayList<>(); 
-			for (int j = 1; j < sh0.getPoints().size(); j++) {
-				Coord c = sh0.getPoints().get(j);
-				if (c.getHighwayCount() > 1) {
-					sharedPoints.add(c);
-				}
+		class SortHelper {
+			final int pos;
+			final BitSet all;
+			public SortHelper(int index, BitSet set) {
+				pos = index;
+				all = set;
 			}
-			if (sharedPoints.isEmpty() || (sh0.getPoints().size() - sharedPoints.size() > maxPoints)) {
-				// merge will not work 
-				noMerge.add(sh0);
-				continue;
-			}
-			
-			assert candidates[i] == null;
-			candidates[i] = new BitSet();
-			BitSet curr = candidates[i];
-			curr.set(i);
-			
-			toMerge.set(i);
-			for (Coord c: sharedPoints) { 
-				BitSet set = coord2Shape.get(c);
-				if (set == null) {
-					set = new BitSet();
-					coord2Shape.put(c, set);
-				} else { 
-					for (int j = set.nextSetBit(0); j >= 0; j = set.nextSetBit(j + 1)) {
-						candidates[j].set(i);
-					}
-					curr.or(set);
-				}
-				set.set(i);
-			}
 		}
-		if (coord2Shape.isEmpty()) {
-			// nothing to do
-			return;
+		List<SortHelper> byNum = new ArrayList<>();
+		for (int i = 0; i < numCandidates; i++) {
+			byNum.add(new SortHelper(i, candidates.get(i)));
 		}
-		coord2Shape.clear();
-		List<ShapeHelper> next = new ArrayList<>();
-		boolean merged = false;
-		BitSet done = new BitSet();
-		BitSet delayed = new BitSet();  
-
-		for (int i = toMerge.nextSetBit(0); i >= 0; i = toMerge.nextSetBit(i + 1)) {
-			if (done.get(i))
+		// sort so that shapes with more neighbours come first
+		byNum.sort((o1, o2) -> o2.all.cardinality() - o1.all.cardinality());
+		for (SortHelper helper : byNum) {
+			final int pos = helper.pos;
+			BitSet all = helper.all;
+			if (all.isEmpty()) {
+				noMerge.add(similarShapes.get(pos));
+				done.set(pos);
+			}
+			if (done.get(pos))
 				continue;
-			BitSet all = candidates[i];
-			if (all.cardinality() <= 1) {
-				if (!all.isEmpty())
-					delayed.set(i);
+			if (all.cardinality() == 1) {
+				delayed.set(pos);
 				continue;
 			}
 			all.andNot(done);
@@ -239,15 +226,37 @@
 			
 			if (all.isEmpty())
 				continue;
-			List<ShapeHelper> result = new ArrayList<>();
+			
+			
+			final boolean print = false; //similarShapes.get(pos).getPoints().stream().anyMatch(p -> p.distance(testp) < 1);
+
+			List<ShapeHelper> result = Collections.singletonList(new ShapeHelper(similarShapes.get(pos)));
 			for (int j = all.nextSetBit(0); j >= 0; j = all.nextSetBit(j + 1)) {
+				if (j == pos)
+					continue;
 				ShapeHelper sh = similarShapes.get(j);
+				if (print) {
+					GpxCreator.createGpx("e:/ld/sh0_" + j, sh.points);
+					for (int k = 0; k < result.size(); k++) {
+						GpxCreator.createGpx("e:/ld/r0_" + k, result.get(k).points);
+					}
+					long dd = 4;
+				}
+				
 				int oldSize = result.size();
 				result = addWithConnectedHoles(result, sh, pattern.getType());
+				if (print) {
+					for (int k = 0; k < result.size(); k++) {
+						GpxCreator.createGpx("e:/ld/r1_" + k, result.get(k).points);
+					}
+					long dd = 4;
+				}
 				if (result.size() < oldSize + 1) {
 					merged = true;
-					log.debug("shape with id", sh.id, "was merged", (oldSize + 1 - result.size()),
-							" time(s) at resolution", resolution);
+					if (log.isDebugEnabled()) {
+						log.debug("shape with id", sh.id, "was merged", (oldSize + 1 - result.size()),
+								" time(s) at resolution", resolution);
+					}
 				}
 			}
 			// XXX : not exact, there may be other combinations of shapes which can be merged
@@ -258,17 +267,15 @@
 		
 		delayed.andNot(done);
 		if (!delayed.isEmpty()) {
-			for (int i = delayed.nextSetBit(0); i >= 0; i = delayed.nextSetBit(i+1)) {
-				noMerge.add(similarShapes.get(i));
-			}
+			delayed.stream().forEach(i -> noMerge.add(similarShapes.get(i)));
 		}
 		delayed.clear();
 		similarShapes.clear();
 		similarShapes.addAll(noMerge);
-		candidates = null;
-		toMerge.clear();
-		if (merged) 
+		candidates.clear();
+		if (merged) {
 			tryMerge(pattern, next);
+		}
 		
 		// Maybe add final step which calls addWithConnectedHoles for all remaining shapes
 		// this will find a few more merges but is still slow for maps with lots of islands 
@@ -277,6 +284,54 @@
 
 
 	/**
+	 * Calculate matrix of shapes which share node
+	 * @param similarShapes
+	 * @return list of sets with indexes of shared nodes, empty for shapes which cannot be merged
+	 */
+	private List<BitSet> createMatrix(List<ShapeHelper> similarShapes) {
+		// abuse highway count to find identical points in different shapes
+		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::resetHighwayCount));
+		similarShapes.forEach(sh -> sh.getPoints().forEach(Coord::incHighwayCount));
+		// decrement counter for duplicated start/end node
+		similarShapes.forEach(sh -> sh.getPoints().get(0).decHighwayCount());
+		
+		// points with count > 1 are probably shared by different shapes, collect the shapes
+		IdentityHashMap<Coord, BitSet> coord2Shape = new IdentityHashMap<>();
+		final List<BitSet>candidates = new ArrayList<>(similarShapes.size());
+		for (int i = 0; i < similarShapes.size(); i++) {
+			candidates.add(new BitSet());
+		}
+		for (int i = 0; i < similarShapes.size(); i++) {
+			ShapeHelper sh0 = similarShapes.get(i);
+			List<Coord> sharedPoints = sh0.getPoints().stream().filter(p -> p.getHighwayCount() > 1)
+					.collect(Collectors.toList());
+			if (sh0.getPoints().get(0).getHighwayCount() > 1)
+				sharedPoints.remove(0);
+			if (sharedPoints.isEmpty() || (sh0.getPoints().size() - sharedPoints.size() > maxPoints)) {
+				// merge will not work
+				continue;
+			}
+
+			BitSet curr = candidates.get(i);
+			curr.set(i);
+
+			for (Coord c : sharedPoints) {
+				BitSet set = coord2Shape.get(c);
+				if (set == null) {
+					set = new BitSet();
+					coord2Shape.put(c, set);
+				} else {
+					final int row = i;
+					set.stream().forEach(j -> candidates.get(j).set(row));
+					curr.or(set);
+				}
+				set.set(i);
+			}
+		}
+		return candidates;
+	}
+
+	/**
 	 * Try to merge a shape with one or more of the shapes in the list.
 	 *  If it cannot be merged, it is added to the list.
 	 *  Holes in shapes are connected with the outer lines,
@@ -366,6 +421,7 @@
 		}
 		if (merged == null)
 			return sh1;
+		
 		ShapeHelper shm = new ShapeHelper(merged);
 		if (Math.abs(shm.areaTestVal) != Math.abs(sh1.areaTestVal) + Math.abs(sh2.areaTestVal)){
 			log.warn("merging shapes skipped for shapes near", points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), 
@@ -372,10 +428,25 @@
 					"(maybe overlapping shapes?)");
 			return sh1;
 		} else {
+//			while (true) {
+//				merged.forEach(Coord::resetHighwayCount);
+//				merged.forEach(Coord::incHighwayCount);
+//				List<Coord> many = merged.stream().filter(p -> p.getHighwayCount() > 2).collect(Collectors.toList());
+//				if (!many.isEmpty()) {
+//					int before = merged.size();
+//					merged = WrongAngleFixer.fixAnglesInShape(merged);
+//					if (merged.size() == before)
+//						break;
+//					shm = new ShapeHelper(merged);
+//				} else {
+//					break;
+//				}
+//			}
 			if (log.isInfoEnabled()){
 				log.info("merge of shapes near",points1.get(sh1PositionsToCheck.getInt(0)).toOSMURL(), 
 						"reduces number of points from",(points1.size()+points2.size()),
 						"to",merged.size());
+
 			}
 		}
 		return shm;
@@ -546,6 +617,7 @@
 			log.error("shape is not closed");
 			return 0;
 		}
+		long test = Long.MAX_VALUE / 2; 
 		Iterator<Coord> polyIter = points.iterator();
 		Coord c2 = polyIter.next();
 		long signedAreaSize = 0;
@@ -554,6 +626,9 @@
 			c2 = polyIter.next();
 			signedAreaSize += (long) (c2.getHighPrecLon() + c1.getHighPrecLon())
 					* (c1.getHighPrecLat() - c2.getHighPrecLat());
+			if (signedAreaSize > test || signedAreaSize < -test) {
+				assert false : "overflow alarm";
+			}
 		}
 		if (Math.abs(signedAreaSize) < SINGLE_POINT_AREA && log.isDebugEnabled()) {
 			log.debug("very small shape near", points.get(0).toOSMURL(), "signed area in high prec map units:", signedAreaSize );
Index: src/uk/me/parabola/util/GpxCreator.java
===================================================================
--- src/uk/me/parabola/util/GpxCreator.java	(revision 4745)
+++ src/uk/me/parabola/util/GpxCreator.java	(working copy)
@@ -86,7 +86,7 @@
 	}
 
 	public static void createGpx(String name, List<Coord> points) {
-		for (int i = 0; i < 2; i++){
+		for (int i = 1; i < 2; i++){
 			String fname = name + (i==0 ? "_mu":"_hp");
 			try {
 				File f = new File(fname);
Index: src/uk/me/parabola/util/ShapeSplitter.java
===================================================================
--- src/uk/me/parabola/util/ShapeSplitter.java	(revision 4745)
+++ src/uk/me/parabola/util/ShapeSplitter.java	(working copy)
@@ -16,17 +16,17 @@
 import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
 import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
-// 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;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 
 public class ShapeSplitter {
 	private static final Logger log = Logger.getLogger(ShapeSplitter.class);
@@ -321,14 +321,15 @@
 	 * @param origList list of shapes to which we append new shapes formed from above
 	 * @param fullArea of orig polygon. used for sign and handling of last line segment
 	 */
-	private static void processLineList(List<MergeCloseHelper> lineInfo, List<List<Coord>> origList, long fullArea) {
+	private static int processLineList(List<MergeCloseHelper> lineInfo, List<List<Coord>> origList, long fullArea) {
+		int errorCount = 0;
 		if (origList == null) // never wanted this side
-			return;
+			return errorCount;
 		MergeCloseHelper firstLine = lineInfo.get(0);
 		if (lineInfo.size() == 1) { // single shape that never crossed line
 			if (!firstLine.points.isEmpty()) // all on this side
 				firstLine.closeAppend(origList, false);
-			return;
+			return errorCount;
 		}
 		// look at last item in list of lines
 		MergeCloseHelper lastLine = lineInfo.get(lineInfo.size()-1);
@@ -343,7 +344,7 @@
 		if (lineInfo.size() == 1) { // simple poly that crossed once and back
 			firstLine.setMoreInfo(0);
 			firstLine.closeAppend(origList, true);
-			return;
+			return errorCount;
 		}
 		// Above were the simple cases - probably 99% of calls.
 
@@ -360,7 +361,7 @@
 		boolean someDirectionsNotSet = false;
 		int areaDirection = 0;
 		String diagMsg = "";
- 		for (MergeCloseHelper thisLine : lineInfo) {
+		for (MergeCloseHelper thisLine : lineInfo) {
 			thisLine.setMoreInfo(fullAreaSign);
 			if (thisLine.direction == 0)
 				someDirectionsNotSet = true;
@@ -380,22 +381,60 @@
 					if (thisLine.direction == 0)
 						thisLine.direction = areaDirection * Long.signum(thisLine.areaToLine);
 		}
-		if (diagMsg != "") {
+		if (!diagMsg.isEmpty()) {
 			log.warn(diagMsg, "Probably self-intersecting polygon", fullAreaSign, someDirectionsNotSet, areaDirection);
-			for (MergeCloseHelper thisLine : lineInfo) {
-				log.warn(thisLine);
-				if (log.isDebugEnabled())
-					for (Coord co : thisLine.points)
-						log.debug("line", co, co.getHighPrecLat(), co.getHighPrecLon());
-			}
+			++errorCount;
 		}
 
 		lineInfo.sort(null);
+		errorCount += processDups(lineInfo);
 
 		int dummy = doLines(0, Integer.MAX_VALUE, null, lineInfo, origList);
 		assert dummy == lineInfo.size();
+		for (MergeCloseHelper thisLine : lineInfo)
+			errorCount += thisLine.errorCount;
+		return errorCount;
 	} // processLineList
 
+	private static int processDups(List<MergeCloseHelper> lineInfo) {
+		// find groups of duplicates, drop equal numbers of different direction (ie keep just 1)
+		int errorCount = 0; // shouldn't be any
+		List<MergeCloseHelper> newList = new ArrayList<>(lineInfo.size());
+		MergeCloseHelper forwardLine = null, backwardLine = null, lastIfDup = null;
+		int directionBalance = 0;
+		for (MergeCloseHelper thisLine : lineInfo) {
+			if (lastIfDup != null && (!thisLine.isDup || (thisLine.lowPoint != lastIfDup.lowPoint ||
+														  thisLine.highPoint != lastIfDup.highPoint ||
+														  Math.abs(thisLine.areaToLine) != Math.abs(lastIfDup.areaToLine)))) {
+				if (directionBalance > 0)
+					newList.add(forwardLine);
+				else if (directionBalance < 0)
+					newList.add(backwardLine);
+				directionBalance = 0;
+			}
+			if (thisLine.isDup) {
+				if (thisLine.direction > 0) {
+					forwardLine = thisLine;
+					++directionBalance;
+				} else {
+					backwardLine = thisLine;
+					--directionBalance;
+				}
+				lastIfDup = thisLine;
+			} else {
+				newList.add(thisLine);
+				lastIfDup = null;
+			}
+		}
+		if (directionBalance > 0)
+			newList.add(forwardLine);
+		else if (directionBalance < 0)
+			newList.add(backwardLine);
+		if (newList.size() < lineInfo.size())
+			lineInfo = newList;
+		return errorCount;
+	} // removeDups
+
 	private static List<Coord> startLine(List<MergeCloseHelper> lineInfo) {
 		MergeCloseHelper thisLine = new MergeCloseHelper();
 		lineInfo.add(thisLine);
@@ -423,6 +462,8 @@
 	 */
 	private static class MergeCloseHelper implements Comparable<MergeCloseHelper> {
 
+		int errorCount = 0;
+		boolean isDup;
 		List<Coord> points;
 		int firstPoint, lastPoint;
 		long startingArea, endingArea; // from runningArea
@@ -469,13 +510,16 @@
 			cmp = other.highPoint - this.highPoint;
 			if (cmp != 0)
 				return cmp;
-			// case where when have same start & end
-			// return the shape as lower than the hole, to handle first
-			cmp = other.areaOrHole - this.areaOrHole;
+			// have same start & end. larger area first
+			cmp = Long.compare(Math.abs(other.areaToLine), Math.abs(this.areaToLine));
 			if (cmp != 0)
 				return cmp;
-			log.warn("Lines hit divider at same points and have same area sign", "this:", this, "other:", other);
-			// after this, don't think anthing else possible, but, for stability
+			// multiple lines appear to follow same path, some can be dropped after sort
+			this.isDup = true;
+			other.isDup = true;
+			// maybe don't need this, if good fix
+			//log.warn("Lines hit divider at same points and have same area", this);
+			// after this, don't think anything else possible, but, for stability
 			return this.direction - other.direction;
 		} // compareTo
 
@@ -483,11 +527,13 @@
 			if (other.areaToLine == 0)
 				return; // spike into this area. cf. closeAppend()
 			// shapes must have opposite directions.
-			if (this.direction == 0 && other.direction == 0)
+			if (this.direction == 0 && other.direction == 0) {
 				log.warn("Direction of shape and hole indeterminate; probably self-intersecting polygon", "this:", this, "other:", other);
-			else if (this.direction != 0 && other.direction != 0 && this.direction == other.direction)
+				++errorCount;
+			} else if (this.direction != 0 && other.direction != 0 && this.direction == other.direction) {
 				log.warn("Direction of shape and hole conflict; probably self-intersecting polygon", "this:", this, "other:", other);
-			else if (this.direction < 0 || other.direction > 0) {
+				++errorCount;
+			} else if (this.direction < 0 || other.direction > 0) {
 				this.points.addAll(other.points);
 				if (this.direction == 0)
 					this.direction = -1;
@@ -555,6 +601,7 @@
 				      List<List<Coord>> lessList, List<List<Coord>> moreList,
 				      Long2ObjectOpenHashMap<Coord> coordPool) {
 
+		int errorCount = 0;
 		List<MergeCloseHelper> newLess = null, newMore = null;
 		List<Coord> lessPoly = null, morePoly = null;
 		if (lessList != null) {
@@ -644,8 +691,64 @@
 			trailAlong = leadAlong;
 			trailRel = leadRel;
 		} // for leadCoord
-		processLineList(newLess, lessList, runningArea);
-		processLineList(newMore, moreList, runningArea);
+		if (log.isDebugEnabled()) { // force it to generate both sides
+			if (lessList == null)
+				lessList = new ArrayList<>();
+			if (moreList == null)
+				moreList = new ArrayList<>();
+		}
+		errorCount += processLineList(newLess, lessList, runningArea);
+		errorCount += processLineList(newMore, moreList, runningArea);
+		if (errorCount > 0) {
+			int lowestPoint = newLess.get(0).lowPoint;
+			log.error("splitErrors:", errorCount, "on", dividingLine, isLongitude, "points", coords.size(), "area", runningArea, "lowest", lowestPoint, coords.get(0).toOSMURL());
+			for (MergeCloseHelper thisLine : newLess)
+				log.warn("LessLoop", thisLine.lowPoint-lowestPoint, thisLine.highPoint-lowestPoint, thisLine.direction, thisLine.areaOrHole, thisLine.areaToLine);
+			for (MergeCloseHelper thisLine : newMore)
+				log.warn("MoreLoop", thisLine.lowPoint-lowestPoint, thisLine.highPoint-lowestPoint, thisLine.direction, thisLine.areaOrHole, thisLine.areaToLine);
+			if (log.isDebugEnabled()) {
+				String fileName = (isLongitude ? "V" : "H") + dividingLine + "_" + lowestPoint;
+				GpxCreator.createGpx(fileName + "/S", coords);  // original shape
+				// NB: lessList/moreList could be non-existent (but see abov), same object or have already have contents
+				int fInx = 0;
+				String filePrefix = lessList == moreList ? "/B" : "/L";
+				for (List<Coord> fragment : lessList) {
+					++fInx;
+					GpxCreator.createGpx(fileName + filePrefix + fInx, fragment);
+				}
+				if (lessList != moreList) {
+					fInx = 0;
+					for (List<Coord> fragment : moreList) {
+						++fInx;
+						GpxCreator.createGpx(fileName + "/M" + fInx, fragment);
+					}
+				}
+			}
+		}
+		if (lessList != null && moreList != null) {
+			List<List<Coord>> outList = new ArrayList<>(lessList);
+			if (moreList != lessList)
+				outList.addAll(moreList);
+			double eps = 0.0001;
+			long testVal = Math.abs(ShapeMergeFilter.calcAreaSizeTestVal(coords));
+			long sumSplit = outList.stream().mapToLong(l -> Math.abs(ShapeMergeFilter.calcAreaSizeTestVal(l))).sum();
+			double ratio = (double) testVal / sumSplit;
+
+			if (ratio < 1 - eps || ratio > 1 + eps) {
+				coords.forEach(Coord::resetHighwayCount);
+				coords.forEach(Coord::incHighwayCount);
+				GpxCreator.createGpx("e:/ld/o", coords,
+						coords.stream().filter(p -> p.getHighwayCount() > 2).collect(Collectors.toList()));
+
+				for (int i = 0; i < outList.size(); i++) {
+					GpxCreator.createGpx("e:/ld/s_" + i, outList.get(i));
+				}
+				log.error("split possibly failed, ratio:", ratio);
+			} else {
+				log.error("split OK, ratio:", ratio);
+			}
+		}
+		
 	} // splitShape
 
 
Index: test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
===================================================================
--- test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java	(revision 4745)
+++ test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java	(working copy)
@@ -233,7 +233,7 @@
 			getPoint(20,55),
 			getPoint(30,55)); // close
 
-		testVariants("test-fill-hole", points1, points2, 1, 6); // expect 8 points if spike is not removed  
+		testVariants("test-fill-hole", points1, points2, 1, 5); // expect 8 points if spike is not removed  
 	}
 
 	@Test
@@ -351,7 +351,7 @@
 	}
 	
 	void testOneVariant(String testId, MapShape s1, MapShape s2, int expectedNumShapes, int expectedNumPoints){
-		ShapeMergeFilter smf = new ShapeMergeFilter(24, false);
+		ShapeMergeFilter smf = new ShapeMergeFilter(24, false, 0);
 		List<MapShape> res = smf.merge(Arrays.asList(s1,s2));
 		assertTrue(testId, res != null);
 		assertEquals(testId,expectedNumShapes, res.size() );
