All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.routing.edgetype.StreetEdge Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package org.opentripplanner.routing.edgetype;

import com.google.common.collect.Iterables;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.common.TurnRestriction;
import org.opentripplanner.common.TurnRestrictionType;
import org.opentripplanner.common.geometry.CompactLineString;
import org.opentripplanner.common.geometry.DirectionUtils;
import org.opentripplanner.common.geometry.GeometryUtils;
import org.opentripplanner.common.geometry.PackedCoordinateSequence;
import org.opentripplanner.common.geometry.SphericalDistanceLibrary;
import org.opentripplanner.common.model.P2;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.core.CarPickupState;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.core.StateEditor;
import org.opentripplanner.routing.core.TraverseMode;
import org.opentripplanner.routing.core.TraverseModeSet;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.vertextype.BarrierVertex;
import org.opentripplanner.routing.vertextype.IntersectionVertex;
import org.opentripplanner.routing.vertextype.OsmVertex;
import org.opentripplanner.routing.vertextype.SplitterVertex;
import org.opentripplanner.routing.vertextype.StreetVertex;
import org.opentripplanner.routing.vertextype.TemporarySplitterVertex;
import org.opentripplanner.util.BitSetUtils;
import org.opentripplanner.util.I18NString;
import org.opentripplanner.util.NonLocalizedString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
 * This represents a street segment.
 * 
 * @author novalis
 * 
 */
public class StreetEdge extends Edge implements Cloneable {

    private static Logger LOG = LoggerFactory.getLogger(StreetEdge.class);

    private static final long serialVersionUID = 1L;

    /* TODO combine these with OSM highway= flags? */
    public static final int CLASS_STREET = 3;
    public static final int CLASS_CROSSING = 4;
    public static final int CLASS_OTHERPATH = 5;
    public static final int CLASS_OTHER_PLATFORM = 8;
    public static final int CLASS_TRAIN_PLATFORM = 16;
    public static final int ANY_PLATFORM_MASK = 24;
    public static final int CROSSING_CLASS_MASK = 7; // ignore platform
    public static final int CLASS_LINK = 32; // on/offramps; OSM calls them "links"

    private static final double GREENWAY_SAFETY_FACTOR = 0.1;

    // TODO(flamholz): do something smarter with the car speed here.
    public static final float DEFAULT_CAR_SPEED = 11.2f;

    /** If you have more than 8 flags, increase flags to short or int */
    private static final int BACK_FLAG_INDEX = 0;
    private static final int ROUNDABOUT_FLAG_INDEX = 1;
    private static final int HASBOGUSNAME_FLAG_INDEX = 2;
    private static final int NOTHRUTRAFFIC_FLAG_INDEX = 3;
    private static final int STAIRS_FLAG_INDEX = 4;
    private static final int SLOPEOVERRIDE_FLAG_INDEX = 5;
    private static final int WHEELCHAIR_ACCESSIBLE_FLAG_INDEX = 6;

    /** back, roundabout, stairs, ... */
    private byte flags;

    /**
     * Length is stored internally as 32-bit fixed-point (millimeters). This allows edges of up to ~2100km.
     * Distances used in calculations and exposed outside this class are still in double-precision floating point meters.
     * Someday we might want to convert everything to fixed point representations.
     */
    private int length_mm;

    /**
     * bicycleSafetyWeight = length * bicycleSafetyFactor. For example, a 100m street with a safety
     * factor of 2.0 will be considered in term of safety cost as the same as a 150m street with a
     * safety factor of 1.0.
     */
    protected float bicycleSafetyFactor;

    private byte[] compactGeometry;
    
    private I18NString name;

    private StreetTraversalPermission permission;

    /** The OSM way ID from whence this came - needed to reference traffic data */
    public long wayId;

    private int streetClass = CLASS_OTHERPATH;
    
    /**
     * The speed (meters / sec) at which an automobile can traverse
     * this street segment.
     */
    private float carSpeed;

    /**
     * The angle at the start of the edge geometry.
     * Internal representation is -180 to +179 integer degrees mapped to -128 to +127 (brads)
     */
    private byte inAngle;

    /** The angle at the start of the edge geometry. Internal representation like that of inAngle. */
    private byte outAngle;

