Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java	(working copy)
@@ -27,7 +27,9 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.function.UnaryOperator;
@@ -104,11 +106,17 @@
 import uk.me.parabola.mkgmap.reader.hgt.HGTConverter;
 import uk.me.parabola.mkgmap.reader.hgt.HGTConverter.InterpolationMethod;
 import uk.me.parabola.mkgmap.reader.hgt.HGTReader;
+import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 import uk.me.parabola.mkgmap.reader.osm.GType;
+import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
+import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
+import uk.me.parabola.mkgmap.reader.osm.Way;
 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.Java2DConverter;
+import uk.me.parabola.util.MultiHashMap;
+import uk.me.parabola.util.ShapeSplitter;
 
 /**
  * This is the core of the code to translate from the general representation
@@ -155,8 +163,8 @@
 	private int minSizePolygon;
 	private String polygonSizeLimitsOpt;
 	private TreeMap<Integer,Integer> polygonSizeLimits;
-	private TreeMap<Integer, Double> dpFilterLineLevelMap = new TreeMap<>(); 
-	private TreeMap<Integer, Double> dpFilterShapeLevelMap = new TreeMap<>(); 
+	private TreeMap<Integer, Double> dpFilterLineResMap = new TreeMap<>(); 
+	private TreeMap<Integer, Double> dpFilterShapeResMap = new TreeMap<>(); 
 	private boolean mergeLines;
 	private boolean mergeShapes;
 
@@ -177,6 +185,7 @@
 	private java.awt.geom.Area demPolygon;
 	private HGTConverter.InterpolationMethod demInterpolationMethod;
 	private boolean allowReverseMerge;
+	private boolean mergeShapesFirst;
 
 	/**
 	 * Construct a new MapBuilder.
@@ -211,8 +220,8 @@
 		String simplifyPolygonErrorsOpt = props.getProperty("simplify-filter-polygon-errors", null);
 		if (simplifyLineErrorsOpt != null && simplifyPolygonErrorsOpt == null)
 			simplifyPolygonErrorsOpt = simplifyLineErrorsOpt;
-		parseLevelOption(dpFilterLineLevelMap, simplifyLineErrorsOpt, "simplify-filter-line-errors", reducePointError);
-		parseLevelOption(dpFilterShapeLevelMap, simplifyPolygonErrorsOpt, "simplify-filter-polygon-errors",
+		parseLevelOption(dpFilterLineResMap, simplifyLineErrorsOpt, "simplify-filter-line-errors", reducePointError);
+		parseLevelOption(dpFilterShapeResMap, simplifyPolygonErrorsOpt, "simplify-filter-polygon-errors",
 				reducePointErrorPolygon);
 		
 		mergeLines = props.containsKey("merge-lines");
@@ -220,7 +229,8 @@
 
 		// undocumented option - usually used for debugging only
 		mergeShapes = !props.getProperty("no-mergeshapes", false);
-
+		mergeShapesFirst = !props.getProperty("no-merge-first", false);
+		
 		makePOIIndex = props.getProperty("make-poi-index", false);
 
 		if(props.getProperty("poi-address") != null)
@@ -807,6 +817,10 @@
 
 		// We start with one map data source.
 		List<SourceSubdiv> srcList = Collections.singletonList(new SourceSubdiv(src, topdiv));
+		if (mergeShapes && mergeShapesFirst && isOverviewComponent) {
+			mergeShapesFirst(src, levels);
+		}
+		src.getShapes().forEach(s -> s.setMpRel(null)); // free memory for MultipolygonRelations
 
 		// Now the levels filled with features.
 		for (LevelInfo linfo : levels) {
@@ -1234,7 +1248,7 @@
 			MapFilter sizeFilter = new SizeFilter(MIN_SIZE_LINE);
 			normalFilters.addFilter(rounder);
 			normalFilters.addFilter(sizeFilter);
-			double errorForRes = dpFilterLineLevelMap.ceilingEntry(res).getValue();
+			double errorForRes = dpFilterLineResMap.ceilingEntry(res).getValue();
 			if(errorForRes > 0) {
 				DouglasPeuckerFilter dp = new DouglasPeuckerFilter(errorForRes);
 				normalFilters.addFilter(dp);
@@ -1299,7 +1313,7 @@
 				filters.addFilter(new SizeFilter(sizefilterVal));
 			//DouglasPeucker behaves at the moment not really optimal at low zooms, but acceptable.
 			//Is there a similar algorithm for polygons?
-			double errorForRes = dpFilterShapeLevelMap.ceilingEntry(res).getValue();
+			double errorForRes = dpFilterShapeResMap.ceilingEntry(res).getValue();
 			if(errorForRes > 0)
 				filters.addFilter(new DouglasPeuckerFilter(errorForRes));
 		}
@@ -1539,4 +1553,80 @@
 			map.addMapObject(pg);
 		}
 	}
+
+	private void mergeShapesFirst(LoadableMapDataSource src, LevelInfo[] levels) {
+		final LevelInfo maxLevel = levels[levels.length-1];
+		MultiHashMap<MultiPolygonRelation, MapShape> mpShapes = new MultiHashMap<>();
+		src.getShapes().stream().filter(s -> s.getMpRel() != null && s.getMinResolution() < maxLevel.getBits())
+				.forEach(s -> mpShapes.add(s.getMpRel(), s));
+		for (Entry<MultiPolygonRelation, List<MapShape>> e : mpShapes.entrySet()) {
+			long diffMax = e.getValue().stream().mapToInt(MapShape::getMaxResolution).distinct().count();
+			long diffMin = e.getValue().stream().mapToInt(MapShape::getMinResolution).distinct().count();
+			long diffNam = e.getValue().stream().map(MapShape::getName).distinct().count();
+			if (diffMax == 1 && diffMin == 1 && diffNam == 1) {
+				// all shapes from the multipolygon are similar, we can use the original rings
+				for(LevelInfo lvl : levels) {
+					int res = lvl.getBits();
+					MapShape pattern = e.getValue().get(0);
+					if (pattern.getMinResolution() > res)
+						continue;
+					buildMPRing(src, lvl.getBits(), pattern, e.getKey());
+				}
+				e.getValue().forEach(s -> s.setMinResolution(maxLevel.getBits() + 1));
+			}
+		}
+	}
+
+	private void buildMPRing(LoadableMapDataSource src, int res, MapShape pattern, MultiPolygonRelation origMp) {
+		List<? extends Way> rings = origMp.getRings();
+		Way largest = origMp.getLargestOuterRing();
+		int shift = 24 - res;
+		final int minSize = (1 << shift) / 2;
+		GeneralRelation gr = new GeneralRelation(FakeIdGenerator.makeFakeId());
+		java.util.Map<Long, Way> wayMap = new LinkedHashMap<>();
+
+		for (int i = 0; i < rings.size(); i++) {
+			List<Coord> poly = new ArrayList<>(rings.get(i).getPoints());
+			boolean mustUseIt = largest == rings.get(i) && pattern.isSkipSizeFilter();
+			if (!mustUseIt && minSize > 0 && Area.getBBox(poly).getMaxDimension() < minSize)
+				continue;
+			Way w = new Way(FakeIdGenerator.makeFakeId(), poly);
+			wayMap.put(w.getId(), w);
+			gr.addElement("", w);
+
+		}
+		List<List<Coord>> list = new ArrayList<>();
+		if (gr.getElements().isEmpty()) {
+			// nothing to do for this level
+		} else if (gr.getElements().size() == 1) {
+			list.add(largest.getPoints());
+		} else {
+			final String codeValue = GType.formatType(pattern.getType());
+			gr.addTag("code", codeValue);
+			MultiPolygonRelation mp = new MultiPolygonRelation(gr, wayMap, Area.PLANET);
+			mp.processElements();
+			for (Way w : wayMap.values()) {
+				if (MultiPolygonRelation.STYLE_FILTER_POLYGON.equals(w.getTag(MultiPolygonRelation.STYLE_FILTER_TAG))
+						&& codeValue.equals(w.getTag("code"))) {
+					if (src.getBounds().contains(Area.getBBox(w.getPoints()))) {
+						list.add(w.getPoints());
+					} else {
+						list.addAll(ShapeSplitter.clipToBounds(w.getPoints(), src.getBounds(), null));
+					}
+				}
+			}
+		}
+
+		for (int i = 0; i < list.size(); i++) {
+			List<Coord> poly = list.get(i);
+			MapShape newShape = pattern.copy();
+
+			newShape.setPoints(poly);
+			newShape.setOsmid(FakeIdGenerator.makeFakeId());
+			newShape.setMinResolution(res);
+			newShape.setMaxResolution(res);
+			newShape.setMpRel(null);
+			src.getShapes().add(newShape);
+		}
+	}
 }
Index: src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(working copy)
@@ -59,7 +59,11 @@
 	public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
 		this.resolution = resolution;
 		this.orderByDecreasingArea = orderByDecreasingArea;
-		maxPoints = PolygonSplitterFilter.MAX_POINT_IN_ELEMENT;
+		int shift = 24 - resolution;
+		if (resolution < 24)
+			maxPoints = PolygonSplitterFilter.MAX_POINT_IN_ELEMENT * (1<<shift);
+		else 
+			maxPoints = PolygonSplitterFilter.MAX_POINT_IN_ELEMENT;
 	}
 
 	/**
Index: src/uk/me/parabola/mkgmap/general/MapShape.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/MapShape.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/general/MapShape.java	(working copy)
@@ -17,6 +17,7 @@
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
 
 /**
  * A shape or polygon is just the same as a line really as far as I can tell.
@@ -25,8 +26,10 @@
  * @author Steve Ratcliffe.
  */
 public class MapShape extends MapLine {// So top code can link objects from here
-	private long osmid; //TODO: remove debug aid
+	private long osmid; 
 	private long fullArea = Long.MAX_VALUE; // meaning unset
+	private MultiPolygonRelation mpRel;
+	
 	public MapShape() {
 		osmid = 0;
 	}
@@ -37,12 +40,15 @@
 		super(s);
 		this.osmid = s.osmid;
 		this.fullArea = s.getFullArea();
+		this.mpRel = s.mpRel;
 	}
 
