package org.osm2world.core.map_data.data; import static org.osm2world.core.math.VectorXZ.X_UNIT; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; import org.osm2world.core.map_data.data.overlaps.MapOverlap; import org.osm2world.core.math.AxisAlignedBoundingBoxXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.osm.data.OSMNode; import org.osm2world.core.world.data.NodeWorldObject; /** * grid representation of an OSM node, * references inbound and outbound {@link MapWaySegment}s. * For each OSM node, one GridNode will be created. */ public class MapNode implements MapElement { private final VectorXZ pos; private final OSMNode osmNode; private List<NodeWorldObject> representations = new ArrayList<NodeWorldObject>(1); private List<MapWaySegment> connectedWaySegments = new ArrayList<MapWaySegment>(); private List<MapSegment> connectedSegments = new ArrayList<MapSegment>(); private List<MapWaySegment> inboundLines = new ArrayList<MapWaySegment>(); //TODO: maybe use list and sort by angle? private List<MapWaySegment> outboundLines = new ArrayList<MapWaySegment>(); private Collection<MapArea> adjacentAreas; public MapNode(VectorXZ pos, OSMNode osmNode) { this.pos = pos; this.osmNode = osmNode; this.adjacentAreas = new ArrayList<MapArea>(); } public VectorXZ getPos() { return pos; } @Override public int getLayer() { if (osmNode.tags.containsKey("layer")) { try { return Integer.parseInt(osmNode.tags.getValue("layer")); } catch (NumberFormatException nfe) { return 0; } } return 0; } public OSMNode getOsmNode() { return osmNode; } @Override public TagGroup getTags() { return getOsmNode().tags; } public Collection<MapArea> getAdjacentAreas() { return adjacentAreas; } public void addInboundLine(MapWaySegment inboundLine) { connectedWaySegments.add(inboundLine); connectedSegments.add(inboundLine); inboundLines.add(inboundLine); sortLinesByAngle(connectedWaySegments); sortLinesByAngle(connectedSegments); sortLinesByAngle(inboundLines); } public void addOutboundLine(MapWaySegment outboundLine) { connectedWaySegments.add(outboundLine); connectedSegments.add(outboundLine); outboundLines.add(outboundLine); sortLinesByAngle(connectedWaySegments); sortLinesByAngle(connectedSegments); sortLinesByAngle(outboundLines); } /** * returns those connected lines that end here. * Sorting is as for {@link #getConnectedWaySegments()}. */ public List<MapWaySegment> getInboundLines() { return inboundLines; } /** * returns those connected lines that start here. * Sorting is as for {@link #getConnectedWaySegments()}. */ public List<MapWaySegment> getOutboundLines() { return outboundLines; } public void addAdjacentArea(MapArea adjacentArea) { adjacentAreas.add(adjacentArea); } //TODO: with all that "needs to be called before x" etc. stuff (also in MapArea), switch to BUILDER? /** needs to be called after adding and completing all adjacent areas */ public void calculateAdjacentAreaSegments() { for (MapArea adjacentArea : adjacentAreas) { for (MapAreaSegment areaSegment : adjacentArea.getAreaSegments()) { if (areaSegment.getStartNode() == this || areaSegment.getEndNode() == this) { connectedSegments.add(areaSegment); } } } sortLinesByAngle(connectedSegments); } /** * returns all way segments connected with this node. * They will be sorted according to the clockwise * (seen from above) angle between the vector * "this node -> other node of the segment" * and the positive x direction. */ public List<MapWaySegment> getConnectedWaySegments() { return connectedWaySegments; } /** * returns all way segments and area segments connected with this node. * Sorted like {@link #getConnectedWaySegments()}. */ public List<MapSegment> getConnectedSegments() { return connectedSegments; } /** * creates the ordering described for {@link #getConnectedSegments()} */ private void sortLinesByAngle(List<? extends MapSegment> lines) { Collections.sort(lines, new Comparator<MapSegment>() { @Override public int compare(MapSegment l1, MapSegment l2) { VectorXZ d1 = l1.getDirection(); VectorXZ d2 = l2.getDirection(); if (inboundLines.contains(l1)) { d1 = d1.invert(); } if (inboundLines.contains(l2)) { d2 = d2.invert(); } //check whether the lines are in the first or second 180° //(the dot product formula will not be useful to distinguish //these cases - it only provides cos 0° to cos 180° - , //so they need to be handled first) if (d1.z < 0 && d2.z > 0) { return -1; } else if (d1.z > 0 && d2.z < 0) { return +1; } //check the actual angles using the dot product with (1,0,0). //Two simplifications apply: //- we don't need to divide by the vector lengths' product, // as getDirection returns vectors whose length is 1 //- we don't need to actually calculate the angle itself, // because if angle a > angle b, then cos a < cos b // (in [0°, 180°]) double comparison = d1.dot(X_UNIT) - d2.dot(X_UNIT); if (comparison == 0) { return 0; } if (d1.z < 0) { //and d2.z < 0 return (comparison > 0) ? -1 : +1; } else { //d1.z > 0 and d2.z > 0 return (comparison > 0) ? +1 : -1; } } }); } @Override public List<NodeWorldObject> getRepresentations() { return representations; } @Override public NodeWorldObject getPrimaryRepresentation() { if (representations.isEmpty()) { return null; } else { return representations.get(0); } } /** * adds a visual representation for this node */ public void addRepresentation(NodeWorldObject representation) { this.representations.add(representation); } @Override public String toString() { return osmNode.toString(); } @Override public Collection<MapOverlap<?,?>> getOverlaps() { return Collections.emptyList(); } @Override public AxisAlignedBoundingBoxXZ getAxisAlignedBoundingBoxXZ() { return new AxisAlignedBoundingBoxXZ(pos.x, pos.z, pos.x, pos.z); } }