    public StreetEdge(StreetVertex v1, StreetVertex v2, LineString geometry,
                      I18NString name, double length,
                      StreetTraversalPermission permission, boolean back) {
        super(v1, v2);
        this.setBack(back);
        this.setGeometry(geometry);
        this.length_mm = (int) (length * 1000); // CONVERT FROM FLOAT METERS TO FIXED MILLIMETERS
        this.bicycleSafetyFactor = 1.0f;
        this.name = name;
        this.setPermission(permission);
        this.setCarSpeed(DEFAULT_CAR_SPEED);
        this.setWheelchairAccessible(true); // accessible by default
        if (geometry != null) {
            try {
                for (Coordinate c : geometry.getCoordinates()) {
                    if (Double.isNaN(c.x)) {
                        System.out.println("X DOOM");
                    }
                    if (Double.isNaN(c.y)) {
                        System.out.println("Y DOOM");
                    }
                }
                // Conversion from radians to internal representation as a single signed byte.
                // We also reorient the angles since OTP seems to use South as a reference
                // while the azimuth functions use North.
                // FIXME Use only North as a reference, not a mix of North and South!
                // Range restriction happens automatically due to Java signed overflow behavior.
                // 180 degrees exists as a negative rather than a positive due to the integer range.
                double angleRadians = DirectionUtils.getLastAngle(geometry);
                outAngle = (byte) Math.round(angleRadians * 128 / Math.PI + 128);
                angleRadians = DirectionUtils.getFirstAngle(geometry);
                inAngle = (byte) Math.round(angleRadians * 128 / Math.PI + 128);
            } catch (IllegalArgumentException iae) {
                LOG.error("exception while determining street edge angles. setting to zero. there is probably something wrong with this street segment's geometry.");
                inAngle = 0;
                outAngle = 0;
            }
        }
    }


    //For testing only
    public StreetEdge(StreetVertex v1, StreetVertex v2, LineString geometry,
                      String name, double length,
                      StreetTraversalPermission permission, boolean back) {
        this(v1, v2, geometry, new NonLocalizedString(name), length, permission, back);
    }


    /**
     * Checks permissions of the street edge if specified modes are allowed to travel.
     *
     * Barriers aren't taken into account. So it can happen that canTraverse returns True.
     * But doTraverse returns false. Since there are barriers on a street.
     *
     * This is because this function is used also on street when searching for start/stop.
     * Those streets are then split. On splitted streets can be possible to drive with a CAR because
     * it is only blocked from one way.
     * @param modes
     * @return
     */
    public boolean canTraverse(TraverseModeSet modes) {
        return getPermission().allows(modes);
    }

    /**
     * Checks if edge is accessible for wheelchair if needed according to tags or if slope is too big.
     *
     * Then it checks if street can be traversed according to street permissions and start/end barriers.
     * This is done with intersection of street and barrier permissions in {@link #canTraverseIncludingBarrier(TraverseMode)}
     *
     * @param options
     * @param mode
     * @return
     */
    private boolean canTraverse(RoutingRequest options, TraverseMode mode) {
        if (options.wheelchairAccessible) {
            if (!isWheelchairAccessible()) {
                return false;
            }
            if (getMaxSlope() > options.maxWheelchairSlope) {
                return false;
            }
        }
        return canTraverseIncludingBarrier(mode);
    }

    /**
     * This checks if start or end vertex is bollard
     * If it is it creates intersection of street edge permissions
     * and from/to barriers.
     * Then it checks if mode is allowed to traverse the edge.
     *
     * By default CAR isn't allowed to traverse barrier but foot and bicycle are.
     * This can be changed with different tags
     *
     * If start/end isn't bollard it just checks the street permissions.
     *
     * It is used in {@link #canTraverse(RoutingRequest, TraverseMode)}
     * @param mode
     * @return
     */
    public boolean canTraverseIncludingBarrier(TraverseMode mode) {
        StreetTraversalPermission permission = getPermission();
        if (fromv instanceof BarrierVertex) {
            permission = permission.intersection(((BarrierVertex) fromv).getBarrierPermissions());
        }
        if (tov instanceof BarrierVertex) {
            permission = permission.intersection(((BarrierVertex) tov).getBarrierPermissions());
        }

        return permission.allows(mode);
    }

