Index: src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java (revision 0) +++ src/uk/me/parabola/mkgmap/reader/osm/FakeIdGenerator.java (revision 0) @@ -0,0 +1,24 @@ +package uk.me.parabola.mkgmap.reader.osm; + +import java.util.concurrent.atomic.AtomicLong; + +public class FakeIdGenerator { + + public static final long START_ID = 1L << 62; + + private static final AtomicLong fakeId = new AtomicLong(START_ID); + + /** + * Retrieves a unique id that can be used to fake OSM ids. + * + * @return a unique id + */ + public static long makeFakeId() { + return fakeId.incrementAndGet(); + } + + public static boolean isFakeId(long id) { + return id >= START_ID; + } + +} Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (revision 1475) +++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (working copy) @@ -1,12 +1,15 @@ 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; -import java.util.AbstractMap; +import java.awt.geom.PathIterator; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -26,9 +29,11 @@ private static final Logger log = Logger .getLogger(MultiPolygonRelation.class); - // private final List outerSegments = new ArrayList(); - // private final List innerSegments = new ArrayList(); private final Map myWayMap; + private final Map roleMap = new HashMap(); + + private final uk.me.parabola.imgfmt.app.Area bbox; + private final java.awt.geom.Area awtBbox; private final ArrayList containsMatrix = new ArrayList(); @@ -39,7 +44,7 @@ private ArrayList rings; private static final List relationTags = Arrays.asList("boundary", - "natural", "landuse", "building", "waterway"); + "natural", "landuse", "land_area", "building", "waterway"); /** * Create an instance based on an existing relation. We need to do this @@ -51,28 +56,108 @@ * @param wayMap * Map of all ways. */ - public MultiPolygonRelation(Relation other, Map wayMap) { + public MultiPolygonRelation(Relation other, Map wayMap, + uk.me.parabola.imgfmt.app.Area bbox) { myWayMap = wayMap; + this.bbox = bbox; + + List points = new ArrayList(5); + points.add(new Coord(bbox.getMinLat(), bbox.getMinLong())); + points.add(new Coord(bbox.getMaxLat(), bbox.getMinLong())); + points.add(new Coord(bbox.getMaxLat(), bbox.getMaxLong())); + points.add(new Coord(bbox.getMinLat(), bbox.getMaxLong())); + points.add(new Coord(bbox.getMinLat(), bbox.getMinLong())); + awtBbox = createArea(points); + setId(other.getId()); + for (Map.Entry pair : other.getElements()) { String role = pair.getKey(); Element el = pair.getValue(); + log.debug(" ", role, el.toBrowseURL()); addElement(role, el); - - // if (role != null && el instanceof Way) { - // Way way = (Way) el; - // if ("outer".equals(role)) { - // outerSegments.add(way); - // } else if ("inner".equals(role)) { - // innerSegments.add(way); - // } - // } + roleMap.put(el.getId(), role); } setName(other.getName()); copyTags(other); } + private String getRole(Element element) { + String role = roleMap.get(element.getId()); + if (role != null) { + return role; + } + + for (Map.Entry r_e : getElements()) { + if (r_e.getValue() == element) { + return r_e.getKey(); + } + } + return null; + } + + /** + * Try to join the two ways. + * + * @param w1 + * first way + * @param w2 + * second way + * @param checkOnly + * true checks only and does not perform the join + * operation + * @return true if w2 way (or could) be joined to w1 + */ + private boolean joinWays(JoinedWay joinWay, JoinedWay tempWay, + boolean checkOnly) { + // use == or equals as comparator?? + if (joinWay.getPoints().get(0) == tempWay.getPoints().get(0)) { + if (checkOnly == false) { + for (Coord point : tempWay.getPoints().subList(1, + tempWay.getPoints().size())) { + joinWay.addPoint(0, point); + } + joinWay.addWay(tempWay); + } + return true; + } else if (joinWay.getPoints().get(joinWay.getPoints().size() - 1) == tempWay + .getPoints().get(0)) { + if (checkOnly == false) { + for (Coord point : tempWay.getPoints().subList(1, + tempWay.getPoints().size())) { + joinWay.addPoint(point); + } + joinWay.addWay(tempWay); + } + return true; + } else if (joinWay.getPoints().get(0) == tempWay.getPoints().get( + tempWay.getPoints().size() - 1)) { + if (checkOnly == false) { + int insertIndex = 0; + for (Coord point : tempWay.getPoints().subList(0, + tempWay.getPoints().size() - 1)) { + joinWay.addPoint(insertIndex, point); + insertIndex++; + } + joinWay.addWay(tempWay); + } + return true; + } else if (joinWay.getPoints().get(joinWay.getPoints().size() - 1) == tempWay + .getPoints().get(tempWay.getPoints().size() - 1)) { + if (checkOnly == false) { + int insertIndex = joinWay.getPoints().size(); + for (Coord point : tempWay.getPoints().subList(0, + tempWay.getPoints().size() - 1)) { + joinWay.addPoint(insertIndex, point); + } + joinWay.addWay(tempWay); + } + return true; + } + return false; + } + /** * Combine a list of way segments to a list of maximally joined ways * @@ -80,7 +165,7 @@ * a list of closed or unclosed ways * @return a list of closed ways */ - private ArrayList joinWays(List> segments) { + private ArrayList joinWays(List segments) { // this method implements RA-1 to RA-4 // TODO check if the closed polygon is valid and implement a // backtracking algorithm to get other combinations @@ -93,14 +178,13 @@ // go through all segments and categorize them to closed and unclosed // list ArrayList unclosedWays = new ArrayList(); - for (Map.Entry orgSegment : segments) { - String role = orgSegment.getKey(); - Way way = orgSegment.getValue(); - - if (way.isClosed()) { - joinedWays.add(new JoinedWay(role, way)); + for (Way orgSegment : segments) { + JoinedWay jw = new JoinedWay(orgSegment); + roleMap.put(jw.getId(), getRole(orgSegment)); + if (orgSegment.isClosed()) { + joinedWays.add(jw); } else { - unclosedWays.add(new JoinedWay(role, way)); + unclosedWays.add(jw); } } @@ -116,6 +200,11 @@ 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 @@ -127,45 +216,47 @@ continue; } - // TODO: compare roles too - // use == or equals as comparator?? - if (joinWay.getPoints().get(0) == tempWay.getPoints().get(0)) { - for (Coord point : tempWay.getPoints().subList(1, - tempWay.getPoints().size())) { - joinWay.addPoint(0, point); - } - joined = true; - } else if (joinWay.getPoints().get( - joinWay.getPoints().size() - 1) == tempWay.getPoints() - .get(0)) { - for (Coord point : tempWay.getPoints().subList(1, - tempWay.getPoints().size())) { - joinWay.addPoint(point); - } - joined = true; - } else if (joinWay.getPoints().get(0) == tempWay.getPoints() - .get(tempWay.getPoints().size() - 1)) { - int insertIndex = 0; - for (Coord point : tempWay.getPoints().subList(0, - tempWay.getPoints().size() - 1)) { - joinWay.addPoint(insertIndex, point); - insertIndex++; - } - joined = true; - } else if (joinWay.getPoints().get( - joinWay.getPoints().size() - 1) == tempWay.getPoints() - .get(tempWay.getPoints().size() - 1)) { - int insertIndex = joinWay.getPoints().size(); - for (Coord point : tempWay.getPoints().subList(0, - tempWay.getPoints().size() - 1)) { - joinWay.addPoint(insertIndex, point); + String tempRole = getRole(tempWay); + // if a role is null then it is used as universal + // check if the roles of the ways are matching + if (joinRole == null || joinRole.equals("") || tempRole == null + || tempRole.equals("") || 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()) { + if (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; + } } - joined = true; } if (joined) { + // we have joined the way unclosedWays.remove(tempWay); - joinWay.addWay(tempWay); + break; + } + } + + if (joined == false && wrongRoleWay != null) { + log.warn("Join ways with different roles. " + toBrowseURL()); + log.warn("Way1:", joinWay, "Role:", getRole(joinWay)); + log.warn("Way2:", wrongRoleWay, "Role:", getRole(wrongRoleWay)); + joined = joinWays(joinWay, wrongRoleWay, false); + if (joined) { + // we have joined the way + unclosedWays.remove(wrongRoleWay); break; } } @@ -191,6 +282,51 @@ return joinedWays; } + private void closeWays(ArrayList wayList) { + // this is a VERY simple algorithm to close the ways + // need to be improved + + for (JoinedWay way : wayList) { + if (way.isClosed() || way.getPoints().size() <= 3) { + continue; + } + Coord p1 = way.getPoints().get(0); + Coord p2 = way.getPoints().get(way.getPoints().size() - 1); + Line2D closingLine = new Line2D.Float(p1.getLongitude(), p1 + .getLatitude(), p2.getLongitude(), p2.getLatitude()); + + 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) { + if (closingLine.intersectsLine(lastPoint.getLongitude(), + lastPoint.getLatitude(), thisPoint.getLongitude(), + thisPoint.getLatitude())) { + intersects = true; + break; + } + } + lastPoint = thisPoint; + } + + if (intersects == false) { + // close the polygon + // the new way segment does not intersect the rest of the + // polygon + log.info("Closing way", way); + log.info("from", way.getPoints().get(0).toOSMURL()); + log.info("to", way.getPoints().get(way.getPoints().size() - 1) + .toOSMURL()); + way.closeWayArtificial(); + } + } + + } + /** * Removes all ways non closed ways from the given list ( * {@link Way#isClosed()} == false) @@ -208,11 +344,9 @@ log.warn("Unclosed polygons in multipolygon relation " + getId() + ":"); } - for (Map.Entry rw : tempWay.getOriginalRoleWays()) { - String role = rw.getKey(); - Way way = rw.getValue(); - log.warn(" - way:", way.getId(), "role:", role, - "osm:", way.toBrowseURL()); + for (Way orgWay : tempWay.getOriginalWays()) { + log.warn(" - way:", orgWay.getId(), "role:", + getRole(orgWay), "osm:", orgWay.toBrowseURL()); } it.remove(); @@ -221,9 +355,19 @@ } } + private BitSet findOutmostRings(BitSet candidates, BitSet roleFilter) { + BitSet realCandidates = ((BitSet) candidates.clone()); + realCandidates.and(roleFilter); + log.debug("Checkmatrix",realCandidates); + BitSet result = findOutmostRings(realCandidates); + log.debug("Outmost",result); + return result; + } + /** - * Finds all rings that are not contained by any other rings. All rings with - * index given by candidates are used. + * Finds all rings that are not contained by any other rings and that match + * to the given role. All rings with index given by candidates + * are used. * * @param candidates * indexes of the rings that should be used @@ -277,43 +421,26 @@ * ways with the role "inner" to the way with the role "outer" */ public void processElements() { - ArrayList> allWays = - new ArrayList>(); + log.info("processing", toBrowseURL()); - for (Map.Entry r_el : getElements()) { - String role = r_el.getKey(); - Element element = r_el.getValue(); + // don't care about outer and inner declaration + // because this is a first try + ArrayList allWays = new ArrayList(); - if (element instanceof Way) { - allWays.add(new AbstractMap.SimpleEntry - (role, (Way) element)); + for (Map.Entry r_e : getElements()) { + if (r_e.getValue() instanceof Way) { + allWays.add((Way) r_e.getValue()); } else { - log.warn("Non way element", element.getId(), "in multipolygon", - getId()); + log.warn("Non way element", r_e.getValue().getId(), + "in multipolygon", getId()); } } rings = joinWays(allWays); + closeWays(rings); removeUnclosedWays(rings); // now we have closed ways == rings only - /* - * ===== start considering outer and inner ways ==== - * ArrayList joinedOuterWays = joinWays(outerWays); - * ArrayList joinedInnerWays = joinWays(innerWays); - * - * removeUnclosedWays(joinedOuterWays); - * removeUnclosedWays(joinedInnerWays); - * - * // at this point we don't care about outer and inner // in the end we - * will compare if outer and inner tags are matching // what we detect - * here and issue some warnings and errors ArrayList - * completeJoinedWays = new ArrayList(); - * completeJoinedWays.addAll(joinedOuterWays); - * completeJoinedWays.addAll(joinedInnerWays); ===== end considering - * outer and inner ways ==== - */ - // check if we have at least one ring left if (rings.isEmpty()) { // do nothing @@ -327,11 +454,36 @@ BitSet unfinishedRings = new BitSet(rings.size()); unfinishedRings.set(0, rings.size()); - Queue ringWorkingQueue = new LinkedBlockingQueue(); + // create bitsets which rings belong to the outer and to the inner role + BitSet innerRings = new BitSet(); + BitSet outerRings = new BitSet(); + int wi = 0; + for (Way w : rings) { + String role = getRole(w); + if ("inner".equals(role)) { + innerRings.set(wi); + } else if ("outer".equals(role)) { + outerRings.set(wi); + } else { + // unknown role => it could be both + innerRings.set(wi); + outerRings.set(wi); + } + wi++; + } - BitSet outmostRings = findOutmostRings(unfinishedRings); + Queue ringWorkingQueue = new LinkedBlockingQueue(); - ringWorkingQueue.addAll(getRingStatus(outmostRings, true)); + BitSet outmostRings = findOutmostRings(unfinishedRings, outerRings); + if (outmostRings.isEmpty()) { + // there's no outmost outer ring + // maybe this is a tile problem + // try to continue with the inner ring + outmostRings = findOutmostRings(unfinishedRings, innerRings); + ringWorkingQueue.addAll(getRingStatus(outmostRings, false)); + } else { + ringWorkingQueue.addAll(getRingStatus(outmostRings, true)); + } while (ringWorkingQueue.isEmpty() == false) { @@ -339,9 +491,8 @@ RingStatus currentRing = ringWorkingQueue.poll(); // QA: check that all ways carry the role "outer/inner" and - // issue - // warnings - checkRoles(currentRing.ring.getOriginalRoleWays(), + // issue warnings + checkRoles(currentRing.ring.getOriginalWays(), (currentRing.outer ? "outer" : "inner")); // this ring is now processed and should not be used by any @@ -358,7 +509,8 @@ // get the holes // these are all rings that are in the main ring // and that are not contained by any other ring - BitSet holeIndexes = findOutmostRings(ringContains); + BitSet holeIndexes = findOutmostRings(ringContains, + (currentRing.outer ? innerRings : outerRings)); ArrayList holes = getRingStatus(holeIndexes, !currentRing.outer); @@ -371,46 +523,40 @@ || hasUsefulTags(currentRing.ring); if (processRing) { - // now construct the ring polygon with its holes + + List innerWays = new ArrayList(holes.size()); for (RingStatus holeRingStatus : holes) { - int[] insert = findCpa(currentRing.ring.getPoints(), - holeRingStatus.ring.getPoints()); - if (insert[0] >= 0 && insert[1] >= 0) { - insertPoints(currentRing.ring, holeRingStatus.ring, - insert[0], insert[1]); - } else { - // this must not happen - log.error("Cannot find cpa in multipolygon " - + toBrowseURL()); - } + innerWays.add(holeRingStatus.ring); + } + + List singularOuterPolygons = processRing(currentRing.ring, + innerWays); + + if (currentRing.ring.getOriginalWays().size() == 1) { + // the original way was a closed polygon which + // has been replaced by the new cutted polygon + // the original way should not appear + // so we remove all tags + currentRing.ring.removeAllTagsDeep(); + } else { + // the ring has been composed by several ways + // they may contain line tags + // however all polygon tags are not processed + // because they are only lines and not polygons + // so we don't have to remove any tag } boolean useRelationTags = currentRing.outer && hasUsefulTags(this); - if (useRelationTags) { // the multipolygon contains tags that overwhelm the - // tags - // out the outer ring - currentRing.ring.copyTags(this); - } else { - // the multipolygon does not contain any relevant tag - // use the segments of the ring and merge the tags - for (Map.Entry roleway : currentRing.ring.getOriginalRoleWays()) { - String role = roleway.getKey(); - Way ringSegment = roleway.getValue(); - // TODO uuh, this is bad => the last of the - // ring segments win - // => any better idea? - for (Map.Entry outSegmentTag : ringSegment - .getEntryIteratable()) { - currentRing.ring.addTag(outSegmentTag.getKey(), - outSegmentTag.getValue()); - } + // tags of the outer ring + for (Way p : singularOuterPolygons) { + p.copyTags(this); } } - polygonResults.add(currentRing.ring); + polygonResults.addAll(singularOuterPolygons); } } @@ -427,18 +573,241 @@ } } + // log.info("Result"); + // StringBuilder sb = new StringBuilder(); + // for (Way resultWay : polygonResults) { + // sb.append("Way: " + resultWay.getId() + " Points: " + // + resultWay.getPoints().size() + " Closed: " + // + resultWay.isClosed() + " Tags: "); + // for (Map.Entry entry : resultWay + // .getEntryIteratable()) { + // sb.append(entry.getKey() + "=" + entry.getValue() + "; "); + // } + // log.info(sb.toString()); + // sb.setLength(0); + // } + // the polygonResults contain all polygons that should be used in the // map - // TODO remove the input stuff? inner ways and outer segments? - for (Way resultWay : polygonResults) { - myWayMap.put(resultWay.getId(), resultWay); + for (Way mpWay : polygonResults) { + // store the ways generated by the multipolygon code + // to the way map + myWayMap.put(mpWay.getId(), mpWay); + } + + cleanup(); + } + + private void cleanup() { + roleMap.clear(); + containsMatrix.clear(); + rings.clear(); + polygonResults.clear(); + } + + private List processRing(Way outerWay, List innerWays) { + // this list contains all non overlapping and singular areas + // of the outer way + List outerAreas = new ArrayList(); + + // 1st create an Area object of the outerWay and put it to the list + // this must be clipped by the bounding box + Area oa = createArea(outerWay.getPoints()); + // the polygons will be later clipped in the style converter + // so it is not necessary to clip it here + // oa.intersect(awtBbox); + outerAreas.add(oa); + + for (Way innerWay : innerWays) { + Area innerArea = createArea(innerWay.getPoints()); + + List outerAfterThisStep = new ArrayList(); + for (Area outerArea : outerAreas) { + // check if this outerArea is probably intersected by the inner + // area + // to save computation time in case it is not + if (outerArea.getBounds().createIntersection( + innerArea.getBounds()).isEmpty()) { + outerAfterThisStep.add(outerArea); + continue; + } + + // cut the hole + outerArea.subtract(innerArea); + if (outerArea.isEmpty()) { + // this outer area space can be abandoned + } else if (outerArea.isSingular()) { + // the area is singular + // => no further splits necessary + outerAfterThisStep.add(outerArea); + } else { + // 1st cut in two halfs in the middle of the inner area + + // Cut the bounding box into two rectangles + Rectangle r1; + Rectangle r2; + + // Get the bounds of this polygon + Rectangle innerBounds = innerArea.getBounds(); + Rectangle outerBounds = outerArea.getBounds(); + if (outerBounds.width > outerBounds.height) { + int cutWidth = (innerBounds.x - outerBounds.x) + + innerBounds.width / 2; + r1 = new Rectangle(outerBounds.x, outerBounds.y, + cutWidth, outerBounds.height); + r2 = new Rectangle(outerBounds.x + cutWidth, + outerBounds.y, outerBounds.width - cutWidth, + outerBounds.height); + } else { + int cutHeight = (innerBounds.y - outerBounds.y) + + innerBounds.height / 2; + r1 = new Rectangle(outerBounds.x, outerBounds.y, + outerBounds.width, cutHeight); + r2 = new Rectangle(outerBounds.x, outerBounds.y + + cutHeight, outerBounds.width, + outerBounds.height - cutHeight); + } + + // Now find the intersection of these two boxes with the + // original + // polygon. This will make two new areas, and each area will + // be one + // (or more) polygons. + Area a1 = outerArea; + Area a2 = (Area) a1.clone(); + a1.intersect(new Area(r1)); + a2.intersect(new Area(r2)); + + outerAfterThisStep.addAll(areaToSingularAreas(a1)); + outerAfterThisStep.addAll(areaToSingularAreas(a2)); + } + } + outerAreas = outerAfterThisStep; + } + + List outerWays = new ArrayList(outerAreas.size()); + for (Area area : outerAreas) { + Way w = singularAreaToWay(area, FakeIdGenerator.makeFakeId()); + if (w != null) { + w.copyTags(outerWay); + outerWays.add(w); + } + } + + return outerWays; + } + + private List areaToSingularAreas(Area area) { + if (area.isEmpty()) { + return Collections.emptyList(); + } else if (area.isSingular()) { + return Collections.singletonList(area); + } else { + List singularAreas = new ArrayList(); + + // all ways in the area MUST define outer areas + // it is not possible that one of the areas define an inner segment + + float[] res = new float[6]; + PathIterator pit = area.getPathIterator(null); + float[] prevPoint = new float[6]; + + Polygon p = null; + while (!pit.isDone()) { + int type = pit.currentSegment(res); + + switch (type) { + case PathIterator.SEG_LINETO: + if (Arrays.equals(res, prevPoint) == false) { + p.addPoint(Math.round(res[0]), Math.round(res[1])); + } + break; + case PathIterator.SEG_CLOSE: + p.addPoint(p.xpoints[0], p.ypoints[0]); + Area a = new Area(p); + if (a.isEmpty() == false) { + singularAreas.add(a); + } + p = null; + break; + case PathIterator.SEG_MOVETO: + if (p != null) { + Area a2 = new Area(p); + if (a2.isEmpty() == false) { + singularAreas.add(a2); + } + } + p = new Polygon(); + p.addPoint(Math.round(res[0]), Math.round(res[1])); + break; + default: + log.warn(toBrowseURL(), "Unsupported path iterator type" + + type, ". This is an mkgmap error."); + } + + System.arraycopy(res, 0, prevPoint, 0, 6); + pit.next(); + } + return singularAreas; + } + } + + private Polygon createPolygon(List points) { + Polygon polygon = new Polygon(); + for (Coord co : points) { + polygon.addPoint(co.getLongitude(), co.getLatitude()); } + return polygon; + } + private Area createArea(List points) { + return new Area(createPolygon(points)); + } + + private Way singularAreaToWay(Area area, long wayId) { + if (area.isSingular() == false) { + log + .warn( + "singularAreaToWay called with non singular area. Multipolygon ", + toBrowseURL()); + } + if (area.isEmpty()) { + log.debug("Empty area.", toBrowseURL()); + return null; + } + + Way w = null; + + float[] res = new float[6]; + PathIterator pit = area.getPathIterator(null); + + while (!pit.isDone()) { + int type = pit.currentSegment(res); + + switch (type) { + case PathIterator.SEG_MOVETO: + w = new Way(wayId); + w.addPoint(new Coord(Math.round(res[1]), Math.round(res[0]))); + break; + case PathIterator.SEG_LINETO: + w.addPoint(new Coord(Math.round(res[1]), Math.round(res[0]))); + break; + case PathIterator.SEG_CLOSE: + w.addPoint(w.getPoints().get(0)); + return w; + default: + log.warn(toBrowseURL(), + "Unsupported path iterator type" + type, + ". This is an mkgmap error."); + } + pit.next(); + } + return w; } private boolean hasUsefulTags(JoinedWay way) { - for (Map.Entry segment : way.getOriginalRoleWays()) { - if (hasUsefulTags(segment.getValue())) { + for (Way segment : way.getOriginalWays()) { + if (hasUsefulTags(segment)) { return true; } } @@ -461,14 +830,12 @@ * @param wayList * @param checkRole */ - private void checkRoles(List> wayList, - String checkRole) { + private void checkRoles(List wayList, String checkRole) { // QA: check that all ways carry the role "inner" and issue warnings - for (Map.Entry rw : wayList) { - String role = rw.getKey(); - Way way = rw.getValue(); - if (!checkRole.equals(role) == false) { - log.warn("Way", way.getId(), "carries role", role, + for (Way tempWay : wayList) { + String realRole = getRole(tempWay); + if (checkRole.equals(realRole) == false) { + log.warn("Way", tempWay.getId(), "carries role", realRole, "but should carry role", checkRole); } } @@ -481,7 +848,7 @@ // mark which ring contains which ring for (int i = 0; i < wayList.size(); i++) { - Way tempRing = wayList.get(i); + JoinedWay tempRing = wayList.get(i); BitSet bitSet = containsMatrix.get(i); for (int j = 0; j < wayList.size(); j++) { @@ -499,9 +866,69 @@ } } } - + + log.debug("Containsmatrix"); + for (BitSet b : containsMatrix) { + log.debug(b); + } } + /* + * this is an alternative createContainsMatrix method seems to speed up only + * if lots of inner ways are in the mulitpolygon + */ + // private void createContainsMatrix(List wayList) { + // long t1 = System.currentTimeMillis(); + // + // for (int i = 0; i < wayList.size(); i++) { + // containsMatrix.add(new BitSet()); + // } + // + // // use this matrix to check which matrix element has been + // // calculated + // ArrayList finishedMatrix = new ArrayList(wayList.size()); + // + // for (int i = 0; i < wayList.size(); i++) { + // BitSet matrixRow = new BitSet(); + // // an element does not contain itself + // matrixRow.set(i); + // finishedMatrix.add(matrixRow); + // } + // + // for (int rowIndex = 0; rowIndex < wayList.size(); rowIndex++) { + // Way potentialOuterRing = wayList.get(rowIndex); + // BitSet containsColumns = containsMatrix.get(rowIndex); + // BitSet finishedCol = finishedMatrix.get(rowIndex); + // + // // get all non calculated columns of the matrix + // for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0 && + // colIndex < wayList.size(); colIndex = finishedCol + // .nextClearBit(colIndex + 1)) { + // + // boolean contains = contains(potentialOuterRing, wayList.get(colIndex)); + // + // if (contains) { + // containsColumns.set(colIndex); + // + // // we also know that the inner ring does not contain the outer ring + // // so we can set the finished bit for this matrix element + // finishedMatrix.get(colIndex).set(rowIndex); + // + // // additionally we know that the outer ring contains all rings + // // that are contained by the inner ring + // containsColumns.or(containsMatrix.get(colIndex)); + // finishedCol.or(containsColumns); + // } + // + // // this matrix element is calculated now + // finishedCol.set(colIndex); + // } + // } + // + // long t2 = System.currentTimeMillis(); + // log.warn("createMatrix",(t2-t1),"ms"); + // } + /** * Checks if the ring with ringIndex1 contains the ring with ringIndex2. * @@ -521,20 +948,17 @@ * @param ring2 * @return true if ring1 contains ring2 */ - private boolean contains(Way ring1, Way ring2) { + private boolean contains(JoinedWay ring1, JoinedWay ring2) { // TODO this is a simple algorithm // might be improved if (ring1.isClosed() == false) { return false; } - Polygon p = new Polygon(); - for (Coord c : ring1.getPoints()) { - p.addPoint(c.getLatitude(), c.getLongitude()); - } + Polygon p = createPolygon(ring1.getPoints()); Coord p0 = ring2.getPoints().get(0); - if (p.contains(p0.getLatitude(), p0.getLongitude()) == false) { + if (p.contains(p0.getLongitude(), p0.getLatitude()) == false) { // we have one point that is not in way1 => way1 does not contain // way2 return false; @@ -555,14 +979,22 @@ p1_2 = p1_1; p1_1 = it1.next(); - boolean intersects = Line2D.linesIntersect(p1_1.getLatitude(), - p1_1.getLongitude(), p1_2.getLatitude(), p1_2 - .getLongitude(), p2_1.getLatitude(), p2_1 - .getLongitude(), p2_2.getLatitude(), p2_2 - .getLongitude()); + boolean intersects = Line2D.linesIntersect(p1_1.getLongitude(), + p1_1.getLatitude(), p1_2.getLongitude(), p1_2 + .getLatitude(), p2_1.getLongitude(), p2_1 + .getLatitude(), p2_2.getLongitude(), p2_2 + .getLatitude()); if (intersects) { - return false; + if ((ring1.isClosedArtificially() && it1.hasNext() == false) + || (ring2.isClosedArtificially() && it2.hasNext() == false)) { + // don't care about this intersection + // one of the rings is closed by this mp code and the + // closing way causes the intersection + log.warn("Way",ring1,"may contain way",ring2,". Ignoring artificial generated intersection."); + } else { + return false; + } } } } @@ -573,213 +1005,96 @@ } /** - * Insert Coordinates into the outer way. - * - * @param outer - * the outer way - * @param inner - * Way to be inserted - * @param out - * Coordinates will be inserted after this point in the outer - * way. - * @param in - * Points will be inserted starting at this index, then from - * element 0 to (including) this element; + * This is a helper class that stores that gives access to the original + * segments of a joined way. */ - private void insertPoints(Way outer, Way inner, int out, int in) { - // TODO this algorithm may generate a self intersecting polygon - // because it does not consider the direction of both ways - // don't know if that's a problem - - List outList = outer.getPoints(); - List inList = inner.getPoints(); - int index = out + 1; - for (int i = in; i < inList.size(); i++) { - outList.add(index++, inList.get(i)); - } - for (int i = 0; i < in; i++) { - outList.add(index++, inList.get(i)); - } + private static class JoinedWay extends Way { + private final List originalWays; + private boolean closedArtifical = false; - // Investigate and see if we can do the first alternative here by - // changing the polygon splitter. If not then always do the alternative - // and remove unused code. - if (outer.getPoints().size() < 0 /* Always use alternative method for now */) { - outList.add(index++, inList.get(in)); - outList.add(index, outList.get(out)); - } else { - // we shift the nodes to avoid duplicate nodes (large areas only) - int oLat = outList.get(out).getLatitude(); - int oLon = outList.get(out).getLongitude(); - int iLat = inList.get(in).getLatitude(); - int iLon = inList.get(in).getLongitude(); - if (Math.abs(oLat - iLat) > Math.abs(oLon - iLon)) { - int delta = (oLon > iLon) ? -1 : 1; - outList.add(index++, new Coord(iLat + delta, iLon)); - outList.add(index, new Coord(oLat + delta, oLon)); - } else { - int delta = (oLat > iLat) ? 1 : -1; - outList.add(index++, new Coord(iLat, iLon + delta)); - outList.add(index, new Coord(oLat, oLon + delta)); - } + public JoinedWay(Way originalWay) { + super(FakeIdGenerator.makeFakeId(), new ArrayList(originalWay + .getPoints())); + this.originalWays = new ArrayList(); + addWay(originalWay); } - } - - private static final class DistIndex implements Comparable { - int index1; - int index2; - double distance; - public DistIndex(int index1, int index2, double distance) { - super(); - this.index1 = index1; - this.index2 = index2; - this.distance = distance; + public void addPoint(int index, Coord point) { + getPoints().add(index, point); } - @Override - public int compareTo(DistIndex o) { - if (distance < o.distance) - return -1; - else if (distance > o.distance) { - return 1; + public void addWay(Way way) { + if (way instanceof JoinedWay) { + for (Way w : ((JoinedWay) way).getOriginalWays()) { + addWay(w); + } } else { - return 0; - } - } - } - - /** - * find the Closest Point of Approach between two coordinate-lists This will - * probably be moved to a Utils class. Note: works only if l2 lies into l1. - * - * @param l1 - * First list of points. - * @param l2 - * Second list of points. - * @return The first element is the index in l1, the second in l2 which are - * the closest together. - */ - private static int[] findCpa(List l1, List l2) { - // calculate and sort all distances first before - // to avoid the very costly calls of intersect - // Limit the size of this list to 500000 entries to - // avoid extreme memory consumption - int maxEntries = 500000; - ArrayList distList = new ArrayList(Math.min(l1 - .size() - * l2.size(), maxEntries)); - - DistIndex minDistance = null; - - int index1 = 0; - for (Coord c1 : l1) { - int index2 = 0; - for (Coord c2 : l2) { - double distance = c1.distanceInDegreesSquared(c2); - distList.add(new DistIndex(index1, index2, distance)); - index2++; - - if (distList.size() == maxEntries) { - Collections.sort(distList); - for (DistIndex newDistance : distList) { - if (minDistance == null - || minDistance.distance > newDistance.distance) { - // this is a new minimum - // test if a line between c1 and c2 intersects - // the outer polygon l1. - if (intersects(l1, l1.get(newDistance.index1), l2 - .get(newDistance.index2)) == false) { - minDistance = newDistance; - break; - } - } else { - break; - } - } - distList.clear(); + this.originalWays.add(way); + addTagsOf(way); + if (getName() == null && way.getName() != null) { + setName(way.getName()); } } - index1++; + log.debug("Joined", this.getId(), "with", way.getId()); } - Collections.sort(distList); - for (DistIndex newDistance : distList) { - if (minDistance == null - || minDistance.distance > newDistance.distance) { - // this is a new minimum - // test if a line between c1 and c2 intersects - // the outer polygon l1. - if (intersects(l1, l1.get(newDistance.index1), l2 - .get(newDistance.index2)) == false) { - minDistance = newDistance; - break; - } - } else { - break; - } + public void closeWayArtificial() { + addPoint(getPoints().get(0)); + closedArtifical = true; } - if (minDistance == null) { - // this should not happen - return new int[] { -1, -1 }; - } else { - return new int[] { minDistance.index1, minDistance.index2 }; + public boolean isClosedArtificially() { + return closedArtifical; } - } - private static boolean intersects(List lc, Coord lp1, Coord lp2) { - Coord c11 = null; - Coord c12 = null; - for (Coord c : lc) { - c12 = c11; - c11 = c; - if (c12 == null) { - continue; - } - - // in case the line intersects in a well known point this is not an - // inline intersection - if (c11.equals(lp1) || c11.equals(lp2) || c12.equals(lp1) - || c12.equals(lp2)) { - continue; - } - - if (Line2D.linesIntersect(c11.getLatitude(), c11.getLongitude(), - c12.getLatitude(), c12.getLongitude(), lp1.getLatitude(), - lp1.getLongitude(), lp2.getLatitude(), lp2.getLongitude())) { - return true; + private void addTagsOf(Way way) { + for (Map.Entry tag : way.getEntryIteratable()) { + if (getTag(tag.getKey()) == null) { + addTag(tag.getKey(), tag.getValue()); + } } } - return false; - } - /** - * This is a helper class that stores that gives access to the original - * segments of a joined way. - */ - private static class JoinedWay extends Way { - private final List> originalRoleWays; - - public JoinedWay(String role, Way originalWay) { - super(-originalWay.getId(), new ArrayList(originalWay - .getPoints())); - this.originalRoleWays = new ArrayList>(); - this.originalRoleWays.add(new AbstractMap.SimpleEntry - (role, originalWay)); + public List getOriginalWays() { + return originalWays; } - public void addPoint(int index, Coord point) { - getPoints().add(index, point); + public void removeAllTagsDeep() { + removeOriginalTags(); + removeAllTags(); } - public void addWay(JoinedWay way) { - originalRoleWays.addAll(way.originalRoleWays); - log.debug("Joined", this.getId(), "with", way.getId()); + public void removeOriginalTags() { + for (Way w : getOriginalWays()) { + if (w instanceof JoinedWay) { + ((JoinedWay) w).removeAllTagsDeep(); + } else { + w.removeAllTags(); + } + } } - public List> getOriginalRoleWays() { - return originalRoleWays; + @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(","); + } + sb.append(w.getId()); + sb.append("["); + sb.append(w.getPoints().size()); + sb.append("P]"); + } + sb.append(")"); + return sb.toString(); } } Index: src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java (revision 1475) +++ src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java (working copy) @@ -20,9 +20,8 @@ import java.io.DataInputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.InputStreamReader; import java.io.IOException; - +import java.io.InputStreamReader; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; @@ -37,6 +36,12 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.Exit; @@ -46,6 +51,7 @@ import uk.me.parabola.mkgmap.general.RoadNetwork; import uk.me.parabola.mkgmap.reader.osm.CoordPOI; import uk.me.parabola.mkgmap.reader.osm.Element; +import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; import uk.me.parabola.mkgmap.reader.osm.GeneralRelation; import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation; import uk.me.parabola.mkgmap.reader.osm.Node; @@ -55,12 +61,6 @@ import uk.me.parabola.mkgmap.reader.osm.Way; import uk.me.parabola.util.EnhancedProperties; -import org.xml.sax.Attributes; -import org.xml.sax.ContentHandler; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; -import org.xml.sax.helpers.DefaultHandler; - /** * Reads and parses the OSM XML format. * @@ -104,8 +104,6 @@ private Area bbox; private Runnable endTask; - private long nextFakeId = 1; - private final boolean reportUndefinedNodes; private final boolean makeOppositeCycleways; private final boolean makeCycleways; @@ -545,8 +543,10 @@ private void endRelation() { String type = currentRelation.getTag("type"); if (type != null) { - if ("multipolygon".equals(type)) - currentRelation = new MultiPolygonRelation(currentRelation, wayMap); + if ("multipolygon".equals(type)) { + Area mpbbox = (bbox == null ? mapper.getBounds() : bbox); + currentRelation = new MultiPolygonRelation(currentRelation, wayMap, mpbbox); + } else if("restriction".equals(type)) { if(ignoreTurnRestrictions) @@ -570,7 +570,7 @@ currentRelation = null; } } - + /** * Receive notification of the end of the document. * @@ -581,7 +581,7 @@ * another exception. */ public void endDocument() throws SAXException { - + for (Node e : exits) { String refTag = Exit.TAG_ROAD_REF; if(e.getTag(refTag) == null) { @@ -1052,10 +1052,6 @@ super.fatalError(e); } - public long makeFakeId() { - return (1L << 62) + nextFakeId++; - } - private long idVal(String id) { try { // attempt to parse id as a number @@ -1065,7 +1061,7 @@ // if that fails, fake a (hopefully) unique value Long fakeIdVal = fakeIdMap.get(id); if(fakeIdVal == null) { - fakeIdVal = makeFakeId(); + fakeIdVal = FakeIdGenerator.makeFakeId(); fakeIdMap.put(id, fakeIdVal); } //System.out.printf("%s = 0x%016x\n", id, fakeIdVal); @@ -1090,7 +1086,7 @@ log.info("clipping " + segment); toBeRemoved.add(segment); for (List pts : clipped) { - long id = makeFakeId(); + long id = FakeIdGenerator.makeFakeId(); Way shore = new Way(id, pts); toBeAdded.add(shore); } @@ -1117,7 +1113,7 @@ // polygon so that the tile's background colour will // match the land colour on the tiles that do contain // some sea - long landId = makeFakeId(); + long landId = FakeIdGenerator.makeFakeId(); Way land = new Way(landId); land.addPoint(nw); land.addPoint(sw); @@ -1131,7 +1127,7 @@ return; } - long multiId = makeFakeId(); + long multiId = FakeIdGenerator.makeFakeId(); Relation seaRelation = null; if(generateSeaUsingMP) { seaRelation = new GeneralRelation(multiId); @@ -1223,7 +1219,7 @@ } } else if(allowSeaSectors) { - seaId = makeFakeId(); + seaId = FakeIdGenerator.makeFakeId(); sea = new Way(seaId); sea.getPoints().addAll(points); sea.addPoint(new Coord(pEnd.getLatitude(), pStart.getLongitude())); @@ -1249,7 +1245,7 @@ } } if (generateSeaBackground) { - seaId = makeFakeId(); + seaId = FakeIdGenerator.makeFakeId(); sea = new Way(seaId); sea.addPoint(nw); sea.addPoint(sw); @@ -1266,7 +1262,7 @@ // now construct inner ways from these segments NavigableSet hits = (NavigableSet) hitMap.keySet(); while (!hits.isEmpty()) { - long id = makeFakeId(); + long id = FakeIdGenerator.makeFakeId(); Way w = new Way(id); wayMap.put(id, w); @@ -1332,7 +1328,8 @@ } if(generateSeaUsingMP) { - seaRelation = new MultiPolygonRelation(seaRelation, wayMap); + Area mpbbox = (bbox == null ? mapper.getBounds() : bbox); + seaRelation = new MultiPolygonRelation(seaRelation, wayMap, mpbbox); relationMap.put(multiId, seaRelation); seaRelation.processElements(); } @@ -1451,8 +1448,8 @@ log.info("merging: ", ways.size(), w1.getId(), w2.getId()); List points2 = w2.getPoints(); Way wm; - if (w1.getId() < (1L << 62)) { - wm = new Way(makeFakeId()); + if (FakeIdGenerator.isFakeId(w1.getId())) { + wm = new Way(FakeIdGenerator.makeFakeId()); ways.remove(w1); ways.add(wm); wm.getPoints().addAll(points1);