package maps.convert.osm2gml; import maps.osm.OSMMap; import maps.osm.OSMNode; import maps.osm.OSMRoad; import maps.osm.OSMBuilding; import maps.osm.OSMWay; import maps.convert.ConvertStep; import java.util.List; import java.util.ArrayList; import java.util.Iterator; import java.util.Set; import java.util.HashSet; import java.util.Collection; import java.util.Collections; import rescuecore2.misc.geometry.Line2D; //import rescuecore2.log.Logger; /** This step cleans the OpenStreetMap data by removing duplicate nodes and way, fixing degenerate ways, and fixing building edge orderings. */ public class CleanOSMStep extends ConvertStep { private TemporaryMap map; /** Construct a CleanOSMStep. @param map The TemporaryMap to clean. */ public CleanOSMStep(TemporaryMap map) { this.map = map; } @Override public String getDescription() { return "Cleaning OpenStreetMap data"; } @Override protected void step() { OSMMap osm = map.getOSMMap(); setProgressLimit(osm.getNodes().size() + (osm.getRoads().size() + osm.getBuildings().size()) * 2 + osm.getBuildings().size()); setStatus("Looking for duplicate nodes"); int nodes = fixNodes(); setStatus("Fixing degenerate ways"); int fixed = fixDegenerateWays(osm.getRoads()); fixed += fixDegenerateWays(osm.getBuildings()); setStatus("Looking for duplicate ways"); int ways = fixDuplicateWays(osm.getRoads()); ways += fixDuplicateWays(osm.getBuildings()); setStatus("Fixing building direction"); int b = fixBuildingDirection(osm.getBuildings()); setStatus("Removed " + nodes + " duplicate nodes and " + ways + " duplicate ways, fixed " + fixed + " degenerate ways, fixed " + b + " clockwise buildings"); } private int fixNodes() { OSMMap osm = map.getOSMMap(); int count = 0; double threshold = ConvertTools.nearbyThreshold(osm, map.getNearbyThreshold()); Set<OSMNode> removed = new HashSet<OSMNode>(); for (OSMNode next : osm.getNodes()) { if (removed.contains(next)) { bumpProgress(); continue; } for (OSMNode test : osm.getNodes()) { if (next == test) { continue; } if (removed.contains(test)) { continue; } if (nearby(next, test, threshold)) { // Remove the test node and replace all references to it with 'next' osm.replaceNode(test, next); removed.add(test); // Logger.debug("Removed duplicate node " + test.getID()); ++count; } } bumpProgress(); } return count; } private int fixDegenerateWays(Collection<? extends OSMWay> ways) { int count = 0; for (OSMWay way : ways) { // Check that no nodes are listed multiple times in sequence List<Long> ids = new ArrayList<Long>(way.getNodeIDs()); Iterator<Long> it = ids.iterator(); if (!it.hasNext()) { // Empty way. Remove it. remove(way); ++count; continue; } long last = it.next(); boolean fixed = false; while (it.hasNext()) { long next = it.next(); if (next == last) { // Duplicate node it.remove(); // Logger.debug("Removed node " + next + " from way " + way.getID()); fixed = true; } last = next; } if (fixed) { way.setNodeIDs(ids); ++count; } bumpProgress(); } return count; } private int fixDuplicateWays(Collection<? extends OSMWay> ways) { int count = 0; Set<OSMWay> removed = new HashSet<OSMWay>(); for (OSMWay next : ways) { if (removed.contains(next)) { bumpProgress(); continue; } // Look at all other roads and see if any are subpaths of this road for (OSMWay test : ways) { if (next == test) { continue; } if (removed.contains(test)) { continue; } List<Long> testIDs = test.getNodeIDs(); if (isSubList(testIDs, next.getNodeIDs())) { remove(test); removed.add(test); ++count; // Logger.debug("Removed way " + test.getID()); } else { Collections.reverse(testIDs); if (isSubList(testIDs, next.getNodeIDs())) { remove(test); removed.add(test); ++count; // Logger.debug("Removed way " + test.getID()); } } } bumpProgress(); } return count; } /** Make sure all buildings have their nodes listed in clockwise order. */ private int fixBuildingDirection(Collection<OSMBuilding> buildings) { OSMMap osm = map.getOSMMap(); // Sum the angles of all right-hand turns // If the total is +360 then order is clockwise, -360 means counterclockwise. int count = 0; for (OSMBuilding building : buildings) { // Logger.debug("Building " + building + " angle sum: " + ConvertTools.getAnglesSum(building, osm)); if (ConvertTools.isClockwise(building, osm)) { // Reverse the order List<Long> ids = building.getNodeIDs(); Collections.reverse(ids); building.setNodeIDs(ids); ++count; } bumpProgress(); } return count; } private boolean nearby(OSMNode first, OSMNode second, double threshold) { double dx = first.getLongitude() - second.getLongitude(); double dy = first.getLatitude() - second.getLatitude(); return (dx >= -threshold && dx <= threshold && dy >= -threshold && dy <= threshold); } private boolean isSubList(List<Long> first, List<Long> second) { return Collections.indexOfSubList(second, first) != -1; } private void remove(OSMWay way) { OSMMap osm = map.getOSMMap(); if (way instanceof OSMRoad) { osm.removeRoad((OSMRoad)way); } else if (way instanceof OSMBuilding) { osm.removeBuilding((OSMBuilding)way); } else { throw new IllegalArgumentException("Don't know how to handle this type of OSMWay: " + way.getClass().getName()); } } private Line2D makeLine(long first, long second) { OSMMap osm = map.getOSMMap(); OSMNode n1 = osm.getNode(first); OSMNode n2 = osm.getNode(second); return new Line2D(n1.getLongitude(), n1.getLatitude(), n2.getLongitude() - n1.getLongitude(), n2.getLatitude() - n1.getLatitude()); } }