    public PackedCoordinateSequence getElevationProfile() {
        return null;
    }

    public boolean isElevationFlattened() {
        return false;
    }

    public float getMaxSlope() {
        return 0.0f;
    }

    @Override
    public double getDistanceMeters() {
        return length_mm / 1000.0;
    }

    @Override
    public State traverse(State s0) {
        final RoutingRequest options = s0.getOptions();
        final TraverseMode currMode = s0.getNonTransitMode();
        StateEditor editor = doTraverse(s0, options, s0.getNonTransitMode());
        State state = (editor == null) ? null : editor.makeState();
        /* Kiss and ride support. Mode transitions occur without the explicit loop edges used in park-and-ride. */
        // TODO Replace with a more general state machine implementation
        if (options.carPickup) {
            if (options.arriveBy) {
                // Check if we can enter the taxi and continue by car
                // Final WALK check needed to prevent infinite recursion.
                if (s0.getCarPickupState().equals(CarPickupState.WALK_FROM_DROP_OFF)
                        && currMode == TraverseMode.WALK) {
                    editor = doTraverse(s0, options, TraverseMode.CAR);
                    if (editor != null) {
                        editor.setTaxiState(CarPickupState.IN_CAR);
                        State forkState = editor.makeState();
                        if (forkState != null) {
                            forkState.addToExistingResultChain(state);
                            return forkState; // return both taxi and walk states
                        }
                    }
                }

                // Check if we can exit the taxi and continue by walking
                // Final CAR check needed to prevent infinite recursion.
                if ( s0.getCarPickupState().equals(CarPickupState.IN_CAR) &&
                        !getPermission().allows(TraverseMode.CAR)
                        && currMode == TraverseMode.CAR) {
                    // Check if it is possible to continue by walking
                    editor = doTraverse(s0, options, TraverseMode.WALK);
                    if (editor != null) {
                        editor.setTaxiState(CarPickupState.WALK_TO_PICKUP);
                        return editor.makeState(); // return only the walk state
                    }
                }
            } else { /* departAfter */
                // Check if we can enter the taxi and continue by car
                // Final WALK check needed to prevent infinite recursion.
                if (s0.getCarPickupState().equals(CarPickupState.WALK_TO_PICKUP)
                    && currMode == TraverseMode.WALK) {
                    editor = doTraverse(s0, options, TraverseMode.CAR);
                    if (editor != null) {
                        editor.setTaxiState(CarPickupState.IN_CAR);
                        State forkState = editor.makeState();
                        if (forkState != null) {
                            forkState.addToExistingResultChain(state);
                            return forkState; // return both the car and the walk state
                        }
                    }
                }

                // Check if we can exit the taxi and continue by walking
                // Final CAR check needed to prevent infinite recursion.
                if ( s0.getCarPickupState().equals(CarPickupState.IN_CAR) &&
                    !getPermission().allows(TraverseMode.CAR)
                    && currMode == TraverseMode.CAR) {
                    // Check if it is possible to continue by walking
                    editor = doTraverse(s0, options, TraverseMode.WALK);
                    if (editor != null) {
                        editor.setTaxiState(CarPickupState.WALK_FROM_DROP_OFF);
                        return editor.makeState(); // return only the walk state
                    }
                }
            }
        }
        return state;
    }

