/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.routing.graph; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.xml.bind.annotation.XmlTransient; import org.opentripplanner.common.MavenVersion; import org.opentripplanner.common.geometry.DirectionUtils; import org.opentripplanner.routing.edgetype.StreetEdge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; public abstract class AbstractVertex implements Vertex { private static final long serialVersionUID = MavenVersion.VERSION.getUID(); private static final Logger LOG = LoggerFactory.getLogger(AbstractVertex.class); private static int maxIndex = 0; private int index; private int groupIndex = -1; /* short debugging name */ private final String label; /* Longer human-readable name for the client */ private String name; private final double x; private final double y; private double distanceToNearestTransitStop = 0; private transient Set<Edge> incoming = new CopyOnWriteArraySet<Edge>(); private transient Set<Edge> outgoing = new CopyOnWriteArraySet<Edge>(); /* PUBLIC CONSTRUCTORS */ public AbstractVertex(Graph g, String label, double x, double y) { this.label = label; this.x = x; this.y = y; this.index = maxIndex ++; // null graph means temporary vertex if (g != null) g.addVertex(this); this.name = "(no name provided)"; } protected AbstractVertex(Graph g, String label, double x, double y, String name) { this(g, label, x, y); this.name = name; } /* PUBLIC METHODS */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("<").append(this.getLabel()); if (this.getCoordinate() != null) { sb.append(" lat,lng=").append(this.getCoordinate().y); sb.append(",").append(this.getCoordinate().x); } sb.append(">"); return sb.toString(); } public int hashCode() { return index; } /* FIELD ACCESSOR METHODS : READ/WRITE */ @Override public void addOutgoing(Edge ee) { if (outgoing.contains(ee)) { LOG.error("repeatedly added edge {} to vertex {}", ee, this); } else { outgoing.add(ee); } } @Override public boolean removeOutgoing(Edge ee) { if (!outgoing.contains(ee)) { LOG.error("Removing edge which isn't connected to this vertex"); } boolean removed = outgoing.remove(ee); if (outgoing.contains(ee)) { LOG.error("edge {} still in edgelist of {} after removed. there must have been multiple copies."); } return removed; } /** Get a collection containing all the edges leading from this vertex to other vertices. */ @Override public Collection<Edge> getOutgoing() { return outgoing; } @Override public void addIncoming(Edge ee) { if (incoming.contains(ee)) { LOG.error("repeatedly added edge {} to vertex {}", ee, this); } else { incoming.add(ee); } } @Override public boolean removeIncoming(Edge ee) { if (!incoming.contains(ee)) { LOG.error("Removing edge which isn't connected to this vertex"); } boolean removed = incoming.remove(ee); if (incoming.contains(ee)) { LOG.error("edge {} still in edgelist of {} after removed. there must have been multiple copies."); } return removed; } /** Get a collection containing all the edges leading from other vertices to this vertex. */ @Override public Collection<Edge> getIncoming() { return incoming; } @Override @XmlTransient public int getDegreeOut() { return outgoing.size(); } @Override @XmlTransient public int getDegreeIn() { return incoming.size(); } @Override public void setDistanceToNearestTransitStop(double distance) { distanceToNearestTransitStop = distance; } @Override public double getDistanceToNearestTransitStop() { return distanceToNearestTransitStop; } @Override public double getX() { return x; } @Override public double getY() { return y; } public double getLon() { return x; } public double getLat() { return y; } @Override public void setGroupIndex(int groupIndex) { this.groupIndex = groupIndex; } @Override @XmlTransient public int getGroupIndex() { return groupIndex; } @Override public String getName() { return this.name; } @Override public void setStreetName(String name) { this.name = name; } /* FIELD ACCESSOR METHODS : READ ONLY */ @Override public String getLabel() { return label; } @XmlTransient public Coordinate getCoordinate() { return new Coordinate(getX(), getY()); } @Override public double azimuthTo(Coordinate other) { return DirectionUtils.getAzimuth(getCoordinate(), other); } @Override public double azimuthTo(Vertex other) { return azimuthTo(other.getCoordinate()); } /** Get this vertex's unique index, that can serve as a hashcode or an index into a table */ @XmlTransient public int getIndex() { return index; } @Override public void setIndex(int index) { this.index = index; } public static int getMaxIndex() { return maxIndex; } /* SERIALIZATION METHODS */ private void writeObject(ObjectOutputStream out) throws IOException { // edge lists are transient out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.incoming = new CopyOnWriteArraySet<Edge>(); this.outgoing = new CopyOnWriteArraySet<Edge>(); index = maxIndex++; } @Override public void compact() { // copy-on-write array list never has extra empty slots // this.outgoing.trimToSize(); // this.incoming.trimToSize(); } /* UTILITY METHODS FOR SEARCHING, GRAPH BUILDING, AND GENERATING WALKSTEPS */ @Override @XmlTransient public List<Edge> getOutgoingStreetEdges() { List<Edge> result = new ArrayList<Edge>(); for (Edge out : this.getOutgoing()) { if (!(out instanceof StreetEdge)) { continue; } result.add((StreetEdge) out); } return result; } @Override public void mergeFrom(Graph graph, Vertex other) { // copy edgelists to avoid concurrent modification List<Edge> edges = new ArrayList<Edge>(); edges.addAll(this.getIncoming()); edges.addAll(this.getOutgoing()); edges.addAll(other.getIncoming()); edges.addAll(other.getOutgoing()); for (Edge e : edges) { // We only support Vertices that are direct edges when merging Vertex from = e.getFromVertex(); Vertex to = e.getToVertex(); if ((from==this && to==other) || (from==other && to==this)) { e.detach(); } else if (from == other) { e.attach(this, to); } else if (to == other) { e.attach(from, this); } } graph.removeVertex(other); } @Override public void removeAllEdges() { for (Edge e : outgoing) { Vertex target = e.getToVertex(); if (target != null) { target.removeIncoming(e); } } for (Edge e : incoming) { Vertex source = e.getFromVertex(); if (source != null) { source.removeOutgoing(e); } } incoming = new CopyOnWriteArraySet<Edge>(); outgoing = new CopyOnWriteArraySet<Edge>(); } /* GRAPH COHERENCY AND TYPE CHECKING */ // Parameterized Class<? extends Edge) gets ugly fast here @SuppressWarnings("unchecked") private static final ValidEdgeTypes VALID_EDGE_TYPES = new ValidEdgeTypes(Edge.class); @XmlTransient @Override public ValidEdgeTypes getValidOutgoingEdgeTypes() { return VALID_EDGE_TYPES; } @XmlTransient @Override public ValidEdgeTypes getValidIncomingEdgeTypes() { return VALID_EDGE_TYPES ; } /* * This may not be necessary if edge constructor types are strictly specified * and addOutgoing is protected */ @Override public boolean edgeTypesValid() { ValidEdgeTypes validOutgoingTypes = getValidOutgoingEdgeTypes(); for (Edge e : getOutgoing()) if (!validOutgoingTypes.isValid(e)) return false; ValidEdgeTypes validIncomingTypes = getValidIncomingEdgeTypes(); for (Edge e : getIncoming()) if (!validIncomingTypes.isValid(e)) return false; return true; } public static final class ValidEdgeTypes { private final Class<? extends Edge>[] classes; // varargs constructor: // a loophole in the law against arrays/collections of parameterized generics public ValidEdgeTypes (Class<? extends Edge>... classes) { this.classes = classes; } public boolean isValid (Edge e) { for (Class<? extends Edge> c : classes) { if (c.isInstance(e)) return true; } return false; } } @Override public int removeTemporaryEdges() { // do nothing, signal 0 other objects affected return 0; } }