Skip to content
Commits on Source (4)
mkgmap (0.0.0+svn4214-2) UNRELEASED; urgency=medium
mkgmap (0.0.0+svn4223-1) unstable; urgency=medium
* New upstream SVN snapshot.
* Bump Standards-Version to 4.2.1, no changes.
-- Bas Couwenberg <sebastic@debian.org> Sun, 05 Aug 2018 20:30:45 +0200
-- Bas Couwenberg <sebastic@debian.org> Sat, 01 Sep 2018 08:36:44 +0200
mkgmap (0.0.0+svn4214-1) unstable; urgency=medium
......
......@@ -534,6 +534,24 @@ not need a map that supports routing.
that support routing. If you specify this option, you do not need
to specify --net.
<p>
;--add-boundary-nodes-at-admin-boundaries=NUM
: This option controls how mkgmap calculates special routing nodes which
are needed by Garmin software to allow routing between different map tiles.
These nodes are written to section 3 and 4 in the NOD file.
When a road crosses the tile boundary (bbox), the road is split at this
point and such a special node is written. This allows routing between
one set of tiles produced by splitter.jar. However, if you create a map
from different sets of tiles, those tiles are likely to overlap.
For the overlapping tiles, none of the entries in NOD3 match and thus
routing across tile border doesn't work when the route is not fully
covered by one of the tiles.
The option tells mkgmap to add special nodes whereever a road touches or
crosses an administratve boundary. The NUM parameter specifies a filter
for the admin_level. Boundaries with a higher admin_level value are ignored.
The default value is 2 (country borders). Another reasonable value might
be 4. A value less or equal to 0 tells mkgmap to ignore intersections at
administrative boundaries.
<p>
;--drive-on=left|right|detect|detect,left|detect,right
: Explicitly specify which side of the road vehicles are
expected to drive on.
......
......@@ -540,6 +540,24 @@ Miscellaneous options:
Use the --bounds option to make sure that the detection
finds the correct country.
--add-boundary-nodes-at-admin-boundaries=NUM
This option controls how mkgmap calculates special routing nodes which
are needed by Garmin software to allow routing between different map tiles.
These nodes are written to section 3 and 4 in the NOD file.
When a road crosses the tile boundary (bbox), the road is split at this
point and such a special node is written. This allows routing between
one set of tiles produced by splitter.jar. However, if you create a map
from different sets of tiles, those tiles are likely to overlap.
For the overlapping tiles, none of the entries in NOD3 match and thus
routing across tile border doesn't work when the route is not fully
covered by one of the tiles.
The option tells mkgmap to add special nodes whereever a road touches or
crosses an administratve boundary. The NUM parameter specifies a filter
for the admin_level. Boundaries with a higher admin_level value are ignored.
The default value is 2 (country borders). Another reasonable value might
be 4. A value less or equal to 0 tells mkgmap to ignore intersections at
administrative boundaries.
--check-roundabouts
Check that roundabouts have the expected direction (clockwise
when vehicles drive on the left). Roundabouts that are complete
......
svn.version: 4214
build.timestamp: 2018-07-28T09:29:55+0100
svn.version: 4223
build.timestamp: 2018-08-13T07:21:37+0100
......@@ -16,6 +16,7 @@
*/
package uk.me.parabola.imgfmt;
import java.awt.geom.Line2D;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
......@@ -28,8 +29,8 @@ import java.util.Calendar;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Some miscellaneous functions that are used within the .img code.
*
......@@ -389,49 +390,6 @@ public class Utils {
return (long)(latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL);
}
/**
* Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa.
* This is a form of intersection check where it is allowed that one line ends on the
* other line or that the two lines overlap.
* @param p1_1 first point of line 1
* @param p1_2 second point of line 1
* @param p2_1 first point of line 2
* @param p2_2 second point of line 2
* @return true if both lines intersect somewhere in the middle of each other
*/
public static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) {
int width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon();
int width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon();
int height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat();
int height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat();
int denominator = ((height2 * width1) - (width2 * height1));
if (denominator == 0) {
// the lines are parallel
// they might overlap but this is ok for this test
return false;
}
int x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon();
int y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat();
double isx = (double)((width2 * y1My3) - (height2 * x1Mx3))
/ denominator;
if (isx <= 0 || isx >= 1) {
return false;
}
double isy = (double)((width1 * y1My3) - (height1 * x1Mx3))
/ denominator;
if (isy <= 0 || isy >= 1) {
return false;
}
return true;
}
public static int numberToPointerSize(int n) {
// moved from imgfmt/app/mdr/MdrSection.java and app/typ/TYPFile.java
if (n <= 0xff)
......@@ -452,4 +410,40 @@ public class Utils {
writer.put3s(longitude);
}
/**
* Finds the intersection of two line segments, also if one ends on the other
* See https://de.wikipedia.org/wiki/Geod%C3%A4tisches_Rechnen#Geradenschnitt
* @param p1_1 the coordinates of the start point of the first specified line segment
* @param p1_2 the coordinates of the end point of the first specified line segment
* @param p2_1 the coordinates of the start point of the second specified line segment
* @param p2_2 the coordinates of the end point of the second specified line segment
* @return null if no intersection was found, else a new Coord instance with the coordinates of the intersection
*/
public static Coord getSegmentSegmentIntersection (Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) {
long x1 = p1_1.getHighPrecLon();
long y1 = p1_1.getHighPrecLat();
long x2 = p1_2.getHighPrecLon();
long y2 = p1_2.getHighPrecLat();
long x3 = p2_1.getHighPrecLon();
long y3 = p2_1.getHighPrecLat();
long x4 = p2_2.getHighPrecLon();
long y4 = p2_2.getHighPrecLat();
if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4))
return null;
long divider = (y2 - y1) * (x4 - x3) - (y4 - y3) * (x2 - x1);
if (divider == 0)
return null; // parallel
double h = ((y4 - y3) * (x1 - x4) - (y1 - y4) * (x4 - x3)) / (double) divider;
if (h < 0 || h > 1) {
return null; // intersection of lines is not on given segment
}
double xs = x1 + h * (x2 - x1);
double ys = y1 + h * (y2 - y1);
return Coord.makeHighPrecCoord((int)Math.round(ys), (int) Math.round(xs));
}
}
......@@ -69,6 +69,28 @@ public class Area {
, Utils.toMapUnit(maxLat), Utils.toMapUnit(maxLong));
}
public static Area getBBox(List<Coord> points) {
int tmpMinLat = Integer.MAX_VALUE;
int tmpMaxLat = Integer.MIN_VALUE;
int tmpMinLong = Integer.MAX_VALUE;
int tmpMaxLong = Integer.MIN_VALUE;
for (Coord co : points) {
int lat = co.getLatitude();
if (lat < tmpMinLat)
tmpMinLat = lat;
if (lat > tmpMaxLat)
tmpMaxLat = lat;
int lon = co.getLongitude();
if (lon < tmpMinLong)
tmpMinLong = lon;
if (lon > tmpMaxLong)
tmpMaxLong = lon;
}
return new Area(tmpMinLat, tmpMinLong, tmpMaxLat, tmpMaxLong);
}
public int getMinLat() {
return minLat;
}
......
......@@ -50,6 +50,7 @@ public class Coord implements Comparable<Coord> {
private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
private final static short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers
private final static short ON_COUNTRY_BORDER = 0x1000; // node is on a country border
private final static int HIGH_PREC_BITS = 30;
public final static int DELTA_SHIFT = HIGH_PREC_BITS - 24;
......@@ -340,7 +341,7 @@ public class Coord implements Comparable<Coord> {
}
/**
* @return if this is the beginning/end of a house number interval
* @return true if this is the beginning/end of a house number interval
*/
public boolean isNumberNode(){
return (flags & HOUSENUMBER_NODE) != 0;
......@@ -357,7 +358,7 @@ public class Coord implements Comparable<Coord> {
}
/**
* @return if this is the beginning/end of a house number interval
* @return true if this was added by the housenumber processing
*/
public boolean isAddedNumberNode(){
return (flags & ADDED_HOUSENUMBER_NODE) != 0;
......@@ -373,6 +374,24 @@ public class Coord implements Comparable<Coord> {
this.flags &= ~ADDED_HOUSENUMBER_NODE;
}
/**
* @return true if this was marked as place on country border.
*/
public boolean getOnCountryBorder() {
return (flags & ON_COUNTRY_BORDER) != 0;
}
/**
* Mark as place on a country border
* @param onCountryBorder
*/
public void setOnCountryBorder(boolean onCountryBorder) {
if (onCountryBorder)
this.flags |= ON_COUNTRY_BORDER;
else
this.flags &= ~ON_COUNTRY_BORDER;
}
public int hashCode() {
// Use a factor for latitude to span over the whole integer range:
// max lat: 4194304
......@@ -682,7 +701,7 @@ public class Coord implements Comparable<Coord> {
* @return true if rounding error is large.
*/
public boolean hasAlternativePos(){
if (getOnBoundary())
if (getOnBoundary() || getOnCountryBorder())
return false;
return (Math.abs(latDelta) > MAX_DELTA || Math.abs(lonDelta) > MAX_DELTA);
}
......@@ -694,7 +713,7 @@ public class Coord implements Comparable<Coord> {
*/
public List<Coord> getAlternativePositions(){
ArrayList<Coord> list = new ArrayList<>();
if (getOnBoundary())
if (getOnBoundary() || getOnCountryBorder())
return list;
int modLatDelta = 0;
int modLonDelta = 0;
......
......@@ -32,19 +32,22 @@ public class CoordNode extends Coord {
* @param longitude The longitude in map units.
* @param id The ID of this routing node.
* @param boundary This is a routing node on the boundary.
* @param onCountryBorder This is a routing node on a country boundary.
*/
public CoordNode(int latitude, int longitude, int id, boolean boundary) {
public CoordNode(int latitude, int longitude, int id, boolean boundary, boolean onCountryBorder) {
super(latitude, longitude);
this.id = id;
setOnBoundary(boundary);
setOnCountryBorder(onCountryBorder);
setNumberNode(true);
preserved(true);
}
public CoordNode(Coord other, int id, boolean boundary){
public CoordNode(Coord other, int id, boolean boundary, boolean onCountryBorder) {
super(other);
this.id = id;
setOnBoundary(boundary);
setOnCountryBorder(onCountryBorder);
setNumberNode(true);
preserved(true);
......
......@@ -76,7 +76,7 @@ public class RouteNode implements Comparable<RouteNode> {
public RouteNode(Coord coord) {
this.coord = (CoordNode) coord;
setBoundary(this.coord.getOnBoundary());
setBoundary(this.coord.getOnBoundary() || this.coord.getOnCountryBorder());
}
private boolean haveLargeOffsets() {
......
......@@ -403,7 +403,7 @@ public class RGNFileReader extends ImgReader {
Utils.toDegrees(currLon)));
if (extra)
line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false));
line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false, false));
else
line.addCoord(new Coord(currLat, currLon));
......@@ -478,7 +478,7 @@ public class RGNFileReader extends ImgReader {
currLon += dx << (24 - div.getResolution());
Coord coord;
if (isnode)
coord = new CoordNode(currLat, currLon, 0/* XXX */, false);
coord = new CoordNode(currLat, currLon, 0/* XXX */, false, false);
else
coord = new Coord(currLat, currLon);
......
......@@ -68,10 +68,6 @@ public class DouglasPeuckerFilter implements MapFilter {
int endIndex = coords.size()-1;
for(int i = endIndex-1; i > 0; i--) {
Coord p = coords.get(i);
//int highwayCount = p.getHighwayCount();
// If a node in the line use the douglas peucker algorithm for upper segment
// TODO: Should consider only nodes connected to roads visible at current resolution.
if (p.preserved()) {
// point is "preserved", don't remove it
douglasPeucker(coords, i, endIndex, maxErrorDistance);
......@@ -100,8 +96,9 @@ public class DouglasPeuckerFilter implements MapFilter {
*/
public static void douglasPeucker(List<Coord> points, int startIndex, int endIndex, double allowedError)
{
if (startIndex >= endIndex)
if (endIndex - startIndex <= 1) {
return;
}
double maxDistance = 0; //Highest distance
int maxIndex = endIndex; //Index of highest distance
......@@ -109,9 +106,7 @@ public class DouglasPeuckerFilter implements MapFilter {
Coord a = points.get(startIndex);
Coord b = points.get(endIndex);
// Find point with highest distance to line between start- and end-point.
// handle also closed or nearly closed lines and spikes on straight lines
for(int i = endIndex-1; i > startIndex; i--) {
Coord p = points.get(i);
double distance = p.shortestDistToLineSegment(a, b);
......@@ -127,20 +122,14 @@ public class DouglasPeuckerFilter implements MapFilter {
}
else {
// All points in tolerance, delete all of them.
// Remove the end-point if it is the same as the start point
if (a.highPrecEquals(b) && points.get(endIndex).preserved() == false)
endIndex++;
if (endIndex - startIndex > 4) {
// faster than many repeated remove actions
points.subList(startIndex + 1, endIndex).clear();
return;
}
// Remove the points in between
} else {
for (int i = endIndex - 1; i > startIndex; i--) {
points.remove(i);
}
}
}
}
}
......@@ -55,7 +55,7 @@ public class RoundCoordsFilter implements MapFilter {
Coord newP;
if(p instanceof CoordNode && checkRouting)
newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary());
newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary(), p.getOnCountryBorder());
else
newP = new Coord(lat, lon);
newP.preserved(p.preserved());
......
......@@ -25,12 +25,16 @@ import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
......@@ -60,6 +64,7 @@ import uk.me.parabola.mkgmap.reader.osm.Element;
import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
import uk.me.parabola.mkgmap.reader.osm.GType;
import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
import uk.me.parabola.mkgmap.reader.osm.Node;
import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
import uk.me.parabola.mkgmap.reader.osm.Relation;
......@@ -70,6 +75,7 @@ import uk.me.parabola.mkgmap.reader.osm.TagDict;
import uk.me.parabola.mkgmap.reader.osm.Tags;
import uk.me.parabola.mkgmap.reader.osm.TypeResult;
import uk.me.parabola.mkgmap.reader.osm.Way;
import uk.me.parabola.util.ElementQuadTree;
import uk.me.parabola.util.EnhancedProperties;
import uk.me.parabola.util.MultiHashMap;
......@@ -113,6 +119,12 @@ public class StyledConverter implements OsmConverter {
public final static String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids";
private final HashMap<Integer, Map<String,MapPoint>> pointMap;
/** boundary ways with admin_level=2 */
private final Set<Way> borders = new LinkedHashSet<>();
private boolean addBoundaryNodesAtAdminBoundaries;
private int admLevelNod3;
private List<ConvertedWay> roads = new ArrayList<>();
private List<ConvertedWay> lines = new ArrayList<>();
private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>();
......@@ -210,6 +222,10 @@ public class StyledConverter implements OsmConverter {
String styleOption= props.getProperty("style-option",null);
styleOptionTags = parseStyleOption(styleOption);
prefixSuffixFilter = new PrefixSuffixFilter(props);
// control calculation of extra nodes in NOD3 / NOD4
admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2);
addBoundaryNodesAtAdminBoundaries = routable && admLevelNod3 > 0;
}
/**
......@@ -308,6 +324,13 @@ public class StyledConverter implements OsmConverter {
removeRestrictionsWithWay(Level.WARNING, way, "is ignored");
return;
}
if (addBoundaryNodesAtAdminBoundaries) {
// is this a country border ?
if (!FakeIdGenerator.isFakeId(way.getId()) && isNod3Border(way)) {
borders.add(way);
}
}
preConvertRules(way);
String styleFilterTag = way.getTag(styleFilterTagKey);
......@@ -372,6 +395,20 @@ public class StyledConverter implements OsmConverter {
}
}
private boolean isNod3Border(Element el) {
if ("administrative".equals(el.getTag("boundary"))) {
String admLevelString = el.getTag("admin_level");
if (admLevelString != null) {
try {
int al = Integer.valueOf(admLevelString);
return al <= admLevelNod3;
} catch (NumberFormatException e) {
}
}
}
return false;
}
private int lineIndex = 0;
private final static short onewayTagKey = TagDict.getInstance().xlate("oneway");
private void addConvertedWay(Way way, GType foundType) {
......@@ -431,6 +468,7 @@ public class StyledConverter implements OsmConverter {
/** One type result for nodes to avoid recreating one for each node. */
private NodeTypeResult nodeTypeResult = new NodeTypeResult();
private class NodeTypeResult implements TypeResult {
private Node node;
/** flag if the rule was fired */
......@@ -582,6 +620,124 @@ public class StyledConverter implements OsmConverter {
}
}
/**
* Find all intersections of roads with country borders.
* If a node exists close to the intersection, use the existing node, else add one. The nodes will be
* written as external nodes to NOD file (NOD3 + NOD4)
*/
private void checkRoutingNodesAtAdminBoundaries() {
if (!addBoundaryNodesAtAdminBoundaries || borders.isEmpty())
return;
long t1 = System.currentTimeMillis();
// prepare boundary ways so that we can search them
List<Element> clippedBorders = new ArrayList<>();
for (Way b : borders) {
List<List<Coord>> clipped = LineClipper.clip(bbox, b.getPoints());
if (clipped == null) {
splitBoundary(clippedBorders, b, b.getPoints());
} else {
for (List<Coord> lco : clipped) {
splitBoundary(clippedBorders, b, lco);
}
}
}
ElementQuadTree qt = new ElementQuadTree(bbox, clippedBorders);
long countChg = 0;
Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>();
for (ConvertedWay r : roads) {
if (!r.isValid())
continue;
Way way = r.getWay();
Area searchRect = Area.getBBox(way.getPoints());
Set<Element> boundaries = qt.get(searchRect);
if (boundaries.isEmpty())
continue;
// the bounding box of the road intersects with one or more bounding boxes of borders
Coord pw1 = way.getPoints().get(0);
int pos = 1;
while (pos < way.getPoints().size()) {
boolean changed = false;
Coord pw2 = way.getPoints().get(pos);
for (Element el : boundaries) {
List<Coord> b = ((Way) el).getPoints();
for (int i = 0; i < b.size() - 1; i++) {
Coord pb1 = b.get(i);
Coord pb2 = b.get(i + 1);
Coord is = Utils.getSegmentSegmentIntersection(pw1, pw2, pb1, pb2);
if (is != null) {
// intersection can be equal to given nodes on road or close to it
double dist1 = is.distance(pw1);
double dist2 = is.distance(pw2);
if (dist1 < dist2 && dist1 < 1) {
if (!pw1.getOnCountryBorder()) {
++countChg;
if (!pw1.getOnBoundary())
log.info("road intersects admin boundary, changing existing node to external routing node at",pw1.toDegreeString());
}
pw1.setOnCountryBorder(true);
} else if (dist2 < dist1 && dist2 < 1) {
if (!pw2.getOnCountryBorder()) {
++countChg;
if (!pw2.getOnBoundary())
log.info("road intersects admin boundary, changing existing node to external routing node at",pw2.toDegreeString());
}
pw2.setOnCountryBorder(true);
} else {
long key = Utils.coord2Long(is);
Coord replacement = commonCoordMap.get(key);
if (replacement == null) {
commonCoordMap.put(key, is);
} else {
assert is.highPrecEquals(replacement);
is = replacement;
}
is.setOnCountryBorder(true);
log.info("road intersects admin boundary, adding external routing node at",is.toDegreeString());
way.getPoints().add(pos, is);
changed = true;
pw2 = is;
}
}
}
}
if (!changed) {
++pos;
pw1 = pw2;
}
}
}
long t2 = System.currentTimeMillis() - t1;
log.info("added",commonCoordMap.size(),"new nodes at country borders");
log.info("marked",countChg,"existing nodes at country borders");
log.info("adding country border routing nodes took " + t2 + " ms");
}
/**
* Split complex border ways into smaller portions.
* @param clippedBorders
* @param orig
* @param points
*/
private void splitBoundary(List<Element> clippedBorders, Way orig, List<Coord> points) {
int pos = 0;
final int max = 20; // seems to be a good compromise
while (pos < points.size()) {
int right = Math.min(points.size(), pos + max);
Way w = new Way(orig.getId(), points.subList(pos, right));
w.setFakeId();
clippedBorders.add(w);
pos += max - 1;
if (pos + 1 == points.size())
pos--;
}
}
/**
* Merges roads with identical attributes (GType, OSM tags) to reduce the size of the
* road network.
......@@ -601,6 +757,9 @@ public class StyledConverter implements OsmConverter {
style.reportStats();
driveOnLeft = calcDrivingSide();
checkRoutingNodesAtAdminBoundaries();
borders.clear();
setHighwayCounts();
findUnconnectedRoads();
rotateClosedWaysToFirstNode();
......@@ -636,7 +795,6 @@ public class StyledConverter implements OsmConverter {
}
deletedRoads = null;
modifiedRoads = null;
mergeRoads();
resetHighwayCounts();
......@@ -961,6 +1119,17 @@ public class StyledConverter implements OsmConverter {
}
else if("through_route".equals(relation.getTag("type"))) {
throughRouteRelations.add(relation);
} else if (addBoundaryNodesAtAdminBoundaries) {
if (relation instanceof MultiPolygonRelation || "boundary".equals(relation.getTag("type"))) {
if (isNod3Border(relation)) {
for (Entry<String, Element> e : relation.getElements()) {
if (FakeIdGenerator.isFakeId(e.getValue().getId()))
continue;
if (e.getValue() instanceof Way)
borders.add((Way) e.getValue());
}
}
}
}
}
......@@ -1361,7 +1530,7 @@ public class StyledConverter implements OsmConverter {
nWay.copyTags(way);
for(Coord co : lco) {
nWay.addPoint(co);
if(co.getOnBoundary()) {
if(co.getOnBoundary() || co.getOnCountryBorder()) {
// this point lies on a boundary
// make sure it becomes a node
co.incHighwayCount();
......@@ -1631,12 +1800,12 @@ public class StyledConverter implements OsmConverter {
arcLength += d;
}
}
if(p.getHighwayCount() > 1) {
if(p.getHighwayCount() > 1 || p.getOnCountryBorder()) {
// this point is a node connecting highways
CoordNode coordNode = nodeIdMap.get(p);
if(coordNode == null) {
// assign a node id
coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary());
coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary(), p.getOnCountryBorder());
nodeIdMap.put(p, coordNode);
}
......@@ -1751,7 +1920,7 @@ public class StyledConverter implements OsmConverter {
Coord coord = points.get(n);
CoordNode thisCoordNode = nodeIdMap.get(coord);
assert thisCoordNode != null : "Way " + debugWayName + " node " + i + " (point index " + n + ") at " + coord.toOSMURL() + " yields a null coord node";
boolean boundary = coord.getOnBoundary();
boolean boundary = coord.getOnBoundary() || coord.getOnCountryBorder();
if(boundary && log.isInfoEnabled()) {
log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node");
}
......
......@@ -149,6 +149,13 @@ public class WrongAngleFixer {
}
replacement.setOnBoundary(true);
}
if (toRepl.getOnCountryBorder()){
if (replacement.equals(toRepl) == false){
log.error("country boundary node is replaced by node with non-equal coordinates at", toRepl.toOSMURL());
assert false : "country boundary node is replaced" ;
}
replacement.setOnCountryBorder(true);
}
toRepl.setReplaced(true);
if (toRepl instanceof CoordPOI) {
CoordPOI cp = (CoordPOI) toRepl;
......@@ -834,7 +841,7 @@ public class WrongAngleFixer {
* @return true if remove is okay
*/
private boolean allowedToRemove(Coord p){
if (p.getOnBoundary())
if (p.getOnBoundary() || p.getOnCountryBorder())
return false;
if (mode == MODE_LINES && p.isEndOfWay())
return false;
......@@ -1100,26 +1107,27 @@ public class WrongAngleFixer {
}
Coord c = getCurrentLocation(replacements);
Coord n = neighbour.getCurrentLocation(replacements);
// check special cases: don't merge if
// 1) both points are via nodes
// 2) both nodes are boundary nodes with non-equal coords
// 3) one point is via node and the other is a boundary node, the result could be that the restriction is ignored.
if (c.getOnBoundary()){
if (n.isViaNodeOfRestriction() || n.getOnBoundary() && c.equals(n) == false)
boolean cOnBoundary = c.getOnBoundary() || c.getOnCountryBorder();
boolean nOnBoundary = n.getOnBoundary() || n.getOnCountryBorder();
if (cOnBoundary){
if (n.isViaNodeOfRestriction() || nOnBoundary && c.equals(n) == false)
return false;
}
if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary()))
if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || nOnBoundary))
return false;
if (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary()))
if (c instanceof CoordPOI && (n instanceof CoordPOI || nOnBoundary))
return false;
if (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary()))
if (n instanceof CoordPOI && (c instanceof CoordPOI || cOnBoundary))
return false;
//TODO: nodes on country borders?
Coord mergePoint;
if (c.getOnBoundary() || c instanceof CoordPOI)
if (cOnBoundary || c instanceof CoordPOI)
mergePoint = c;
else if (n.getOnBoundary() || n instanceof CoordPOI)
else if (nOnBoundary || n instanceof CoordPOI)
mergePoint = n;
else if (c.equals(n))
mergePoint = c;
......
......@@ -944,6 +944,7 @@ public class ExtNumbers {
Coord closePoint = getRoad().getPoints().get(index);
Coord toAdd = new Coord(closePoint);
toAdd.setOnBoundary(closePoint.getOnBoundary());
toAdd.setOnCountryBorder(closePoint.getOnCountryBorder());
toAdd.incHighwayCount();
// we have to make sure that the road starts and ends with a CoordNode!
this.endInRoad = addAsNumberNode(splitSegment, toAdd);
......
......@@ -152,26 +152,7 @@ public final class CoastlineFileLoader {
if (log.isDebugEnabled())
log.debug("Create coastline way", id, "with", points.size(),
"points");
Coord firstPoint = getPoints().get(0);
int minLat = firstPoint.getLatitude();
int maxLat = firstPoint.getLatitude();
int minLong = firstPoint.getLongitude();
int maxLong = firstPoint.getLongitude();
for (Coord c : getPoints()) {
if (c.getLatitude() < minLat) {
minLat = c.getLatitude();
} else if (c.getLatitude() > maxLat) {
maxLat = c.getLatitude();
}
if (c.getLongitude() < minLong) {
minLong = c.getLongitude();
} else if (c.getLongitude() > maxLong) {
maxLong = c.getLongitude();
}
}
bbox = new Area(minLat, minLong, maxLat, maxLong);
bbox = Area.getBBox(points);
}
@Override
......
......@@ -67,9 +67,7 @@ public class ElementSaver {
private final boolean ignoreTurnRestrictions;
/** name of the tag that contains a ;-separated list of tag names that should be removed after all elements have been processed */
public static final String MKGMAP_REMOVE_TAG = "mkgmap:removetags";
/** tagvalue of the {@link ElementSaver#MKGMAP_REMOVE_TAG} if all tags should be removed */
public static final String MKGMAP_REMOVE_TAG_ALL_KEY = "mkgmap:ALL";
public static final short MKGMAP_REMOVE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:removetags");
public ElementSaver(EnhancedProperties args) {
if (args.getProperty("preserve-element-order", false)) {
......
......@@ -34,23 +34,18 @@ public class MultiPolygonFinishHook extends OsmReadingHooksAdaptor {
long t1 = System.currentTimeMillis();
log.info("Finishing multipolygons");
for (Way way : saver.getWays().values()) {
String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG);
String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY);
if (removeTag == null) {
continue;
}
if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) {
if (log.isDebugEnabled())
log.debug("Remove all tags from way",way.getId(),way.toTagString());
way.removeAllTags();
} else {
String[] tagsToRemove = removeTag.split(";");
if (log.isDebugEnabled())
if (log.isDebugEnabled()) {
log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString());
}
way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY);
for (String rTag : tagsToRemove) {
way.deleteTag(rTag);
}
way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG);
}
}
log.info("Multipolygon hook finished in "+(System.currentTimeMillis()-t1)+" ms");
......
......@@ -18,6 +18,7 @@ import java.awt.Rectangle;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
......@@ -37,7 +38,6 @@ import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.util.Java2DConverter;
......@@ -1632,7 +1632,7 @@ public class MultiPolygonRelation extends Relation {
|| (prevLatField == 0 && prevLonField == 0);
boolean intersects = intersectionPossible
&& Utils.linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
&& linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
if (intersects) {
if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
......@@ -1725,12 +1725,54 @@ public class MultiPolygonRelation extends Relation {
Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong());
Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong());
Coord ne = new Coord(tileBounds.getMaxLat(), tileBounds.getMaxLong());
return Utils.linesCutEachOther(nw, sw, p1_1, p1_2)
|| Utils.linesCutEachOther(sw, se, p1_1, p1_2)
|| Utils.linesCutEachOther(se, ne, p1_1, p1_2)
|| Utils.linesCutEachOther(ne, nw, p1_1, p1_2);
return linesCutEachOther(nw, sw, p1_1, p1_2)
|| linesCutEachOther(sw, se, p1_1, p1_2)
|| linesCutEachOther(se, ne, p1_1, p1_2)
|| linesCutEachOther(ne, nw, p1_1, p1_2);
}
/**
* XXX: This code presumes that certain tests were already done!
* Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa.
* This is a form of intersection check where it is allowed that one line ends on the
* other line or that the two lines overlap.
* @param p1_1 first point of line 1
* @param p1_2 second point of line 1
* @param p2_1 first point of line 2
* @param p2_2 second point of line 2
* @return true if both lines intersect somewhere in the middle of each other
*/
private static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) {
long width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon();
long width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon();
long height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat();
long height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat();
long denominator = ((height2 * width1) - (width2 * height1));
if (denominator == 0) {
// the lines are parallel
// they might overlap but this is ok for this test
return false;
}
long x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon();
long y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat();
double isx = (double)((width2 * y1My3) - (height2 * x1Mx3))
/ denominator;
if (isx <= 0 || isx >= 1) {
return false;
}
double isy = (double)((width1 * y1My3) - (height1 * x1Mx3))
/ denominator;
if (isy <= 0 || isy >= 1) {
return false;
}
return true;
}
private List<JoinedWay> getWaysFromPolygonList(BitSet selection) {
if (selection.isEmpty()) {
......@@ -1873,11 +1915,9 @@ public class MultiPolygonRelation extends Relation {
* @param way
* a joined way
* @param tagname
* the tag to be removed (<code>null</code> means remove all
* tags)
* the tag to be removed
* @param tagvalue
* the value of the tag to be removed (<code>null</code> means
* don't check the value)
* the value of the tag to be removed
*/
private void removeTagInOrgWays(JoinedWay way, String tagname,
String tagvalue) {
......@@ -1888,54 +1928,33 @@ public class MultiPolygonRelation extends Relation {
continue;
}
boolean remove = false;
if (tagname == null) {
// remove all tags
remove = true;
} else if (tagvalue == null) {
// remove the tag without comparing the value
remove = w.getTag(tagname) != null;
} else if (tagvalue.equals(w.getTag(tagname))) {
remove = true;
if (tagvalue.equals(w.getTag(tagname))) {
if (log.isDebugEnabled()) {
log.debug("Will remove", tagname + "=" + w.getTag(tagname), "from way", w.getId(), w.toTagString());
}
if (remove) {
if (tagname == null) {
// remove all tags
if (log.isDebugEnabled())
log.debug("Will remove all tags from", w.getId(), w
.toTagString());
removeTagsInOrgWays(w, tagname);
} else {
if (log.isDebugEnabled())
log.debug("Will remove", tagname + "="
+ w.getTag(tagname), "from way", w.getId(), w
.toTagString());
removeTagsInOrgWays(w, tagname);
}
}
}
}
protected void removeTagsInOrgWays(Way way, String tag) {
if (tag == null) {
way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY);
if (tag == null || tag.isEmpty()) {
return;
}
if (tag.isEmpty()) {
String tagsToRemove = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY);
if (tagsToRemove == null) {
tagsToRemove = tag;
} else if (tag.equals(tagsToRemove)) {
return;
}
String removedTagsTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG);
if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removedTagsTag)) {
// cannot add more tags to remove
} else {
String[] keys = tagsToRemove.split(";");
if (Arrays.asList(keys).contains(tag)) {
return;
}
if (removedTagsTag == null) {
way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, tag);
} else if (removedTagsTag.equals(tag) == false) {
way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, removedTagsTag+";"+tag);
tagsToRemove += ";" + tag;
}
way.addTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY, tagsToRemove);
}
/**
......