    /** return a StateEditor rather than a State so that we can make parking/mode switch modifications for kiss-and-ride. */
    private StateEditor doTraverse(State s0, RoutingRequest options, TraverseMode traverseMode) {
        if (traverseMode == null) return null;
        boolean walkingBike = options.walkingBike;
        boolean backWalkingBike = s0.isBackWalkingBike();
        TraverseMode backMode = s0.getBackMode();
        Edge backEdge = s0.getBackEdge();
        if (backEdge != null) {
            // No illegal U-turns.
            // NOTE(flamholz): we check both directions because both edges get a chance to decide
            // if they are the reverse of the other. Also, because it doesn't matter which direction
            // we are searching in - these traversals are always disallowed (they are U-turns in one direction
            // or the other).
            // TODO profiling indicates that this is a hot spot.
            if (this.isReverseOf(backEdge) || backEdge.isReverseOf(this)) {
                return null;
            }
        }

        // Ensure we are actually walking, when walking a bike
        backWalkingBike &= TraverseMode.WALK.equals(backMode);
        walkingBike &= TraverseMode.WALK.equals(traverseMode);

        /* Check whether this street allows the current mode. If not and we are biking, attempt to walk the bike. */
        if (!canTraverse(options, traverseMode)) {
            if (traverseMode == TraverseMode.BICYCLE) {
                return doTraverse(s0, options.bikeWalkingOptions, TraverseMode.WALK);
            }
            return null;
        }

        // Automobiles have variable speeds depending on the edge type
        double speed = calculateSpeed(options, traverseMode, s0.getTimeInMillis());
        
        double time = getEffectiveWalkDistance() / speed;
        double weight;
        // TODO(flamholz): factor out this bike, wheelchair and walking specific logic to somewhere central.
        if (options.wheelchairAccessible) {
            weight = getEffectiveBikeDistance() / speed;
        } else if (traverseMode.equals(TraverseMode.BICYCLE)) {
            time = getEffectiveBikeDistance() / speed;
            switch (options.optimize) {
            case SAFE:
                weight = bicycleSafetyFactor * getDistanceMeters() / speed;
                break;
            case GREENWAYS:
                weight = bicycleSafetyFactor * getDistanceMeters() / speed;
                if (bicycleSafetyFactor <= GREENWAY_SAFETY_FACTOR) {
                    // greenways are treated as even safer than they really are
                    weight *= 0.66;
                }
                break;
            case FLAT:
                /* see notes in StreetVertex on speed overhead */
                weight = getDistanceMeters() / speed + getEffectiveBikeWorkCost();
                break;
            case QUICK:
                weight = getEffectiveBikeDistance() / speed;
                break;
            case TRIANGLE:
                double quick = getEffectiveBikeDistance();
                double safety = bicycleSafetyFactor * getDistanceMeters();
                // TODO This computation is not coherent with the one for FLAT
                double slope = getEffectiveBikeWorkCost();
                weight = quick * options.bikeTriangleTimeFactor + slope
                        * options.bikeTriangleSlopeFactor + safety
                        * options.bikeTriangleSafetyFactor;
                weight /= speed;
                break;
            default:
                weight = getDistanceMeters() / speed;
            }
        } else {
            if (walkingBike) {
                // take slopes into account when walking bikes
                time = getEffectiveBikeDistance() / speed;
            }
            weight = time;
            if (traverseMode.equals(TraverseMode.WALK)) {
                // take slopes into account when walking
                // FIXME: this causes steep stairs to be avoided. see #1297.
                double distance = getEffectiveWalkDistance();
                weight = distance / speed;
                time = weight; //treat cost as time, as in the current model it actually is the same (this can be checked for maxSlope == 0)
                /*
                // debug code
                if(weight > 100){
                    double timeflat = length_mm / speed;


                    System.out.format("line length: %.1f m, slope: %.3f ---> distance: %.1f , weight: %.1f , time (flat):  %.1f %n", getDistance(), getMaxSlope(), distance, weight, timeflat);
                }
                */
            }
        }

        if (isStairs()) {
            weight *= options.stairsReluctance;
        } else {
            // TODO: this is being applied even when biking or driving.
            weight *= options.walkReluctance;
        }

        StateEditor s1 = s0.edit(this);
        s1.setBackMode(traverseMode);
        s1.setBackWalkingBike(walkingBike);

        /* Handle no through traffic areas. */
        if (this.isNoThruTraffic()) {
            // Record transition into no-through-traffic area.
            if (backEdge instanceof StreetEdge && !((StreetEdge)backEdge).isNoThruTraffic()) {
                s1.setEnteredNoThroughTrafficArea();
            }
            // If we transitioned into a no-through-traffic area at some point, check if we are exiting it.
            if (s1.hasEnteredNoThroughTrafficArea()) {
                // Only Edges are marked as no-thru, but really we need to avoid creating dominant, pruned states
                // on thru _Vertices_. This could certainly be improved somehow.
                for (StreetEdge se : Iterables.filter(s1.getVertex().getOutgoing(), StreetEdge.class)) {
                    if (!se.isNoThruTraffic()) {
                        // This vertex has at least one through-traffic edge. We can't dominate it with a no-thru state.
                        return null;
                    }
                }
            }
        }

        int roundedTime = (int) Math.ceil(time);

        /* Compute turn cost. */
        StreetEdge backPSE;
        if (backEdge instanceof StreetEdge) {
            backPSE = (StreetEdge) backEdge;
            RoutingRequest backOptions = backWalkingBike ?
                    s0.getOptions().bikeWalkingOptions : s0.getOptions();
            double backSpeed = backPSE.calculateSpeed(backOptions, backMode, s0.getTimeInMillis());
            final double realTurnCost;  // Units are seconds.

            // Apply turn restrictions
            if (options.arriveBy && !canTurnOnto(backPSE, s0, backMode)) {
                return null;
            } else if (!options.arriveBy && !backPSE.canTurnOnto(this, s0, traverseMode)) {
                return null;
            }

            /*
             * This is a subtle piece of code. Turn costs are evaluated differently during
             * forward and reverse traversal. During forward traversal of an edge, the turn
             * *into* that edge is used, while during reverse traversal, the turn *out of*
             * the edge is used.
             *
             * However, over a set of edges, the turn costs must add up the same (for
             * general correctness and specifically for reverse optimization). This means
             * that during reverse traversal, we must also use the speed for the mode of
             * the backEdge, rather than of the current edge.
             */
            if (options.arriveBy && tov instanceof IntersectionVertex) { // arrive-by search
                IntersectionVertex traversedVertex = ((IntersectionVertex) tov);

                realTurnCost = backOptions.getIntersectionTraversalCostModel().computeTraversalCost(
                        traversedVertex, this, backPSE, backMode, backOptions, (float) speed,
                        (float) backSpeed);
            } else if (!options.arriveBy && fromv instanceof IntersectionVertex) { // depart-after search
                IntersectionVertex traversedVertex = ((IntersectionVertex) fromv);

                realTurnCost = options.getIntersectionTraversalCostModel().computeTraversalCost(
                        traversedVertex, backPSE, this, traverseMode, options, (float) backSpeed,
                        (float) speed);                
            } else {
                // In case this is a temporary edge not connected to an IntersectionVertex
                LOG.debug("Not computing turn cost for edge {}", this);
                realTurnCost = 0; 
            }

            if (!traverseMode.isDriving()) {
                s1.incrementWalkDistance(realTurnCost / 100);  // just a tie-breaker
            }

            int turnTime = (int) Math.ceil(realTurnCost);
            roundedTime += turnTime;
            weight += options.turnReluctance * realTurnCost;
        }

        if (walkingBike || TraverseMode.BICYCLE.equals(traverseMode)) {
            if (!(backWalkingBike || TraverseMode.BICYCLE.equals(backMode))) {
                s1.incrementTimeInSeconds(options.bikeSwitchTime);
                s1.incrementWeight(options.bikeSwitchCost);
            }
        }

        if (!traverseMode.isDriving()) {
            s1.incrementWalkDistance(getEffectiveBikeDistance());
        }

        s1.incrementTimeInSeconds(roundedTime);
        
        s1.incrementWeight(weight);

        return s1;
    }