+	@Override
 	public MapShape copy() {
 		return new MapShape(this);
 	}
 
+	@Override
 	public void setDirection(boolean direction) {
 		throw new IllegalArgumentException(
 				"can't set a direction on a polygon");
@@ -68,5 +74,18 @@
 		}
 		return this.fullArea;
 	}
+	
+	/**
+	 * @return the mpRel, null if the shape was not created from a multipolygon 
+	 */
+	public MultiPolygonRelation getMpRel() {
+		return mpRel;
+	}
 
+	/**
+	 * @param mpRel the mpRel to set
+	 */
+	public void setMpRel(MultiPolygonRelation mpRel) {
+		this.mpRel = mpRel;
+	}
 }
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(working copy)
@@ -451,7 +451,7 @@
 			if (areaVal == 0)
 				areaVal = way.getFullArea();
 			shape.setFullArea(areaVal);
-
+			shape.setMpRel(way.getMpRel());
 			clipper.clipShape(shape, collector);
 		}
 
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java	(working copy)
@@ -67,8 +67,8 @@
 	private Map<Long, Way> mpPolygons = new LinkedHashMap<>();
 	
 	
-	protected ArrayList<BitSet> containsMatrix;
-	protected ArrayList<JoinedWay> polygons;
+	protected List<BitSet> containsMatrix;
+	protected List<JoinedWay> polygons;
 	protected Set<JoinedWay> intersectingPolygons;
 	
 	protected double largestSize;
