package org.osm2world.core.world.network; import java.util.ArrayList; import java.util.List; import org.osm2world.core.map_data.data.MapNode; import org.osm2world.core.map_data.data.MapSegment; import org.osm2world.core.math.InvalidGeometryException; import org.osm2world.core.math.PolygonXZ; import org.osm2world.core.math.SimplePolygonXZ; import org.osm2world.core.math.VectorXZ; import org.osm2world.core.world.creation.NetworkCalculator; import org.osm2world.core.world.data.OutlineNodeWorldObject; public abstract class JunctionNodeWorldObject extends OutlineNodeWorldObject { protected boolean informationProvided = false; protected List<VectorXZ> cutVectors; protected List<VectorXZ> cutCenters; protected List<Float> widths; /** * sets the results of {@link NetworkCalculator}'s calculations. * * Cut information will not be created for all way/area segments. * The lists can therefore contain null entries. * * @param cutCenters * centers of the cuts to each; * indices are the same as for the GridNode's {@link MapNode#getConnectedSegments()} * @param cutVectors * vectors describing indicating the cut line, * pointing to the right from the node's pov; * for indices see junctionCutCenters * @param widths * widths of the junction cut; * for indices see junctionCutCenters */ public void setInformation(List<VectorXZ> cutCenters, List<VectorXZ> cutVectors, List<Float> widths) { this.informationProvided = true; this.cutCenters = cutCenters; this.cutVectors = cutVectors; this.widths = widths; } public JunctionNodeWorldObject(MapNode node) { super(node); } //TODO formerly @Override, currently useless // public double getClearingAbove(VectorXZ pos) { // // current solution: maximum of connected segments' clearings. // // Could probably find a more intelligent method. // // double max = 0; // for (MapWaySegment waySegment : node.getConnectedWaySegments()) { // WaySegmentWorldObject rep = waySegment.getPrimaryRepresentation(); // if (rep != null) { // double clearing = rep.getClearingAbove(node.getPos()); // if (clearing > max) { // max = clearing; // } // } // } // return max; // } //TODO formerly @Override, currently useless // public double getClearingBelow(VectorXZ pos) { // // current solution: maximum of connected segments' clearings. // // Could probably find a more intelligent method. // // double max = 0; // for (MapWaySegment waySegment : node.getConnectedWaySegments()) { // WaySegmentWorldObject rep = waySegment.getPrimaryRepresentation(); // if (rep != null) { // double clearing = rep.getClearingBelow(node.getPos()); // if (clearing > max) { // max = clearing; // } // } // } // return max; // } @Override public SimplePolygonXZ getOutlinePolygonXZ() { List<VectorXZ> vectors = new ArrayList<VectorXZ>(cutCenters.size()*2+1); for (int i=0; i < cutCenters.size(); i++) { if (cutCenters.get(i) == null) continue; VectorXZ left = getCutNode(i, false); VectorXZ right = getCutNode(i, true); if (left != null) { vectors.add(left); } if (right != null) { vectors.add(right); } } /* try to convert into a valid, counterclockwise simple polygon */ if (vectors.size() > 2) { vectors.add(vectors.get(0)); //close polygon PolygonXZ poly = new PolygonXZ(vectors); try { SimplePolygonXZ simplePoly = poly.asSimplePolygon(); if (simplePoly.isClockwise()) { return simplePoly.reverse(); } else { return simplePoly; } } catch (InvalidGeometryException e) { //deal with non-simple polygons //TODO: this should be prevented from ever happening return null; } } else { return null; } } /** * calculates the left or right node of a cut * (Only available if junction information for this representation has been * provided using {@link #setInformation(List, List, List)}). * * @return cut node position; null if connected section #i has no outline */ protected VectorXZ getCutNode(int i, boolean right) { checkInformationProvided(); VectorXZ cutCenter = cutCenters.get(i); VectorXZ cutVector = cutVectors.get(i); Float width = widths.get(i); if (cutCenter == null) { return null; } else { if (right) { return cutCenter.add(cutVector.mult(width * 0.5f)); } else { return cutCenter.subtract(cutVector.mult(width * 0.5f)); } } } /** * provides outline for the areas covered by the junction. * * The from and to indices refer to the list * returned by the underlying {@link MapNode}'s * {@link MapNode#getConnectedSegments()} method. */ public List<VectorXZ> getOutline(int from, int to) { checkInformationProvided(); List<VectorXZ> outline = new ArrayList<VectorXZ>(); List<MapSegment> segments = node.getConnectedSegments(); assert from >= 0 && from < segments.size(); assert to >= 0 && to < segments.size(); int i = from; while (i != to) { VectorXZ newNodeA = getCutNode(i, false); if (newNodeA != null) { outline.add(newNodeA); } int nextI = i - 1; if (nextI < 0) { nextI = segments.size() - 1; } VectorXZ newNodeB = getCutNode(nextI, true); if (newNodeB != null) { outline.add(newNodeB); } i = nextI; } return outline; } /** * throws an IllegalStateException if information hasn't been * provided by a {@link NetworkCalculator} */ private void checkInformationProvided() throws IllegalStateException { if (!informationProvided) { throw new IllegalStateException("no junction information" + " has been set for this representation"); } } }