    private double calculateOverageWeight(double firstValue, double secondValue, double maxValue,
            double softPenalty, double overageRate) {
        // apply penalty if we stepped over the limit on this traversal
        boolean applyPenalty = false;
        double overageValue;

        if(firstValue <= maxValue && secondValue > maxValue){
            applyPenalty = true;
            overageValue = secondValue - maxValue;
        } else {
            overageValue = secondValue - firstValue;
        }

        // apply overage and add penalty if necessary
        return (overageRate * overageValue) + (applyPenalty ? softPenalty : 0.0);
    }

    /**
     * Calculate the average automobile traversal speed of this segment, given
     * the RoutingRequest, and return it in meters per second.
     */
    private double calculateCarSpeed(RoutingRequest options) {
        return getCarSpeed();
    }
    
    /**
     * Calculate the speed appropriately given the RoutingRequest and traverseMode and the current wall clock time.
     * Note: this is not strictly symmetrical, because in a forward search we get the speed based on the
     * time we enter this edge, whereas in a reverse search we get the speed based on the time we exit
     * the edge.
     */
    public double calculateSpeed(RoutingRequest options, TraverseMode traverseMode, long timeMillis) {
        if (traverseMode == null) {
            return Double.NaN;
        } else if (traverseMode.isDriving()) {
            // NOTE: Automobiles have variable speeds depending on the edge type
            return calculateCarSpeed(options);
        }
        return options.getSpeed(traverseMode);
    }