@@ -373,7 +373,7 @@
 	 * @param maxCloseDist max distance between ends for artificial close
 	 * 
 	 */
-	protected void closeWays(ArrayList<JoinedWay> wayList, double maxCloseDist) {
+	protected void closeWays(List<JoinedWay> wayList, double maxCloseDist) {
 		for (JoinedWay way : wayList) {
 			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
 				continue;
@@ -562,7 +562,7 @@
 	 * @param wayList
 	 *            list of ways
 	 */
-	protected void removeUnclosedWays(ArrayList<JoinedWay> wayList) {
+	protected void removeUnclosedWays(List<JoinedWay> wayList) {
 		Iterator<JoinedWay> it = wayList.iterator();
 		boolean firstWarn = true;
 		while (it.hasNext()) {
@@ -599,7 +599,7 @@
 	 * This reduces error messages from problems on the tile bounds.
 	 * @param wayList list of ways
 	 */
-	protected void removeWaysOutsideBbox(ArrayList<JoinedWay> wayList) {
+	protected void removeWaysOutsideBbox(List<JoinedWay> wayList) {
 		ListIterator<JoinedWay> wayIter = wayList.listIterator();
 		while (wayIter.hasNext()) {
 			JoinedWay w = wayIter.next();
@@ -976,6 +976,7 @@
 
 					MultiPolygonCutter cutter = new MultiPolygonCutter(this, tileArea, commonCoordMap);
 					singularOuterPolygons = cutter.cutOutInnerPolygons(currentPolygon.polygon, innerWays);
+					singularOuterPolygons.forEach(s -> s.setMpRel(this));
 				}
 				
 				if (!singularOuterPolygons.isEmpty()) {
@@ -1272,7 +1273,7 @@
 		commonCoordMap = null;
 		roleMap.clear();
 		containsMatrix = null;
-		polygons = null;
+//		polygons = null;
 		tileArea = null;
 		intersectingPolygons = null;
 		outerWaysForLineTagging = null;
@@ -1284,7 +1285,7 @@
 		outerPolygons = null;
 		taggedOuterPolygons = null;
 		
-		largestOuterPolygon = null;
+//		largestOuterPolygon = null;
 	}
 
 	/**
@@ -2186,4 +2187,12 @@
 			return polygon + "_" + outer;
 		}
 	}
+
+	public Way getLargestOuterRing() {
+		return largestOuterPolygon;
+	}
+	
+	public List<JoinedWay> getRings() {
+		return polygons;
+	}
 }
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java	(revision 4756)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java	(working copy)
@@ -35,6 +35,7 @@
 	private static final Logger log = Logger.getLogger(Way.class);
 	private final List<Coord> points;
 	private long fullArea = Long.MAX_VALUE; // meaning unset
+	private MultiPolygonRelation mpRel; 
 
 	// This will be set if a way is read from an OSM file and the first node is the same node as the last
 	// one in the way. This can be set to true even if there are missing nodes and so the nodes that we
@@ -67,6 +68,7 @@
 		dup.complete = this.complete;
 		dup.isViaWay = this.isViaWay;
 		dup.fullArea = this.getFullArea();
+		dup.mpRel = this.mpRel;
 		return dup;
 	}
 
@@ -302,4 +304,18 @@
 		}
 		return length;
 	}
+
+	/**
+	 * @return the mpRel, null if the way was not created from a multipolygon with inner rings
+	 */
+	public MultiPolygonRelation getMpRel() {
+		return mpRel;
+	}
+
+	/**
+	 * @param mpRel the mpRel to set
+	 */
+	public void setMpRel(MultiPolygonRelation mpRel) {
+		this.mpRel = mpRel;
+	}
 }
