/* 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.io.Serializable;
import java.util.*;
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;
import java.util.Locale;
import org.opentripplanner.util.I18NString;
import org.opentripplanner.util.NonLocalizedString;
/**
* A vertex in the graph. Each vertex has a longitude/latitude location, as well as a set of
* incoming and outgoing edges.
*/
public abstract class Vertex implements Serializable, Cloneable {
private static final long serialVersionUID = MavenVersion.VERSION.getUID();
private static final Logger LOG = LoggerFactory.getLogger(Vertex.class);
private static int maxIndex = 0;
private int index;
/* short debugging name */
private final String label;
/* Longer human-readable name for the client */
private I18NString name;
private final double x;
private final double y;
private transient Edge[] incoming = new Edge[0];
private transient Edge[] outgoing = new Edge[0];
/* CONSTRUCTORS */
protected Vertex(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 = new NonLocalizedString("(no name provided)");
}
protected Vertex(Graph g, String label, double x, double y, I18NString 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();
}
@Override
public int hashCode() {
return index;
}
/* EDGE UTILITY METHODS (use arrays to eliminate copy-on-write set objects) */
/**
* A static helper method to avoid repeated code for outgoing and incoming lists.
* Synchronization must be handled by the caller, to avoid passing edge array pointers that may be invalidated.
*/
private static Edge[] addEdge(Edge[] existing, Edge e) {
Edge[] copy = new Edge[existing.length + 1];
int i;
for (i = 0; i < existing.length; i++) {
if (existing[i] == e) {
LOG.error("repeatedly added edge {}", e);
return existing;
}
copy[i] = existing[i];
}
copy[i] = e; // append the new edge to the copy of the existing array
return copy;
}
/**
* A static helper method to avoid repeated code for outgoing and incoming lists.
* Synchronization must be handled by the caller, to avoid passing edge array pointers that may be invalidated.
*/
private static Edge[] removeEdge(Edge[] existing, Edge e) {
int nfound = 0;
for (int i = 0, j = 0; i < existing.length; i++) {
if (existing[i] == e) nfound++;
}
if (nfound == 0) {
LOG.error("Requested removal of an edge which isn't connected to this vertex.");
return existing;
}
if (nfound > 1) {
LOG.error("There are multiple copies of the edge to be removed.)");
}
Edge[] copy = new Edge[existing.length - nfound];
for (int i = 0, j = 0; i < existing.length; i++) {
if (existing[i] != e) copy[j++] = existing[i];
}
return copy;
}
/* FIELD ACCESSOR METHODS : READ/WRITE */
public void addOutgoing(Edge edge) {
synchronized (this) {
outgoing = addEdge(outgoing, edge);
}
}
/** @return whether the edge was found and removed. */
public boolean removeOutgoing(Edge edge) {
synchronized (this) {
int n = outgoing.length;
outgoing = removeEdge(outgoing, edge);
return (outgoing.length < n);
}
}
public void addIncoming(Edge edge) {
synchronized (this) {
incoming = addEdge(incoming, edge);
}
}
/** @return whether the edge was found and removed. */
public boolean removeIncoming(Edge edge) {
synchronized (this) {
int n = incoming.length;
incoming = removeEdge(incoming, edge);
return (incoming.length < n);
}
}
/**
* Get a collection containing all the edges leading from this vertex to other vertices.
* There is probably some overhead to creating the wrapper ArrayList objects, but this
* allows filtering and combining edge lists using stock Collection-based methods.
*/
public Collection<Edge> getOutgoing() {
return Arrays.asList(outgoing);
}
/** Get a collection containing all the edges leading from other vertices to this vertex. */
public Collection<Edge> getIncoming() {
return Arrays.asList(incoming);
}
@XmlTransient
public int getDegreeOut() {
return outgoing.length;
}
@XmlTransient
public int getDegreeIn() {
return incoming.length;
}
/** Get the longitude of the vertex */
public double getX() {
return x;
}
/** Get the latitude of the vertex */
public double getY() {
return y;
}
/** Get the longitude of the vertex */
public double getLon() {
return x;
}
/** Get the latitude of the vertex */
public double getLat() {
return y;
}
/** If this vertex is located on only one street, get that street's name
* in english localization */
public String getName() {
return this.name.toString();
}
/** If this vertex is located on only one street, get that street's name
* in provided localization
* @param locale wanted localization */
public String getName(Locale locale) {
return this.name.toString(locale);
}
/* FIELD ACCESSOR METHODS : READ ONLY */
/** Every vertex has a label which is globally unique. */
public String getLabel() {
return label;
}
@XmlTransient
public Coordinate getCoordinate() {
return new Coordinate(getX(), getY());
}
/** Get the bearing, in degrees, between this vertex and another coordinate. */
public double azimuthTo(Coordinate other) {
return DirectionUtils.getAzimuth(getCoordinate(), other);
}
/** Get the bearing, in degrees, between this vertex and another. */
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;
}
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 Edge[0];
this.outgoing = new Edge[0];
index = maxIndex++;
}
/* UTILITY METHODS FOR SEARCHING, GRAPH BUILDING, AND GENERATING WALKSTEPS */
@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;
}
}