    @Override
    public double weightLowerBound(RoutingRequest options) {
        return timeLowerBound(options) * options.walkReluctance;
    }

    @Override
    public double timeLowerBound(RoutingRequest options) {
        return this.getDistanceMeters() / options.getStreetSpeedUpperBound();
    }

    /**
     * This gets the effective length for bikes and wheelchairs, taking slopes into account. This
     * can be divided by the speed on a flat surface to get the duration.
     */
    public double getEffectiveBikeDistance() {
        return getDistanceMeters();
    }

    /**
     * This gets the effective work amount for bikes, taking the effort required to traverse the
     * slopes into account.
     */
    public double getEffectiveBikeWorkCost() {
        return getDistanceMeters();
    }

    @Override
    public double getEffectiveWalkDistance() {
        return getDistanceMeters();
    }

    public void setBicycleSafetyFactor(float bicycleSafetyFactor) {
        this.bicycleSafetyFactor = bicycleSafetyFactor;
    }

    public float getBicycleSafetyFactor() {
        return bicycleSafetyFactor;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
    }

    public String toString() {
        return "StreetEdge(" + name + ", " + fromv + " -> " + tov
                + " length=" + this.getDistanceMeters() + " carSpeed=" + this.getCarSpeed()
                + " permission=" + this.getPermission() + ")";
    }

