[Git][debian-gis-team/mkgmap][upstream] New upstream version 0.0.0+svn4897

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Tue Mar 1 21:23:44 GMT 2022



Bas Couwenberg pushed to branch upstream at Debian GIS Project / mkgmap


Commits:
177b18df by Bas Couwenberg at 2022-03-01T22:11:51+01:00
New upstream version 0.0.0+svn4897
- - - - -


24 changed files:

- resources/mkgmap-version.properties
- resources/styles/default/lines
- src/uk/me/parabola/imgfmt/app/Section.java
- src/uk/me/parabola/imgfmt/app/lbl/PlacesHeader.java
- src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
- src/uk/me/parabola/imgfmt/app/mdr/Mdr15.java
- src/uk/me/parabola/imgfmt/app/mdr/Mdr1SubHeader.java
- src/uk/me/parabola/imgfmt/app/net/NODHeader.java
- src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
- src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
- src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
- src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
- src/uk/me/parabola/log/Logger.java
- src/uk/me/parabola/mkgmap/build/MapBuilder.java
- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
- src/uk/me/parabola/mkgmap/reader/osm/Relation.java
- src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
- src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
- src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
- src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
- src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
- src/uk/me/parabola/mkgmap/reader/osm/xml/OsmXmlHandler.java
- src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java
- src/uk/me/parabola/util/GpxCreator.java


Changes:

=====================================
resources/mkgmap-version.properties
=====================================
@@ -1,2 +1,2 @@
-svn.version: 4885
-build.timestamp: 2022-01-30T08:18:40+0000
+svn.version: 4897
+build.timestamp: 2022-02-25T06:32:43+0000


=====================================
resources/styles/default/lines
=====================================
@@ -227,7 +227,8 @@ highway=rest_area | highway=services {set tmp:stopMopUp=yes}
 # It is considered best not to process unrecognised highway types
 #highway=* & area!=yes & tmp:stopMopUp!=yes [0x07 road_class=0 road_speed=0 resolution 23]
 
-natural=coastline [0x15 resolution 12]
+# Showing the coastline adds almost nothing visuals of the map, so it's disabled:
+#natural=coastline [0x15 resolution 12]
 power=line [0x29 resolution 21]
 
 railway=platform & tmp:stopMopUp!=yes [0x16 road_class=0 road_speed=0 resolution 23]


=====================================
src/uk/me/parabola/imgfmt/app/Section.java
=====================================
@@ -108,7 +108,7 @@ public class Section {
 		return size/itemSize;
 	}
 
