[Git][debian-gis-team/mkgmap][master] 4 commits: New upstream version 0.0.0+svn4897
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Tue Mar 1 21:23:29 GMT 2022
Bas Couwenberg pushed to branch master 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
- - - - -
47a9b48b by Bas Couwenberg at 2022-03-01T22:12:06+01:00
Update upstream source from tag 'upstream/0.0.0+svn4897'
Update to upstream version '0.0.0+svn4897'
with Debian dir 46a3f8aeec63081fe6b62c39e8bd3d2c95755bb0
- - - - -
a1687565 by Bas Couwenberg at 2022-03-01T22:12:27+01:00
New upstream SVN snapshot.
- - - - -
02cd9cfd by Bas Couwenberg at 2022-03-01T22:13:57+01:00
Set distribution to unstable.
- - - - -
25 changed files:
- debian/changelog
- 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:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+mkgmap (0.0.0+svn4897-1) unstable; urgency=medium
+
+ * New upstream SVN snapshot.
+
+ -- Bas Couwenberg <sebastic at debian.org> Tue, 01 Mar 2022 22:13:48 +0100
+
mkgmap (0.0.0+svn4885-1) unstable; urgency=medium
* New upstream SVN snapshot.
=====================================
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/-/compare/54c395dea6afbd7c1f8f1769f5624d7174dd3999...02cd9cfd1624d3e43f7446626c95377a22ce5a2c
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/mkgmap/-/compare/54c395dea6afbd7c1f8f1769f5624d7174dd3999...02cd9cfd1624d3e43f7446626c95377a22ce5a2c
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/8592fd3c/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list