    @Override
    public StreetEdge clone() {
        try {
            return (StreetEdge) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
    
    public boolean canTurnOnto(Edge e, State state, TraverseMode mode) {
        for (TurnRestriction turnRestriction : getTurnRestrictions(state.getOptions().rctx.graph)) {
            /* FIXME: This is wrong for trips that end in the middle of turnRestriction.to
             */

            // NOTE(flamholz): edge to be traversed decides equivalence. This is important since 
            // it might be a temporary edge that is equivalent to some graph edge.
            if (turnRestriction.type == TurnRestrictionType.ONLY_TURN) {
                if (!e.isEquivalentTo(turnRestriction.to) && turnRestriction.modes.contains(mode) &&
                        turnRestriction.active(state.getTimeSeconds())) {
                    return false;
                }
            } else {
                if (e.isEquivalentTo(turnRestriction.to) && turnRestriction.modes.contains(mode) &&
                        turnRestriction.active(state.getTimeSeconds())) {
                    return false;
                }
            }
        }
        return true;
    }

	@Override
	public String getName() {
		return this.name.toString();
	}

	/**
	* Gets non-localized I18NString (Used when splitting edges)
	* @return non-localized Name
	*/
	public I18NString getRawName() {
		return this.name;
	}

	public String getName(Locale locale) {
		return this.name.toString(locale);
	}

	public void setName(I18NString name) {
		this.name = name;
	}

	public LineString getGeometry() {
		return CompactLineString.uncompactLineString(fromv.getLon(), fromv.getLat(), tov.getLon(), tov.getLat(), compactGeometry, isBack());
	}

	private void setGeometry(LineString geometry) {
		this.compactGeometry = CompactLineString.compactLineString(fromv.getLon(), fromv.getLat(), tov.getLon(), tov.getLat(), isBack() ? (LineString)geometry.reverse() : geometry, isBack());
	}

	public void shareData(StreetEdge reversedEdge) {
	    if (Arrays.equals(compactGeometry, reversedEdge.compactGeometry)) {
	        compactGeometry = reversedEdge.compactGeometry;
	    } else {
	        LOG.warn("Can't share geometry between {} and {}", this, reversedEdge);
	    }
	}

	public boolean isWheelchairAccessible() {
		return BitSetUtils.get(flags, WHEELCHAIR_ACCESSIBLE_FLAG_INDEX);
	}

	public void setWheelchairAccessible(boolean wheelchairAccessible) {
        flags = BitSetUtils.set(flags, WHEELCHAIR_ACCESSIBLE_FLAG_INDEX, wheelchairAccessible);
	}

	public StreetTraversalPermission getPermission() {
		return permission;
	}

	public void setPermission(StreetTraversalPermission permission) {
		this.permission = permission;
	}

	public int getStreetClass() {
		return streetClass;
	}

	public void setStreetClass(int streetClass) {
		this.streetClass = streetClass;
	}

	/**
	 * Marks that this edge is the reverse of the one defined in the source
	 * data. Does NOT mean fromv/tov are reversed.
	 */
	public boolean isBack() {
	    return BitSetUtils.get(flags, BACK_FLAG_INDEX);
	}

	public void setBack(boolean back) {
            flags = BitSetUtils.set(flags, BACK_FLAG_INDEX, back);
	}

	public boolean isRoundabout() {
            return BitSetUtils.get(flags, ROUNDABOUT_FLAG_INDEX);
	}

	public void setRoundabout(boolean roundabout) {
	    flags = BitSetUtils.set(flags, ROUNDABOUT_FLAG_INDEX, roundabout);
	}

	public boolean hasBogusName() {
	    return BitSetUtils.get(flags, HASBOGUSNAME_FLAG_INDEX);
	}

	public void setHasBogusName(boolean hasBogusName) {
	    flags = BitSetUtils.set(flags, HASBOGUSNAME_FLAG_INDEX, hasBogusName);
	}

	public boolean isNoThruTraffic() {
            return BitSetUtils.get(flags, NOTHRUTRAFFIC_FLAG_INDEX);
	}

	public void setNoThruTraffic(boolean noThruTraffic) {
	    flags = BitSetUtils.set(flags, NOTHRUTRAFFIC_FLAG_INDEX, noThruTraffic);
	}

	/**
	 * This street is a staircase
	 */
	public boolean isStairs() {
            return BitSetUtils.get(flags, STAIRS_FLAG_INDEX);
	}

	public void setStairs(boolean stairs) {
	    flags = BitSetUtils.set(flags, STAIRS_FLAG_INDEX, stairs);
	}

	public float getCarSpeed() {
		return carSpeed;
	}

	public void setCarSpeed(float carSpeed) {
		this.carSpeed = carSpeed;
	}

	public boolean isSlopeOverride() {
	    return BitSetUtils.get(flags, SLOPEOVERRIDE_FLAG_INDEX);
	}

	public void setSlopeOverride(boolean slopeOverride) {
	    flags = BitSetUtils.set(flags, SLOPEOVERRIDE_FLAG_INDEX, slopeOverride);
	}

    /**
     * Return the azimuth of the first segment in this edge in integer degrees clockwise from South.
     * TODO change everything to clockwise from North
     */
	public int getInAngle() {
		return (int) Math.round(this.inAngle * 180 / 128.0);
	}

    /** Return the azimuth of the last segment in this edge in integer degrees clockwise from South. */
	public int getOutAngle() {
		return (int) Math.round(this.outAngle * 180 / 128.0);
	}

    protected List getTurnRestrictions(Graph graph) {
        return graph.getTurnRestrictions(this);
    }

    /** calculate the length of this street segement from its geometry */
    protected void calculateLengthFromGeometry () {
        double accumulatedMeters = 0;

        LineString geom = getGeometry();

        for (int i = 1; i < geom.getNumPoints(); i++) {
            accumulatedMeters += SphericalDistanceLibrary.distance(geom.getCoordinateN(i - 1), geom.getCoordinateN(i));
        }

        length_mm = (int) (accumulatedMeters * 1000);
    }

    /** Split this street edge and return the resulting street edges */
    public P2 split(SplitterVertex v, boolean destructive) {
        P2 geoms = GeometryUtils.splitGeometryAtPoint(getGeometry(), v.getCoordinate());

        StreetEdge e1 = null;
        StreetEdge e2 = null;

        if (destructive) {
            e1 = new StreetEdge((StreetVertex) fromv, v, geoms.first, name, 0, permission, this.isBack());
            e2 = new StreetEdge(v, (StreetVertex) tov, geoms.second, name, 0, permission, this.isBack());

            // copy the wayId to the split edges, so we can trace them back to their parent if need be
            e1.wayId = this.wayId;
            e2.wayId = this.wayId;

            // figure the lengths, ensuring that they sum to the length of this edge
            e1.calculateLengthFromGeometry();
            e2.calculateLengthFromGeometry();

            // we have this code implemented in both directions, because splits are fudged half a millimeter
            // when the length of this is odd. We want to make sure the lengths of the split streets end up
            // exactly the same as their backStreets so that if they are split again the error does not accumulate
            // and so that the order in which they are split does not matter.
            if (!isBack()) {
                // cast before the divide so that the sum is promoted
                double frac = (double) e1.length_mm / (e1.length_mm + e2.length_mm);
                e1.length_mm = (int) (length_mm * frac);
                e2.length_mm = length_mm - e1.length_mm;
            }
            else {
                // cast before the divide so that the sum is promoted
                double frac = (double) e2.length_mm / (e1.length_mm + e2.length_mm);
                e2.length_mm = (int) (length_mm * frac);
                e1.length_mm = length_mm - e2.length_mm;
            }

            // TODO: better handle this temporary fix to handle bad edge distance calculation
            if (e1.length_mm < 0) {
                LOG.error("Edge 1 ({}) split at vertex at {},{} has length {} mm. Setting to 1 mm.", e1.wayId, v.getLat(), v.getLon(), e1.length_mm);
                e1.length_mm = 1;
            }
            if (e2.length_mm < 0) {
                LOG.error("Edge 2 ({}) split at vertex at {},{}  has length {} mm. Setting to 1 mm.", e2.wayId, v.getLat(), v.getLon(), e2.length_mm);
                e2.length_mm = 1;
            }

            if (e1.length_mm < 0 || e2.length_mm < 0) {
                e1.tov.removeIncoming(e1);
                e1.fromv.removeOutgoing(e1);
                e2.tov.removeIncoming(e2);
                e2.fromv.removeOutgoing(e2);
                throw new IllegalStateException("Split street is longer than original street!");
            }

            for (StreetEdge e : new StreetEdge[] { e1, e2 }) {
                e.setBicycleSafetyFactor(getBicycleSafetyFactor());
                e.setHasBogusName(hasBogusName());
                e.setStairs(isStairs());
                e.setWheelchairAccessible(isWheelchairAccessible());
                e.setBack(isBack());
            }
        } else {
            if (((TemporarySplitterVertex) v).isEndVertex()) {
                e1 = new TemporaryPartialStreetEdge(this, (StreetVertex) fromv, v, geoms.first, name);
                e1.setNoThruTraffic(this.isNoThruTraffic());
                e1.setStreetClass(this.getStreetClass());
            } else {
                e2 = new TemporaryPartialStreetEdge(this, v, (StreetVertex) tov, geoms.second, name);
                e2.setNoThruTraffic(this.isNoThruTraffic());
                e2.setStreetClass(this.getStreetClass());
            }
        }
        return new P2<>(e1, e2);
    }

    /**
     * Get the starting OSM node ID of this edge. Note that this information is preserved when an
     * edge is split, so both edges will have the same starting and ending nodes.
     */
    public long getStartOsmNodeId () {
        if (fromv instanceof OsmVertex)
            return ((OsmVertex) fromv).nodeId;
        // get information from the splitter vertex so this edge gets the same traffic information it got before
        // it was split.
        else if (fromv instanceof SplitterVertex)
            return ((SplitterVertex) fromv).previousNodeId;
        else
            return -1;
    }

    /**
     * Get the ending OSM node ID of this edge. Note that this information is preserved when an
     * edge is split, so both edges will have the same starting and ending nodes.
     */
    public long getEndOsmNodeId () {
        if (tov instanceof OsmVertex)
            return ((OsmVertex) tov).nodeId;
            // get information from the splitter vertex so this edge gets the same traffic information it got before
            // it was split.
        else if (tov instanceof SplitterVertex)
            return ((SplitterVertex) tov).nextNodeId;
        else
            return -1;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy