package org.osm2world.core.map_data.creation; import static java.util.Collections.emptyList; import static org.osm2world.core.math.VectorXZ.distance; import static org.osm2world.core.util.FaultTolerantIterationUtil.iterate; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.configuration.Configuration; import org.openstreetmap.josm.plugins.graphview.core.data.Tag; import org.openstreetmap.josm.plugins.graphview.core.data.osmosis.OSMFileDataSource; import org.openstreetmap.osmosis.core.domain.v0_6.Bound; import org.osm2world.core.map_data.creation.index.MapDataIndex; import org.osm2world.core.map_data.creation.index.MapIntersectionGrid; import org.osm2world.core.map_data.data.MapArea; import org.osm2world.core.map_data.data.MapAreaSegment; import org.osm2world.core.map_data.data.MapData; import org.osm2world.core.map_data.data.MapElement; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapWaySegment; import org.osm2world.core.map_data.data.overlaps.MapIntersectionWW; import org.osm2world.core.map_data.data.overlaps.MapOverlapAA; import org.osm2world.core.map_data.data.overlaps.MapOverlapNA; import org.osm2world.core.map_data.data.overlaps.MapOverlapType; import org.osm2world.core.map_data.data.overlaps.MapOverlapWA; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.GeometryUtil; import org.osm2world.core.math.InvalidGeometryException; import org.osm2world.core.math.LineSegmentXZ; import org.osm2world.core.math.PolygonWithHolesXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.osm.data.OSMData; import org.osm2world.core.osm.data.OSMElement; import org.osm2world.core.osm.data.OSMNode; import org.osm2world.core.osm.data.OSMRelation; import org.osm2world.core.osm.data.OSMWay; import org.osm2world.core.osm.ruleset.HardcodedRuleset; import org.osm2world.core.osm.ruleset.Ruleset; import org.osm2world.core.util.FaultTolerantIterationUtil.Operation; /** * converts {@link OSMData} into the internal map data representation */ public class OSMToMapDataConverter { private final Ruleset ruleset = new HardcodedRuleset(); private final MapProjection mapProjection; private final Configuration config; private static final Tag MULTIPOLYON_TAG = new Tag("type", "multipolygon"); public OSMToMapDataConverter(MapProjection mapProjection, Configuration config) { this.mapProjection = mapProjection; this.config = config; } public MapData createMapData(OSMData osmData) throws IOException { final List<MapNode> mapNodes = new ArrayList<MapNode>(); final List<MapWaySegment> mapWaySegs = new ArrayList<MapWaySegment>(); final List<MapArea> mapAreas = new ArrayList<MapArea>(); createMapElements(osmData, mapNodes, mapWaySegs, mapAreas); MapData mapData = new MapData(mapNodes, mapWaySegs, mapAreas, calculateFileBoundary(osmData.getBounds())); calculateIntersectionsInMapData(mapData); return mapData; } /** * creates {@link MapElement}s * based on OSM data from an {@link OSMFileDataSource} * and adds them to collections */ private void createMapElements(OSMData osmData, final List<MapNode> mapNodes, final List<MapWaySegment> mapWaySegs, final List<MapArea> mapAreas) { /* create MapNode for each OSM node */ final Map<OSMNode, MapNode> nodeMap = new HashMap<OSMNode, MapNode>(); for (OSMNode node : osmData.getNodes()) { VectorXZ nodePos = mapProjection.calcPos(node.lat, node.lon); MapNode mapNode = new MapNode(nodePos, node); mapNodes.add(mapNode); nodeMap.put(node, mapNode); } /* create areas ... */ final Map<OSMWay, MapArea> areaMap = new HashMap<OSMWay, MapArea>(); /* ... based on multipolygons */ iterate(osmData.getRelations(), new Operation<OSMRelation>() { @Override public void perform(OSMRelation relation) { if (relation.tags.contains(MULTIPOLYON_TAG)) { for (MapArea area : MultipolygonAreaBuilder. createAreasForMultipolygon(relation, nodeMap)) { mapAreas.add(area); for (MapNode boundaryMapNode : area.getBoundaryNodes()) { boundaryMapNode.addAdjacentArea(area); } if (area.getOsmObject() instanceof OSMWay) { areaMap.put((OSMWay)area.getOsmObject(), area); } } } } }); /* ... based on coastline ways */ for (MapArea area : MultipolygonAreaBuilder.createAreasForCoastlines( osmData, nodeMap, mapNodes, calculateFileBoundary(osmData.getBounds()))) { mapAreas.add(area); for (MapNode boundaryMapNode : area.getBoundaryNodes()) { boundaryMapNode.addAdjacentArea(area); } } /* ... based on closed ways */ for (OSMWay way : osmData.getWays()) { if (way.isClosed() && !areaMap.containsKey(way)) { //create MapArea only if at least one tag is an area tag for (Tag tag : way.tags) { if (ruleset.isAreaTag(tag)) { //TODO: check whether this is old-style MP outer List<MapNode> nodes = new ArrayList<MapNode>(way.nodes.size()); for (OSMNode boundaryOSMNode : way.nodes) { nodes.add(nodeMap.get(boundaryOSMNode)); } try { MapArea mapArea = new MapArea((OSMElement)way, nodes); mapAreas.add(mapArea); areaMap.put(way, mapArea); } catch (InvalidGeometryException e) { System.err.println(e); } break; } } } } /* ... for empty terrain */ AxisAlignedBoundingBoxXZ terrainBoundary = calculateFileBoundary(osmData.getBounds()); if (terrainBoundary != null && config.getBoolean("createTerrain", true)) { EmptyTerrainBuilder.createAreasForEmptyTerrain( mapNodes, mapAreas, terrainBoundary); } else { //TODO fall back on data boundary if file does not contain bounds } /* finish calculations */ for (MapNode node : nodeMap.values()) { node.calculateAdjacentAreaSegments(); } /* create way segments from remaining ways */ for (OSMWay way : osmData.getWays()) { if (!way.tags.isEmpty() && !areaMap.containsKey(way)) { OSMNode previousNode = null; for (OSMNode node : way.nodes) { if (previousNode != null) { MapWaySegment mapWaySeg = new MapWaySegment( way, nodeMap.get(previousNode), nodeMap.get(node)); mapWaySegs.add(mapWaySeg); nodeMap.get(previousNode).addOutboundLine(mapWaySeg); nodeMap.get(node).addInboundLine(mapWaySeg); } previousNode = node; } } } } /** * calculates intersections and adds the information to the * {@link MapElement}s */ private static void calculateIntersectionsInMapData(MapData mapData) { MapDataIndex index = new MapIntersectionGrid(mapData.getDataBoundary()); for (MapElement e1 : mapData.getMapElements()) { /* collect all nearby elements */ Collection<? extends Iterable<MapElement>> leaves = index.insertAndProbe(e1); Iterable<MapElement> nearbyElements; if (leaves.size() == 1) { nearbyElements = leaves.iterator().next(); } else { // collect and de-duplicate elements from all the leaves Set<MapElement> elementSet = new HashSet<MapElement>(); for (Iterable<MapElement> leaf : leaves) { for (MapElement e : leaf) { elementSet.add(e); } } nearbyElements = elementSet; } for (MapElement e2 : nearbyElements) { if (e1 == e2) { continue; } addOverlapBetween(e1, e2); } } } /** * adds the overlap between two {@link MapElement}s * to both, if it exists. It calls the appropriate * subtype-specific addOverlapBetween method */ private static void addOverlapBetween(MapElement e1, MapElement e2) { if (e1 instanceof MapWaySegment && e2 instanceof MapWaySegment) { addOverlapBetween((MapWaySegment) e1, (MapWaySegment) e2); } else if (e1 instanceof MapWaySegment && e2 instanceof MapArea) { addOverlapBetween((MapWaySegment) e1, (MapArea) e2); } else if (e1 instanceof MapArea && e2 instanceof MapWaySegment) { addOverlapBetween((MapWaySegment) e2, (MapArea) e1); } else if (e1 instanceof MapArea && e2 instanceof MapArea) { addOverlapBetween((MapArea) e1, (MapArea) e2); } else if (e1 instanceof MapNode && e2 instanceof MapArea) { addOverlapBetween((MapNode) e1, (MapArea) e2); } else if (e1 instanceof MapArea && e2 instanceof MapNode) { addOverlapBetween((MapNode) e2, (MapArea) e1); } } /** * adds the overlap between two {@link MapWaySegment}s * to both, if it exists */ private static void addOverlapBetween( MapWaySegment line1, MapWaySegment line2) { if (line1.isConnectedTo(line2)) { return; } VectorXZ intersection = GeometryUtil.getLineSegmentIntersection( line1.getStartNode().getPos(), line1.getEndNode().getPos(), line2.getStartNode().getPos(), line2.getEndNode().getPos()); if (intersection != null) { /* add the intersection */ MapIntersectionWW newIntersection = new MapIntersectionWW(line1, line2, intersection); line1.addOverlap(newIntersection); line2.addOverlap(newIntersection); } } /** * adds the overlap between a {@link MapWaySegment} * and a {@link MapArea} to both, if it exists */ private static void addOverlapBetween( MapWaySegment line, MapArea area) { final LineSegmentXZ segmentXZ = line.getLineSegment(); /* check whether the line corresponds to one of the area segments */ for (MapAreaSegment areaSegment : area.getAreaSegments()) { if (areaSegment.sharesBothNodes(line)) { MapOverlapWA newOverlap = new MapOverlapWA(line, area, MapOverlapType.SHARE_SEGMENT, Collections.<VectorXZ>emptyList(), Collections.<MapAreaSegment>emptyList()); line.addOverlap(newOverlap); area.addOverlap(newOverlap); return; } } /* calculate whether the line contains or intersects the area (or neither) */ boolean contains; boolean intersects; { final PolygonWithHolesXZ polygon = area.getPolygon(); if (!line.isConnectedTo(area)) { intersects = polygon.intersects(segmentXZ); contains = !intersects && polygon.contains(segmentXZ); } else { /* check whether the line intersects the area somewhere * else than just at the common node(s). */ intersects = false; double segmentLength = distance(segmentXZ.p1, segmentXZ.p2); for (VectorXZ pos : polygon.intersectionPositions(segmentXZ)) { if (distance(pos, segmentXZ.p1) > segmentLength / 100 && distance(pos, segmentXZ.p2) > segmentLength / 100) { intersects = true; break; } } /* check whether the area contains the line's center. * Unless the line intersects the area outline, * this means that the area contains the line itself. */ contains = !intersects && polygon.contains(segmentXZ.getCenter()); } } /* add an overlap if detected */ if (contains || intersects) { /* find out which area segments intersect the way segment */ List<VectorXZ> intersectionPositions = emptyList(); List<MapAreaSegment> intersectingSegments = emptyList(); if (intersects) { intersectionPositions = new ArrayList<VectorXZ>(); intersectingSegments = new ArrayList<MapAreaSegment>(); for (MapAreaSegment areaSegment : area.getAreaSegments()) { VectorXZ intersection = segmentXZ.getIntersection( areaSegment.getStartNode().getPos(), areaSegment.getEndNode().getPos()); if (intersection != null) { intersectionPositions.add(intersection); intersectingSegments.add(areaSegment); } } } /* add the overlap */ MapOverlapWA newOverlap = new MapOverlapWA(line, area, intersects ? MapOverlapType.INTERSECT : MapOverlapType.CONTAIN, intersectionPositions, intersectingSegments); line.addOverlap(newOverlap); area.addOverlap(newOverlap); } } /** * adds the overlap between two {@link MapArea}s * to both, if it exists */ private static void addOverlapBetween( MapArea area1, MapArea area2) { /* check whether the areas have a shared segment */ Collection<MapAreaSegment> area1Segments = area1.getAreaSegments(); Collection<MapAreaSegment> area2Segments = area2.getAreaSegments(); for (MapAreaSegment area1Segment : area1Segments) { for (MapAreaSegment area2Segment : area2Segments) { if (area1Segment.sharesBothNodes(area2Segment)) { MapOverlapAA newOverlap = new MapOverlapAA(area1, area2, MapOverlapType.SHARE_SEGMENT); area1.addOverlap(newOverlap); area2.addOverlap(newOverlap); return; } } } /* calculate whether one area contains the other * or whether their outlines intersect (or neither) */ boolean contains1 = false; boolean contains2 = false; boolean intersects = false; { final PolygonWithHolesXZ polygon1 = area1.getPolygon(); final PolygonWithHolesXZ polygon2 = area2.getPolygon(); /* determine common nodes */ Set<VectorXZ> commonNodes = new HashSet<VectorXZ>(); for (SimplePolygonXZ p : polygon1.getPolygons()) { commonNodes.addAll(p.getVertices()); } Set<VectorXZ> nodes2 = new HashSet<VectorXZ>(); for (SimplePolygonXZ p : polygon2.getPolygons()) { nodes2.addAll(p.getVertices()); } commonNodes.retainAll(nodes2); /* check whether the areas' outlines intersects somewhere * else than just at the common node(s). */ intersectionPosCheck: for (VectorXZ pos : polygon1.intersectionPositions(polygon2)) { boolean trueIntersection = true; for (VectorXZ commonNode : commonNodes) { if (distance(pos, commonNode) < 0.01) { trueIntersection = false; } } if (trueIntersection) { intersects = true; break intersectionPosCheck; } } /* check whether one area contains the other */ if (polygon1.contains(polygon2.getOuter())) { contains1 = true; } else if (polygon2.contains(polygon1.getOuter())) { contains2 = true; } } /* add an overlap if detected */ if (contains1 || contains2 || intersects) { /* add the overlap */ MapOverlapAA newOverlap = null; if (contains1) { newOverlap = new MapOverlapAA(area2, area1, MapOverlapType.CONTAIN); } else if (contains2) { newOverlap = new MapOverlapAA(area1, area2, MapOverlapType.CONTAIN); } else { newOverlap = new MapOverlapAA(area1, area2, MapOverlapType.INTERSECT); } area1.addOverlap(newOverlap); area2.addOverlap(newOverlap); } } private static void addOverlapBetween(MapNode node, MapArea area) { if (area.getPolygon().contains(node.getPos())) { /* add the overlap */ MapOverlapNA newOverlap = new MapOverlapNA(node, area, MapOverlapType.CONTAIN); area.addOverlap(newOverlap); } } private AxisAlignedBoundingBoxXZ calculateFileBoundary( Collection<Bound> bounds) { Collection<VectorXZ> boundedPoints = new ArrayList<VectorXZ>(); for (Bound bound : bounds) { boundedPoints.add(mapProjection.calcPos(bound.getBottom(), bound.getLeft())); boundedPoints.add(mapProjection.calcPos(bound.getBottom(), bound.getRight())); boundedPoints.add(mapProjection.calcPos(bound.getTop(), bound.getLeft())); boundedPoints.add(mapProjection.calcPos(bound.getTop(), bound.getRight())); } if (boundedPoints.isEmpty()) { return null; } else { return new AxisAlignedBoundingBoxXZ(boundedPoints); } } }