-	protected int getExtraValue() {
+	public int getExtraValue() {
 		return extraValue;
 	}
 
@@ -117,10 +117,16 @@ public class Section {
 	}
 
 	public void readSectionInfo(ImgFileReader reader, boolean withItemSize) {
+		readSectionInfo(reader, withItemSize, false);
+	}
+
+	public void readSectionInfo(ImgFileReader reader, boolean withItemSize, boolean withExtraValue) {
 		setPosition(reader.get4());
 		setSize(reader.get4());
 		if (withItemSize)
 			setItemSize(reader.get2u());
+		if (withExtraValue)
+			setExtraValue(reader.get4());
 	}
 
 	public SectionWriter makeSectionWriter(ImgFileWriter writer) {


=====================================
src/uk/me/parabola/imgfmt/app/lbl/PlacesHeader.java
=====================================
@@ -122,19 +122,15 @@ public class PlacesHeader {
 	}
 
 	void readFileHeader(ImgFileReader reader) {
-		reader.position(0x1fL + reader.getGMPOffset());
+		reader.position(reader.getGMPOffset() + 0x1fL);
 
-		country.readSectionInfo(reader, true);
-		reader.get4();
+		country.readSectionInfo(reader, true, true);
 
-		region.readSectionInfo(reader, true);
-		reader.get4();
+		region.readSectionInfo(reader, true, true);
 
-		city.readSectionInfo(reader, true);
-		reader.get4();
+		city.readSectionInfo(reader, true, true);
 
-		poiIndex.readSectionInfo(reader, true);
-		reader.get4();
+		poiIndex.readSectionInfo(reader, true, true);
 
 		poiProperties.readSectionInfo(reader, false);
 		reader.get(); // offset multiplier
@@ -143,20 +139,15 @@ public class PlacesHeader {
 		reader.get2u();
 		reader.get();
 
-		poiTypeIndex.readSectionInfo(reader, true);
-		reader.get4();
+		poiTypeIndex.readSectionInfo(reader, true, true);
 
-		zip.readSectionInfo(reader, true);
-		reader.get4();
+		zip.readSectionInfo(reader, true, true);
 
-		highway.readSectionInfo(reader, true);
-		reader.get4();
+		highway.readSectionInfo(reader, true, true);
 
-		exitFacility.readSectionInfo(reader, true);
-		reader.get4();
+		exitFacility.readSectionInfo(reader, true, true);
 
-		highwayData.readSectionInfo(reader, true);
-		reader.get4();
+		highwayData.readSectionInfo(reader, true, true);
 	}
 
 	int getLastPos() {


=====================================
src/uk/me/parabola/imgfmt/app/mdr/MDRFile.java
=====================================
@@ -443,6 +443,8 @@ public class MDRFile extends ImgFile {
 	 * @return An offset value.
 	 */
 	private int createString(String str) {
+		if (forDevice)
+			return -1;
 		return mdr15.createString(str);
 	}
 }


=====================================
src/uk/me/parabola/imgfmt/app/mdr/Mdr15.java
=====================================
@@ -49,7 +49,11 @@ public class Mdr15 extends MdrSection {
 		setConfig(config);
 
 		charset = config.getSort().getCharset();
-
+		if (config.isForDevice()) {
+			tempFile = null;
+			stringFile = null;
+			return;
+		}
 		try {
 			tempFile = File.createTempFile("strings", null, config.getOutputDir());
 			tempFile.deleteOnExit();
@@ -98,7 +102,9 @@ public class Mdr15 extends MdrSection {
 	public void releaseMemory() {
 		strings = null;
 		try {
-			stringFile.close();
+			if (stringFile != null) {
+				stringFile.close();
+			}
 		} catch (IOException e) {
 			throw new MapFailedException("Could not close string temporary file");
 		}


=====================================
src/uk/me/parabola/imgfmt/app/mdr/Mdr1SubHeader.java
=====================================
@@ -52,11 +52,7 @@ public class Mdr1SubHeader {
 				writer.put4(section.getPosition());
 			else {
 				writer.put4(section.getPosition());
-				int size = section.getSize();
-				if (size == 0)
-					writer.put4(0);
-				else
-					writer.put4(size / section.getItemSize());
+				writer.put4(section.getNumItems());
 			}
 		}
 	}


=====================================
src/uk/me/parabola/imgfmt/app/net/NODHeader.java
=====================================
@@ -72,10 +72,8 @@ public class NODHeader extends CommonHeader {
         align = reader.get1u();
         mult1 = reader.get1u();
         tableARecordLen = reader.get2u();
-        roads.readSectionInfo(reader, false);
-        reader.get4();
-        boundary.readSectionInfo(reader, true);
-		reader.get4();
+        roads.readSectionInfo(reader, false, true);
+        boundary.readSectionInfo(reader, true, true);
 		if (getHeaderLength() > 0x3f) {
 			highClassBoundary.readSectionInfo(reader, false);
 


=====================================
src/uk/me/parabola/imgfmt/app/trergn/RGNFileReader.java
=====================================
@@ -295,7 +295,7 @@ public class RGNFileReader extends ImgReader {
 		byte[] bitstream = reader.get(len);
 		BitReader br = new BitReader(bitstream);
 		// This reads the bit stream and adds all the points found
-		readBitStream(br, div, line, extra, len, base);
+		readBitStream(br, div.getShift(), line, extra, len, base);
 	}
 
 
@@ -336,7 +336,7 @@ public class RGNFileReader extends ImgReader {
 		BitReader br = new BitReader(bitstream);
 	
 		// This reads the bit stream and adds all the points found, 
-		readBitStream(br, div, line, false, len, base);
+		readBitStream(br, div.getShift(), line, false, len, base);
 	
 		if (hasLabel){
 			int labelOffset = reader.get3u();
@@ -379,13 +379,13 @@ public class RGNFileReader extends ImgReader {
 	/**
 	 * Read the bit stream for a single line in the file.
 	 * @param br The bit stream reader.
-	 * @param div The subdivision that the line is in.
+	 * @param shift delta values are shifted to the left
 	 * @param line The line itself.
 	 * @param extra True if there is an 'extra' bit in the stream. Used for nodes.
 	 * @param len The length of the stream.
 	 * @param base The base size of the deltas.
 	 */
-	private void readBitStream(BitReader br, Subdivision div, Polyline line, boolean extra, int len, int base) {
+	private void readBitStream(BitReader br, final int shift, Polyline line, boolean extra, int len, int base) {
 		int currLat = line.getLat();
 		int currLon = line.getLong();
 
@@ -472,8 +472,8 @@ public class RGNFileReader extends ImgReader {
 			if (!isnode && dx == 0 && dy == 0)
 				continue;
 			
-			currLat += dy << (24 - div.getResolution());
-			currLon += dx << (24 - div.getResolution());
+			currLat += dy << shift;
+			currLon += dx << shift;
 			Coord coord;
 			if (isnode)
 				coord = new CoordNode(currLat, currLon, 0/* XXX */, false, false);


=====================================
src/uk/me/parabola/imgfmt/app/trergn/Subdivision.java
=====================================
@@ -565,17 +565,18 @@ public class Subdivision {
 	 * Corresponds to {@link #writeExtTypeOffsetsRecord(ImgFileWriter)} 
 	 * @param reader the reader
 	 * @param sdPrev the pred. sub-div or null
+	 * @param size the record size
+	 * @param tre7Magic the magic value from TRE7 section 
 	 */
-	public void readExtTypeOffsetsRecord(ImgFileReader reader,
-			Subdivision sdPrev, int size) {
-		extTypeAreasOffset = reader.get4();
-		extTypeLinesOffset = reader.get4();
-		extTypePointsOffset = reader.get4();
-		if (size > 12) {
-			reader.get();  // kinds
-		}
-		if (size > 13)
-			reader.get(size-13);
+	public void readExtTypeOffsetsRecord(ImgFileReader reader, Subdivision sdPrev, int size, int tre7Magic) {
+		long nextPos = reader.position() + size;
+		if ((tre7Magic & 0x1) != 0) 
+			extTypeAreasOffset = reader.get4();
+		if ((tre7Magic & 0x2) != 0)
+			extTypeLinesOffset = reader.get4();
+		if ((tre7Magic & 0x4) != 0)
+			extTypePointsOffset = reader.get4();
+		reader.position(nextPos); 
 		assert extTypeAreasOffset >= 0;
 		assert extTypeLinesOffset >= 0;
 		assert extTypePointsOffset >= 0;
@@ -584,25 +585,29 @@ public class Subdivision {
 			sdPrev.extTypeAreasSize = extTypeAreasOffset - sdPrev.extTypeAreasOffset;
 			sdPrev.extTypeLinesSize = extTypeLinesOffset - sdPrev.extTypeLinesOffset;
 			sdPrev.extTypePointsSize = extTypePointsOffset - sdPrev.extTypePointsOffset;
-			assert extTypeAreasSize >= 0;
-			assert extTypeLinesSize >= 0;
-			assert extTypePointsSize >= 0;
+			assert sdPrev.extTypeAreasSize >= 0;
+			assert sdPrev.extTypeLinesSize >= 0;
+			assert sdPrev.extTypePointsSize >= 0;
 		}
 	}
 	/**
 	 * Set the sizes for the extended type data. See {@link #writeLastExtTypeOffsetsRecord(ImgFileWriter)} 
+	 * @param reader the reader
+	 * @param size the record size
+	 * @param tre7Magic the magic value from TRE7 section 
 	 */
-	public void readLastExtTypeOffsetsRecord(ImgFileReader reader, int size) {
-		extTypeAreasSize = reader.get4() - extTypeAreasOffset;
-		extTypeLinesSize = reader.get4() - extTypeLinesOffset;
-		extTypePointsSize = reader.get4() - extTypePointsOffset;
+	public void readLastExtTypeOffsetsRecord(ImgFileReader reader, int size, int tre7Magic) {
+		long nextPos = reader.position() + size;
+		if ((tre7Magic & 0x1) != 0) 
+			extTypeAreasSize = reader.get4() - extTypeAreasOffset;
+		if ((tre7Magic & 0x2) != 0) 
+			extTypeLinesSize = reader.get4() - extTypeLinesOffset;
+		if ((tre7Magic & 0x4) != 0) 
+			extTypePointsSize = reader.get4() - extTypePointsOffset;
 		assert extTypeAreasSize >= 0;
 		assert extTypeLinesSize >= 0;
 		assert extTypePointsSize >= 0;
-		if (size > 12) {
-			byte test = reader.get();
-			assert test == 0;
-		}
+		reader.position(nextPos);
 	}
 	
 	/**


=====================================
src/uk/me/parabola/imgfmt/app/trergn/TREFileReader.java
=====================================
@@ -26,6 +26,7 @@ import uk.me.parabola.imgfmt.app.labelenc.CodeFunctions;
 import uk.me.parabola.imgfmt.app.labelenc.DecodedText;
 import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
 import uk.me.parabola.imgfmt.fs.ImgChannel;
+import uk.me.parabola.log.Logger;
 import uk.me.parabola.util.EnhancedProperties;
 
 /**
@@ -47,7 +48,7 @@ public class TREFileReader extends ImgReader {
 	private static final Subdivision[] EMPTY_SUBDIVISIONS = new Subdivision[0];
 
 	private final TREHeader header = new TREHeader();
-
+	private int tre7Magic;
 
 	public TREFileReader(ImgChannel chan) {
 		this(chan, 0);
@@ -58,6 +59,7 @@ public class TREFileReader extends ImgReader {
 
 		setReader(new BufferedImgFileReader(chan, gmpOffset));
 		header.readHeader(getReader());
+		tre7Magic = header.getTre7Magic();
 		readMapLevels();
 		readSubdivs();
 		readExtTypeOffsetsRecords();
@@ -142,14 +144,38 @@ public class TREFileReader extends ImgReader {
 	 * Read the extended type info for the sub divisions. Corresponds to {@link TREFile#writeExtTypeOffsetsRecords()}.
 	 */
 	private void readExtTypeOffsetsRecords() {
+		if ((tre7Magic & 7) == 0) {
+			return;
+		}
 		ImgFileReader reader = getReader();
 		int start = header.getExtTypeOffsetsPos();
 		int end = start + header.getExtTypeOffsetsSize();
-			
+		int recSize = header.getExtTypeSectionSize();
+		if (recSize == 0)
+			return;
 		reader.position(start);
 		Subdivision sd = null;
 		Subdivision sdPrev = null;
-		for (int count = 0; count < levelDivs.length && reader.position() < end; count++) {
+		if (header.getExtTypeOffsetsSize() % recSize != 0) {
+			Logger.defaultLogger.error("TRE7 data seems to have varying length records, don't know how to read extended types offsets");
+			return;
+			
+		}
+		int available = header.getExtTypeOffsetsSize() / recSize;
+		// with record size > 13 there may be no data for the first level(s).
+		int firstDivIndex = 0;
+		for (int i = levelDivs.length-1; i >= 0; i--) {
+			available -= levelDivs[i].length;
+			if (available < 0) {
+				Logger.defaultLogger.error("TRE7 data contains unexpected values, don't know how to read extended types offsets");
+				return;
+			}
+			if (available == 1) {
+				firstDivIndex = i;
+				break;
+			}
+		}
+		for (int count = firstDivIndex; count < levelDivs.length && reader.position() < end; count++) {
 			Subdivision[] divs = levelDivs[count];
 			if (divs == null)
 				break;
@@ -157,13 +183,13 @@ public class TREFileReader extends ImgReader {
 			for (int i = 0; i < divs.length; i++) {
 				sdPrev = sd;
 				sd = divs[i];
-				sd.readExtTypeOffsetsRecord(reader, sdPrev, header.getExtTypeSectionSize());
+				sd.readExtTypeOffsetsRecord(reader, sdPrev, recSize, tre7Magic);
 			}
 		}
 		if(sd != null && reader.position() < end) {
-			sd.readLastExtTypeOffsetsRecord(reader, header.getExtTypeSectionSize());
+			sd.readLastExtTypeOffsetsRecord(reader, recSize, tre7Magic);
 		}
-		
+		assert reader.position() == end : "Failed to read TRE7"; 
 	}
 
 
@@ -224,7 +250,7 @@ public class TREFileReader extends ImgReader {
 
 		// First do the ones in the TRE header gap
 		ImgFileReader reader = getReader();
-		reader.position(header.getHeaderLength());
+		reader.position(reader.getGMPOffset() + header.getHeaderLength());
 		List<String> msgs = new ArrayList<>();
 		while (reader.position() < header.getHeaderLength() + header.getMapInfoSize()) {
 			byte[] m = reader.getZString();
@@ -293,5 +319,8 @@ public class TREFileReader extends ImgReader {
 			data[i] = (byte) (((upper << 4) & 0xf0) | (lower & 0xf));
 		}
 	}
-	 	
+	 
+	public int getTre7Magic() {
+		return tre7Magic;
+	}
 }


=====================================
src/uk/me/parabola/imgfmt/app/trergn/TREHeader.java
=====================================
@@ -16,11 +16,10 @@
  */
 package uk.me.parabola.imgfmt.app.trergn;
 
-import uk.me.parabola.imgfmt.Utils;
-
 import java.io.IOException;
 
 import uk.me.parabola.imgfmt.ReadFailedException;
+import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.CommonHeader;
 import uk.me.parabola.imgfmt.app.ImgFileReader;
@@ -127,8 +126,7 @@ public class TREHeader extends CommonHeader {
 		subdivPos = reader.get4();
 		subdivSize = reader.get4();
 
-		copyright.readSectionInfo(reader, true);
-		reader.get4();
+		copyright.readSectionInfo(reader, true, true);
 
 		poiDisplayFlags = reader.get1u();
 		displayPriority = reader.get3u();
@@ -136,12 +134,9 @@ public class TREHeader extends CommonHeader {
 		reader.get2u();
 		reader.get();
 
-		polyline.readSectionInfo(reader, true);
-		reader.get4();
-		polygon.readSectionInfo(reader, true);
-		reader.get4();
-		points.readSectionInfo(reader, true);
-		reader.get4();
+		polyline.readSectionInfo(reader, true, true);
+		polygon.readSectionInfo(reader, true, true);
+		points.readSectionInfo(reader, true, true);
 
 		int mapInfoOff = mapLevelPos;
 		if (subdivPos < mapInfoOff)
@@ -151,12 +146,13 @@ public class TREHeader extends CommonHeader {
 
 		mapInfoSize = mapInfoOff - getHeaderLength();
 		if (getHeaderLength() > 116) {
-			reader.position(116);
+			assert reader.position() == reader.getGMPOffset() + 116;
 			mapId = reader.get4();
 		}
 		if (getHeaderLength() > 120) {
 			reader.get4();
-			extTypeOffsets.readSectionInfo(reader, true);
+			assert reader.position() == reader.getGMPOffset() + 124;
+			extTypeOffsets.readSectionInfo(reader, true, true);
 		}
 	}
 
@@ -322,7 +318,6 @@ public class TREHeader extends CommonHeader {
 	}
 
 	public void setCopyrightPos(int copyrightPos) {
-		//this.copyrightPos = copyrightPos;
 		copyright.setPosition(copyrightPos);
 	}
 
@@ -400,13 +395,19 @@ public class TREHeader extends CommonHeader {
 	public int getExtTypeOffsetsPos() {
 		return extTypeOffsets.getPosition();
 	}
+
 	public int getExtTypeOffsetsSize() {
 		return extTypeOffsets.getSize();
 	}
+
 	public int getExtTypeSectionSize() {
 		return extTypeOffsets.getItemSize();
 	}
 
+	public int getTre7Magic() {
+		return extTypeOffsets.getExtraValue();
+	}
+
 	public Section getCopyrightSection() {
 		return copyright;
 	}


=====================================
src/uk/me/parabola/log/Logger.java
=====================================
@@ -106,7 +106,7 @@ public class Logger {
 
 	/**
 	 * The default setup, which is basically not to do any logging apart from
-	 * showing warnings and errors (and I may remove that).
+	 * showing errors.
 	 */
 	private static void staticSetup() {
 		// Static setup.


=====================================
src/uk/me/parabola/mkgmap/build/MapBuilder.java
=====================================
@@ -90,8 +90,8 @@ import uk.me.parabola.mkgmap.filters.RemoveEmpty;
 import uk.me.parabola.mkgmap.filters.RemoveObsoletePointsFilter;
 import uk.me.parabola.mkgmap.filters.RoundCoordsFilter;
 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
-import uk.me.parabola.mkgmap.filters.SizeFilter;
 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter.MapShapeComparator;
+import uk.me.parabola.mkgmap.filters.SizeFilter;
 import uk.me.parabola.mkgmap.general.CityInfo;
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
@@ -1637,6 +1637,7 @@ public class MapBuilder implements Configurable {
 		} else {
 			final String codeValue = GType.formatType(pattern.getType());
 			gr.addTag("code", codeValue);
+			gr.addTag("expect-self-intersection", "true");
 			MultiPolygonRelation mp = new MultiPolygonRelation(gr, wayMap, src.getBounds());
 			mp.processElements();
 			for (Way w : wayMap.values()) {


=====================================
src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
=====================================
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011-2014.
+ * Copyright (C) 2011-2021.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 3 or
@@ -13,7 +13,6 @@
 
 package uk.me.parabola.mkgmap.reader.osm;
 
-import java.awt.Polygon;
 import java.awt.Rectangle;
 import java.awt.geom.Area;
 import java.awt.geom.Line2D;
@@ -27,6 +26,7 @@ import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Locale;
@@ -39,9 +39,13 @@ import java.util.logging.Level;
 import java.util.stream.Collectors;
 
 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+import uk.me.parabola.imgfmt.ExitException;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.IsInUtil;
 import uk.me.parabola.util.Java2DConverter;
+import uk.me.parabola.util.MultiIdentityHashMap;
+import uk.me.parabola.util.ShapeSplitter;
 
 /**
  * Representation of an OSM Multipolygon Relation.<br/>
@@ -56,27 +60,31 @@ public class MultiPolygonRelation extends Relation {
 	public static final String STYLE_FILTER_TAG = "mkgmap:stylefilter";
 	public static final String STYLE_FILTER_LINE = "polyline";
 	public static final String STYLE_FILTER_POLYGON = "polygon";
-	
+
 	/** A tag that is set with value true on each polygon that is created by the mp processing. */
 	public static final short TKM_MP_CREATED = TagDict.getInstance().xlate("mkgmap:mp_created");
 	private static final short TKM_MP_ROLE = TagDict.getInstance().xlate("mkgmap:mp_role");
 	private static final short TKM_CACHE_AREA_SIZEKEY = TagDict.getInstance().xlate("mkgmap:cache_area_size");
-	private final Map<Long, Way> tileWayMap;
-	private final Map<Long, String> roleMap = new HashMap<>();
- 
-	private Map<Long, Way> mpPolygons = new LinkedHashMap<>();
 	
+	public static final String ROLE_OUTER = "outer";  
+	public static final String ROLE_INNER = "inner";  
+
+	private static final byte INT_ROLE_NULL = 1; 
+	private static final byte INT_ROLE_INNER = 2; 
+	private static final byte INT_ROLE_OUTER = 4; 
+	private static final byte INT_ROLE_BLANK = 8; 
+	private static final byte INT_ROLE_OTHER = 16; 
+
+	/** maps ids to ways, will be extended with joined ways */
+	private final Map<Long, Way> tileWayMap; // never clear!
 	
-	protected List<BitSet> containsMatrix;
+
 	protected List<JoinedWay> polygons;
-	protected Set<JoinedWay> intersectingPolygons;
-	
-	protected double largestSize;
+	private Map<Long, Way> mpPolygons = new LinkedHashMap<>();
+
 	protected JoinedWay largestOuterPolygon;
-	private Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>(); 
-	
+	private Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>();
 	protected Set<Way> outerWaysForLineTagging;
-	protected Map<String, String> outerTags;
 
 	private final uk.me.parabola.imgfmt.app.Area tileBounds;
 	private Area tileArea;
@@ -84,16 +92,11 @@ public class MultiPolygonRelation extends Relation {
 	private Coord cOfG = null;
 	
 	// the sum of all outer polygons area size 
-	private double mpAreaSize = 0;
+	private double mpAreaSize;
 	
-	/** 
-	 * A point that has a lower or equal squared distance from 
-	 * a line is treated as if it lies one the line.<br/>
-	 * 1.0d is very exact. 2.0d covers rounding problems when converting
-	 * OSM locations to mkgmap internal format. A larger value 
-	 * is more tolerant against imprecise OSM data.
-	 */
-	private static final double OVERLAP_TOLERANCE_DISTANCE = 2.0d;
+	private boolean noRecalc;
+
+	private boolean renderingFailed;
 	
 	/**
 	 * Create an instance based on an existing relation. We need to do this
@@ -107,8 +110,7 @@ public class MultiPolygonRelation extends Relation {
 	 * @param bbox
 	 *            The bounding box of the tile
 	 */
-	public MultiPolygonRelation(Relation other, Map<Long, Way> wayMap,
-			uk.me.parabola.imgfmt.app.Area bbox) {
+	public MultiPolygonRelation(Relation other, Map<Long, Way> wayMap, uk.me.parabola.imgfmt.app.Area bbox) {
 		this.tileWayMap = wayMap;
 		this.tileBounds = bbox;
 		// create an Area for the bbox to clip the polygons
@@ -116,24 +118,10 @@ public class MultiPolygonRelation extends Relation {
 
 		setId(other.getId());
 		copyTags(other);
-		this.setTagsIncomplete(other.getTagsIncomplete());
 
+		other.getElements().forEach(e -> addElement(e.getKey(), e.getValue()));
 		if (log.isDebugEnabled()) {
-			log.debug("Construct multipolygon", toBrowseURL(), toTagString());
-		}
-
-		for (Map.Entry<String, Element> pair : other.getElements()) {
-			String role = pair.getKey();
-			Element el = pair.getValue();
-			if (log.isDebugEnabled()) {
-				log.debug(" ", role, el.toBrowseURL(), el.toTagString());
-			}
-			if (roleMap.containsKey(el.getId()) )
-				log.warn("repeated member with id", el.getId(), "in multipolygon relation", this.getId(), "is ignored");
-			else {
-				addElement(role, el);
-				roleMap.put(el.getId(), role);
-			}
+			log.debug("Constructed multipolygon", toBrowseURL(), toTagString());
 		}
 	}
 	
@@ -153,347 +141,304 @@ public class MultiPolygonRelation extends Relation {
 	}
 	
 	/**
-	 * Retrieves the mp role of the given element.
+	 * Retrieves the role of the given element based on the role in the MP.
 	 * 
-	 * @param element
-	 *            the element
-	 * @return the role of the element
+	 * @param jw the element
+	 * @return either ROLE_INNER, ROLE_OUTER or null.
 	 */
-	protected String getRole(Element element) {
-		String role = roleMap.get(element.getId());
-		if (role != null && ("outer".equals(role) || "inner".equals(role))) {
-			return role;
-		}
-
+	private static String getRole(JoinedWay jw) {
+		if (jw.intRole == INT_ROLE_INNER)
+			return ROLE_INNER;
+		if (jw.intRole == INT_ROLE_OUTER)
+			return ROLE_OUTER;
 		return null;
 	}
 
 	/**
-	 * Try to join the two ways.
+	 * Combine a list of way segments to a list of maximally joined ways. There are
+	 * lots of possible ways to do this but the result should be predictable, so
+	 * that multiple runs of mkgmap produce the same polygons.
 	 * 
-	 * @param joinWay
-	 *            the way to which tempWay is added in case both ways could be
-	 *            joined and checkOnly is false.
-	 * @param tempWay
-	 *            the way to be added to joinWay
-	 * @param checkOnly
-	 *            <code>true</code> checks only and does not perform the join
-	 *            operation
-	 * @return <code>true</code> if tempWay way is (or could be) joined to
-	 *         joinWay
+	 * @return a list of maximally joined ways
 	 */
-	private static boolean joinWays(JoinedWay joinWay, JoinedWay tempWay, boolean checkOnly) {
-		boolean reverseTempWay = false;
-		int insIdx = -1;
-		int firstTmpIdx = 1;
-		boolean joinable = false;
+	private List<JoinedWay> joinWays() {
+		List<JoinedWay> joinedWays = new ArrayList<>();
+		List<JoinedWay> unclosedWays = new LinkedList<>();
 		
-		// use == or equals as comparator??
-		if (joinWay.getFirstPoint() == tempWay.getFirstPoint()) {
-			insIdx = 0;
-			reverseTempWay = true;
-			firstTmpIdx = 1;
-			joinable = true;
-		} else if (joinWay.getLastPoint() == tempWay.getFirstPoint()) {
-			insIdx = joinWay.getPoints().size();
-			firstTmpIdx = 1;
-			joinable = true;
-		} else if (joinWay.getFirstPoint() == tempWay.getLastPoint()) {
-			insIdx = 0; 
-			firstTmpIdx = 0;
-			joinable = true;
-		} else if (joinWay.getLastPoint() == tempWay.getLastPoint()) {
-			insIdx = joinWay.getPoints().size();
-			reverseTempWay = true;
-			firstTmpIdx = 0;
-			joinable = true;
-		}
+		parseElements(joinedWays, unclosedWays);
 		
-		if (!checkOnly && joinable){
-			int lastIdx = tempWay.getPoints().size();
-			if (firstTmpIdx == 0) {
-				// the last temp point is already contained in the joined way - do not copy it
-				lastIdx--;
-			}
-					
-			List<Coord> tempCoords = tempWay.getPoints().subList(firstTmpIdx,lastIdx);
-			
-			if (reverseTempWay) {
-				// the remp coords need to be reversed so copy the list
-				tempCoords = new ArrayList<>(tempCoords);
-				// and reverse it
-				Collections.reverse(tempCoords);
+		if (unclosedWays.isEmpty())
+			return joinedWays;
+		
+		if (unclosedWays.size() > 1) {
+			// first try to combine ways in the given order
+			joinInGivenOrder(joinedWays, unclosedWays);
+		}
+		if (unclosedWays.size() == 1) {
+			joinedWays.add(unclosedWays.remove(0));
+		}
+		if (!unclosedWays.isEmpty()) {
+			// members are not fully ordered or we have unclosed rings
+			joinWithIndex(joinedWays, unclosedWays);
+		}
+		joinedWays.addAll(unclosedWays);
+		if(log.isInfoEnabled()) {
+			for (JoinedWay jw : joinedWays) {
+				if (Integer.bitCount(jw.intRole) > 1) {
+					log.info("Joined polygon ways have different roles", this.toBrowseURL(), jw.toString());
+				}
 			}
-			
-			joinWay.getPoints().addAll(insIdx, tempCoords);
-			joinWay.addWay(tempWay);
 		}
-		return joinable;
+		return joinedWays;
 	}
 
 	/**
-	 * Combine a list of way segments to a list of maximally joined ways
+	 * Go through list of elements, do some basic checks and separate the ways into
+	 * closed and unclosed ways. The (last) label node is used to set cOfG.
 	 * 
-	 * @param segments
-	 *            a list of closed or unclosed ways
-	 * @return a list of closed ways
+	 * @param closedWays   list to which closed ways are added
+	 * @param unclosedWays list to which unclosed ways are added
 	 */
-	protected ArrayList<JoinedWay> joinWays(List<Way> segments) {
-		// TODO check if the closed polygon is valid and implement a backtracking algorithm to get other combinations
-
-		ArrayList<JoinedWay> joinedWays = new ArrayList<>();
-		if (segments == null || segments.isEmpty()) {
-			return joinedWays;
-		}
+	private void parseElements(List<JoinedWay> closedWays, List<JoinedWay> unclosedWays) {
+		Map<Long, Way> dupCheck = new HashMap<>();
 
-		// go through all segments and categorize them to closed and unclosed
-		// list
-		ArrayList<JoinedWay> unclosedWays = new ArrayList<>();
-		for (Way orgSegment : segments) {
-			JoinedWay jw = new JoinedWay(orgSegment);
-			roleMap.put(jw.getId(), getRole(orgSegment));
-			if (orgSegment.isClosed()) {
-				if (!orgSegment.isComplete()) {
-					// the way is closed in planet but some points are missing in this tile
-					// we can close it artificially
-					if (log.isDebugEnabled())
-						log.debug("Close incomplete but closed polygon:",orgSegment);
-					jw.closeWayArtificially();
+		for (Map.Entry<String, Element> entry : getElements()) {
+			String role = entry.getKey();
+			Element el = entry.getValue();
+			if (el instanceof Way) {
+				Way wayEl = (Way) el;
+				if (dupCheck.put(wayEl.getId(), wayEl) != null) {
+					log.warn("repeated way member with id", el.getId(), "is ignored in multipolygon relation", toBrowseURL());
+				} else if (wayEl.getPoints().size() <= 1) {
+					log.warn("Way", wayEl, "has", wayEl.getPoints().size(),
+							 "points and cannot be used for the multipolygon", toBrowseURL());
+				} else {
+					JoinedWay jw = new JoinedWay(wayEl, role);
+					if (jw.intRole == INT_ROLE_OTHER) 
+						log.warn("Way role invalid", role, el.toBrowseURL(),
+								 "in multipolygon", toBrowseURL(), toTagString());
+					if (wayEl.isClosedInOSM() && !wayEl.hasIdenticalEndPoints() && !wayEl.isComplete()) {
+						// the way is closed in planet but some points are missing in this tile
+						// we can close it artificially, it is very likely outside of the tile bounds
+						if (log.isDebugEnabled())
+							log.debug("Close incomplete but closed polygon:", wayEl);
+						jw.closeWayArtificially();
+					}
+					if (jw.hasIdenticalEndPoints())
+						closedWays.add(jw);
+					else {
+						unclosedWays.add(jw);
+					}
 				}
-				assert 	jw.hasIdenticalEndPoints() : "way is not closed";
-				joinedWays.add(jw);
+			} else if (el instanceof Node) {
+				if ("label".equals(role))
+					cOfG = ((Node) el).getLocation();
+				else if (!"admin_centre".equals(role)) 
+					log.warn("Node with unknown role is ignored", role, el.toBrowseURL(),
+							 "in multipolygon", toBrowseURL(), toTagString());
 			} else {
-				unclosedWays.add(jw);
+				log.warn("Non Way/Node member with role is ignored", role, el.toBrowseURL(),
+						 "in multipolygon", toBrowseURL(), toTagString());
+			}
+		}
+	}
+
+	/**
+	 * Combines ways in the given order. Closed ways are added to closedWays, unclosed ways remain in unclosed.  
+	 * Stops when two ways cannot be joined in the given order.
+	 * @param closedWays
+	 * @param unclosedWays
+	 */
+	private void joinInGivenOrder(List<JoinedWay> closedWays, List<JoinedWay> unclosedWays) {
+		JoinedWay work = null;
+		while (unclosedWays.size() > 1) {
+			if (work == null)
+				work = unclosedWays.get(0);
+			if (!work.canJoin(unclosedWays.get(1)))
+				break;
+			work.joinWith(unclosedWays.get(1));
+			unclosedWays.remove(1);
+			if (work.hasIdenticalEndPoints()) {
+				closedWays.add(work);
+				unclosedWays.remove(0);
+				work = null;
 			}
 		}
+	}
 
+	private void joinWithIndex(List<JoinedWay> closedWays, List<JoinedWay> unclosedWays) {
+		MultiIdentityHashMap<Coord, JoinedWay> index = new MultiIdentityHashMap<>();
+		unclosedWays.forEach(jw -> {
+			index.add(jw.getFirstPoint(), jw);
+			index.add(jw.getLastPoint(), jw);
+		});
+
+		List<JoinedWay> finishedUnclosed = new ArrayList<>();
 		while (!unclosedWays.isEmpty()) {
 			JoinedWay joinWay = unclosedWays.remove(0);
 
-			// check if the current way is already closed or if it is the last
-			// way
-			if (joinWay.hasIdenticalEndPoints() || unclosedWays.isEmpty()) {
-				joinedWays.add(joinWay);
+			List<JoinedWay> candidates = index.get(joinWay.getLastPoint());
+			if (candidates.size() != 2) {
+				candidates = index.get(joinWay.getFirstPoint());
+			}
+			if (candidates.size() <= 1) {
+				// cannot join further
+				finishedUnclosed.add(joinWay);
+				// no need to maintain index
 				continue;
 			}
-
-			boolean joined = false;
-
-			// if we have a way that could be joined but which has a wrong role
-			// then store it here and check in the end if it's working
-			JoinedWay wrongRoleWay = null;
-			String joinRole = getRole(joinWay);
-
-			// go through all ways and check if there is a way that can be
-			// joined with it
-			// in this case join the two ways
-			// => add all points of tempWay to joinWay, remove tempWay and put
-			// joinWay to the beginning of the list
-			// (not optimal but understandable - can be optimized later)
-			for (JoinedWay tempWay : unclosedWays) {
-				if (tempWay.hasIdenticalEndPoints()) {
-					continue;
-				}
-
-				String tempRole = getRole(tempWay);
-				// if a role is not 'inner' or 'outer' then it is used as
-				// universal
-				// check if the roles of the ways are matching
-				if ((!"outer".equals(joinRole) && !"inner".equals(joinRole))
-						|| (!"outer".equals(tempRole) && !"inner".equals(tempRole))
-						|| (joinRole != null && joinRole.equals(tempRole))) {
-					// the roles are matching => try to join both ways
-					joined = joinWays(joinWay, tempWay, false);
-				} else {
-					// the roles are not matching => test if both ways would
-					// join
-
-					// as long as we don't have an alternative way with wrong
-					// role
-					// or if the alternative way is shorter then check if
-					// the way with the wrong role could be joined
-					if (wrongRoleWay == null || wrongRoleWay.getPoints().size() < tempWay.getPoints().size()
-							&& joinWays(joinWay, tempWay, true)) {
-						// save this way => maybe we will use it in the end
-						// if we don't find any other way
-						wrongRoleWay = tempWay;
-					}
-				}
-
-				if (joined) {
-					// we have joined the way
-					unclosedWays.remove(tempWay);
-					break;
-				}
+			// we will join
+			candidates.remove(joinWay);
+			JoinedWay other = candidates.get(0);
+			if (candidates.size() > 1) {
+				// we have alternatives, prefer one that closes the ring. 
+				other = candidates.stream().filter(joinWay::buildsRingWith).findFirst().orElse(other);
 			}
+			
+			// maintain index, we don't know which node is removed by the joining
+			index.removeMapping(other.getFirstPoint(), other);
+			index.removeMapping(other.getLastPoint(), other);
+			index.removeMapping(joinWay.getFirstPoint(), joinWay);
+			index.removeMapping(joinWay.getLastPoint(), joinWay);
 
-			if (!joined && wrongRoleWay != null) {
-
-				log.warn("Join ways with different roles. Multipolygon: "
-						+ toBrowseURL());
-				log.warn("Way1 Role:", getRole(joinWay));
-				logWayURLs(Level.WARNING, "-", joinWay);
-				log.warn("Way2 Role:", getRole(wrongRoleWay));
-				logWayURLs(Level.WARNING, "-", wrongRoleWay);
-
-				joined = joinWays(joinWay, wrongRoleWay, false);
-				if (joined) {
-					// we have joined the way
-					unclosedWays.remove(wrongRoleWay);
-					break;
-				}
-			}
+			unclosedWays.remove(other); // needs sequential search. Could set a flag in JoinedWay instead
+			joinWay.joinWith(other);
 
-			if (joined) {
-				if (joinWay.hasIdenticalEndPoints()) {
-					// it's closed => don't process it again
-					joinedWays.add(joinWay);
-				} else if (unclosedWays.isEmpty()) {
-					// no more ways to join with
-					// it's not closed but we cannot join it more
-					joinedWays.add(joinWay);
-				} else {
-					// it is not yet closed => process it once again
-					unclosedWays.add(0, joinWay);
-				}
+			if (joinWay.hasIdenticalEndPoints()) {
+				closedWays.add(joinWay);
 			} else {
-				// it's not closed but we cannot join it more
-				joinedWays.add(joinWay);
+				index.add(joinWay.getFirstPoint(), joinWay);
+				index.add(joinWay.getLastPoint(), joinWay);
+				unclosedWays.add(0, joinWay);
 			}
 		}
-
-		return joinedWays;
+		unclosedWays.addAll(finishedUnclosed);
 	}
 
 	/**
-	 * Try to close all unclosed ways in the given list of ways.
+	 * Try to close unclosed way.
 	 * 
-	 * @param wayList
-	 *            a list of ways
-	 * @param maxCloseDist max distance between ends for artificial close
+	 * @param way the joined way
 	 * 
 	 */
-	protected void closeWays(List<JoinedWay> wayList, double maxCloseDist) {
-		for (JoinedWay way : wayList) {
-			if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3) {
-				continue;
-			}
-			Coord p1 = way.getFirstPoint();
-			Coord p2 = way.getLastPoint();
-
-			if (!tileBounds.insideBoundary(p1) && !tileBounds.insideBoundary(p2)
-			// both points lie outside the bbox or on the bbox
-			// check if both points are on the same side of the bounding box
-					&& (p1.getLatitude() <= tileBounds.getMinLat() && p2.getLatitude() <= tileBounds.getMinLat())
-					|| (p1.getLatitude() >= tileBounds.getMaxLat() && p2.getLatitude() >= tileBounds.getMaxLat())
-					|| (p1.getLongitude() <= tileBounds.getMinLong() && p2.getLongitude() <= tileBounds.getMinLong())
-					|| (p1.getLongitude() >= tileBounds.getMaxLong() && p2.getLongitude() >= tileBounds.getMaxLong())) {
-				// they are on the same side outside of the bbox
-				// so just close them without worrying about if
-				// they intersect itself because the intersection also
-				// is outside the bbox
-				way.closeWayArtificially();
-				log.info("Endpoints of way", way, "are both outside the bbox. Closing it directly.");
-				continue;
-			}
+	private void tryCloseSingleWays(JoinedWay way) {
+		if (way.hasIdenticalEndPoints() || way.getPoints().size() < 3)
+			return;
+		
+		Coord p1 = way.getFirstPoint();
+		Coord p2 = way.getLastPoint();
+
+		if ((p1.getLatitude() <= tileBounds.getMinLat() && p2.getLatitude() <= tileBounds.getMinLat())
+				|| (p1.getLatitude() >= tileBounds.getMaxLat() && p2.getLatitude() >= tileBounds.getMaxLat())
+				|| (p1.getLongitude() <= tileBounds.getMinLong() && p2.getLongitude() <= tileBounds.getMinLong())
+				|| (p1.getLongitude() >= tileBounds.getMaxLong() && p2.getLongitude() >= tileBounds.getMaxLong())) {
+			// both points lie outside the bbox or on the bbox and 
+			// they are on the same side of the bbox
+			// so just close them without worrying about if
+			// they intersect itself because the intersection also
+			// is outside the bbox
+			way.closeWayArtificially();
+			log.info("Endpoints of way", way, "are both outside the bbox. Closing it directly.");
+			return;
+		}
+		
+		// calc the distance to close
+		double closeDist = way.getFirstPoint().distance(way.getLastPoint());
+		if (closeDist > getMaxCloseDist()) 
+			return;
 			
-			Line2D closingLine = new Line2D.Double(p1.getHighPrecLon(), 
-					p1.getHighPrecLat(), p2.getHighPrecLon(), p2.getHighPrecLat());
-
-			boolean intersects = false;
-			Coord lastPoint = null;
-			// don't use the first and the last point
-			// the closing line can intersect only in one point or complete.
-			// Both isn't interesting for this check
-			for (Coord thisPoint : way.getPoints().subList(1, way.getPoints().size() - 1)) {
-				if (lastPoint != null && closingLine.intersectsLine(lastPoint.getHighPrecLon(), lastPoint.getHighPrecLat(),
-						thisPoint.getHighPrecLon(), thisPoint.getHighPrecLat())) {
-					intersects = true;
-					break;
-				}
-				lastPoint = thisPoint;
-			}
-
-			if (!intersects) {
-				// close the polygon
-				// the new way segment does not intersect the rest of the polygon
-				boolean doClose = true;
-				if (maxCloseDist > 0) {
-					// calc the distance to close
-					double closeDist = way.getFirstPoint().distance(way.getLastPoint());
-					doClose = closeDist < maxCloseDist;
-				}
-				if (doClose) {
-					if (log.isInfoEnabled()) {
-						log.info("Closing way", way);
-						log.info("from", way.getFirstPoint().toOSMURL());
-						log.info("to", way.getLastPoint().toOSMURL());
-					}
-					// mark this ways as artificially closed
-					way.closeWayArtificially();
-				}
+		// We may get here with boundary preparer, for example when a country extract doesn't contain the 
+		// complete data. It is assumed that the country border is very close to the cutting polygon that was
+		// used for the country extract.
+		Line2D closingLine = new Line2D.Double(p1.getHighPrecLon(), 
+				p1.getHighPrecLat(), p2.getHighPrecLon(), p2.getHighPrecLat());
+
+		boolean intersects = false;
+		Coord lastPoint = null;
+		// don't use the first and the last point
+		// the closing line can intersect only in one point or complete.
+		// Both isn't interesting for this check
+		for (Coord thisPoint : way.getPoints().subList(1, way.getPoints().size() - 1)) {
+			if (lastPoint != null && closingLine.intersectsLine(lastPoint.getHighPrecLon(), lastPoint.getHighPrecLat(),
+					thisPoint.getHighPrecLon(), thisPoint.getHighPrecLat())) {
+				intersects = true;
+				break;
+			}
+			lastPoint = thisPoint;
+		}
+
+		if (!intersects) {
+			// close the polygon
+			// the new way segment does not intersect the rest of the polygon
+			if (log.isInfoEnabled()) {
+				log.info("Closing way", way);
+				log.info("from", way.getFirstPoint().toOSMURL());
+				log.info("to", way.getLastPoint().toOSMURL());
 			}
+			// mark this ways as artificially closed
+			way.closeWayArtificially();
 		}
-	}
+	} 
 
-	
-	protected static class ConnectionData {
-		public Coord c1;
-		public Coord c2;
-		public JoinedWay w1;
-		public JoinedWay w2;
+	private static class ConnectionData {
+		Coord c1;
+		Coord c2;
+		JoinedWay w1;
+		JoinedWay w2;
 		// sometimes the connection of both points cannot be done directly but with an intermediate point 
-		public Coord imC;
-		public double distance;
-		public ConnectionData() {
-			
-		}
+		Coord imC;
+		double distance;
 	}
-	
-	protected boolean connectUnclosedWays(List<JoinedWay> allWays) {
-		List<JoinedWay> unclosed = new ArrayList<>();
 
-		for (JoinedWay w : allWays) {
-			if (!w.hasIdenticalEndPoints()) {
-				unclosed.add(w);
-			}
-		}
+	/**
+	 * Try to connect pairs of ways to closed rings or a single way by adding a
+	 * point outside of the tileBounds.
+	 * 
+	 * @param allWays     list of ways
+	 * @param onlyOutside if true, only connect ways outside of the tileBounds
+	 * @return true if anything was closed
+	 */
+	private boolean connectUnclosedWays(List<JoinedWay> allWays, boolean onlyOutside) {
+		List<JoinedWay> unclosed = allWays.stream().filter(w->!w.hasEqualEndPoints()).collect(Collectors.toList());
+		
 		// try to connect ways lying outside or on the bbox
 		if (!unclosed.isEmpty()) {
 			log.debug("Checking", unclosed.size(), "unclosed ways for connections outside the bbox");
-			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>();
+			Map<Coord, JoinedWay> openEnds = new IdentityHashMap<>();
 			
 			// check all ways for endpoints outside or on the bbox
 			for (JoinedWay w : unclosed) {
-				Coord c1 = w.getFirstPoint();
-				Coord c2 = w.getLastPoint();
-				if (!tileBounds.insideBoundary(c1)) {
-					log.debug("Point", c1, "of way", w.getId(), "outside bbox");
-					outOfBboxPoints.put(c1, w);
-				}
-
-				if (!tileBounds.insideBoundary(c2)) {
-					log.debug("Point", c2, "of way", w.getId(), "outside bbox");
-					outOfBboxPoints.put(c2, w);
+				for (Coord e : Arrays.asList(w.getFirstPoint(), w.getLastPoint())) { 
+					if (!onlyOutside) {
+						openEnds.put(e, w);
+					} else {
+						if (!tileBounds.insideBoundary(e)) {
+							log.debug("Point", e, "of way", w.getId(), "outside bbox");
+							openEnds.put(e, w);
+						}
+					}
 				}
 			}
-			
-			if (outOfBboxPoints.size() < 2) {
-				log.debug(outOfBboxPoints.size(), "point outside the bbox. No connection possible.");
+
+			if (openEnds.size() < 2) {
+				log.debug(openEnds.size(), "point outside the bbox. No connection possible.");
 				return false;
 			}
 			
 			List<ConnectionData> coordPairs = new ArrayList<>();
-			ArrayList<Coord> coords = new ArrayList<>(outOfBboxPoints.keySet());
+			ArrayList<Coord> coords = new ArrayList<>(openEnds.keySet());
 			for (int i = 0; i < coords.size(); i++) {
 				for (int j = i + 1; j < coords.size(); j++) {
 					ConnectionData cd = new ConnectionData();
 					cd.c1 = coords.get(i);
 					cd.c2 = coords.get(j);
-					cd.w1 = outOfBboxPoints.get(cd.c1);					
-					cd.w2 = outOfBboxPoints.get(cd.c2);					
+					cd.w1 = openEnds.get(cd.c1);					
+					cd.w2 = openEnds.get(cd.c2);
+					
+					if (!onlyOutside && cd.w1 == cd.w2) 
+						continue; // was already tested in tryCloseSingleWays() 
 					
-					if (lineCutsBbox(cd.c1, cd.c2)) {
+					if (onlyOutside && lineCutsBbox(cd.c1, cd.c2)) {
 						// Check if the way can be closed with one additional point
 						// outside the bounding box.
 						// The additional point is combination of the coords of both endpoints.
@@ -503,16 +448,20 @@ public class MultiPolygonRelation extends Relation {
 						// multi-polygons.
 						Coord edgePoint1 = new Coord(cd.c1.getLatitude(), cd.c2.getLongitude());
 						Coord edgePoint2 = new Coord(cd.c2.getLatitude(), cd.c1.getLongitude());
-
+						
+						List<Coord> possibleEdges = new ArrayList<>();
 						if (!lineCutsBbox(cd.c1, edgePoint1) && !lineCutsBbox(edgePoint1, cd.c2)) {
-							cd.imC = edgePoint1;
-						} else if (!lineCutsBbox(cd.c1, edgePoint2) && !lineCutsBbox(edgePoint2, cd.c2)) {
-							cd.imC = edgePoint2;
-						} else {
+							possibleEdges.add(edgePoint1);
+						} 
+						if (!lineCutsBbox(cd.c1, edgePoint2) && !lineCutsBbox(edgePoint2, cd.c2)) {
+							possibleEdges.add(edgePoint2);
+						} 
+						if (possibleEdges.size() != 1) {
 							// both endpoints are on opposite sides of the bounding box
 							// automatically closing such points would create wrong polygons in most cases
 							continue;
 						}
+						cd.imC = possibleEdges.get(0);
 						cd.distance = cd.c1.distance(cd.imC) + cd.imC.distance(cd.c2);
 					} else {
 						cd.distance = cd.c1.distance(cd.c2);
@@ -520,15 +469,15 @@ public class MultiPolygonRelation extends Relation {
 					coordPairs.add(cd);
 				}
 			}
-			
+
 			if (coordPairs.isEmpty()) {
 				log.debug("All potential connections cross the bbox. No connection possible.");
 				return false;
-			} else {
-				// retrieve the connection with the minimum distance
-				ConnectionData minCon = Collections.min(coordPairs,
-						(o1, o2) -> Double.compare(o1.distance, o2.distance));
+			}
+			// retrieve the connection with the minimum distance
+			ConnectionData minCon = Collections.min(coordPairs, (o1, o2) -> Double.compare(o1.distance, o2.distance));
 
+			if (onlyOutside || minCon.distance < getMaxCloseDist()) {
 				if (minCon.w1 == minCon.w2) {
 					log.debug("Close a gap in way", minCon.w1);
 					if (minCon.imC != null)
@@ -553,7 +502,6 @@ public class MultiPolygonRelation extends Relation {
 		}
 		return false;
 	}
-
 	
 	/**
 	 * Removes all non closed ways from the given list.
@@ -562,34 +510,30 @@ public class MultiPolygonRelation extends Relation {
 	 * @param wayList
 	 *            list of ways
 	 */
-	protected void removeUnclosedWays(List<JoinedWay> wayList) {
+	private void removeUnclosedWays(List<JoinedWay> wayList) {
 		Iterator<JoinedWay> it = wayList.iterator();
 		boolean firstWarn = true;
 		while (it.hasNext()) {
-			JoinedWay tempWay = it.next();
-			if (!tempWay.hasIdenticalEndPoints()) {
-				// warn only if the way intersects the bounding box 
-				boolean inBbox = tempWay.intersects(tileBounds);
-				if (inBbox) {
+			JoinedWay jw = it.next();
+			if (!jw.hasIdenticalEndPoints()) {
+				// warn only if the way bbox intersects the bounding box 
+				if (jw.getArea().intersects(tileBounds)) {
 					if (firstWarn) {
 						log.warn(
 							"Cannot join the following ways to closed polygons. Multipolygon",
 							toBrowseURL(), toTagString());
 						firstWarn = false;
 					}
-					logWayURLs(Level.WARNING, "- way:", tempWay);
-					logFakeWayDetails(Level.WARNING, tempWay);
-				}
-
-				it.remove();
-				
-				if (inBbox) {
-					String role = getRole(tempWay);
-					if (role == null || "".equals(role) || "outer".equals(role)) {
+					logWayURLs(Level.WARNING, "- way:", jw);
+					logFakeWayDetails(Level.WARNING, jw);
+					String role = getRole(jw);
+					if (role == null || ROLE_OUTER.equals(role)) {
 						// anyhow add the ways to the list for line tagging
-						outerWaysForLineTagging.addAll(tempWay.getOriginalWays());
+						outerWaysForLineTagging.addAll(jw.getOriginalWays());
 					}
 				}
+
+				it.remove();
 			}
 		}
 	}
@@ -599,7 +543,7 @@ public class MultiPolygonRelation extends Relation {
 	 * This reduces error messages from problems on the tile bounds.
 	 * @param wayList list of ways
 	 */
-	protected void removeWaysOutsideBbox(List<JoinedWay> wayList) {
+	private void removeWaysOutsideBbox(List<JoinedWay> wayList) {
 		ListIterator<JoinedWay> wayIter = wayList.listIterator();
 		while (wayIter.hasNext()) {
 			JoinedWay w = wayIter.next();
@@ -644,208 +588,242 @@ public class MultiPolygonRelation extends Relation {
 		return true;
 	}
 
-
-	/**
-	 * Find all polygons that are not contained by any other polygon.
-	 * 
-	 * @param candidates
-	 *            all polygons that should be checked
-	 * @param roleFilter
-	 *            an additional filter
-	 * @return all polygon indexes that are not contained by any other polygon
-	 */
-	private BitSet findOutmostPolygons(BitSet candidates, BitSet roleFilter) {
-		BitSet realCandidates = ((BitSet) candidates.clone());
-		realCandidates.and(roleFilter);
-		return findOutmostPolygons(realCandidates);
-	}
-
 	/**
-	 * Finds all polygons that are not contained by any other polygons and that match
-	 * to the given role. All polygons with index given by <var>candidates</var>
-	 * are used.
-	 * 
-	 * @param candidates
-	 *            indexes of the polygons that should be used
-	 * @return the bits of all outermost polygons are set to true
+	 * Process the ways in this relation. Tries to join the ways to closed rings and
+	 * detect inner/outer status and calls methods to process them.
 	 */
-	protected BitSet findOutmostPolygons(BitSet candidates) {
-		BitSet outmostPolygons = new BitSet();
-
-		// go through all candidates and check if they are contained by any
-		// other candidate
-		for (int candidateIndex = candidates.nextSetBit(0); candidateIndex >= 0; candidateIndex = candidates
-				.nextSetBit(candidateIndex + 1)) {
-			// check if the candidateIndex polygon is not contained by any
-			// other candidate polygon
-			boolean isOutmost = true;
-			for (int otherCandidateIndex = candidates.nextSetBit(0); otherCandidateIndex >= 0; otherCandidateIndex = candidates
-					.nextSetBit(otherCandidateIndex + 1)) {
-				if (contains(otherCandidateIndex, candidateIndex)) {
-					// candidateIndex is not an outermost polygon because it is
-					// contained by the otherCandidateIndex polygon
-					isOutmost = false;
-					break;
-				}
-			}
-			if (isOutmost) {
-				// this is an outermost polygon
-				// put it to the bitset
-				outmostPolygons.set(candidateIndex);
-			}
+	public final void processElements() {
+		log.info("Processing multipolygon", toBrowseURL());
+		
+		// check if it makes sense to process the mp 
+		if (!isUsable()) { 
+			log.info("Do not process multipolygon", getId(), "because it has no style relevant tags.");
+			return;
+		}
+		polygons = buildRings();
+		if (polygons.isEmpty())
+			return;
+		
+		// trigger setting area before start cutting...
+		// do like this to disguise function with side effects
+		polygons.forEach(jw -> jw.setFullArea(jw.getFullArea()));
+		
+		if (polygons.stream().allMatch(jw -> jw.intRole == INT_ROLE_INNER || jw.intRole == INT_ROLE_OTHER)) {
+			log.warn("Multipolygon", toBrowseURL(),
+				"does not contain any way tagged with role=outer or empty role.");
+			cleanup();
+			return;
+		}
+		
+		//TODO: trunk uses more complex logic that also takes the inners into account
+		largestOuterPolygon = getLargest(polygons);
+		
+		List<List<JoinedWay>> partitions = new ArrayList<>();
+		if ("boundary".equals(getTag("type"))) {
+			partitions.add(polygons);
+		} else {	 
+			divideLargest(polygons, partitions, 0);
+		}
+		for (List<JoinedWay> some : partitions) {
+			processPartition(new Partition(some));
+			if (renderingFailed)
+				break;
 		}
 
-		return outmostPolygons;
+		tagOuterWays();
+		
+		postProcessing();
+		cleanup();
 	}
 
-	protected ArrayList<PolygonStatus> getPolygonStatus(BitSet outmostPolygons,
-			String defaultRole) {
-		ArrayList<PolygonStatus> polygonStatusList = new ArrayList<>();
-		for (int polyIndex = outmostPolygons.nextSetBit(0); polyIndex >= 0; polyIndex = outmostPolygons
-				.nextSetBit(polyIndex + 1)) {
-			// polyIndex is the polygon that is not contained by any other
-			// polygon
-			JoinedWay polygon = polygons.get(polyIndex);
-			String role = getRole(polygon);
-			// if the role is not explicitly set use the default role
-			if (role == null || "".equals(role)) {
-				role = defaultRole;
-			} 
-			polygonStatusList.add(new PolygonStatus("outer".equals(role), polyIndex, polygon));
-		}
-		// sort by role and then by number of points, this improves performance
-		// in the routines which add the polygons to areas
-		if (polygonStatusList.size() > 2) {
-			polygonStatusList.sort((o1, o2) -> {
-				if (o1.outer != o2.outer)
-					return (o1.outer) ? -1 : 1;
-				return o1.polygon.getPoints().size() - o2.polygon.getPoints().size();
-			});
-		}
-		return polygonStatusList;
-	}
+	private List<JoinedWay> buildRings() {
+		List<JoinedWay> polygons = joinWays();
 
-	/**
-	 * Creates a list of all original ways of the multipolygon. 
-	 * @return all source ways
-	 */
-	protected List<Way> getSourceWays() {
-		ArrayList<Way> allWays = new ArrayList<>();
+		outerWaysForLineTagging = new HashSet<>();
+		
+		polygons = filterUnclosed(polygons);
+		
+		do {
+			polygons.forEach(this::tryCloseSingleWays);
+		} while (connectUnclosedWays(polygons, assumeDataInBoundsIsComplete()));
 
-		for (Map.Entry<String, Element> entry : getElements()) {
-			if (entry.getValue() instanceof Way) {
-				if (((Way) entry.getValue()).getPoints().isEmpty()) {
-					log.warn("Way", entry.getValue(), "has no points and cannot be used for the multipolygon",
-							toBrowseURL());
-				} else {
-					allWays.add((Way) entry.getValue());
-				}
-			} else if (!(entry.getValue() instanceof Node)
-					|| (!"admin_centre".equals(entry.getKey()) && !"label".equals(entry.getKey()))) {
-				log.warn("Non way member in role", entry.getKey(), entry.getValue().toBrowseURL(),
-						"in multipolygon", toBrowseURL(), toTagString());
+		removeUnclosedWays(polygons);
+
+		// now only closed ways are left => polygons only
+
+		// check if we have at least one polygon left
+		boolean hasPolygons = !polygons.isEmpty();
+
+		removeWaysOutsideBbox(polygons);
+
+		if (polygons.isEmpty()) {
+			// do nothing
+			if (log.isInfoEnabled()) {
+				log.info("Multipolygon", toBrowseURL(),
+						hasPolygons ? "is completely outside the bounding box. It is not processed."
+								: "does not contain a closed polygon.");
 			}
+			tagOuterWays();
+			cleanup();
 		}
-		return allWays;
+		return polygons;
 	}
 	
-	
-	// unfinishedPolygons marks which polygons are not yet processed
-	protected BitSet unfinishedPolygons;
-
-	// create bitsets which polygons belong to the outer and to the inner role
-	protected BitSet innerPolygons;
-	protected BitSet taggedInnerPolygons;
-	protected BitSet outerPolygons;
-	protected BitSet taggedOuterPolygons;
+	private static JoinedWay getLargest(List<JoinedWay> polygons) {
+		double maxSize = -1;
+		int maxPos = -1;
+		for (int i = 0; i< polygons.size(); i++) {
+			JoinedWay closed = polygons.get(i);
+			double size = calcAreaSize(closed.getPoints());
+			if (size > maxSize) {
+				maxSize = size;
+				maxPos = i;
+			}
+		}
+		return polygons.get(maxPos);
+	}
 
-	private boolean noRecalc;
 
 	/**
-	 * Process the ways in this relation. Joins way with the role "outer" Adds
-	 * ways with the role "inner" to the way with the role "outer"
+	 * Calculate the bounds of given collection of joined ways
+	 * @param polygons list of polygons
+	 * @return the bounds
 	 */
-	public void processElements() {
-		log.info("Processing multipolygon", toBrowseURL());
-		
-		polygons = buildRings();
-		if (polygons.isEmpty())
+	private static uk.me.parabola.imgfmt.app.Area calcBounds(Collection<JoinedWay> polygons) {
+		int minLat = Integer.MAX_VALUE;
+		int minLon = Integer.MAX_VALUE;
+		int maxLat = Integer.MIN_VALUE;
+		int maxLon = Integer.MIN_VALUE;
+		for (JoinedWay jw : polygons) {
+			if (jw.minLat < minLat)
+				minLat = jw.minLat;
+			if (jw.minLon < minLon)
+				minLon = jw.minLon;
+			if (jw.maxLat > maxLat)
+				maxLat = jw.maxLat;
+			if (jw.maxLon > maxLon)
+				maxLon = jw.maxLon;
+		}
+		return new uk.me.parabola.imgfmt.app.Area(minLat, minLon, maxLat, maxLon);
+	}
+
+
+	void processPartition(Partition partition) {
+		if (partition.outerPolygons.isEmpty()) {
+			renderingFailed = true;
+			log.error("Internal error: Failed to render " + this);
 			return;
-		
-		// the intersectingPolygons marks all intersecting/overlapping polygons
-		intersectingPolygons = new HashSet<>();
-		
-		// check which polygons lie inside which other polygon 
-		createContainsMatrix(polygons);
-
-		// unfinishedPolygons marks which polygons are not yet processed
-		unfinishedPolygons = new BitSet(polygons.size());
-		unfinishedPolygons.set(0, polygons.size());
-
-		// create bitsets which polygons belong to the outer and to the inner role
-		innerPolygons = new BitSet();
-		taggedInnerPolygons = new BitSet();
-		outerPolygons = new BitSet();
-		taggedOuterPolygons = new BitSet();
-		
-		int wi = 0;
-		for (Way w : polygons) {
-			w.setFullArea(w.getFullArea()); // trigger setting area before start cutting...
-			// do like this to disguise function with side effects
-			String role = getRole(w);
-			if ("inner".equals(role)) {
-				innerPolygons.set(wi);
-				taggedInnerPolygons.set(wi);
-			} else if ("outer".equals(role)) {
-				outerPolygons.set(wi);
-				taggedOuterPolygons.set(wi);
-			} else {
-				// unknown role => it could be both
-				innerPolygons.set(wi);
-				outerPolygons.set(wi);
-			}
-			wi++;
 		}
+			
+		Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<>();
+		
+		polygonWorkingQueue.addAll(partition.getPolygonStatus(null));
+		processQueue(partition, polygonWorkingQueue);
 
-		if (outerPolygons.isEmpty()) {
-			log.warn("Multipolygon", toBrowseURL(),
-				"does not contain any way tagged with role=outer or empty role.");
-			cleanup();
-			return;
+		if (doReporting() && log.isLoggable(Level.WARNING)) {
+			partition.reportProblems();
 		}
 
-		Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<>();
-		BitSet nestedOuterPolygons = new BitSet();
-		BitSet nestedInnerPolygons = new BitSet();
+	}
 
-		BitSet outmostPolygons;
-		BitSet outmostInnerPolygons = new BitSet();
-		boolean outmostInnerFound;
-		do {
-			outmostInnerFound = false;
-			outmostPolygons = findOutmostPolygons(unfinishedPolygons);
+	protected boolean doReporting() {
+		return true;
+	}
 
-			if (outmostPolygons.intersects(taggedInnerPolygons)) {
-				outmostInnerPolygons.or(outmostPolygons);
-				outmostInnerPolygons.and(taggedInnerPolygons);
 
-				if (log.isDebugEnabled())
-					log.debug("wrong inner polygons: " + outmostInnerPolygons);
-				// do not process polygons tagged with role=inner but which are
-				// not contained by any other polygon
-				unfinishedPolygons.andNot(outmostInnerPolygons);
-				outmostPolygons.andNot(outmostInnerPolygons);
-				outmostInnerFound = true;
+	protected boolean isUsable() {
+		// TODO: Would be good to have a hook to filter unwanted MP, e.g.  
+		for (Map.Entry<String, String> tagEntry : this.getTagEntryIterator()) {
+			String tagName = tagEntry.getKey();
+			// all tags are style relevant
+			// except: type and mkgmap:* 
+			if (!"type".equals(tagName) && !tagName.startsWith("mkgmap:")) {
+				return true;
 			}
-		} while (outmostInnerFound);
+		}
+		return false;
+	}
+
+	/**
+	 * Should return true if extra ways with style filter {@code STYLE_FILTER_LINE}
+	 * should be added to the {@code tileWayMap}. Overwrite if those ways are not needed. 
+	 * @return true if extra ways with style filter {@code STYLE_FILTER_LINE}
+	 * should be added to the {@code tileWayMap}
+	 */
+	protected boolean needsWaysForOutlines() {
+		return true;
+	}
+
+	protected boolean assumeDataInBoundsIsComplete() {
+		// we assume that data inside tile boundaries is complete
+		return true; 
+	}
+
+	private void tagOuterWays() {
+		if (outerWaysForLineTagging.isEmpty())
+			return;
+		
+		final Way patternWayForLineCopies;
+		if (needsWaysForOutlines()) {
+			// create pattern way with tags for outline of this multipolygon
+			patternWayForLineCopies = new Way(0);
+			patternWayForLineCopies.copyTags(this);
+			patternWayForLineCopies.deleteTag("type");
+			patternWayForLineCopies.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
+			patternWayForLineCopies.addTag(TKM_MP_CREATED, "true");
+			if (needsAreaSizeTag()) {
+				patternWayForLineCopies.addTag(TKM_CACHE_AREA_SIZEKEY, getAreaSizeString());
+			}
+		} else { 
+			patternWayForLineCopies = null;
+		}
+		
+		// Go through all original outer ways, create a copy if wanted, tag them
+		// with the mp tags and mark them only to be used for polyline processing
+		// This enables the style file to decide if the polygon information or
+		// the simple line information should be used.
 		
-		if (!outmostPolygons.isEmpty()) {
-			polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, "outer"));
+		for (Way orgOuterWay : outerWaysForLineTagging) {
+			if (patternWayForLineCopies != null) {
+				Way lineTagWay =  new Way(getOriginalId(), orgOuterWay.getPoints());
+				lineTagWay.markAsGeneratedFrom(this);
+				lineTagWay.copyTags(patternWayForLineCopies);
+				if (log.isDebugEnabled())
+					log.debug("Add line way", lineTagWay.getId(), lineTagWay.toTagString());
+				tileWayMap.put(lineTagWay.getId(), lineTagWay);
+			}
+			
+			for (Entry<String, String> tag : this.getTagEntryIterator()) {
+				// mark the tag for removal in the original way if it has the same value
+				if (tag.getValue().equals(orgOuterWay.getTag(tag.getKey()))) {
+					markTagsForRemovalInOrgWays(orgOuterWay, tag.getKey());
+				}
+			}
 		}
+	}
+
+	/**
+	 * Filter unclosed ways which have one or both end points outside of the tile bounds.
+	 * @param polygons the list of ways to filter
+	 * @return the original list if {@link allowCloseOutsideBBox} returns false, else the filtered list
+	 */
+	private List<JoinedWay> filterUnclosed(List<JoinedWay> polygons) {
+		if (assumeDataInBoundsIsComplete())
+			return polygons;
+		return polygons.stream().filter(w -> {
+			Coord first = w.getFirstPoint();
+			Coord last = w.getLastPoint();
+			return first == last || tileBounds.contains(first) && tileBounds.contains(last);
+		}).collect(Collectors.toList());
+	}
 
-		boolean outmostPolygonProcessing = true;
+	/**
+	 * The main routine to cut or split the rings of one partition containing a list of polygons
+	 * @param partition the partition
+	 * @param polygonWorkingQueue the queue that contains the initial outer rings
+	 */
+	protected void processQueue(Partition partition, Queue<PolygonStatus> polygonWorkingQueue) {
 		
-	
 		while (!polygonWorkingQueue.isEmpty()) {
 
 			// the polygon is not contained by any other unfinished polygon
@@ -853,54 +831,9 @@ public class MultiPolygonRelation extends Relation {
 
 			// this polygon is now processed and should not be used by any
 			// further step
-			unfinishedPolygons.clear(currentPolygon.index);
+			partition.markFinished(currentPolygon);
 
-			BitSet polygonContains = new BitSet();
-			polygonContains.or(containsMatrix.get(currentPolygon.index));
-			// use only polygon that are contained by the polygon
-			polygonContains.and(unfinishedPolygons);
-			// polygonContains is the intersection of the unfinished and
-			// the contained polygons
-
-			// get the holes
-			// these are all polygons that are in the main polygon
-			// and that are not contained by any other polygon
-			boolean holesOk;
-			BitSet holeIndexes;
-			do {
-				holeIndexes = findOutmostPolygons(polygonContains);
-				holesOk = true;
-
-				if (currentPolygon.outer) {
-					// for role=outer only role=inner is allowed
-					if (holeIndexes.intersects(taggedOuterPolygons)) {
-						BitSet addOuterNestedPolygons = new BitSet();
-						addOuterNestedPolygons.or(holeIndexes);
-						addOuterNestedPolygons.and(taggedOuterPolygons);
-						nestedOuterPolygons.or(addOuterNestedPolygons);
-						holeIndexes.andNot(addOuterNestedPolygons);
-						// do not process them
-						unfinishedPolygons.andNot(addOuterNestedPolygons);
-						polygonContains.andNot(addOuterNestedPolygons);
-						
-						// recalculate the holes again to get all inner polygons 
-						// in the nested outer polygons
-						holesOk = false;
-					}
-				} else {
-					// for role=inner both role=inner and role=outer is supported
-					// although inner in inner is not officially allowed
-					if (holeIndexes.intersects(taggedInnerPolygons)) {
-						// process inner in inner but issue a warning later
-						BitSet addInnerNestedPolygons = new BitSet();
-						addInnerNestedPolygons.or(holeIndexes);
-						addInnerNestedPolygons.and(taggedInnerPolygons);
-						nestedInnerPolygons.or(addInnerNestedPolygons);
-					}
-				}
-			} while (!holesOk);
-
-			ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes, (currentPolygon.outer ? "inner" : "outer"));
+			List<PolygonStatus> holes = partition.getPolygonStatus(currentPolygon);
 
 			// these polygons must all be checked for holes
 			polygonWorkingQueue.addAll(holes);
@@ -911,29 +844,15 @@ public class MultiPolygonRelation extends Relation {
 				outerWaysForLineTagging.addAll(currentPolygon.polygon.getOriginalWays());
 			}
 			
-			// calculate the size of the polygon
-			double outerAreaSize = currentPolygon.polygon.getSizeOfArea();
-			if (outerAreaSize > largestSize) {
-				// subtract the holes
-				for (PolygonStatus hole : holes) {
-					outerAreaSize -= hole.polygon.getSizeOfArea();
-				}
-				// is it still larger than the largest known polygon?
-				if (outerAreaSize > largestSize) {
-					largestOuterPolygon = currentPolygon.polygon;
-					largestSize = outerAreaSize;
-				}
-			}
-			
 			// check if the polygon is an outer polygon or 
 			// if there are some holes
-			boolean processPolygon = currentPolygon.outer || (!holes.isEmpty());
+			boolean processPolygon = currentPolygon.outer || !holes.isEmpty();
 
 			if (processPolygon) {
 				List<Way> singularOuterPolygons;
 				if (holes.isEmpty()) {
-					singularOuterPolygons = Collections
-							.singletonList((Way) new JoinedWay(currentPolygon.polygon));
+					Way w = new Way(currentPolygon.polygon.getId(), currentPolygon.polygon.getPoints());
+					singularOuterPolygons = Collections.singletonList(w);
 				} else {
 					List<Way> innerWays = new ArrayList<>(holes.size());
 					for (PolygonStatus polygonHoleStatus : holes) {
@@ -949,16 +868,15 @@ public class MultiPolygonRelation extends Relation {
 				
 				if (!singularOuterPolygons.isEmpty()) {
 					// handle the tagging 
-					if (currentPolygon.outer && hasStyleRelevantTags(this)) {
+					if (currentPolygon.outer) {
 						// use the tags of the multipolygon
 						for (Way p : singularOuterPolygons) {
 							// overwrite all tags
 							p.copyTags(this);
 							p.deleteTag("type");
 						}
-						// remove the multipolygon tags in the original ways of the current polygon
-						removeTagsInOrgWays(this, currentPolygon.polygon);
 					} else {
+						// we have a nested MP with one or more outer rings inside an inner ring 
 						// use the tags of the original ways
 						currentPolygon.polygon.mergeTagsFromOrgWays();
 						for (Way p : singularOuterPolygons) {
@@ -966,21 +884,9 @@ public class MultiPolygonRelation extends Relation {
 							p.copyTags(currentPolygon.polygon);
 						}
 						// remove the current polygon tags in its original ways
-						removeTagsInOrgWays(currentPolygon.polygon, currentPolygon.polygon);
+						markTagsForRemovalInOrgWays(currentPolygon.polygon);
 					}
 				
-					if (currentPolygon.outer && outmostPolygonProcessing) {
-						// this is the outer most polygon - copy its tags. They will be used
-						// later for tagging of the lines
-
-						// all cut polygons have the same tags - copy them from the first polygon
-						Way outerWay = singularOuterPolygons.get(0);
-						for (Entry<String, String> tag : outerWay.getTagEntryIterator()) {
-							outerTags.put(tag.getKey(), tag.getValue());
-						}
-						outmostPolygonProcessing = false;
-					}
-					
 					long fullArea = currentPolygon.polygon.getFullArea();
 					for (Way mpWay : singularOuterPolygons) {
 						// put the cut out polygons to the
@@ -994,700 +900,92 @@ public class MultiPolygonRelation extends Relation {
 						mpWay.addTag(TKM_MP_CREATED, "true");
 						
 						if (currentPolygon.outer) {
-							mpWay.addTag(TKM_MP_ROLE, "outer");
-							if (isAreaSizeCalculated())
+							mpWay.addTag(TKM_MP_ROLE, ROLE_OUTER);
+							if (needsAreaSizeTag())
 								mpAreaSize += calcAreaSize(mpWay.getPoints());
 						} else {
-							mpWay.addTag(TKM_MP_ROLE, "inner");
+							mpWay.addTag(TKM_MP_ROLE, ROLE_INNER);
 						}
 						
-						getMpPolygons().put(mpWay.getId(), mpWay);
+						mpPolygons.put(mpWay.getId(), mpWay);
 					}
 				}
 			}
 		}
-		
-		if (log.isLoggable(Level.WARNING) && (outmostInnerPolygons.cardinality() + unfinishedPolygons.cardinality()
-				+ nestedOuterPolygons.cardinality() + nestedInnerPolygons.cardinality() >= 1)) {
-			log.warn("Multipolygon", toBrowseURL(), toTagString(), "contains errors.");
-
-			BitSet outerUnusedPolys = new BitSet();
-			outerUnusedPolys.or(unfinishedPolygons);
-			outerUnusedPolys.or(outmostInnerPolygons);
-			outerUnusedPolys.or(nestedOuterPolygons);
-			outerUnusedPolys.or(nestedInnerPolygons);
-			outerUnusedPolys.or(unfinishedPolygons);
-			// use only the outer polygons
-			outerUnusedPolys.and(outerPolygons);
-			for (JoinedWay w : getWaysFromPolygonList(outerUnusedPolys)) {
-				outerWaysForLineTagging.addAll(w.getOriginalWays());
-			}
-			
-			runIntersectionCheck(unfinishedPolygons);
-			runOutmostInnerPolygonCheck(outmostInnerPolygons);
-			runNestedOuterPolygonCheck(nestedOuterPolygons);
-			runNestedInnerPolygonCheck(nestedInnerPolygons);
-			runWrongInnerPolygonCheck(unfinishedPolygons, innerPolygons);
-
-			// we have at least one polygon that could not be processed
-			// Probably we have intersecting or overlapping polygons
-			// one possible reason is if the relation overlaps the tile
-			// bounds
-			// => issue a warning
-			List<JoinedWay> lostWays = getWaysFromPolygonList(unfinishedPolygons);
-			for (JoinedWay w : lostWays) {
-				log.warn("Polygon", w, "is not processed due to an unknown reason.");
-				logWayURLs(Level.WARNING, "-", w);
-			}
-		}
-
-		if (!hasStyleRelevantTags(this)) {
-			// add tags to the multipolygon that are taken from the outer ways
-			// they may be required by some hooks (e.g. Area2POIHook)
-			for (Entry<String, String> tags : outerTags.entrySet()) {
-				addTag(tags.getKey(), tags.getValue());
-			}
-		}
-		String mpAreaSizeStr = null;
-		if (isAreaSizeCalculated()) {
-			// calculate tag value for mkgmap:cache_area_size only once
-			mpAreaSizeStr = String.format(Locale.US, "%.3f", mpAreaSize);
-		}
-		// Go through all original outer ways, create a copy, tag them
-		// with the mp tags and mark them only to be used for polyline processing
-		// This enables the style file to decide if the polygon information or
-		// the simple line information should be used.
-		for (Way orgOuterWay : outerWaysForLineTagging) {
-			Way lineTagWay =  new Way(getOriginalId(), orgOuterWay.getPoints());
-			lineTagWay.markAsGeneratedFrom(this);
-			lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
-			lineTagWay.addTag(TKM_MP_CREATED, "true");
-			if (mpAreaSizeStr != null) {
-				// assign the area size of the whole multipolygon to all outer polygons
-				lineTagWay.addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr);
-			}
-			for (Entry<String,String> tag : outerTags.entrySet()) {
-				lineTagWay.addTag(tag.getKey(), tag.getValue());
-				
-				// remove the tag from the original way if it has the same value
-				if (tag.getValue().equals(orgOuterWay.getTag(tag.getKey()))) {
-					removeTagsInOrgWays(orgOuterWay, tag.getKey());
-				}
-			}
-	
-			if (log.isDebugEnabled())
-				log.debug("Add line way", lineTagWay.getId(), lineTagWay.toTagString());
-			tileWayMap.put(lineTagWay.getId(), lineTagWay);
-		}
-		
-		postProcessing();
-		cleanup();
 	}
-	
-	private List<JoinedWay> buildRings() {
-		List<Way> allWays = getSourceWays();
-		
-		// check if it makes sense to process the mp 
-		if (!isMpProcessable(allWays)) {
-			log.info("Do not process multipolygon", getId(), "because it has no style relevant tags.");
-			return Collections.emptyList();
-		}
 
-		
-		// join all single ways to polygons, try to close ways and remove non closed ways 
-		polygons = joinWays(allWays);
-		
-		outerWaysForLineTagging = new HashSet<>();
-		outerTags = new HashMap<>();
-		
-		do {
-			closeWays(polygons, getMaxCloseDist());
-		} while (connectUnclosedWays(polygons));
-
-		removeUnclosedWays(polygons);
-
-		// now only closed ways are left => polygons only
-
-		// check if we have at least one polygon left
-		boolean hasPolygons = !polygons.isEmpty();
-
-		removeWaysOutsideBbox(polygons);
-		
-		if (polygons.isEmpty()) {
-			// do nothing
-			if (log.isInfoEnabled()) {
-				log.info("Multipolygon", toBrowseURL(),
-						hasPolygons ? "is completely outside the bounding box. It is not processed."
-								: "does not contain a closed polygon.");
-			}
-			tagOuterWays();
-			cleanup();
-		}
-
-		return polygons;
-	}
 
 	protected double getMaxCloseDist() {
-		return -1; // 
+		return Double.MAX_VALUE; // overwritten in BoundaryRelation
 	}
 
+	private String getAreaSizeString() {
+		return String.format(Locale.US, "%.3f", mpAreaSize); 
+	}
 
 	protected void postProcessing() {
-		
-		if (isAreaSizeCalculated()) {
+		String mpAreaSizeStr = null;
+		if (needsAreaSizeTag()) {
 			// assign the area size of the whole multipolygon to all outer polygons
-			String mpAreaSizeStr = String.format(Locale.US, "%.3f", mpAreaSize); 
+			mpAreaSizeStr = getAreaSizeString(); 
 			addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr);
-			for (Way w : mpPolygons.values()) {
-				if ("outer".equals(w.getTag(TKM_MP_ROLE))) {
-					w.addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr);
-				}
-			}
-		}
-
-		for (Way w : mpPolygons.values()) {
-			w.deleteTag("mkgmap:mp_role");
-		}
-		// copy all polygons created by the multipolygon algorithm to the global way map
-		tileWayMap.putAll(mpPolygons);
-		
-		if (largestOuterPolygon != null) {
-			// check if the mp contains a node with role "label" 
-			for (Map.Entry<String, Element> entry : getElements()) {
-				if (entry.getValue() instanceof Node && "label".equals(entry.getKey())) {
-					// yes => use the label node as reference point
-					cOfG = ((Node) entry.getValue()).getLocation();
-					break;
-				}
-			}
-			
-			if (cOfG == null) {
-				// use the center of the largest polygon as reference point
-				cOfG = largestOuterPolygon.getCofG();
-			}
 		}
-	}
-	
-	private void runIntersectionCheck(BitSet unfinishedPolys) {
-		if (intersectingPolygons.isEmpty()) {
-			// nothing to do
-			return;
-		}
-
-		log.warn("Some polygons are intersecting. This is not allowed in multipolygons.");
 
-		boolean oneOufOfBbox = false;
-		for (JoinedWay polygon : intersectingPolygons) {
-			int pi = polygons.indexOf(polygon);
-			unfinishedPolys.clear(pi);
-
-			boolean outOfBbox = false;
-			for (Coord c : polygon.getPoints()) {
-				if (!tileBounds.contains(c)) {
-					outOfBbox = true;
-					oneOufOfBbox = true;
-					break;
+		if (!renderingFailed) {
+			for (Way w : mpPolygons.values()) {
+				String role = w.deleteTag(TKM_MP_ROLE); 
+				if (mpAreaSizeStr != null && ROLE_OUTER.equals(role)) {
+					w.addTag(TKM_CACHE_AREA_SIZEKEY, mpAreaSizeStr);
 				}
 			}
-
-			logWayURLs(Level.WARNING, (outOfBbox ? "*" : "-"), polygon);
+			// copy all polygons created by the multipolygon algorithm to the global way map
+			tileWayMap.putAll(mpPolygons);
 		}
-		
-		for (JoinedWay polygon : intersectingPolygons) {
-			// print out the details of the original ways
-			logFakeWayDetails(Level.WARNING, polygon);
-		}
-		
-		if (oneOufOfBbox) {
-			log.warn("Some of these intersections/overlaps may be caused by incomplete data on bounding box edges (*).");
-		}
-	}
-
-	private void runNestedOuterPolygonCheck(BitSet nestedOuterPolygons) {
-		// just print out warnings
-		// the check has been done before
-		for (int wiIndex = nestedOuterPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = nestedOuterPolygons
-				.nextSetBit(wiIndex + 1)) {
-			JoinedWay outerWay = polygons.get(wiIndex);
-			log.warn("Polygon",	outerWay, "carries role outer but lies inside an outer polygon. Potentially its role should be inner.");
-			logFakeWayDetails(Level.WARNING, outerWay);
+		if (cOfG == null && largestOuterPolygon != null) {
+			// use the center of the largest polygon as reference point
+			cOfG = largestOuterPolygon.getCofG();
 		}
+		// XXX: maybe keep the cOfg data from a label node? 
+		if (largestOuterPolygon == null) 
+			cOfG = null; 
 	}
 	
-	private void runNestedInnerPolygonCheck(BitSet nestedInnerPolygons) {
-		// just print out warnings
-		// the check has been done before
-		for (int wiIndex = nestedInnerPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = nestedInnerPolygons
-				.nextSetBit(wiIndex + 1)) {
-			JoinedWay innerWay = polygons.get(wiIndex);
-			log.warn("Polygon",	innerWay, "carries role", getRole(innerWay), "but lies inside an inner polygon. Potentially its role should be outer.");
-			logFakeWayDetails(Level.WARNING, innerWay);
-		}
-	}	
-	
-	private void runOutmostInnerPolygonCheck(BitSet outmostInnerPolygons) {
-		// just print out warnings
-		// the check has been done before
-		for (int wiIndex = outmostInnerPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = outmostInnerPolygons
-				.nextSetBit(wiIndex + 1)) {
-			JoinedWay innerWay = polygons.get(wiIndex);
-			log.warn("Polygon",	innerWay, "carries role", getRole(innerWay), "but is not inside any other polygon. Potentially it does not belong to this multipolygon.");
-			logFakeWayDetails(Level.WARNING, innerWay);
-		}
-	}
-
-	private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, BitSet innerPolygons) {
-		// find all unfinished inner polygons that are not contained by any
-		BitSet wrongInnerPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons);
-		if (log.isDebugEnabled()) {
-			log.debug("unfinished", unfinishedPolygons);
-			log.debug("inner", innerPolygons);
-			// other polygon
-			log.debug("wrong", wrongInnerPolygons);
-		}
-		if (!wrongInnerPolygons.isEmpty()) {
-			// we have an inner polygon that is not contained by any outer polygon
-			// check if
-			for (int wiIndex = wrongInnerPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = wrongInnerPolygons
-					.nextSetBit(wiIndex + 1)) {
-				BitSet containedPolygons = new BitSet();
-				containedPolygons.or(unfinishedPolygons);
-				containedPolygons.and(containsMatrix.get(wiIndex));
-
-				JoinedWay innerWay = polygons.get(wiIndex);
-				if (containedPolygons.isEmpty()) {
-					log.warn("Polygon",	innerWay, "carries role", getRole(innerWay),
-						"but is not inside any outer polygon. Potentially it does not belong to this multipolygon.");
-					logFakeWayDetails(Level.WARNING, innerWay);
-				} else {
-					log.warn("Polygon",	innerWay, "carries role", getRole(innerWay),
-						"but is not inside any outer polygon. Potentially the roles are interchanged with the following",
-						(containedPolygons.cardinality() > 1 ? "ways" : "way"), ".");
-
-					for (int wrIndex = containedPolygons.nextSetBit(0); wrIndex >= 0; wrIndex = containedPolygons
-							.nextSetBit(wrIndex + 1)) {
-						logWayURLs(Level.WARNING, "-", polygons.get(wrIndex));
-						unfinishedPolygons.set(wrIndex);
-						wrongInnerPolygons.set(wrIndex);
-					}
-					logFakeWayDetails(Level.WARNING, innerWay);
-				}
-
-				unfinishedPolygons.clear(wiIndex);
-				wrongInnerPolygons.clear(wiIndex);
-			}
-		}
-	}
-
 	protected void cleanup() {
 		mpPolygons = null;
-		commonCoordMap = null;
-		roleMap.clear();
-		containsMatrix = null;
-//		polygons = null;
 		tileArea = null;
-		intersectingPolygons = null;
 		outerWaysForLineTagging = null;
-		outerTags = null;
-		
-		unfinishedPolygons = null;
-		innerPolygons = null;
-		taggedInnerPolygons = null;
-		outerPolygons = null;
-		taggedOuterPolygons = null;
-//		largestOuterPolygon = null;
-	}
-
-	/**
-	 * Retrieves if the given element contains tags that may be relevant
-	 * for style processing. If it has no relevant tag it will probably be 
-	 * dropped by the style.
-	 * 
-	 * @param element the OSM element
-	 * @return <code>true</code> has style relevant tags
-	 */
-	protected boolean hasStyleRelevantTags(Element element) {
-		if (element instanceof MultiPolygonRelation && ((MultiPolygonRelation) element).getTagsIncomplete()) {
-			return true;
-		}
-		
-		for (Map.Entry<String, String> tagEntry : element.getTagEntryIterator()) {
-			String tagName = tagEntry.getKey();
-			// all tags are style relevant
-			// except: type (for relations), mkgmap:* 
-			boolean isStyleRelevant = !(element instanceof Relation && "type".equals(tagName))
-					&& !tagName.startsWith("mkgmap:");
-			if (isStyleRelevant) {
-				return true;
-			}
-		}
-		return false;
-	}
-	
-	/**
-	 * Checks if this mp should be processed or if it is needless to process it
-	 * because there is no result.
-	 * @param ways the list of ways of the mp
-	 * @return <code>true</code> the mp processing will have a result; 
-	 * 		   <code>false</code> the mp processing will fail 
-	 */
-	private boolean isMpProcessable(Collection<Way> ways) {
-		// Check if the multipolygon itself or the member ways have a
-		// tag. If not it does not make sense to process the mp because 
-		// the output will not change anything
-		if (hasStyleRelevantTags(this)) {
-			return true;
-		}
-
-		for (Way w : ways) {
-			if (hasStyleRelevantTags(w)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * Creates a matrix which polygon contains which polygon. A polygon does not
-	 * contain itself.
-	 * 
-	 * @param polygonList
-	 *            a list of polygons
-	 */
-	protected void createContainsMatrix(List<JoinedWay> polygonList) {
-		containsMatrix = new ArrayList<>();
-		for (int i = 0; i < polygonList.size(); i++) {
-			containsMatrix.add(new BitSet());
-		}
-
-		long t1 = System.currentTimeMillis();
-
-		if (log.isDebugEnabled())
-			log.debug("createContainsMatrix listSize:", polygonList.size());
-
-		// use this matrix to check which matrix element has been
-		// calculated
-		ArrayList<BitSet> finishedMatrix = new ArrayList<>(polygonList
-				.size());
-
-		for (int i = 0; i < polygonList.size(); i++) {
-			BitSet matrixRow = new BitSet();
-			// a polygon does not contain itself
-			matrixRow.set(i);
-			finishedMatrix.add(matrixRow);
-		}
-
-		for (int rowIndex = 0; rowIndex < polygonList.size(); rowIndex++) {
-			JoinedWay potentialOuterPolygon = polygonList.get(rowIndex);
-			BitSet containsColumns = containsMatrix.get(rowIndex);
-			BitSet finishedCol = finishedMatrix.get(rowIndex);
-			
-			// the polygon need to be created only sometimes
-			// so use a lazy creation to improve performance
-			WayAndLazyPolygon lazyPotOuterPolygon = new WayAndLazyPolygon(potentialOuterPolygon);
-
-			// get all non calculated columns of the matrix
-			for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0
-					&& colIndex < polygonList.size(); colIndex = finishedCol
-					.nextClearBit(colIndex + 1)) {
-
-				JoinedWay innerPolygon = polygonList.get(colIndex);
-
-				if (potentialOuterPolygon.getBounds().intersects(
-						innerPolygon.getBounds()))
-				{
-					boolean contains = contains(lazyPotOuterPolygon, innerPolygon);
-					
-					if (contains) {
-						containsColumns.set(colIndex);
-
-						// we also know that the inner polygon does not contain the
-						// outer polygon
-						// so we can set the finished bit for this matrix
-						// element
-						finishedMatrix.get(colIndex).set(rowIndex);
-
-						// additionally we know that the outer polygon contains all
-						// polygons that are contained by the inner polygon
-						containsColumns.or(containsMatrix.get(colIndex));
-						finishedCol.or(containsColumns);
-					}
-				} else {
-					// both polygons do not intersect
-					// we can flag both matrix elements as finished
-					finishedMatrix.get(colIndex).set(rowIndex);
-					finishedMatrix.get(rowIndex).set(colIndex);
-				}
-				// this matrix element is calculated now
-				finishedCol.set(colIndex);
-			}
-		}
-
-		if (log.isDebugEnabled()) {
-			long t2 = System.currentTimeMillis();
-			log.debug("createMatrix for", polygonList.size(), "polygons took",
-				(t2 - t1), "ms");
-
-			log.debug("Containsmatrix:");
-			int i = 0;
-			boolean noContained = true;
-			for (BitSet b : containsMatrix) {
-				if (!b.isEmpty()) {
-					log.debug(i,"contains",b);
-					noContained = false;
-				}
-				i++;
-			}
-			if (noContained) {
-				log.debug("Matrix is empty");
-			}
-		}
-	}
-
-	
-	/**
-	 * This is a helper class that creates a high precision polygon for a way 
-	 * on request only.
-	 */
-	private static class WayAndLazyPolygon {
-		private final JoinedWay way;
-		private Polygon polygon;
-		
-		public WayAndLazyPolygon(JoinedWay way) {
-			this.way = way;
-		}
-
-		public final JoinedWay getWay() {
-			return this.way;
-		}
-
-		public final Polygon getPolygon() {
-			if (this.polygon == null) {
-				this.polygon = Java2DConverter.createHighPrecPolygon(this.way.getPoints());
-			}
-			return this.polygon;
-		}
-	}
-	
-	/**
-	 * Checks if the polygon with polygonIndex1 contains the polygon with polygonIndex2.
-	 * 
-	 * @return true if polygon(polygonIndex1) contains polygon(polygonIndex2)
-	 */
-	private boolean contains(int polygonIndex1, int polygonIndex2) {
-		return containsMatrix.get(polygonIndex1).get(polygonIndex2);
+		commonCoordMap = null;
 	}
 
 	/**
-	 * Checks if polygon1 contains polygon2.
+	 * Checks if polygon1 contains polygon2 without intersection. 
 	 * 
-	 * @param polygon1
+	 * @param expectedOuter
 	 *            a closed way
-	 * @param polygon2
+	 * @param expectedInner
 	 *            a 2nd closed way
-	 * @return true if polygon1 contains polygon2
+	 * @return true if polygon1 contains polygon2 without intersection.
 	 */
-	private boolean contains(WayAndLazyPolygon polygon1, JoinedWay polygon2) {
-		if (!polygon1.getWay().hasIdenticalEndPoints()) {
+	private static boolean calcContains(JoinedWay expectedOuter, JoinedWay expectedInner) {
+		if (!expectedOuter.hasIdenticalEndPoints()) {
 			return false;
 		}
 		// check if the bounds of polygon2 are completely inside/enclosed the bounds
 		// of polygon1
-		if (!polygon1.getWay().getBounds().contains(polygon2.getBounds())) {
+		if (!expectedOuter.getBounds().contains(expectedInner.getBounds())) {
 			return false;
 		}
-
-		// check first if one point of polygon2 is in polygon1
-
-		// ignore intersections outside the bounding box
-		// so it is necessary to check if there is at least one
-		// point of polygon2 in polygon1 ignoring all points outside the bounding box
-		boolean onePointContained = false;
-		boolean allOnLine = true;
-		for (Coord px : polygon2.getPoints()) {
-			if (polygon1.getPolygon().contains(px.getHighPrecLon(), px.getHighPrecLat())) {
-				// there's one point that is in polygon1 and in the bounding
-				// box => polygon1 may contain polygon2
-				onePointContained = true;
-				if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
-					allOnLine = false;
-					break;
-				}
-			} else if ((!polygon1.getWay().closedArtificially && !polygon2.closedArtificially
-					|| tileBounds.contains(px)) && !locatedOnLine(px, polygon1.getWay().getPoints())) {
-				// there's one point that is not in polygon1
-				// if both polygons were complete => polygon1 does not contain polygon2
-				// if point is inside bounding box => polygon1 does not contain polygon2
-				return false;
-			}
-		}
 		
-		if (allOnLine) {
-			onePointContained = false;
-			// all points of polygon2 lie on lines of polygon1
-			// => the middle of each line polygon must NOT lie outside polygon1
-			ArrayList<Coord> middlePoints2 = new ArrayList<>(polygon2.getPoints().size());
-			Coord p1 = null;
-			for (Coord p2 : polygon2.getPoints()) {
-				if (p1 != null) {
-					Coord pm = p1.makeBetweenPoint(p2, 0.5);
-					middlePoints2.add(pm);
-				}
-				p1 = p2;
-			}
-			
-			for (Coord px : middlePoints2) {
-				if (polygon1.getPolygon().contains(px.getHighPrecLon(), px.getHighPrecLat())){
-					// there's one point that is in polygon1 and in the bounding
-					// box => polygon1 may contain polygon2
-					onePointContained = true;
-					break;
-				} else if (tileBounds.contains(px) && !locatedOnLine(px, polygon1.getWay().getPoints())) {
-					// there's one point that is not in polygon1 but inside the
-					// bounding box => polygon1 does not contain polygon2
-					return false;
-				}
-			}			
-		}
-
-		if (!onePointContained) {
-			// no point of polygon2 is in polygon1 => polygon1 does not contain polygon2
-			return false;
-		}
+//		Coord test = expectedInner.getPointInside();
+//		if (test != null) {
+//			// we know that point test is inside expectedInner
+//			int quick = IsInUtil.isPointInShape(test, expectedOuter.getPoints());
+//			// if point is ON we can assume that a part of the inner is OUT
+//			return quick == IsInUtil.IN;
+//		}
 		
-		Iterator<Coord> it1 = polygon1.getWay().getPoints().iterator();
-		Coord p11 = it1.next();
-
-		while (it1.hasNext()) {
-			Coord p12 = p11;
-			p11 = it1.next();
-
-			if (!polygon2.linePossiblyIntersectsWay(p11, p12)) {
-				// don't check it - this segment of the outer polygon
-				// definitely does not intersect the way
-				continue;
-			}
-
-			int lonMin = Math.min(p11.getLongitude(), p12.getLongitude());
-			int lonMax = Math.max(p11.getLongitude(), p12.getLongitude());
-			int latMin = Math.min(p11.getLatitude(), p12.getLatitude());
-			int latMax = Math.max(p11.getLatitude(), p12.getLatitude());
-
-			// check all lines of way1 and way2 for intersections
-			Iterator<Coord> it2 = polygon2.getPoints().iterator();
-			Coord p21 = it2.next();
-
-			// for speedup we divide the area around the second line into
-			// a 3x3 matrix with lon(-1,0,1) and lat(-1,0,1).
-			// -1 means below min lon/lat of bbox line p1_1-p1_2
-			// 0 means inside the bounding box of the line p1_1-p1_2
-			// 1 means above max lon/lat of bbox line p1_1-p1_2
-			int lonField = p21.getLongitude() < lonMin ? -1 : p21.getLongitude() > lonMax ? 1 : 0;
-			int latField = p21.getLatitude() < latMin ? -1 : p21.getLatitude() > latMax ? 1 : 0;
-
-			int prevLonField = lonField;
-			int prevLatField = latField;
-
-			while (it2.hasNext()) {
-				Coord p22 = p21;
-				p21 = it2.next();
-
-				int changes = 0;
-				// check if the field of the 3x3 matrix has changed
-				if ((lonField >= 0 && p11.getLongitude() < lonMin)
-						|| (lonField <= 0 && p11.getLongitude() > lonMax)) {
-					changes++;
-					lonField = p11.getLongitude() < lonMin ? -1 : p11.getLongitude() > lonMax ? 1 : 0;
-				}
-				if ((latField >= 0 && p11.getLatitude() < latMin)
-						|| (latField <= 0 && p11.getLatitude() > latMax)) {
-					changes++;
-					latField = p11.getLatitude() < latMin ? -1 : p11.getLatitude() > latMax ? 1 : 0;
-				}
-
-				// an intersection is possible if
-				// latField and lonField has changed
-				// or if we come from or go to the inner matrix field
-				boolean intersectionPossible = (changes == 2)
-						|| (latField == 0 && lonField == 0)
-						|| (prevLatField == 0 && prevLonField == 0);
-
-				boolean intersects = intersectionPossible && linesCutEachOther(p11, p12, p21, p22);
-				
-				if (intersects) {
-					if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
-							|| (polygon2.isClosedArtificially() && !it2.hasNext())) {
-						// don't care about this intersection
-						// one of the polygons is closed by this mp code and the
-						// closing segment causes the intersection
-						log.info("Polygon", polygon1, "may contain polygon", polygon2,
-							". Ignoring artificial generated intersection.");
-					} else if ((!tileBounds.contains(p11))
-							|| (!tileBounds.contains(p12))
-							|| (!tileBounds.contains(p21))
-							|| (!tileBounds.contains(p22))) {
-						// at least one point is outside the bounding box
-						// we ignore the intersection because the ways may not
-						// be complete
-						// due to removals of the tile splitter or osmosis
-						log.info("Polygon", polygon1, "may contain polygon", polygon2,
-							". Ignoring because at least one point is outside the bounding box.");
-					} else {
-						// store them in the intersection polygons set
-						// the error message will be printed out in the end of
-						// the mp handling
-						intersectingPolygons.add(polygon1.getWay());
-						intersectingPolygons.add(polygon2);
-						return false;
-					}
-				}
-
-				prevLonField = lonField;
-				prevLatField = latField;
-			}
-		}
-
-		// don't have any intersection
-		// => polygon1 contains polygon2
-		return true;
-	}
-
-	/**
-	 * Checks if the point p is located on one line of the given points.
-	 * @param p a point
-	 * @param points a list of points; all consecutive points are handled as lines
-	 * @return true if p is located on one line given by points
-	 */
-	private static boolean locatedOnLine(Coord p, List<Coord> points) {
-		Coord cp1 = null;
-		for (Coord cp2 : points) {
-			if (p.highPrecEquals(cp2)) { 
-				return true;
-			}
-
-			try {
-				if (cp1 == null // first init
-						|| p.getHighPrecLon() < Math.min(cp1.getHighPrecLon(), cp2.getHighPrecLon())
-						|| p.getHighPrecLon() > Math.max(cp1.getHighPrecLon(), cp2.getHighPrecLon())
-						|| p.getHighPrecLat() < Math.min(cp1.getHighPrecLat(), cp2.getHighPrecLat())
-						|| p.getHighPrecLat() > Math.max(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
-					continue;
-				}
-
-				double dist = Line2D.ptSegDistSq(cp1.getHighPrecLon(), cp1.getHighPrecLat(),
-						cp2.getHighPrecLon(), cp2.getHighPrecLat(),
-						p.getHighPrecLon(), p.getHighPrecLat());
-
-				if (dist <= OVERLAP_TOLERANCE_DISTANCE) {
-					log.debug("Point", p, "is located on line between", cp1, "and",
-						cp2, ". Distance:", dist);
-					return true;
-				}
-			} finally {
-				cp1 = cp2;
-			}
-		}
-		return false;
+		int x = IsInUtil.isLineInShape(expectedInner.getPoints(), expectedOuter.getPoints(), expectedInner.getArea());
+		return (x & IsInUtil.OUT) == 0;
 	}
 
 	private boolean lineCutsBbox(Coord p1, Coord p2) {
@@ -1739,18 +1037,6 @@ public class MultiPolygonRelation extends Relation {
 		return (isy > 0 &&  isy < 1);
 	}
 
-	private List<JoinedWay> getWaysFromPolygonList(BitSet selection) {
-		if (selection.isEmpty()) {
-			return Collections.emptyList();
-		}
-		List<JoinedWay> wayList = new ArrayList<>(selection
-				.cardinality());
-		for (int i = selection.nextSetBit(0); i >= 0; i = selection.nextSetBit(i + 1)) {
-			wayList.add(polygons.get(i));
-		}
-		return wayList;
-	}
-
 	private static void logWayURLs(Level level, String preMsg, Way way) {
 		if (log.isLoggable(level)) {
 			if (way instanceof JoinedWay) {
@@ -1792,132 +1078,85 @@ public class MultiPolygonRelation extends Relation {
 			return;
 		}
 		
-		boolean containsOrgFakeWay = false;
-		for (Way orgWay : fakeWay.getOriginalWays()) {
-			if (FakeIdGenerator.isFakeId(orgWay.getId())) {
-				containsOrgFakeWay = true;
-			}
-		}
-		
-		if (!containsOrgFakeWay) {
+		if (fakeWay.getOriginalWays().stream().noneMatch(w -> FakeIdGenerator.isFakeId(w.getId())))
 			return;
-		}
 		
 		// the fakeWay consists only of other faked ways
 		// there should be more information about these ways
 		// so that it is possible to retrieve the original
 		// OSM ways
 		// => log the start and end points
-		
-		for (Way orgWay : fakeWay.getOriginalWays()) {
-			log.log(logLevel, "Way", orgWay.getId(), "is composed of other artificial ways. Details:");
-			log.log(logLevel, " Start:", orgWay.getFirstPoint().toOSMURL());
-			if (orgWay.hasEqualEndPoints()) {
-				// the way is closed so start and end are equal - log the point in the middle of the way
-				int mid = orgWay.getPoints().size()/2;
-				log.log(logLevel, " Mid:  ", orgWay.getPoints().get(mid).toOSMURL());
-			} else {
-				log.log(logLevel, " End:  ", orgWay.getLastPoint().toOSMURL());
-			}
-		}		
-	}
-
-	protected void tagOuterWays() {
-		Map<String, String> tags;
-		if (hasStyleRelevantTags(this)) {
-			tags = new HashMap<>();
-			for (Entry<String, String> relTag : getTagEntryIterator()) {
-				tags.put(relTag.getKey(), relTag.getValue());
-			}
-		} else {
-			tags = JoinedWay.getMergedTags(outerWaysForLineTagging);
-		}
-		
-		
-		// Go through all original outer ways, create a copy, tag them
-		// with the mp tags and mark them only to be used for polyline processing
-		// This enables the style file to decide if the polygon information or
-		// the simple line information should be used.
-		for (Way orgOuterWay : outerWaysForLineTagging) {
-			Way lineTagWay =  new Way(getOriginalId(), orgOuterWay.getPoints());
-			lineTagWay.markAsGeneratedFrom(this);
-			lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
-			lineTagWay.addTag(TKM_MP_CREATED, "true");
-			for (Entry<String, String> tag : tags.entrySet()) {
-				lineTagWay.addTag(tag.getKey(), tag.getValue());
-				
-				// remove the tag from the original way if it has the same value
-				if (tag.getValue().equals(orgOuterWay.getTag(tag.getKey()))) {
-					removeTagsInOrgWays(orgOuterWay, tag.getKey());
-				}
+		
+		for (Way orgWay : fakeWay.getOriginalWays()) {
+			log.log(logLevel, "Way", orgWay.getId(), "is composed of other artificial ways. Details:");
+			log.log(logLevel, " Start:", orgWay.getFirstPoint().toOSMURL());
+			if (orgWay.hasEqualEndPoints()) {
+				// the way is closed so start and end are equal - log the point in the middle of the way
+				int mid = orgWay.getPoints().size()/2;
+				log.log(logLevel, " Mid:  ", orgWay.getPoints().get(mid).toOSMURL());
+			} else {
+				log.log(logLevel, " End:  ", orgWay.getLastPoint().toOSMURL());
 			}
-			
-			if (log.isDebugEnabled())
-				log.debug("Add line way", lineTagWay.getId(), lineTagWay.toTagString());
-			tileWayMap.put(lineTagWay.getId(), lineTagWay);
-		}
+		}		
 	}
-	
-	
+
 	/**
-	 * Marks all tags of the original ways of the given JoinedWay that are also
-	 * contained in the given tagElement for removal.
+	 * Marks all tags of the original ways of the given JoinedWay for removal.
 	 * 
-	 * @param tagElement
-	 *            an element contains the tags to be removed
-	 * @param way
-	 *            a joined way
+	 * @param way a joined way
 	 */
-	private void removeTagsInOrgWays(Element tagElement, JoinedWay way) {
-		for (Entry<String, String> tag : tagElement.getTagEntryIterator()) {
-			removeTagInOrgWays(way, tag.getKey(), tag.getValue());
+	private static void markTagsForRemovalInOrgWays(JoinedWay way) {
+		for (Entry<String, String> tag : way.getTagEntryIterator()) {
+			markTagForRemovalInOrgWays(way, tag.getKey(), tag.getValue());
 		}
 	}
 
 	/**
-	 * Mark the given tag of all original ways of the given JoinedWay.
+	 * Mark the given tag for removal in all original ways of the given way.
 	 * 
-	 * @param way
-	 *            a joined way
-	 * @param tagname
-	 *            the tag to be removed
-	 * @param tagvalue
-	 *            the value of the tag to be removed
+	 * @param way      a joined way
+	 * @param tagKey   the tag to be removed
+	 * @param tagvalue the value of the tag to be removed
 	 */
-	private void removeTagInOrgWays(JoinedWay way, String tagname, String tagvalue) {
+	private static void markTagForRemovalInOrgWays(JoinedWay way, String tagKey, String tagvalue) {
 		for (Way w : way.getOriginalWays()) {
 			if (w instanceof JoinedWay) {
-				// remove the tags recursively
-				removeTagInOrgWays((JoinedWay) w, tagname, tagvalue);
-				continue;
-			}
-
-			if (tagvalue.equals(w.getTag(tagname))) {
-				if (log.isDebugEnabled()) {
-					log.debug("Will remove", tagname + "=" + w.getTag(tagname), "from way", w.getId(), w.toTagString());
-				}
-				removeTagsInOrgWays(w, tagname);
+				// remove the tag recursively
+				markTagForRemovalInOrgWays((JoinedWay) w, tagKey, tagvalue);
+			} else if (tagvalue.equals(w.getTag(tagKey))) {
+				markTagsForRemovalInOrgWays(w, tagKey);
 			}
 		}
 	}
 	
-	protected void removeTagsInOrgWays(Way way, String tag) {
-		if (tag == null || tag.isEmpty()) {
-			return;
+	/**
+	 * Add given tag key to the special tag which contains the list of tag keys
+	 * which are to be removed in MultiPolygonFinishHook.
+	 * 
+	 * @param way    the way
+	 * @param tagKey the tag key
+	 */
+	private static void markTagsForRemovalInOrgWays(Way way, String tagKey) {
+		if (tagKey == null || tagKey.isEmpty()) {
+			return; // should not happen
 		}
+
 		String tagsToRemove = way.getTag(ElementSaver.TKM_REMOVETAGS);
 		
 		if (tagsToRemove == null) {
-			tagsToRemove = tag;
-		} else if (tag.equals(tagsToRemove)) {
+			tagsToRemove = tagKey;
+		} else if (tagKey.equals(tagsToRemove)) {
 			return;
 		} else {
 			String[] keys = tagsToRemove.split(";");
-			if (Arrays.asList(keys).contains(tag)) {
+			if (Arrays.asList(keys).contains(tagKey)) {
 				return;
 			}
-			tagsToRemove += ";" + tag;
+			tagsToRemove += ";" + tagKey;
 		} 
+		if (log.isDebugEnabled()) {
+			log.debug("Will remove", tagKey + "=" + way.getTag(tagKey), "from way", way.getId(), way.toTagString());
+		}
 		way.addTag(ElementSaver.TKM_REMOVETAGS, tagsToRemove);
 	}
 	
@@ -1925,7 +1164,7 @@ public class MultiPolygonRelation extends Relation {
 	 * Flag if the area size of the mp should be calculated and added as tag.
 	 * @return {@code true} area size should be calculated; {@code false} area size should not be calculated
 	 */
-	protected boolean isAreaSizeCalculated() {
+	protected boolean needsAreaSizeTag() {
 		return true;
 	}
 
@@ -1968,26 +1207,29 @@ public class MultiPolygonRelation extends Relation {
 		return Math.abs(areaSize);
 	}
 
-
 	/**
 	 * This is a helper class that gives access to the original
-	 * segments of a joined way.
+	 * segments of a joined way or a string of ways. It may be unclosed.
 	 */
 	public static final class JoinedWay extends Way {
 		private final List<Way> originalWays;
+		private byte intRole;  
 		private boolean closedArtificially;
+		private Coord pointInside;
+		private boolean doPointInsideCalcs = true;
 
 		private int minLat;
 		private int maxLat;
 		private int minLon;
 		private int maxLon;
 		private Rectangle bounds;
+		private uk.me.parabola.imgfmt.app.Area area;
 
-		public JoinedWay(Way originalWay) {
+		public JoinedWay(Way originalWay, String givenRole) {
 			super(originalWay.getOriginalId(), originalWay.getPoints());
 			markAsGeneratedFrom(originalWay);
 			originalWays = new ArrayList<>();
-			addWay(originalWay);
+			addWay(originalWay, roleToInt(givenRole));
 
 			// we have to initialize the min/max values
 			Coord c0 = originalWay.getFirstPoint();
@@ -1997,6 +1239,36 @@ public class MultiPolygonRelation extends Relation {
 			updateBounds(originalWay.getPoints());
 		}
 
+		public JoinedWay(JoinedWay other, List<Coord> points) {
+			super(other.getOriginalId(), points);
+			markAsGeneratedFrom(other);
+			originalWays = new ArrayList<>(other.getOriginalWays());
+			intRole = other.intRole;
+			closedArtificially = other.closedArtificially;
+			// we have to initialize the min/max values
+			Coord c0 = points.get(0);
+			minLat = maxLat = c0.getLatitude();
+			minLon = maxLon = c0.getLongitude();
+
+			updateBounds(points);
+			
+		}
+
+		private byte roleToInt(String role) {
+			if (role == null)
+				return INT_ROLE_NULL;
+			switch (role) {
+			case ROLE_INNER:
+				return INT_ROLE_INNER;
+			case ROLE_OUTER:
+				return INT_ROLE_OUTER;
+			case "":
+				return INT_ROLE_BLANK;
+			default:
+				return INT_ROLE_OTHER;
+			}
+		}
+
 		public void addPoint(int index, Coord point) {
 			getPoints().add(index, point);
 			updateBounds(point);
@@ -2035,29 +1307,12 @@ public class MultiPolygonRelation extends Relation {
 				maxLon = lon;
 				bounds = null;
 			}
-
-			
 		}
+		
 		private void updateBounds(Coord point) {
 			updateBounds(point.getLatitude(), point.getLongitude());
 		}
 		
-		/**
-		 * Checks if this way intersects the given bounding box at least with
-		 * one point.
-		 * 
-		 * @param bbox
-		 *            the bounding box
-		 * @return <code>true</code> if this way intersects or touches the
-		 *         bounding box; <code>false</code> else
-		 */
-		public boolean intersects(uk.me.parabola.imgfmt.app.Area bbox) {
-			return (maxLat >= bbox.getMinLat() 
-					&& minLat <= bbox.getMaxLat() 
-					&& maxLon >= bbox.getMinLong()
-					&& minLon <= bbox.getMaxLong());
-		}
-
 		public Rectangle getBounds() {
 			if (bounds == null) {
 				// note that we increase the rectangle by 1 because intersects
@@ -2070,25 +1325,34 @@ public class MultiPolygonRelation extends Relation {
 			return bounds;
 		}
 
-		public boolean linePossiblyIntersectsWay(Coord p1, Coord p2) {
-			return getBounds().intersectsLine(p1.getLongitude(),
-					p1.getLatitude(), p2.getLongitude(), p2.getLatitude());
+		public uk.me.parabola.imgfmt.app.Area getArea() {
+			if (area == null) {
+				area = new uk.me.parabola.imgfmt.app.Area(minLat, minLon, maxLat, maxLon);
+			}
+
+			return area;
 		}
 
-		public void addWay(Way way) {
+		public void addWay(Way way, int internalRole) {
 			if (way instanceof JoinedWay) {
-				for (Way w : ((JoinedWay) way).getOriginalWays()) {
-					addWay(w);
-				}
+				originalWays.addAll(((JoinedWay) way).getOriginalWays());
+				this.intRole |= ((JoinedWay) way).intRole;
 				updateBounds((JoinedWay) way);
 			} else {
 				if (log.isDebugEnabled()) {
 					log.debug("Joined", this.getId(), "with", way.getId());
 				}
 				this.originalWays.add(way);
+				this.intRole |= internalRole;
 			}
 		}
 
+		public void addWay(JoinedWay way) {
+			originalWays.addAll(way.originalWays);
+			this.intRole |= way.intRole;
+			updateBounds(way);
+		}
+
 		public void closeWayArtificially() {
 			addPoint(getPoints().get(0));
 			closedArtificially = true;
@@ -2098,6 +1362,11 @@ public class MultiPolygonRelation extends Relation {
 			return closedArtificially;
 		}
 
+		/**
+		 * Get common tags of all ways.
+		 * @param ways the collection of ways
+		 * @return map with common tags, might be empty but will never be null
+		 */
 		public static Map<String, String> getMergedTags(Collection<Way> ways) {
 			Map<String, String> mergedTags = new HashMap<>();
 			boolean first = true;
@@ -2109,23 +1378,14 @@ public class MultiPolygonRelation extends Relation {
 					}
 					first = false;
 				} else {
-					// for all other ways all non matching tags are removed
-					ArrayList<String> tagsToRemove = null;
-					for (Map.Entry<String, String> tag : mergedTags.entrySet()) {
-						String wayTagValue = way.getTag(tag.getKey());
-						if (wayTagValue != null && !tag.getValue().equals(wayTagValue)) {
-							// the tags are different
-							if (tagsToRemove == null) {
-								tagsToRemove = new ArrayList<>();
-							}
-							tagsToRemove.add(tag.getKey());
-						}
-					}
-					if (tagsToRemove != null) {
-						for (String tag : tagsToRemove) {
-							mergedTags.remove(tag);
-						}
+					if (mergedTags.isEmpty()) {
+						break;
 					}
+					// remove tags with different value
+					mergedTags.entrySet().removeIf(tag -> {
+						String wayTagValue = way.getTag(tag.getKey());
+						return (wayTagValue != null && !tag.getValue().equals(wayTagValue));
+					});
 				}
 			}
 			return mergedTags;
@@ -2141,50 +1401,98 @@ public class MultiPolygonRelation extends Relation {
 			removeAllTags();
 			
 			Map<String, String> mergedTags = getMergedTags(getOriginalWays());
-			for (Entry<String, String> tag : mergedTags.entrySet()) {
-				addTag(tag.getKey(), tag.getValue());
-			}
+			mergedTags.forEach(this::addTag);
 		}
 
 		public List<Way> getOriginalWays() {
 			return originalWays;
 		}
 		
+		@Override
+		public String toString() {
+			final String prefix = getId() + "(" + getPoints().size() + "P)(";
+			return getOriginalWays().stream().map(w -> w.getId() + "[" + w.getPoints().size() + "P]")
+					.collect(Collectors.joining(",", prefix, ")"));
+		}
+
+		public boolean canJoin(JoinedWay other) {
+			return getFirstPoint() == other.getFirstPoint() || getFirstPoint() == other.getLastPoint()
+					|| getLastPoint() == other.getFirstPoint() || getLastPoint() == other.getLastPoint();
+		}
+
+		public boolean buildsRingWith(JoinedWay other) {
+			return getFirstPoint() == other.getFirstPoint() && getLastPoint() == other.getLastPoint()
+					|| getFirstPoint() == other.getLastPoint() && getLastPoint() == other.getFirstPoint();
+		}
+		
 		/**
-		 * Retrieves a measurement of the area covered by this polygon. The 
-		 * returned value has no unit. It is just a rough comparable value
-		 * because it uses a rectangular coordinate system without correction.
-		 * @return size of the covered areas (0 if the way is not closed)
+		 * Join the other way.
+		 * 
+		 * @param other     the way to be added to this 
+		 * @throws ExitException if ways cannot be joined 
 		 */
-		public double getSizeOfArea() {
-			return MultiPolygonRelation.calcAreaSize(getPoints());
+		private void joinWith(JoinedWay other) {
+			boolean reverseOther = false;
+			int insIdx = -1;
+			int firstOtherIdx = 1;
+			
+			if (this.getFirstPoint() == other.getFirstPoint()) {
+				insIdx = 0;
+				reverseOther = true;
+				firstOtherIdx = 1;
+			} else if (this.getLastPoint() == other.getFirstPoint()) {
+				insIdx = this.getPoints().size();
+				firstOtherIdx = 1;
+			} else if (this.getFirstPoint() == other.getLastPoint()) {
+				insIdx = 0; 
+				firstOtherIdx = 0;
+			} else if (this.getLastPoint() == other.getLastPoint()) {
+				insIdx = this.getPoints().size();
+				reverseOther = true;
+				firstOtherIdx = 0;
+			} else {
+				String msg = "Cannot join " + this.getBasicLogInformation() + " with " + other.getBasicLogInformation();
+				log.error(msg);
+				throw new ExitException(msg);
+			}
+			
+			int lastIdx = other.getPoints().size();
+			if (firstOtherIdx == 0) {
+				// the last temp point is already contained in the joined way - do not copy it
+				lastIdx--;
+			}
+
+			List<Coord> tempCoords = other.getPoints().subList(firstOtherIdx,lastIdx);
+
+			if (reverseOther) {
+				// the temp coords need to be reversed so copy the list
+				tempCoords = new ArrayList<>(tempCoords);
+				// and reverse it
+				Collections.reverse(tempCoords);
+			}
+
+			this.getPoints().addAll(insIdx, tempCoords);
+			this.addWay(other);
 		}
 
-		@Override
-		public String toString() {
-			StringBuilder sb = new StringBuilder(200);
-			sb.append(getId());
-			sb.append("(");
-			sb.append(getPoints().size());
-			sb.append("P)(");
-			boolean first = true;
-			for (Way w : getOriginalWays()) {
-				if (first) {
-					first = false;
-				} else {
-					sb.append(",");
+		/**
+		 * Try to find a point that is inside the polygon and store the result.
+		 * 
+		 * @return null or a point that is inside
+		 */
+		public Coord getPointInside() {
+			if (doPointInsideCalcs) {
+				doPointInsideCalcs = false;
+				Coord test = super.getCofG();
+				if (IsInUtil.isPointInShape(test, getPoints()) == IsInUtil.IN) {
+					pointInside = test;
 				}
-				sb.append(w.getId());
-				sb.append("[");
-				sb.append(w.getPoints().size());
-				sb.append("P]");
 			}
-			sb.append(")");
-			return sb.toString();
+			return pointInside;
 		}
 	}
 
-	public static class PolygonStatus {
+	protected static class PolygonStatus {
 		public final boolean outer;
 		public final int index;
 		public final JoinedWay polygon;
@@ -2200,13 +1508,507 @@ public class MultiPolygonRelation extends Relation {
 		}
 	}
 
+	/**
+	 * Helper class to bundle objects related to a list of polygons
+	 *
+	 */
+	protected class Partition {
+		/** list of polygons with a fixed order */
+		final List<JoinedWay> polygons; 
+
+		final List<BitSet> containsMatrix;
+		// Various BitSets which relate to the content of field polygons  
+		/** unfinishedPolygons marks which polygons are not yet processed */
+		public final BitSet unfinishedPolygons;
+
+		// reporting: BitSets which polygons belong to the outer and to the inner role
+		final BitSet innerPolygons;
+		final BitSet taggedInnerPolygons;
+		final BitSet outerPolygons;
+		final BitSet taggedOuterPolygons;
+		final BitSet nestedOuterPolygons;
+		final BitSet nestedInnerPolygons;
+		final BitSet outmostInnerPolygons;
+		
+		public Partition(List<JoinedWay> list) {
+			this.polygons = Collections.unmodifiableList(list);
+			innerPolygons = new BitSet(list.size());
+			taggedInnerPolygons = new BitSet(list.size());
+			outerPolygons = new BitSet(list.size());
+			taggedOuterPolygons = new BitSet(list.size());
+			analyseRelationRoles();
+			// unfinishedPolygons marks which polygons are not yet processed
+			unfinishedPolygons = new BitSet(list.size());
+			unfinishedPolygons.set(0, list.size());
+			// check which polygons lie inside which other polygon
+			containsMatrix = createContainsMatrix(list);
+			nestedOuterPolygons = new BitSet(list.size());
+			nestedInnerPolygons = new BitSet(list.size());
+			outmostInnerPolygons = new BitSet(list.size());
+		}
+		
+		public void markFinished(PolygonStatus currentPolygon) {
+			unfinishedPolygons.clear(currentPolygon.index);
+		}
+
+		/**
+		 * Creates a matrix which polygon contains which polygon. A polygon does not
+		 * contain itself.
+		 * @return 
+		 */
+		private List<BitSet> createContainsMatrix(List<JoinedWay> polygons) {
+			List<BitSet> matrix = new ArrayList<>();
+			for (int i = 0; i < polygons.size(); i++) {
+				matrix.add(new BitSet());
+			}
+
+			long t1 = System.currentTimeMillis();
+
+			if (log.isDebugEnabled())
+				log.debug("createContainsMatrix listSize:", polygons.size());
+
+			// use this matrix to check which matrix element has been
+			// calculated
+			ArrayList<BitSet> finishedMatrix = new ArrayList<>(polygons.size());
+
+			for (int i = 0; i < polygons.size(); i++) {
+				BitSet matrixRow = new BitSet();
+				// a polygon does not contain itself
+				matrixRow.set(i);
+				finishedMatrix.add(matrixRow);
+			}
+			
+			for (int rowIndex = 0; rowIndex < polygons.size(); rowIndex++) {
+				JoinedWay potentialOuterPolygon = polygons.get(rowIndex);
+				BitSet containsColumns = matrix.get(rowIndex);
+				BitSet finishedCol = finishedMatrix.get(rowIndex);
+
+				// get all non calculated columns of the matrix
+				for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0
+						&& colIndex < polygons.size(); colIndex = finishedCol
+						.nextClearBit(colIndex + 1)) {
+
+					JoinedWay innerPolygon = polygons.get(colIndex);
+
+					if (potentialOuterPolygon.getBounds().intersects(innerPolygon.getBounds())) {
+						boolean contains = calcContains(potentialOuterPolygon, innerPolygon);
+						if (contains) {
+							containsColumns.set(colIndex);
+
+							// we also know that the inner polygon does not contain the
+							// outer polygon
+							// so we can set the finished bit for this matrix
+							// element
+							finishedMatrix.get(colIndex).set(rowIndex);
+
+							// additionally we know that the outer polygon contains all
+							// polygons that are contained by the inner polygon
+							containsColumns.or(matrix.get(colIndex));
+							finishedCol.or(containsColumns);
+						}
+					} else {
+						// both polygons do not intersect
+						// we can flag both matrix elements as finished
+						finishedMatrix.get(colIndex).set(rowIndex);
+						finishedMatrix.get(rowIndex).set(colIndex);
+					}
+					// this matrix element is calculated now
+					finishedCol.set(colIndex);
+				}
+			}
+
+			if (log.isDebugEnabled()) {
+				long t2 = System.currentTimeMillis();
+				log.debug("createMatrix for", polygons.size(), "polygons took", (t2 - t1), "ms");
+
+				log.debug("Containsmatrix:");
+				int i = 0;
+				boolean noContained = true;
+				for (BitSet b : matrix) {
+					if (!b.isEmpty()) {
+						log.debug(i, "contains", b);
+						noContained = false;
+					}
+					i++;
+				}
+				if (noContained) {
+					log.debug("Matrix is empty");
+				}
+			}
+			return matrix;
+		}
+
+		
+		/**
+		 * 
+		 * @return
+		 */
+		private BitSet getOutmostRingsAndMatchWithRoles() {
+			BitSet outmostPolygons;
+			boolean outmostInnerFound;
+			do {
+				outmostInnerFound = false;
+				outmostPolygons = findOutmostPolygons(unfinishedPolygons);
+
+				if (outmostPolygons.intersects(taggedInnerPolygons)) {
+					// found outmost ring(s) with role inner
+					outmostInnerPolygons.or(outmostPolygons);
+					outmostInnerPolygons.and(taggedInnerPolygons);
+
+					if (log.isDebugEnabled())
+						log.debug("wrong inner polygons: " + outmostInnerPolygons);
+					// do not process polygons tagged with role=inner but which are
+					// not contained by any other polygon
+					unfinishedPolygons.andNot(outmostInnerPolygons);
+					outmostPolygons.andNot(outmostInnerPolygons);
+					outmostInnerFound = true;
+				}
+			} while (outmostInnerFound);
+			return outmostPolygons;
+		}
+
+		/**
+		 * Analyse roles in ways and fill corresponding sets.
+		 */
+		private void analyseRelationRoles() {
+			for (int i = 0; i < polygons.size(); i++) {
+				JoinedWay jw = polygons.get(i);
+				if (jw.intRole == INT_ROLE_INNER) {
+					innerPolygons.set(i);
+					taggedInnerPolygons.set(i);
+				} else if (jw.intRole == INT_ROLE_OUTER) {
+					outerPolygons.set(i);
+					taggedOuterPolygons.set(i);
+				} else {
+					// unknown role => it could be both
+					innerPolygons.set(i);
+					outerPolygons.set(i);
+				}
+			}
+		}
+
+		/**
+		 * Report problems which are probably caused by OSM data errors or missing/incomplete data.   
+		 */
+		public void reportProblems() {
+			if (outmostInnerPolygons.cardinality() + unfinishedPolygons.cardinality()
+					+ nestedOuterPolygons.cardinality() + nestedInnerPolygons.cardinality() >= 1) {
+				log.warn("Multipolygon", toBrowseURL(), toTagString(), "contains errors.");
+
+				BitSet outerUnusedPolys = new BitSet();
+				outerUnusedPolys.or(unfinishedPolygons);
+				outerUnusedPolys.or(outmostInnerPolygons);
+				outerUnusedPolys.or(nestedOuterPolygons);
+				outerUnusedPolys.or(nestedInnerPolygons);
+				outerUnusedPolys.or(unfinishedPolygons);
+				// use only the outer polygons
+				outerUnusedPolys.and(outerPolygons);
+				for (JoinedWay w : bitsetToList(outerUnusedPolys)) {
+					//TODO: How do we get here?
+					outerWaysForLineTagging.addAll(w.getOriginalWays());
+				}
+
+				runOutmostInnerPolygonCheck(polygons, outmostInnerPolygons);
+				runNestedOuterPolygonCheck(polygons, nestedOuterPolygons);
+				runNestedInnerPolygonCheck(polygons, nestedInnerPolygons);
+				runWrongInnerPolygonCheck(polygons, unfinishedPolygons, innerPolygons);
+
+				// we have at least one polygon that could not be processed
+				// Probably we have intersecting or overlapping polygons
+				// one possible reason is if the relation overlaps the tile
+				// bounds
+				// => issue a warning
+				List<JoinedWay> lostWays = bitsetToList(unfinishedPolygons);
+				for (JoinedWay w : lostWays) {
+					log.warn("Polygon", w, "is not processed due to an unknown reason.");
+					logWayURLs(Level.WARNING, "-", w);
+				}
+			}
+		}
+
+		private List<JoinedWay> bitsetToList(BitSet selection) {
+			return selection.stream().mapToObj(polygons::get).collect(Collectors.toList());
+		}
+
+		private void runNestedOuterPolygonCheck(List<JoinedWay> polygons, BitSet nestedOuterPolygons) {
+			// just print out warnings
+			// the check has been done before
+			nestedOuterPolygons.stream().forEach(idx ->  {
+				JoinedWay outerWay = polygons.get(idx);
+				log.warn("Polygon",	outerWay, "carries role outer but lies inside an outer polygon. Potentially its role should be inner.");
+				logFakeWayDetails(Level.WARNING, outerWay);
+			});
+		}
+		
+		private void runNestedInnerPolygonCheck(List<JoinedWay> polygons, BitSet nestedInnerPolygons) {
+			// just print out warnings
+			// the check has been done before
+			nestedInnerPolygons.stream().forEach(idx -> {
+				JoinedWay innerWay = polygons.get(idx);
+				log.warn("Polygon",	innerWay, "carries role", getRole(innerWay), "but lies inside an inner polygon. Potentially its role should be outer.");
+				logFakeWayDetails(Level.WARNING, innerWay);
+			});
+		}	
+		
+		private void runOutmostInnerPolygonCheck(List<JoinedWay> polygons, BitSet outmostInnerPolygons) {
+			// just print out warnings
+			// the check has been done before
+			outmostInnerPolygons.stream().forEach(idx -> {
+				JoinedWay innerWay = polygons.get(idx);
+				log.warn("Polygon",	innerWay, "carries role", getRole(innerWay), "but is not inside any other polygon. Potentially it does not belong to this multipolygon.");
+				logFakeWayDetails(Level.WARNING, innerWay);
+			});
+		}
+
+		private void runWrongInnerPolygonCheck(List<JoinedWay> polygons, BitSet unfinishedPolygons, BitSet innerPolygons) {
+			// find all unfinished inner polygons that are not contained by any
+			BitSet wrongInnerPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons);
+			if (log.isDebugEnabled()) {
+				log.debug("unfinished", unfinishedPolygons);
+				log.debug(ROLE_INNER, innerPolygons);
+				// other polygon
+				log.debug("wrong", wrongInnerPolygons);
+			}
+			if (!wrongInnerPolygons.isEmpty()) {
+				// we have an inner polygon that is not contained by any outer polygon
+				// check if
+				wrongInnerPolygons.stream().forEach(wiIndex -> {
+					BitSet containedPolygons = new BitSet();
+					containedPolygons.or(unfinishedPolygons);
+					containedPolygons.and(containsMatrix.get(wiIndex));
+
+					JoinedWay innerWay = polygons.get(wiIndex);
+					if (containedPolygons.isEmpty()) {
+						log.warn("Polygon",	innerWay, "carries role", getRole(innerWay),
+							"but is not inside any outer polygon. Potentially it does not belong to this multipolygon.");
+						logFakeWayDetails(Level.WARNING, innerWay);
+					} else {
+						log.warn("Polygon",	innerWay, "carries role", getRole(innerWay),
+							"but is not inside any outer polygon. Potentially the roles are interchanged with the following",
+							(containedPolygons.cardinality() > 1 ? "ways" : "way"), ".");
+						containedPolygons.stream().forEach(wrIndex -> {
+							logWayURLs(Level.WARNING, "-", polygons.get(wrIndex));
+							unfinishedPolygons.set(wrIndex);
+							wrongInnerPolygons.set(wrIndex);
+						});
+						logFakeWayDetails(Level.WARNING, innerWay);
+					}
+
+					unfinishedPolygons.clear(wiIndex);
+					wrongInnerPolygons.clear(wiIndex);
+				});
+			}
+		}
+
+		/**
+		 * Checks if the polygon with polygonIndex1 contains the polygon with polygonIndex2.
+		 * 
+		 * @return true if polygon(polygonIndex1) contains polygon(polygonIndex2)
+		 */
+		private boolean contains(int polygonIndex1, int polygonIndex2) {
+			return containsMatrix.get(polygonIndex1).get(polygonIndex2);
+		}
+
+		/**
+		 * Find all polygons that are not contained by any other polygon.
+		 * 
+		 * @param candidates
+		 *            all polygons that should be checked
+		 * @param roleFilter
+		 *            an additional filter
+		 * @return all polygon indexes that are not contained by any other polygon
+		 */
+		private BitSet findOutmostPolygons(BitSet candidates, BitSet roleFilter) {
+			BitSet realCandidates = ((BitSet) candidates.clone());
+			realCandidates.and(roleFilter);
+			return findOutmostPolygons(realCandidates);
+		}
+
+		/**
+		 * Finds all polygons that are not contained by any other polygons and that
+		 * match the given role. All polygons with index given by
+		 * <var>candidates</var> are tested.
+		 * 
+		 * @param candidates indexes of the polygons that should be used
+		 * @return set of indexes of all outermost polygons 
+		 */
+		private BitSet findOutmostPolygons(BitSet candidates) {
+			BitSet outmostPolygons = new BitSet();
+
+			// go through all candidates and check if they are contained by any
+			// other candidate
+			candidates.stream().forEach(candidateIndex -> {
+				// check if the candidateIndex polygon is not contained by any
+				// other candidate polygon
+				boolean isOutmost = candidates.stream()
+						.noneMatch(otherCandidateIndex -> contains(otherCandidateIndex, candidateIndex));
+				if (isOutmost) {
+					// this is an outermost polygon
+					// put it to the bitset
+					outmostPolygons.set(candidateIndex);
+				}
+			});
+
+			return outmostPolygons;
+		}
+
+		public List<PolygonStatus> getPolygonStatus(PolygonStatus currentPolygon) {
+			ArrayList<PolygonStatus> polygonStatusList = new ArrayList<>();
+			BitSet outmostPolygons;
+			final String defaultRole;
+			if (currentPolygon == null) {
+				outmostPolygons = getOutmostRingsAndMatchWithRoles();
+				defaultRole = ROLE_OUTER;
+			} else {
+				outmostPolygons = checkRoleAgainstGeometry(currentPolygon);
+				defaultRole = currentPolygon.outer ? ROLE_INNER : ROLE_OUTER;
+			}
+			outmostPolygons.stream().forEach(polyIndex -> {
+				// polyIndex is the polygon that is not contained by any other
+				// polygon
+				JoinedWay polygon = polygons.get(polyIndex);
+				String role = getRole(polygon);
+				// if the role is not explicitly set use the default role
+				if (role == null || "".equals(role)) {
+					role = defaultRole;
+				} 
+				polygonStatusList.add(new PolygonStatus(ROLE_OUTER.equals(role), polyIndex, polygon));
+			});
+			
+			// sort by role and then by number of points, this improves performance
+			// in the routines which add the polygons to areas
+			if (polygonStatusList.size() > 2) {
+				polygonStatusList.sort((o1, o2) -> {
+					if (o1.outer != o2.outer)
+						return (o1.outer) ? -1 : 1;
+					return o1.polygon.getPoints().size() - o2.polygon.getPoints().size();
+				});
+			}
+			return polygonStatusList;
+		}
+
+		/**
+		 * Check the roles of polygons against the actual findings in containsMatrix. Not sure what this does so far.
+		 * @param currentPolygon the current polygon
+		 * @return set of polygon indexes which are considered as holes of the current polygon  
+		 */
+		public BitSet checkRoleAgainstGeometry(PolygonStatus currentPolygon) {
+			BitSet polygonContains = new BitSet();
+			polygonContains.or(containsMatrix.get(currentPolygon.index));
+			// use only polygon that are contained by the polygon
+			polygonContains.and(unfinishedPolygons);
+			// polygonContains is the intersection of the unfinished and
+			// the contained polygons
+
+			// get the holes
+			// these are all polygons that are in the current polygon
+			// and that are not contained by any other polygon
+			boolean holesOk;
+			BitSet holeIndexes;
+			do {
+				holeIndexes = findOutmostPolygons(polygonContains);
+				holesOk = true;
+
+				if (currentPolygon.outer) {
+					// for role=outer only role=inner is allowed
+					if (holeIndexes.intersects(taggedOuterPolygons)) {
+						BitSet addOuterNestedPolygons = new BitSet();
+						addOuterNestedPolygons.or(holeIndexes);
+						addOuterNestedPolygons.and(taggedOuterPolygons);
+						nestedOuterPolygons.or(addOuterNestedPolygons);
+						holeIndexes.andNot(addOuterNestedPolygons);
+						// do not process them
+						unfinishedPolygons.andNot(addOuterNestedPolygons);
+						polygonContains.andNot(addOuterNestedPolygons);
+						
+						// recalculate the holes again to get all inner polygons 
+						// in the nested outer polygons
+						holesOk = false;
+					}
+				} else {
+					// for role=inner both role=inner and role=outer is supported
+					// although inner in inner is not officially allowed
+					if (holeIndexes.intersects(taggedInnerPolygons)) {
+						// process inner in inner but issue a warning later
+						BitSet addInnerNestedPolygons = new BitSet();
+						addInnerNestedPolygons.or(holeIndexes);
+						addInnerNestedPolygons.and(taggedInnerPolygons);
+						nestedInnerPolygons.or(addInnerNestedPolygons);
+					}
+				}
+			} while (!holesOk);
+			return holeIndexes;
+		}
+	}
+	
+	private void divideLargest(List<JoinedWay> partition, List<List<JoinedWay>> partitions, int depth) {
+		if (partition.isEmpty())
+			return;
+		// probably complex polygons with crossing /self intersecting ways will be problematic 
+		if (depth >= 10 || partition.size() < 2 || tagIsLikeYes("expect-self-intersection")) {
+			partitions.add(partition);
+			return;
+		}
+		JoinedWay mostComplex = partition.get(0);
+		for (JoinedWay jw : partition) {
+			if (mostComplex.getPoints().size() < jw.getPoints().size())
+				mostComplex = jw;
+		}
+		if (mostComplex.getPoints().size() > 2000) {
+			uk.me.parabola.imgfmt.app.Area fullArea = calcBounds(partition);
+			uk.me.parabola.imgfmt.app.Area[] areas;
+			final int niceSplitShift = 11; 
+			if (fullArea.getHeight() > fullArea.getWidth())
+				areas = fullArea.split(1, 2, niceSplitShift);
+			else 
+				areas = fullArea.split(2, 1, niceSplitShift);
+			// code to calculate dividingLine taken from MapSplitter
+			if (areas != null && areas.length == 2) {
+				int dividingLine = 0;
+				boolean isLongitude = false;
+				boolean commonLine = true;
+				if (areas[0].getMaxLat() == areas[1].getMinLat()) {
+					dividingLine = areas[0].getMaxLat();
+				} else if (areas[0].getMaxLong() == areas[1].getMinLong()) {
+					dividingLine = areas[0].getMaxLong();
+					isLongitude = true;
+				} else {
+					commonLine = false;
+					log.error("Split into 2 expects shared edge between the areas");
+				}
+				if (commonLine) {
+					List<JoinedWay> dividedLess = new ArrayList<>();
+					List<JoinedWay> dividedMore = new ArrayList<>();
+					for (int i = 0; i < partition.size(); i++) {
+						JoinedWay jw = partition.get(i);
+						
+						List<List<Coord>> lessList = new ArrayList<>();
+						List<List<Coord>> moreList = new ArrayList<>();
+						ShapeSplitter.splitShape(jw.getPoints(), dividingLine << Coord.DELTA_SHIFT, isLongitude,
+								lessList, moreList, commonCoordMap);
+						
+						lessList.forEach(part -> dividedLess.add(new JoinedWay(jw, part)));
+						moreList.forEach(part -> dividedMore.add(new JoinedWay(jw, part)));
+					}
+					divideLargest(dividedLess, partitions, depth + 1);
+					divideLargest(dividedMore, partitions, depth + 1);
+					return;
+				}
+			}
+		} 
+		partitions.add(partition);
+	}
+
+
 	public Way getLargestOuterRing() {
 		return largestOuterPolygon;
 	}
 	
 	public List<JoinedWay> getRings() {
-		if (polygons== null)
+		if (polygons == null) {
 			polygons = buildRings();
+			cleanup();
+		}
 		return polygons;
 	}
 


=====================================
src/uk/me/parabola/mkgmap/reader/osm/Relation.java
=====================================
@@ -12,8 +12,6 @@ import java.util.Map;
  */
 public abstract class Relation extends Element {
 	private final List<Map.Entry<String,Element>> elements = new ArrayList<>();
-	// if set, one or more tags were ignored because they are not used in the style or in mkgmap 
-	private boolean tagsIncomplete;
 
 	/**
 	 * Add a (role, Element) pair to this Relation.
@@ -40,20 +38,4 @@ public abstract class Relation extends Element {
 	public String kind() {
 		return "relation";
 	}
-
-	/**
-	 * Used in MultipolygonRelation.
-	 * @param tagsIncomplete
-	 */
-	public void setTagsIncomplete(boolean tagsIncomplete) {
-		this.tagsIncomplete = tagsIncomplete;
-	}
-	
-	/**
-	 * @return true if any tag was removed by the loader
-	 */
-	public boolean getTagsIncomplete() {
-		return tagsIncomplete;
-	}
-	
 }


=====================================
src/uk/me/parabola/mkgmap/reader/osm/SeaPolygonRelation.java
=====================================
@@ -56,7 +56,7 @@ public class SeaPolygonRelation extends MultiPolygonRelation {
 	}
 
 	@Override
-	protected boolean isAreaSizeCalculated() {
+	protected boolean needsAreaSizeTag() {
 		return false;
 	}
 	


=====================================
src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java
=====================================
@@ -175,7 +175,6 @@ public class OsmBinHandler extends OsmHandler {
 				long id = binRel.getId();
 				GeneralRelation rel = new GeneralRelation(id);
 
-				boolean tagsIncomplete = false;
 				for (int j = 0; j < binRel.getKeysCount(); j++) {
 					String key = getStringById(binRel.getKeys(j));
 					String val = getStringById(binRel.getVals(j));
@@ -185,12 +184,10 @@ public class OsmBinHandler extends OsmHandler {
 						key = "type";
 					else
 						key = keepTag(key, val);
-					if (key == null)
-						tagsIncomplete = true;
-					else
+					if (key != null) {
 						rel.addTagFromRawOSM(key, val);
+					}
 				}
-				rel.setTagsIncomplete(tagsIncomplete);
 				long lastMid = 0;
 
 				for (int j = 0; j < binRel.getMemidsCount(); j++) {


=====================================
src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryElementSaver.java
=====================================
@@ -84,6 +84,7 @@ public class BoundaryElementSaver extends ElementSaver {
 		return false;
 	}
 	
+	@Override
 	public void addRelation(Relation rel) {
 		if (isBoundary(rel)) {
 			BoundaryRelation bRel = (BoundaryRelation) createMultiPolyRelation(rel);
@@ -96,17 +97,22 @@ public class BoundaryElementSaver extends ElementSaver {
 		}
 	}
 	
+	@Override
 	public void deferRelation(long id, Relation rel, String role) {
-		return;
+		// nothing to do unless we have to process role subarea
 	}
 	
+	@Override
 	public Relation createMultiPolyRelation(Relation rel) {
 		return new BoundaryRelation(rel, wayMap, getBoundingBox());
 	}
 
+	@Override
 	public void addNode(Node node) {
+		// nothing to do
 	}
 
+	@Override
 	public void convert(OsmConverter converter) {
 		nodeMap = null;
 


=====================================
src/uk/me/parabola/mkgmap/reader/osm/boundary/BoundaryRelation.java
=====================================
@@ -13,21 +13,11 @@
 package uk.me.parabola.mkgmap.reader.osm.boundary;
 
 import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.List;
-import java.util.ListIterator;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Queue;
-import java.util.concurrent.LinkedBlockingQueue;
 
 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.reader.osm.MultiPolygonRelation;
 import uk.me.parabola.mkgmap.reader.osm.Relation;
 import uk.me.parabola.mkgmap.reader.osm.Way;
@@ -35,9 +25,6 @@ import uk.me.parabola.util.Java2DConverter;
 
 
 public class BoundaryRelation extends MultiPolygonRelation {
-	private static final Logger log = Logger
-	.getLogger(BoundaryRelation.class);
-
 	private java.awt.geom.Area outerResultArea;
 	
 	/** keeps the result of the multipolygon processing */
@@ -58,363 +45,97 @@ public class BoundaryRelation extends MultiPolygonRelation {
 		return boundary;
 	}
 	
+	@Override
+	protected boolean isUsable() {
+		return true; // we assume that tags were already checked by calling code
+	}
+
 	/**
-	 * Process the ways in this relation. Joins way with the role "outer" Adds
-	 * ways with the role "inner" to the way with the role "outer"
+	 * Tile bounds have a different meaning when boundaries are compiled. We expect
+	 * either planet or a bbox around a country extract in tile bounds. A country
+	 * extract typically only contains the complete admin_level boundaries for one
+	 * country but also many incomplete boundaries for neighbouring countries. <br>
+	 * We may either ignore all incomplete boundaries or try to close them using the
+	 * shape ([country].poly) file. The latter should improve LocationHook results
+	 * for data outside the country.  
+	 * 
+	 * @return false
 	 */
-	public void processElements() {
-		log.info("Processing multipolygon", toBrowseURL());
+	@Override
+	protected boolean assumeDataInBoundsIsComplete() {
+		return false;
+	}
 	
-		List<Way> allWays = getSourceWays();
-		
-		// join all single ways to polygons, try to close ways and remove non closed ways 
-		polygons = joinWays(allWays);
-		
-		outerWaysForLineTagging = new HashSet<>();
-		outerTags = new HashMap<>();
-		
-		removeOutOfBbox(polygons);
-
-		do {
-			closeWays(polygons, getMaxCloseDist());
-		} while (connectUnclosedWays(polygons));
-
-		removeUnclosedWays(polygons);
-
-		// now only closed ways are left => polygons only
-
-		// check if we have at least one polygon left
-		boolean hasPolygons = !polygons.isEmpty();
-
-		removeWaysOutsideBbox(polygons);
-
-		if (polygons.isEmpty()) {
-			// do nothing
-			if (log.isInfoEnabled()) {
-				if (hasPolygons)
-					log.info("Multipolygon", toBrowseURL(),
-							"is completely outside the bounding box. It is not processed.");
-				else
-					log.info("Multipolygon " + toBrowseURL() + " does not contain a closed polygon.");
-			}
-			tagOuterWays();
-			cleanup();
-			return;
-		}
-		
-		// the intersectingPolygons marks all intersecting/overlapping polygons
-		intersectingPolygons = new HashSet<>();
-		
-		// check which polygons lie inside which other polygon 
-		createContainsMatrix(polygons);
-
-		// unfinishedPolygons marks which polygons are not yet processed
-		unfinishedPolygons = new BitSet(polygons.size());
-		unfinishedPolygons.set(0, polygons.size());
-
-		// create bitsets which polygons belong to the outer and to the inner role
-		innerPolygons = new BitSet();
-		taggedInnerPolygons = new BitSet();
-		outerPolygons = new BitSet();
-		taggedOuterPolygons = new BitSet();
-		
-		int wi = 0;
-		for (Way w : polygons) {
-			String role = getRole(w);
-			if ("inner".equals(role)) {
-				innerPolygons.set(wi);
-				taggedInnerPolygons.set(wi);
-			} else if ("outer".equals(role)) {
-				outerPolygons.set(wi);
-				taggedOuterPolygons.set(wi);
-			} else {
-				// unknown role => it could be both
-				innerPolygons.set(wi);
-				outerPolygons.set(wi);
-			}
-			wi++;
-		}
-
-		if (outerPolygons.isEmpty()) {
-			log.warn("Multipolygon", toBrowseURL(),
-				"does not contain any way tagged with role=outer or empty role.");
-			cleanup();
-			return;
-		}
-
-		Queue<PolygonStatus> polygonWorkingQueue = new LinkedBlockingQueue<>();
-		BitSet nestedOuterPolygons = new BitSet();
-		BitSet nestedInnerPolygons = new BitSet();
-
-		BitSet outmostPolygons ;
-		BitSet outmostInnerPolygons = new BitSet();
-		boolean outmostInnerFound;
-		do {
-			outmostInnerFound = false;
-			outmostPolygons = findOutmostPolygons(unfinishedPolygons);
-
-			if (outmostPolygons.intersects(taggedInnerPolygons)) {
-				outmostInnerPolygons.or(outmostPolygons);
-				outmostInnerPolygons.and(taggedInnerPolygons);
-
-				if (log.isDebugEnabled())
-					log.debug("wrong inner polygons: " + outmostInnerPolygons);
-				// do not process polygons tagged with role=inner but which are
-				// not contained by any other polygon
-				unfinishedPolygons.andNot(outmostInnerPolygons);
-				outmostPolygons.andNot(outmostInnerPolygons);
-				outmostInnerFound = true;
-			}
-		} while (outmostInnerFound);
-		
-		if (!outmostPolygons.isEmpty()) {
-			polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, "outer"));
-		}
-
-		boolean outmostPolygonProcessing = true;
-		
-		
-		outerResultArea = new java.awt.geom.Area();
+	@Override
+	protected boolean needsWaysForOutlines() {
+		return false; 
+	}
+	
+	@Override
+	protected void processQueue(Partition partition, Queue<PolygonStatus> polygonWorkingQueue) {
+		if (outerResultArea == null)
+			outerResultArea = new java.awt.geom.Area();
 		
 		while (!polygonWorkingQueue.isEmpty()) {
-
+	
 			// the polygon is not contained by any other unfinished polygon
 			PolygonStatus currentPolygon = polygonWorkingQueue.poll();
-
+	
 			// this polygon is now processed and should not be used by any
 			// further step
-			unfinishedPolygons.clear(currentPolygon.index);
-
-			BitSet polygonContains = new BitSet();
-			polygonContains.or(containsMatrix.get(currentPolygon.index));
-			// use only polygon that are contained by the polygon
-			polygonContains.and(unfinishedPolygons);
-			// polygonContains is the intersection of the unfinished and
-			// the contained polygons
-
-			// get the holes
-			// these are all polygons that are in the main polygon
-			// and that are not contained by any other polygon
-			boolean holesOk;
-			BitSet holeIndexes;
-			do {
-				holeIndexes = findOutmostPolygons(polygonContains);
-				holesOk = true;
-
-				if (currentPolygon.outer) {
-					// for role=outer only role=inner is allowed
-					if (holeIndexes.intersects(taggedOuterPolygons)) {
-						BitSet addOuterNestedPolygons = new BitSet();
-						addOuterNestedPolygons.or(holeIndexes);
-						addOuterNestedPolygons.and(taggedOuterPolygons);
-						nestedOuterPolygons.or(addOuterNestedPolygons);
-						holeIndexes.andNot(addOuterNestedPolygons);
-						// do not process them
-						unfinishedPolygons.andNot(addOuterNestedPolygons);
-						polygonContains.andNot(addOuterNestedPolygons);
-						
-						// recalculate the holes again to get all inner polygons 
-						// in the nested outer polygons
-						holesOk = false;
-					}
-				} else {
-					// for role=inner both role=inner and role=outer is supported
-					// although inner in inner is not officially allowed
-					if (holeIndexes.intersects(taggedInnerPolygons)) {
-						// process inner in inner but issue a warning later
-						BitSet addInnerNestedPolygons = new BitSet();
-						addInnerNestedPolygons.or(holeIndexes);
-						addInnerNestedPolygons.and(taggedInnerPolygons);
-						nestedInnerPolygons.or(addInnerNestedPolygons);
-					}
-				}
-			} while (!holesOk);
-
-			ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes, 
-				(currentPolygon.outer ? "inner" : "outer"));
+			partition.markFinished(currentPolygon);
+	
+			List<PolygonStatus> holes = partition.getPolygonStatus(currentPolygon);
 
 			// these polygons must all be checked for holes
 			polygonWorkingQueue.addAll(holes);
-
+	
 			if (currentPolygon.outer) {
 				// add the original ways to the list of ways that get the line tags of the mp
 				// the joined ways may be changed by the auto closing algorithm
 				outerWaysForLineTagging.addAll(currentPolygon.polygon.getOriginalWays());
-			}
-			
-			if (currentPolygon.outer) {
+
 				java.awt.geom.Area toAdd = Java2DConverter.createArea(currentPolygon.polygon.getPoints());
 				if (outerResultArea.isEmpty())
 					outerResultArea = toAdd;
 				else
 					outerResultArea.add(toAdd);
-
-				for (Way outerWay : currentPolygon.polygon.getOriginalWays()) {
-					if (outmostPolygonProcessing) {
-						for (Entry<String, String> tag : outerWay.getTagEntryIterator()) {
-							outerTags.put(tag.getKey(), tag.getValue());
-						}
-						outmostPolygonProcessing = false;
-					} else {
-						for (String tag : new ArrayList<>(outerTags.keySet())) {
-							if (!outerTags.get(tag).equals(outerWay.getTag(tag))) {
-								outerTags.remove(tag);
-							}
-						}
-					}
-				}
 			} else {
-				outerResultArea.subtract(Java2DConverter
-						.createArea(currentPolygon.polygon.getPoints()));
-			}
-		}
-		
-		if (hasStyleRelevantTags(this)) {
-			outerTags.clear();
-			for (Entry<String,String> mpTags : getTagEntryIterator()) {
-				if ("type".equals(mpTags.getKey())==false) {
-					outerTags.put(mpTags.getKey(), mpTags.getValue());
-				}
-			}
-		} else {
-			for (Entry<String,String> mpTags : outerTags.entrySet()) {
-				addTag(mpTags.getKey(), mpTags.getValue());
-			}
-		}
-		
-		// Go through all original outer ways, create a copy, tag them
-		// with the mp tags and mark them only to be used for polyline processing
-		// This enables the style file to decide if the polygon information or
-		// the simple line information should be used.
-		for (Way orgOuterWay : outerWaysForLineTagging) {
-//			Way lineTagWay =  new Way(FakeIdGenerator.makeFakeId(), orgOuterWay.getPoints());
-//			lineTagWay.setName(orgOuterWay.getName());
-//			lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
-			for (Entry<String,String> tag : outerTags.entrySet()) {
-//				lineTagWay.addTag(tag.getKey(), tag.getValue());
-				
-				// remove the tag from the original way if it has the same value
-				if (tag.getValue().equals(orgOuterWay.getTag(tag.getKey()))) {
-					removeTagsInOrgWays(orgOuterWay, tag.getKey());
-				}
+				outerResultArea.subtract(Java2DConverter.createArea(currentPolygon.polygon.getPoints()));
 			}
-			
-//			if (log.isDebugEnabled())
-//				log.debug("Add line way", lineTagWay.getId(), lineTagWay.toTagString());
-//			tileWayMap.put(lineTagWay.getId(), lineTagWay);
 		}
-		
-		postProcessing();
-		cleanup();
 	}
 
-	protected boolean connectUnclosedWays(List<JoinedWay> allWays) {
-		List<JoinedWay> unclosed = new ArrayList<>();
-
-		for (JoinedWay w : allWays) {
-			if (!w.hasIdenticalEndPoints()) {
-				unclosed.add(w);
-			}
-		}
-		// try to connect ways lying outside or on the bbox
-		if (unclosed.size() >= 2) {
-			log.debug("Checking",unclosed.size(),"unclosed ways for connections outside the bbox");
-			Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>();
-			
-			// check all ways for endpoints outside or on the bbox
-			for (JoinedWay w : unclosed) {
-				Coord c1 = w.getFirstPoint();
-				Coord c2 = w.getLastPoint();
-				outOfBboxPoints.put(c1, w);
-				outOfBboxPoints.put(c2, w);
-			}
-			
-			if (outOfBboxPoints.size() < 2) {
-				log.debug(outOfBboxPoints.size(),"point outside the bbox. No connection possible.");
-				return false;
-			}
-			
-			List<ConnectionData> coordPairs = new ArrayList<>();
-			ArrayList<Coord> coords = new ArrayList<>(outOfBboxPoints.keySet());
-			for (int i = 0; i < coords.size(); i++) {
-				for (int j = i + 1; j < coords.size(); j++) {
-					ConnectionData cd = new ConnectionData();
-					cd.c1 = coords.get(i);
-					cd.c2 = coords.get(j);
-					cd.w1 = outOfBboxPoints.get(cd.c1);					
-					cd.w2 = outOfBboxPoints.get(cd.c2);					
-					
-					cd.distance = cd.c1.distance(cd.c2);
-					coordPairs.add(cd);
-				}
-			}
-			
-			if (coordPairs.isEmpty()) {
-				log.debug("All potential connections cross the bbox. No connection possible.");
-				return false;
-			} else {
-				// retrieve the connection with the minimum distance
-				ConnectionData minCon = Collections.min(coordPairs,
-						(o1, o2) -> Double.compare(o1.distance, o2.distance));
-				
-				if (minCon.distance < getMaxCloseDist()) {
-
-					if (minCon.w1 == minCon.w2) {
-						log.debug("Close a gap in way", minCon.w1);
-						if (minCon.imC != null)
-							minCon.w1.getPoints().add(minCon.imC);
-						minCon.w1.closeWayArtificially();
-					} else {
-						log.debug("Connect", minCon.w1, "with", minCon.w2);
-						if (minCon.w1.getFirstPoint() == minCon.c1) {
-							Collections.reverse(minCon.w1.getPoints());
-						}
-						if (minCon.w2.getFirstPoint() != minCon.c2) {
-							Collections.reverse(minCon.w2.getPoints());
-						}
-
-						minCon.w1.getPoints().addAll(minCon.w2.getPoints());
-						minCon.w1.addWay(minCon.w2);
-						allWays.remove(minCon.w2);
-					}
-					return true;
-				}
-			}
-		}
+	@Override
+	protected boolean doReporting() {
 		return false;
 	}
-	
+
 	@Override
 	protected double getMaxCloseDist() {
-		double dist = 1000;
-		String admString= getTag("admin_level");
-		
-		if ("2".equals(admString)) {
-			dist = 50000;
-		} else if ("3".equals(admString)) {
-			dist = 20000;
-		}else if ("4".equals(admString)) {
-			dist = 4000;
+		String admString = getTag("admin_level");
+		if (admString == null)
+			return 1000;
+		switch (admString) {
+		case "2": return 50000; 
+		case "3": return 20000;
+		case "4": return 4000; 
+		default:
+			return 1000;
 		}
-		return dist;
 	}
-	
-	private void removeOutOfBbox(List<JoinedWay> polygons) {
-		ListIterator<JoinedWay> pIter = polygons.listIterator();
-		while (pIter.hasNext()) {
-			JoinedWay w = pIter.next();
-			Coord first = w.getFirstPoint();
-			Coord last =  w.getLastPoint();
-			if (first != last) {
-				// the way is not closed
-				// check if one of start/endpoint is out of the bounding box
-				// in this case it is too risky to close it
-				if (!getTileBounds().contains(first) || !getTileBounds().contains(last)) {
-					pIter.remove();
-				}
-			}
-		}
 
+	
+	@Override
+	public String toString() {
+		String basicInfo = "boundary r" + getId();
+		String admLevel = getTag("admin_level");
+		if (admLevel != null)
+			return basicInfo + " admlvl=" + admLevel + " (" + getTag("name") + ")";
+		String postal = getTag("postal_code");
+		if (postal != null)
+			return basicInfo + " postal_code=" + postal;
+		return basicInfo;
 	}
 
 	@Override


=====================================
src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java
=====================================
@@ -292,13 +292,11 @@ public class O5mBinHandler extends OsmHandler{
 			if (el != null) // ignore non existing ways caused by splitting files
 				rel.addElement(role, el);
 		}
-		boolean tagsIncomplete = readTags(rel);
-		rel.setTagsIncomplete(tagsIncomplete);
+		readTags(rel);
 		saver.addRelation(rel);
 	}
 	
-	private boolean readTags(Element elem) {
-		boolean tagsIncomplete = false;
+	private void readTags(Element elem) {
 		while (bytesToRead > 0) {
 			readStringPair();
 			String key = stringPair[0];
@@ -309,13 +307,11 @@ public class O5mBinHandler extends OsmHandler{
 				key = "type";
 			else
 				key = keepTag(key, val);
-			if (key != null)
+			if (key != null) {
 				elem.addTagFromRawOSM(key, val);
-			else
-				tagsIncomplete = true;
+			}
 		}
 		assert bytesToRead == 0;
-		return tagsIncomplete;
 	}
 
 	/**


=====================================
src/uk/me/parabola/mkgmap/reader/osm/xml/OsmXmlHandler.java
=====================================
@@ -302,9 +302,7 @@ public class OsmXmlHandler extends OsmHandler {
 					key = "type";
 				else
 					key = keepTag(key, val);
-				if (key == null) {
-					currentRelation.setTagsIncomplete(true);
-				} else {
+				if (key != null) {
 					currentRelation.addTagFromRawOSM(key, val);
 				}
 			}


=====================================
src/uk/me/parabola/mkgmap/sea/optional/PrecompSeaMerger.java
=====================================
@@ -18,7 +18,7 @@ import java.awt.geom.Path2D;
 import java.awt.geom.Rectangle2D;
 import java.util.AbstractMap.SimpleEntry;
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -158,22 +158,19 @@ class PrecompSeaMerger implements Runnable {
 			// no land in this tile => create a sea way only
 			ways.addAll(convertToWays(new Area(mergeData.bounds), "sea"));
 		} else {
-			Map<Long, Way> wayMap = new HashMap<>();
-			List<List<Coord>> landParts = Java2DConverter
-					.areaToShapes(mergeData.landArea);
+			Map<Long, Way> wayMap = new LinkedHashMap<>();
+			List<List<Coord>> landParts = Java2DConverter.areaToShapes(mergeData.landArea);
+			Relation rel = new GeneralRelation(FakeIdGenerator.makeFakeId());
 			for (List<Coord> landPoints : landParts) {
 				Way landWay = new Way(FakeIdGenerator.makeFakeId(), landPoints);
 				wayMap.put(landWay.getId(), landWay);
+				rel.addElement("inner", landWay);
 			}
 
 			Way seaWay = new Way(FakeIdGenerator.makeFakeId(), uk.me.parabola.imgfmt.app.Area.PLANET.toCoords());
 			seaWay.setClosedInOSM(true);
 			wayMap.put(seaWay.getId(), seaWay);
-
-			Relation rel = new GeneralRelation(FakeIdGenerator.makeFakeId());
-			for (Way w : wayMap.values()) {
-				rel.addElement((w == seaWay ? "outer" : "inner"), w);
-			}
+			rel.addElement("outer", seaWay);
 
 			// process the tile as sea multipolygon to create simple polygons only
 			MultiPolygonRelation mpr = new MultiPolygonRelation(rel, wayMap,
@@ -182,7 +179,7 @@ class PrecompSeaMerger implements Runnable {
 				// do not calculate the area size => it is not required and adds
 				// a superfluous tag 
 				@Override
-				protected boolean isAreaSizeCalculated() {
+				protected boolean needsAreaSizeTag() {
 					return false;
 				}
 			};


=====================================
src/uk/me/parabola/util/GpxCreator.java
=====================================
@@ -1,5 +1,6 @@
 package uk.me.parabola.util;
 
+import java.awt.Shape;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.PrintWriter;
@@ -72,7 +73,24 @@ public class GpxCreator {
 	public static void createAreaGpx(String name, Area bbox) {
 		GpxCreator.createGpx(name, bbox.toCoords());
 	}
-	
+
+	/**
+	 * Create gpx file(s) for java Shape. 
+	 * @param baseDir the base directory name
+	 * @param shape the shape to convert
+	 */
+	public static void createShapeGpx(String baseDir, Shape shape) {
+		// have to convert to area to make sure that clockwise/counterclockwise idea works for inner/outer
+		java.awt.geom.Area area = shape instanceof java.awt.geom.Area ? (java.awt.geom.Area) shape
+				: new java.awt.geom.Area(shape);
+		List<List<Coord>> shapes = Java2DConverter.areaToShapes(area);
+		for (int i = 0; i < shapes.size(); i++) {
+			List<Coord> points = shapes.get(i);
+			String extName = baseDir + Integer.toString(i) + "_" + (Way.clockwise(points) ? "o" : "i");
+			GpxCreator.createGpx(extName, points);
+		}
+	}	
+
 	/**
 	 * Creates a gpx file for each way. The filename is the baseDir plus the id
 	 * of the way.



View it on GitLab: https://salsa.debian.org/debian-gis-team/mkgmap/-/commit/177b18df00616b0550a734b5042e9c456c0a8faa

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/mkgmap/-/commit/177b18df00616b0550a734b5042e9c456c0a8faa
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20220301/ca6a12be/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list