Package org.osm2world.core.world.network

Source Code of org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject

package org.osm2world.core.world.network;

import static java.lang.Double.*;
import static java.util.Collections.emptyList;
import static org.openstreetmap.josm.plugins.graphview.core.util.ValueStringParser.parseIncline;
import static org.osm2world.core.map_elevation.creation.EleConstraintEnforcer.ConstraintType.*;
import static org.osm2world.core.map_elevation.data.GroundState.*;
import static org.osm2world.core.math.VectorXZ.distanceSquared;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

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.MapOverlap;
import org.osm2world.core.map_data.data.overlaps.MapOverlapType;
import org.osm2world.core.map_data.data.overlaps.MapOverlapWA;
import org.osm2world.core.map_elevation.creation.EleConstraintEnforcer;
import org.osm2world.core.map_elevation.data.EleConnector;
import org.osm2world.core.map_elevation.data.EleConnectorGroup;
import org.osm2world.core.map_elevation.data.GroundState;
import org.osm2world.core.map_elevation.data.WaySegmentElevationProfile;
import org.osm2world.core.math.AxisAlignedBoundingBoxXZ;
import org.osm2world.core.math.GeometryUtil;
import org.osm2world.core.math.InvalidGeometryException;
import org.osm2world.core.math.PolygonXYZ;
import org.osm2world.core.math.SimplePolygonXZ;
import org.osm2world.core.math.VectorXYZ;
import org.osm2world.core.math.VectorXZ;
import org.osm2world.core.math.datastructures.IntersectionTestObject;
import org.osm2world.core.world.data.AbstractAreaWorldObject;
import org.osm2world.core.world.data.WaySegmentWorldObject;
import org.osm2world.core.world.data.WorldObject;
import org.osm2world.core.world.data.WorldObjectWithOutline;
import org.osm2world.core.world.modules.BridgeModule;
import org.osm2world.core.world.modules.TunnelModule;

public abstract class AbstractNetworkWaySegmentWorldObject
  implements NetworkWaySegmentWorldObject, WaySegmentWorldObject,
             IntersectionTestObject, WorldObjectWithOutline {

  public final MapWaySegment segment;
 
  private VectorXZ startCutVector = null;
  private VectorXZ endCutVector = null;
 
  private VectorXZ startOffset = VectorXZ.NULL_VECTOR;
  private VectorXZ endOffset = VectorXZ.NULL_VECTOR;
 
  protected EleConnectorGroup connectors;
 
  private List<VectorXZ> centerlineXZ = null;
 
  private List<VectorXZ> leftOutlineXZ = null;
  private List<VectorXZ> rightOutlineXZ = null;
 
  private SimplePolygonXZ outlinePolygonXZ = null;
 
  private Boolean broken = null;
 
  protected AbstractNetworkWaySegmentWorldObject(MapWaySegment segment) {
    this.segment = segment;
  }
 
  @Override
  public final MapWaySegment getPrimaryMapElement() {
    return segment;
  }
 
  @Override
  public void setStartCutVector(VectorXZ cutVector) {
    this.startCutVector = cutVector;
  }
 
  @Override
  public void setEndCutVector(VectorXZ cutVector) {
    this.endCutVector = cutVector;
  }

  @Override
  public VectorXZ getStartCutVector() {
    return startCutVector;
  }

  @Override
  public VectorXZ getEndCutVector() {
    return endCutVector;
  }
 
  public VectorXZ getCutVectorAt(MapNode node) {
    if (node == segment.getStartNode()) {
      return getStartCutVector();
    } else if (node == segment.getEndNode()) {
      return getEndCutVector();
    } else {
      throw new IllegalArgumentException("node is not part of the line");
    }
  }
 
  @Override
  public void setStartOffset(VectorXZ offsetVector) {
    this.startOffset = offsetVector;
  }
 
  @Override
  public void setEndOffset(VectorXZ offsetVector) {
    this.endOffset = offsetVector;
  }
 
  protected VectorXZ getStartWithOffset() {
    return segment.getStartNode().getPos().add(startOffset); //SUGGEST (performance): cache? [also getEnd*]
  }
 
  protected VectorXZ getEndWithOffset() {
    return segment.getEndNode().getPos().add(endOffset);
  }
 
  /**
   * calculates centerline and outlines, along with their connectors
   * TODO: perform before construction, to simplify the object and avoid {@link #broken}
   */
  private void calculateXZGeometry() {
 
    if (startCutVector == null || endCutVector == null) {
      throw new IllegalStateException("cannot calculate outlines before cut vectors");
    }
   
    connectors = new EleConnectorGroup();
   
    { /* calculate centerline */
           
      centerlineXZ = new ArrayList<VectorXZ>();
     
      final VectorXZ start = getStartWithOffset();
      final VectorXZ end = getEndWithOffset();
     
      centerlineXZ.add(start);
     
      connectors.add(new EleConnector(start,
          segment.getStartNode(), getGroundState(segment.getStartNode())));
     
      // add intersections along the centerline
     
      for (MapOverlap<?,?> overlap : segment.getOverlaps()) {
       
        if (overlap.getOther(segment).getPrimaryRepresentation() == null)
          continue;
       
        if (overlap instanceof MapIntersectionWW) {
         
          MapIntersectionWW intersection = (MapIntersectionWW) overlap;
         
          if (GeometryUtil.isBetween(intersection.pos, start, end)) {
           
            centerlineXZ.add(intersection.pos);
           
            connectors.add(new EleConnector(intersection.pos,
                null, getGroundState()));
           
          }
         
        } else if (overlap instanceof MapOverlapWA
            && overlap.type == MapOverlapType.INTERSECT) {
         
          if (!(overlap.getOther(segment).getPrimaryRepresentation()
              instanceof AbstractAreaWorldObject)) continue;
         
          MapOverlapWA overlapWA = (MapOverlapWA) overlap;
         
          for (int i = 0; i < overlapWA.getIntersectionPositions().size(); i++) {
         
            VectorXZ pos = overlapWA.getIntersectionPositions().get(i);
           
            if (GeometryUtil.isBetween(pos, start, end)) {
             
              centerlineXZ.add(pos);
             
              connectors.add(new EleConnector(pos,
                  null, getGroundState()));
             
            }
         
          }
         
        }
       
       
      }
     
      // finish the centerline
     
      centerlineXZ.add(end);

      connectors.add(new EleConnector(end,
          segment.getEndNode(), getGroundState(segment.getEndNode())));
     
      if (centerlineXZ.size() > 3) {
       
        // sort by distance from start
        Collections.sort(centerlineXZ, new Comparator<VectorXZ>() {
          @Override
          public int compare(VectorXZ v1, VectorXZ v2) {
            return Double.compare(
                distanceSquared(v1, start),
                distanceSquared(v2, start));
          }
        });
       
      }
     
    }
   
    { /* calculate left and right outlines */
     
      leftOutlineXZ = new ArrayList<VectorXZ>(centerlineXZ.size());
      rightOutlineXZ = new ArrayList<VectorXZ>(centerlineXZ.size());
     
      assert centerlineXZ.size() >= 2;
     
      double halfWidth = getWidth() * 0.5f;
     
      VectorXZ centerStart = centerlineXZ.get(0);
      leftOutlineXZ.add(centerStart.add(startCutVector.mult(-halfWidth)));
      rightOutlineXZ.add(centerStart.add(startCutVector.mult(halfWidth)));
     
      connectors.add(new EleConnector(leftOutlineXZ.get(0),
          segment.getStartNode(), getGroundState(segment.getStartNode())));
      connectors.add(new EleConnector(rightOutlineXZ.get(0),
          segment.getStartNode(), getGroundState(segment.getStartNode())));
     
      for (int i = 1; i < centerlineXZ.size() - 1; i++) {
       
        leftOutlineXZ.add(centerlineXZ.get(i).add(segment.getRightNormal().mult(-halfWidth)));
        rightOutlineXZ.add(centerlineXZ.get(i).add(segment.getRightNormal().mult(halfWidth)));
       
        connectors.add(new EleConnector(leftOutlineXZ.get(i),
            null, getGroundState()));
        connectors.add(new EleConnector(rightOutlineXZ.get(i),
            null, getGroundState()));
       
      }
     
      VectorXZ centerEnd = centerlineXZ.get(centerlineXZ.size() - 1);
      leftOutlineXZ.add(centerEnd.add(endCutVector.mult(-halfWidth)));
      rightOutlineXZ.add(centerEnd.add(endCutVector.mult(halfWidth)));
     
      connectors.add(new EleConnector(leftOutlineXZ.get(leftOutlineXZ.size() - 1),
          segment.getEndNode(), getGroundState(segment.getEndNode())));
      connectors.add(new EleConnector(rightOutlineXZ.get(rightOutlineXZ.size() - 1),
          segment.getEndNode(), getGroundState(segment.getEndNode())));
     
    }
   
    { /* calculate the outline loop */
     
      List<VectorXZ> outlineLoopXZ =
          new ArrayList<VectorXZ>(centerlineXZ.size() * 2 + 1);
     
      outlineLoopXZ.addAll(rightOutlineXZ);
     
      List<VectorXZ> left = new ArrayList<VectorXZ>(leftOutlineXZ);
      Collections.reverse(left);
      outlineLoopXZ.addAll(left);
     
      outlineLoopXZ.add(outlineLoopXZ.get(0));
     
      // check for brokenness
     
      try {
        outlinePolygonXZ = new SimplePolygonXZ(outlineLoopXZ);
        broken = outlinePolygonXZ.isClockwise();
      } catch (InvalidGeometryException e) {
        broken = true;
        connectors = EleConnectorGroup.EMPTY;
      }
   
    }
   
  }
 
  /**
   * determines whether the node is connected to the terrain based on the
   * segments connected to it
   *
   * @param node  one of the nodes of {@link #segment}
   */
  private GroundState getGroundState(MapNode node) {
   
    WorldObject primaryWO = node.getPrimaryRepresentation();
   
    if (primaryWO != null) {
     
      return primaryWO.getGroundState();
     
    } else if (this.getGroundState() == ON) {
     
      return ON;
     
    } else {
     
      boolean allAbove = true;
      boolean allBelow = true;
     
      for (MapWaySegment segment : node.getConnectedWaySegments()) {
        if (segment.getPrimaryRepresentation() != null) {
          switch (segment.getPrimaryRepresentation().getGroundState()) {
          case ABOVE: allBelow = false; break;
          case BELOW: allAbove = false; break;
          case ON: return ON;
          }
        }
      }
     
      if (allAbove) {
        return ABOVE;
      } else if (allBelow) {
        return BELOW;
      } else {
        return ON;
      }
     
    }
   
  }
 
  /**
   * implementation of {@link WorldObject#getGroundState()}.
   * This version checks for bridge and tunnel tags to make the decision.
   * If that is not desired, subclasses may override the method.
   */
  @Override
  public GroundState getGroundState() {
    if (BridgeModule.isBridge(segment.getTags())) {
      return GroundState.ABOVE;
    } else if (TunnelModule.isTunnel(segment.getTags())) {
      return GroundState.BELOW;
    } else {
      return GroundState.ON;
    }
  }

  @Override
  public EleConnectorGroup getEleConnectors() {
   
    if (connectors == null) {
      calculateXZGeometry();
    }
   
    return connectors;
   
  }
 
  @Override
  public void defineEleConstraints(EleConstraintEnforcer enforcer) {
   
    if (isBroken()) return;
   
    //TODO: maybe save connectors separately right away
   
    List<EleConnector> center = getCenterlineEleConnectors();
    List<EleConnector> left = connectors.getConnectors(getOutlineXZ(false));
    List<EleConnector> right = connectors.getConnectors(getOutlineXZ(true));
   
    /* left and right connectors have the same ele as their center conn. */
   
    for (int i = 0; i < center.size(); i++) {
     
      enforcer.requireSameEle(center.get(i), left.get(i));
      enforcer.requireSameEle(center.get(i), right.get(i));
     
    }
   
    /* incline should be honored */
   
    String inclineValue = segment.getTags().getValue("incline");
       
    if (inclineValue != null) {
     
      double minIncline = NaN;
      double maxIncline = NaN;
     
      if ("up".equals(inclineValue)) {
       
        minIncline = 0.1;
       
      } else if ("down".equals(inclineValue)) {
       
        maxIncline = -0.1;
       
      } else {
       
        Float incline = parseIncline(inclineValue);
       
        if (incline != null) {
          if (incline > 0) {
            minIncline = incline / 100.0 * 0.5;
            maxIncline = incline / 100.0 * 1.1;
          } else if (incline < 0) {
            maxIncline = incline / 100.0 * 0.5;
            minIncline = incline / 100.0 * 1.1;
          } else {
           
            enforcer.requireSameEle(center);
           
          }
        }
       
      }
     
      if (!isNaN(minIncline)) {
        enforcer.requireIncline(MIN, minIncline, center);
      }
     
      if (!isNaN(maxIncline)) {
        enforcer.requireIncline(MAX, maxIncline, center);
      }
     
    }
   
    //TODO sensible maximum incline for road and rail; and waterway down-incline
    // ... take incline differences, steps etc. into account => move into Road, Rail separately
   
    /* ensure a smooth transition from previous segment */
   
    //TODO this might be more elegant with an "Invisible Connector WO"
   
    List<MapWaySegment> connectedSegments =
        segment.getStartNode().getConnectedWaySegments();
   
    if (connectedSegments.size() == 2) {
     
      MapWaySegment previousSegment = null;
     
      for (MapWaySegment connectedSegment : connectedSegments) {
        if (connectedSegment != this.segment) {
          previousSegment = connectedSegment;
        }
      }
     
      WorldObject previousWO = previousSegment.getPrimaryRepresentation();
     
      if (previousWO instanceof AbstractNetworkWaySegmentWorldObject) {
       
        AbstractNetworkWaySegmentWorldObject previous =
            (AbstractNetworkWaySegmentWorldObject)previousWO;
       
        if (!previous.isBroken()) {
         
          List<EleConnector> previousCenter =
              previous.getCenterlineEleConnectors();
         
          enforcer.requireSmoothness(
              previousCenter.get(previousCenter.size() - 2),
              center.get(0),
              center.get(1));
         
        }
       
      }

    }
   
    /* ensure smooth transitions within the way itself */
   
    for (int i = 0; i + 2 < center.size(); i++) {
      enforcer.requireSmoothness(
          center.get(i),
          center.get(i+1),
          center.get(i+2));
    }
   
  }
 
  protected List<EleConnector> getCenterlineEleConnectors() {

    if (isBroken()) return emptyList();
   
    return connectors.getConnectors(getCenterlineXZ());
   
  }
 
  /**
   * returns a sequence of node running along the center of the
   * line from start to end (each with offset).
   * Uses the {@link WaySegmentElevationProfile} for adding
   * elevation information.
   */
  public List<VectorXZ> getCenterlineXZ() {
   
    if (centerlineXZ == null) {
      calculateXZGeometry();
    }
   
    return centerlineXZ;
   
  }
 
  /**
   * 3d version of {@link #getCenterlineXZ()}.
   * Only available after elevation calculation.
   */
  public List<VectorXYZ> getCenterline() {
    return connectors.getPosXYZ(getCenterlineXZ());
  }
 
  /**
   * Variant of {@link #getOutline(boolean)}.
   * This one is already available before elevation calculation.
   */
  public List<VectorXZ> getOutlineXZ(boolean right) {
   
    if (right) {
     
      if (rightOutlineXZ == null) {
        calculateXZGeometry();
      }
     
      return rightOutlineXZ;
     
    } else { //left
     
      if (leftOutlineXZ == null) {
        calculateXZGeometry();
      }
     
      return leftOutlineXZ;
     
    }
   
  }
 
  /**
   * provides the left or right border (a line at an appropriate distance
   * from the center line), taking into account cut vectors, offsets and
   * elevation information.
   * Available after cut vectors, offsets and elevation information
   * have been calculated.
   *
   * Left and right border have the same number of nodes as the elevation
   * profile's {@link WaySegmentElevationProfile#getPointsWithEle()}.
   * //TODO: compatible with future offset/clearing influences?
   */
  public List<VectorXYZ> getOutline(boolean right) {
    return connectors.getPosXYZ(getOutlineXZ(right));
  }
 
  @Override
  public SimplePolygonXZ getOutlinePolygonXZ() {
   
    if (outlinePolygonXZ == null) {
      calculateXZGeometry();
    }
   
    if (isBroken()) {
      return null;
    } else {
      return outlinePolygonXZ;
    }
   
  }
 
  @Override
  public PolygonXYZ getOutlinePolygon() {
   
    if (isBroken()) {
      return null;
    } else {
      return connectors.getPosXYZ(outlinePolygonXZ);
    }
   
  }
 
  /**
   * checks whether this segment has a broken outline.
   * That can happen e.g. if it lies between two junctions that are too close
   * together.
   */
  public boolean isBroken() {
   
    if (broken == null) {
      calculateXZGeometry();
    }
   
    //TODO filter out broken objects during creation in the world module
    return broken;
   
  }
 
  /**
   * returns a point on the start or end cut line.
   *
   * @param start  point is on the start cut if true, on the end cut if false
   * @param relativePosFromLeft  0 is the leftmost point, 1 the rightmost.
   *                             Values in between are for interpolation.
   */
  public VectorXYZ getPointOnCut(boolean start, double relativePosFromLeft) {
   
    assert 0 <= relativePosFromLeft && relativePosFromLeft <= 1;
   
    VectorXZ position = start ? getStartWithOffset() : getEndWithOffset();
    VectorXZ cutVector = start ? getStartCutVector() : getEndCutVector();
   
    return connectors.getPosXYZ(position.add(cutVector.mult(
        (-0.5 + relativePosFromLeft) * getWidth())));
   
  }

  @Override
  public VectorXZ getStartOffset() {
    return startOffset;
  }

  @Override
  public VectorXZ getEndOffset() {
    return endOffset;
  }
 
  @Override
  public VectorXZ getStartPosition() {
    return segment.getStartNode().getPos().add(getStartOffset());
  }
 
  @Override
  public VectorXZ getEndPosition() {
    return segment.getEndNode().getPos().add(getEndOffset());
  }
 
  @Override
  public AxisAlignedBoundingBoxXZ getAxisAlignedBoundingBoxXZ() {
   
    if (isBroken() || getOutlinePolygonXZ() == null) {
      return null;
    } else {
      return new AxisAlignedBoundingBoxXZ(
          getOutlinePolygonXZ().getVertexCollection());
    }
   
  }
 
  @Override
  public String toString() {
    return this.getClass().getSimpleName() + "(" + segment + ")";
  }
 
}
TOP

Related Classes of org.osm2world.core.world.network.AbstractNetworkWaySegmentWorldObject

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.