org.life.sl.routefinder.Label.java Source code

Java tutorial

Introduction

Here is the source code for org.life.sl.routefinder.Label.java

Source

package org.life.sl.routefinder;

/*
JMapMatcher
    
Copyright (c) 2011 Bernhard Barkow, Hans Skov-Petersen, Bernhard Snizek and Contributors
    
mail: bikeability@life.ku.dk
web: http://www.bikeability.dk
    
This program is free software; you can redistribute it and/or modify it under 
the terms of the GNU 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/>.
*/

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
//import org.geotools.feature.FeatureCollections;
import org.geotools.feature.SchemaException;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import org.life.sl.mapmatching.EdgeStatistics;
import org.life.sl.orm.HibernateUtil;
import org.life.sl.orm.OSMNode;
import org.life.sl.routefinder.RouteFinder.LabelTraversal;
import org.life.sl.tools.RouteDBExporter;
import org.life.sl.utils.MathUtil;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.operation.linemerge.LineMergeEdge;
import com.vividsolutions.jts.planargraph.DirectedEdge;
import com.vividsolutions.jts.planargraph.Edge;
import com.vividsolutions.jts.planargraph.Node;

/**
 * This class is used by the RouteGenerator to search for routes, and can also be used
 * to represent a route. Normally you would use a List of edges for that.
 * @author Pimin Konstantin Kefaloukos
 *
 */
public class Label implements Comparable<Label> {

    /**
     * Comparator comparing only the last edge of two labels
     * @author bb
     */
    public static class LastEdgeComparator implements Comparator<Label> {
        RouteFinder.LabelTraversal sortOrder;

        public LastEdgeComparator(RouteFinder.LabelTraversal so) {
            sortOrder = so;
        }

        public int compare(Label arg0, Label arg1) {
            int r = arg0.compareTo_LE(arg1, sortOrder == LabelTraversal.BestFirstDR);
            if (sortOrder == LabelTraversal.WorstFirst)
                r = -r;
            return r;
        }
    }

    /**
     * Comparator comparing only the direction of the last edge of two labels
     * @author bb
     */
    public static class LastEdgeDirectionComparator implements Comparator<Label> {
        double ODAngle;

        public LastEdgeDirectionComparator(Node startNode, Node endNode) {
            DirectedEdge e = new DirectedEdge(startNode, endNode, endNode.getCoordinate(), true);
            ODAngle = e.getAngle();
        }

        public int compare(Label arg0, Label arg1) {
            int r = arg0.compareDir_LE(arg1, ODAngle);
            return r;
        }
    }

    /**
     * Comparator comparing the length of two labels
     * @author bb
     */
    public static class LengthComparator implements Comparator<Label> {
        public int compare(Label arg0, Label arg1) {
            return arg0.compareTo_length(arg1);
        }
    }

    /**
     * A more universal Comparator considering the length AND the match score of two labels
     * @author bb
     */
    public static class UniversalComparator implements Comparator<Label> {
        private double l_ms_weight = 0; // weighting factor

        public UniversalComparator(double l_ms_weight) {
            this.l_ms_weight = l_ms_weight;
        }

        public int compare(Label arg0, Label arg1) {
            return arg0.compareTo_lengthMS(arg1, l_ms_weight);
        }
    }

    private final double kCoordEps = 2.e0; ///< tolerance for coordinate comparison (if (x1-x2 < kCoordEps) then x1==x2)

    private Label parent; ///< The parent of the Label
    private Node node; ///< The node associated with this Label
    private DirectedEdge backEdge; ///< The GeoEdge leading back to the node associated with the parent Label - BUT: in the direction of motion!
    private double length = 0.; ///< if the label represents a route, this is the length of the route (sum of all backEdges)
    private double lastEdgeLength = 0.; ///< length of last backEdge
    private double score = -1.; ///< the score of the label (evaluated according to edge statistics)
    private short scoreCount = -1; ///< the unweighted score of the label (nearest points-count)
    //private double lastScore = 0.;   ///< the same but considering only the last edge   
    private short lastScoreCount = 0;
    private double pathSizeAttr = 0; ///< the Path Size Attribute (in a global context)
    private double ODDirection = 0.; ///< the direction relative to the OD connection (+1 = parallel, 0 = normal, -1 = antiparallel)
    private boolean isChoice = false; ///< true, if this is the chosen route in the choice experiment (helpful for selecting the routes to store)
    private boolean isShortest = false; ///< true, if this Label represents the shortest path

    private List<Node> nodeList = null; ///< list of Nodes (not OSM database nodes!)
    private List<DirectedEdge> edgeList = null; ///< list of Edges (not OSM database edges!)
    private int[] nodeIDs = null; ///< list of OSMNode IDs
    private int[] edgeIDs = null; ///< list of OSMEdge IDs

    private static com.vividsolutions.jts.geom.GeometryFactory fact = new com.vividsolutions.jts.geom.GeometryFactory();

    /**
     * Create a new Label as descendant of a parent label
     * @param parent The Label that this Label was expanded from
     * @param node The node associated with this Label.
     * @param backEdge The edge leading back to the parent of the Label.
     * @param length The new accumulative length of the route represented by this Label.
     */
    public Label(Label parent, Node node, DirectedEdge backEdge, double length, double lastEdgeLength) {
        //      System.out.println("Label(+) " + node.getCoordinate());
        this.parent = parent;
        this.node = node;
        this.backEdge = backEdge;
        this.lastEdgeLength = lastEdgeLength;
        this.length = length;
    }

    /**
     * Create a new "empty" Label at a node;
     * parent and backEdge are set to null, length is set to 0.0 and expand is set to true.
     * @param node The node associated with this Label.
     */
    public Label(Node node) {
        this.parent = null;
        this.node = node;
        this.backEdge = null;
        this.length = 0.;
        this.lastEdgeLength = 0.;
    }

    /**
     * string representation
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return node.toString();
    }

    @Override
    public boolean equals(Object o) {
        boolean b = false;
        Label l = (Label) o;
        if (l.score == this.score && l.length == this.length) { // just to avoid having to create and compare the whole node lists
            // Node comparison does not work efficiently, so we restrict ourselves to the length and match score as indicators
            // TODO: check if this is feasible!
            //List<Node> nodes0 = this.getNodes();
            //List<Node> nodes1 = l.getNodes();
            //b = nodes0.containsAll(nodes1);
            b = true;
        }
        return b;
    }

    /**
     * Comparison method
     * @param arg0 the other object to compare this to
     * @return 1 if this object is larger than the other, -1 if smaller, 0 if equal
     */
    public int compareTo(Label arg0) {
        int r = 0;
        double ov = arg0.getScore();
        if (this.score > ov)
            r = 1;
        else if (this.score < ov)
            r = -1;
        //      int ov = arg0.getScoreCount();
        //      if (this.scoreCount > ov) r = 1;
        //      else if (this.scoreCount < ov) r = -1;
        return r;
    }

    /**
     * Comparison method using only the last edge;
     * in addition to the score, the direction relative to the last edge is evaluated if necessary
     * @param arg0 the other object to compare this to
     * @return 1 if this object is larger than the other, -1 if smaller, 0 if equal
     */
    public int compareTo_LE(Label arg0, boolean useDR) {
        int r = 0;
        int ov = arg0.getLastScoreCount();
        if (this.lastScoreCount > ov)
            r = 1;
        else if (this.lastScoreCount < ov)
            r = -1;
        else {
            r = compareTo(arg0); // default fallback: if both are equal, sort them according to their total score
            if (ov == 0 && useDR) { // both edges have no points associated: compare directions (dead reckoning)
                if (parent != null && parent.backEdge != null) {
                    /* Some notes:
                     * - both edges should have the same parent!
                     * - at the root node (parent=null), this comparison should not be invoked anyway
                     * - at the first node, we don't have a reference direction yet;
                         but if we are at the first node after root and don't have GPS points, we have a problem anyway */
                    if (Math.abs(this.getAngleDiff()) < Math.abs(arg0.getAngleDiff()))
                        r = 1;
                    else
                        r = -1; // (smaller deviation is better)
                }
            }
        }
        return r;
    }

    /**
     * Comparison method using the OD-direction of the last edge;
     * @param arg0 the other object to compare this to
     * @return 1 if this object is larger than the other, -1 if smaller, 0 if equal
     */
    public int compareDir_LE(Label arg0, double ODAngle) {
        int r = 0;
        if (parent != null) {// && parent.backEdge != null) {
            /* Some notes:
             * - both edges should have the same parent!
             * - at the root node (parent=null), this comparison should not be invoked anyway
             */
            if (Math.abs(this.getODDirection(ODAngle)) > Math.abs(arg0.getODDirection(ODAngle)))
                r = 1;
            else
                r = -1; // (larger value (more parallel to OD) is better)
        }
        return r;
    }

    /**
     * Comparison method using the route length
     * @param arg0 the other object to compare this to
     * @return 1 if this object is larger (longer) than the other, -1 if smaller (shorter), 0 if equal
     */
    public int compareTo_length(Label arg0) {
        int r = 0;
        double ov = arg0.getLength();
        if (this.length > ov)
            r = 1;
        else if (this.length < ov)
            r = -1;
        return r;
    }

    /**
     * Comparison method using both the route length and the match score;
     * shorter length and higher match score mean "smaller" (meaning "better" in natural order sorting)
     * @param arg0 the other object to compare this to
     * @param l_ms_weight weighting factor: 0 = consider only the match score, 1 = consider only the length
     * @return 1 if this object is larger (longer) than the other, -1 if smaller (shorter), 0 if equal
     */
    public int compareTo_lengthMS(Label arg0, double l_ms_weight) {
        double r_ms = Math.abs(arg0.score / this.score); // > 1 ... this > arg0
        double r_l = Math.abs(this.length / arg0.length); // > 1 ... this > arg0
        double f = r_ms * (1. - l_ms_weight) + r_l * l_ms_weight; // the weighted combination of both factors
        int r = 0;
        if (f > 1)
            r = 1;
        else if (f < 1)
            r = -1;
        return r;
    }

    // The following are getter methods:

    /**
     * @return Returns the parent of the Label
     */
    public Label getParent() {
        return this.parent;
    }

    /**
     * @return Returns the GeoNode associated with this label. GeoNodes can be associated with any number
     * of Labels, but Labels can only be associated with one GeoNode.
     */
    public Node getNode() {
        return this.node;
    }

    /**
     * @return Returns the edge leading back to the parent of this Label.
     */
    public DirectedEdge getBackEdge() {
        return this.backEdge;
    }

    /**
     * @return Returns the length of the route represented by this Label.
     */
    public double getLength() {
        return this.length;
    }

    /**
     * Convenience function: returns distance from the current to another node 
     * @param node1 the other node
     * @return Cartesian distance between current node and node1 
     */
    public double getDistanceTo(Node node1) {
        return node.getCoordinate().distance(node1.getCoordinate());
    }

    /**
     * Computes the angular difference between the two previous edges
     * @return
     */
    public double getAngleDiff() {
        if (parent != null && parent.backEdge != null) {
            return MathUtil.mapAngle_radians(backEdge.getAngle() - (parent.backEdge.getSym().getAngle() - Math.PI));
        } else
            return 0; // at the first node, we don't have a reference direction yet
    }

    public double getODDirection() {
        return ODDirection;
    }

    /**
     * compute and return ODDirection, the angle between the last edge and the connection from Origin to Destination 
     * @param ODAngle
     * @return ODDirection
     */
    public double getODDirection(double ODAngle) {
        if (parent != null) {// && parent.backEdge != null) {
            ODDirection = 1.
                    - 2. * Math.abs(MathUtil.mapAngle_radians(Math.abs(backEdge.getAngle() - ODAngle)) / Math.PI); // should be +1 for parallel, 0 for normal, -1 for antiparallel
        } else
            ODDirection = 0; // at the first node, we don't have a reference direction yet
        return ODDirection;
    }

    /**
     * calculate the weighted and unweighted score of this label from the edge statistics;
     *    this is a measure of the quality of the route: number of fitting data points, normalized to route length
     *  (the recursive variant is about 3 times faster than doing it explicitely for all edges)
     * @param eStat edge statistics
     */
    public void calcScore(EdgeStatistics eStat) {
        // calculate the score recursively:
        score = 0.;
        scoreCount = 0;
        if (parent != null) {
            lastScoreCount = eStat.getCount(backEdge.getEdge());
            scoreCount = (short) (parent.getScoreCount(eStat) + lastScoreCount);
            //         lastScore = (double)lastScoreCount / lastEdgeLength;
            //score = Math.round(parent.getScore(eStat) * parent.getLength()) + eStat.getCount(backEdge.getEdge());   // backEdge should be the last edge in the label
            if (length > 0.)
                score = scoreCount / length;
        }
    }

    /**
     * @return the score of this label, freshly calculated from the edge statistics, if necessary
     * @param eStat edge statistics
     */
    public double getScore(EdgeStatistics eStat) {
        if (score < 0.)
            calcScore(eStat);
        return score;
    }

    /**
     * @return the score of this label (getter method)
     */
    public double getScore() {
        return score;
    }

    /**
     * @return the unweighted score of this label (nearest points-count), freshly calculated from the edge statistics, if necessary
     * @param eStat edge statistics
     */
    public short getScoreCount(EdgeStatistics eStat) {
        if (scoreCount < 0)
            calcScore(eStat);
        return scoreCount;
    }

    public short getScoreCount() {
        return scoreCount;
    }

    //   public double getLastScore() {
    //      return lastScore;
    //   }
    public short getLastScoreCount() {
        return lastScoreCount;
    }

    public double getLastEdgeLength() {
        return this.lastEdgeLength;
    }

    public boolean isChoice() {
        return isChoice;
    }

    public void setChoice(boolean isChoice) {
        this.isChoice = isChoice;
    }

    public boolean isShortest() {
        return isShortest;
    }

    public void setShortest(boolean isShortest) {
        this.isShortest = isShortest;
    }

    /**
     * Return a parent label n generations back
     * @param n how far back in the history
     * @return label n generations back
     */
    public Label getNthParent(int n) {
        int i = n;
        Label label = this;
        while (label.parent != null && i > 0) {
            label = label.parent;
            i--;
        }
        return label;
    }

    /**
     * Given a directed edge, this method calculates how many times the undirected parent edge has been visited by the route
     * represented by this Label.
     * @param dirEdge The directed edge we are querying about.
     * @return An int value indicating the degree of overlap for the edge. Counting occurrences in both directions.
     */
    public int getOccurrencesOfEdge(DirectedEdge dirEdge) {
        Label label = this;
        int n = 0;
        while (label.getBackEdge() != null) {
            Edge undirectedBackEdge = label.getBackEdge().getEdge();
            if (dirEdge.getEdge() == undirectedBackEdge)
                n++; // important: compare the undirected edges!
            label = label.getParent();
        }
        return n;
    }

    /**
     * Given a directed edge, this method calculates how many times the undirected parent edge has been visited by the route
     * represented by this Label - alternative to getOccurrencesOfEdge().
     * @param dirEdge The directed edge we are querying about.
     * @return An int value indicating the degree of overlap for the edge. Counting occurrences in both directions (direction independent).
     */
    public int getOccurrencesOfEdge_2(DirectedEdge dirEdge, boolean useDir) {
        int n = 0;
        edgeList = getRouteAsEdges();
        for (DirectedEdge e : edgeList) {
            if (useDir) { // consider direction
                if (dirEdge == e)
                    n++;
            } else { // direction independent
                if (dirEdge.getEdge() == e.getEdge())
                    n++; // important: compare the undirected edges!
            }
        }
        return n;
    }

    /**
     * Given a node, this method calculates how many times that node has been visited by the route
     * represented by this Label.
     * @param node The node we are querying about.
     * @return An int value indicating the degree of overlap for the node.
     */
    public int getOccurrencesOfNode(Node node) {
        Label label = this;
        int n = 0;
        while (label != null) {
            if (node == label.getNode())
                n++;
            label = label.getParent();
        }
        return n;
    }

    /**
     * Calculate the overlap factor, a value in [0...1]: 0 means no overlap, 1 means the whole route is contained in lbl0.
     * See also: E. Frejinger, M. Bierlaire, M. Ben-Akiva: Expanded Path Size Attribute, March 2009 
     * @param lbl0 the other label (route) to compare this one to
     * @param useDir if true, the edge direction is considered, if not, the overlap is computed independent of the edge direction 
     * @return the overlap factor [0...1]
     */
    public double getOverlap(Label lbl0, boolean useDir) {
        double ps = 0;
        for (DirectedEdge e : this.getRouteAsEdges()) {
            if (lbl0.getOccurrencesOfEdge_2(e, useDir) != 0) { // direction independent
                ps += ((LineMergeEdge) e.getEdge()).getLine().getLength(); // add length of this edge
            }
        }
        ps /= getLength();
        return ps;
    }

    /**
     * Check if this Label is contained in a list of Labels 
     * @param labels the list of labels to check
     * @return true, if labels contains this label
     */
    public boolean isContainedIn(List<Label> labels) {
        boolean b = false;
        for (Label l : labels) {
            if (this.equals(l)) {
                b = true;
                break;
            } // don't store the route if identical to any existing 
        }
        return b;
    }

    /**
     * Calculate the overlap factor of this label and all routes in a given set of labels. 
     * @param labels a list of labels
     * @param useDir if true, the edge direction is considered, if not, the overlap is computed independent of the edge direction
     * @param maxOverlap threshold: if > 0, the comparison is only performed until an overlap factor above this threshold is found (to improve performance) 
     * @return the maximum overlap factor [0...1] between this route and all those in the given set of labels (unless maxOverlap is given)
     */
    public double getOverlapWithSet(List<Label> labels, boolean useDir, double maxOverlap) {
        double ps = 0;
        for (Label l : labels) {
            ps = Math.max(ps, getOverlap(l, useDir));
            if (maxOverlap > 0 && ps > maxOverlap)
                break; // speed things up if we only check for a threshold
            // TODO: extend that by doing a two-way comparison?
            ps = Math.max(ps, l.getOverlap(this, useDir)); // ??
            if (maxOverlap > 0 && ps > maxOverlap)
                break;
        }
        //      System.out.print(".");
        return ps;
    }

    /**
     * Calculate the Path Size in a global context.
     * See also: E. Frejinger, M. Bierlaire, M. Ben-Akiva: Expanded Path Size Attribute, March 2009 
     * @param weights a HashMap containing the number of occurrences of all Edges 
     * @return the Path Size Attribute
     */
    public double getPathSize_global(HashMap<Integer, Integer> weights) {
        double ps = 0;
        for (DirectedEdge de : this.getRouteAsEdges()) {
            Edge e = de.getEdge();
            // the edge ID is used as key for the HashMap 
            @SuppressWarnings("unchecked")
            HashMap<String, Object> userdata = (HashMap<String, Object>) e.getData(); // the user data object of the Edge
            Integer edgeID = (Integer) userdata.get("id");
            Integer ne = (weights.containsKey(edgeID) ? weights.get(edgeID) : 1);
            //System.out.print(ne + "\t");
            ps += ((LineMergeEdge) e).getLine().getLength() / (double) ne; // add length of this edge, divided by number of uses
        }
        pathSizeAttr = ps / getLength(); // store in class variable
        //System.out.println("\n" + getLength() + " ps: " + ps);
        return pathSizeAttr;
    }

    public double getPathSizeAttr() {
        return pathSizeAttr;
    }

    /**
     * Create a list of all the directed edges along this route, starting at the origin;
     * for efficiency, this list is only compiled once and cached in edgeList.
     * @return the route represented by this label as a list of directed edges
     */
    public List<DirectedEdge> getRouteAsEdges() {
        if (edgeList == null || edgeList.size() == 0) {
            edgeList = new ArrayList<DirectedEdge>();
            Label label = this;
            DirectedEdge be = null;
            while (label.getParent() != null) {
                be = label.getBackEdge();
                if (be != null)
                    edgeList.add(be);
                label = label.getParent();
            }
            Collections.reverse(edgeList); // now, the topmost label represents the first edge in the list
        }
        return edgeList;
    }

    /**
     * Create a list of all the Nodes along this route, starting with the origin;
     * for efficiency, this list is only compiled once and cached in nodeList.
     * @return a list of all the Nodes of this label starting with the origin 
     */
    public List<Node> getNodes() {
        if (nodeList == null || nodeList.size() == 0) {
            nodeList = new ArrayList<Node>();
            Label label = this;
            while (label != null) {
                nodeList.add(label.getNode());
                label = label.getParent();
            }
            Collections.reverse(nodeList); // now, the origin node is first in the list
        }
        return nodeList;
    }

    /**
     * Retrieve the database IDs of all edges
     * @return array with edge IDs
     */
    public int[] getEdgeIDs() {
        if (edgeIDs == null) {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            List<DirectedEdge> edges = getRouteAsEdges();
            edgeIDs = new int[edges.size()];
            int n = 0;
            for (DirectedEdge e : edges) { // for each node along the route:
                @SuppressWarnings("unchecked")
                HashMap<String, Object> ed = (HashMap<String, Object>) e.getEdge().getData();
                edgeIDs[n++] = (Integer) ed.get("id");
            }
            //session.getTransaction().commit();
        }
        return edgeIDs;
    }

    /**
     * Retrieve the database IDs of nodes and edges;
     * faster than getNodeIDs(), but the nodes are not returned in the correct order along the route
     * @see getNodeIDs()
     * @return array with node IDs
     */
    public int[] getNodeIDs_unordered() {
        if (nodeIDs == null) {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();

            LineString ls = getLineString();
            String s = "select id from osmnode where ST_DWithin(ST_GeomFromText('" + ls + "', 0), geom, "
                    + kCoordEps + ")";
            //System.out.println(s);
            Query nodeRes = session.createSQLQuery(s);
            @SuppressWarnings("unchecked")
            List<Integer> nodes = nodeRes.list();
            // transform to int[] array:
            int n = 0;
            nodeIDs = new int[nodes.size()];
            Iterator<Integer> it = nodes.iterator();
            while (it.hasNext()) {
                nodeIDs[n++] = it.next().intValue();
            }
            //session.getTransaction().commit();
        }
        for (int i : nodeIDs)
            System.out.print(i + "\t");
        System.out.println();
        return nodeIDs;
    }

    /**
     * Retrieve the database IDs of nodes and edges
     * @see getNodeIDs_unordered()
     * @return array with node IDs
     */
    public int[] getNodeIDs() {
        if (nodeIDs == null) {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            session.beginTransaction();
            List<DirectedEdge> edges = getRouteAsEdges();
            int nEdges = edges.size();
            nodeIDs = new int[nEdges + 1];
            /*int n = 0, lastValidEdgeID = 0;
            for (DirectedEdge e : edges) {      // for each node along the route:
               @SuppressWarnings("unchecked")
               HashMap<String, Object> ed = (HashMap<String, Object>) e.getEdge().getData();
               int eID = (Integer)ed.get("id");
                
               Node node = e.getFromNode();   // node at beginning of edge
               Coordinate c_n = node.getCoordinate();
                
               // get node ID from database:
               // TODO: make this more efficient (now we have 2 queries per edge!)
               int nodeID = 0;
               if (n == nEdges-1 && eID < 0) {   // last edge may be split!
                  eID = lastValidEdgeID;
               }
               if (eID > 0) {
                  lastValidEdgeID = eID;
                  String s = " from OSMEdge where id=" + eID;
                  s = "from OSMNode where (id = (select fromnode"+s+") or id = (select tonode"+s+"))";
                  Query nodeRes = session.createQuery(s);
                  // TODO: make this more efficient using a PostGIS spatial query with indexing?
                  // match coordinates:
                  @SuppressWarnings("unchecked")
                  Iterator<OSMNode> it = nodeRes.iterate();
                  while (it.hasNext()) {
              OSMNode on = it.next();
              Coordinate onc = on.getGeometry().getCoordinate();
              if (Math.abs(c_n.x - onc.x) < kCoordEps && Math.abs(c_n.y - onc.y) < kCoordEps) {
                 nodeID = on.getId();
                 break;
              }                        
                  }
               }   // now, nodeID is either 0 or the database ID of the corresponding node
               nodeIDs[n++] = nodeID;
            }
            for (int i : nodeIDs) System.out.print(i+"\t");System.out.println();
            nodeIDs = new int[nEdges + 1];*/

            //first, collect edge IDs:
            ArrayList<Integer> edgeIDs = new ArrayList<Integer>(nEdges);
            String se = "";
            for (DirectedEdge e : edges) { // for each node along the route:
                @SuppressWarnings("unchecked")
                HashMap<String, Object> ed = (HashMap<String, Object>) e.getEdge().getData();
                int eID = (Integer) ed.get("id");
                edgeIDs.add(eID);
                se += "," + eID;
            }
            se = se.substring(1); // remove leading comma
            String s = " from OSMEdge where id in (" + se + ")";
            Criteria cr = session.createCriteria(OSMNode.class);
            cr.add(Restrictions
                    .sqlRestriction("(id in (select fromnode" + s + ") or id in (select tonode" + s + "))"));
            cr.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
            @SuppressWarnings("unchecked")
            List<OSMNode> osmNodes = cr.list();
            //for (OSMNode o : osmNodes) System.out.print(o.getId()+"\t");
            //System.out.println();

            List<Node> nodes = getNodes();
            Coordinate c = null, onc = null;
            int nodeID;
            int n = 0;
            for (Node nd : nodes) {
                c = nd.getCoordinate();
                nodeID = 0;
                for (OSMNode on : osmNodes) {
                    onc = on.getGeometry().getCoordinate();
                    if (Math.abs(c.x - onc.x) < kCoordEps && Math.abs(c.y - onc.y) < kCoordEps) {
                        nodeID = on.getId();
                        osmNodes.remove(onc); // we won't need this again, assuming each node occurs only once!
                        break;
                    }
                }
                nodeIDs[n++] = nodeID;
            }
            //for (int i : nodeIDs) System.out.print(i+"\t");System.out.println();

            //session.getTransaction().commit();
        }

        //for (int i : nodeIDs) System.out.print(i+"\t"); System.out.println();
        return nodeIDs;
    }

    /**
     * Create a list of all the Nodes along this route, starting with the origin;
     * for efficiency, this list is only compiled once and cached in nodeList.
     * @return a list of all the Nodes of this label starting with the origin 
     */
    public List<Label> getLabels() {
        ArrayList<Label> lblList = new ArrayList<Label>();
        Label label = this;
        while (label != null) {
            lblList.add(label);
            label = label.getParent();
        }
        Collections.reverse(lblList); // now, the first label is first in the list
        return lblList;
    }

    /**
     * @return an array of Coordinates of all nodes (sorted from start to end)
     */
    public Coordinate[] getCoordinates() {
        List<Node> nodes = getNodes();
        Coordinate[] coordinates = new Coordinate[nodes.size()];
        int i = 0;
        for (Node curNode : nodes) {
            coordinates[i++] = curNode.getCoordinate();
        }
        return coordinates;
    }

    /**
     * @return an array of Coordinates of all vertices (sorted from start to end) (not just nodes!)
     */
    public LineString getLineString() {
        ArrayList<Coordinate> coordinates = new ArrayList<Coordinate>();
        List<DirectedEdge> edges = getRouteAsEdges();

        // check edge 0 versus edge 1
        @SuppressWarnings("unchecked")
        HashMap<String, Object> data0 = (HashMap<String, Object>) edges.get(0).getEdge().getData();
        LineString ls0 = (LineString) data0.get("geom");
        if (edges.size() > 1 && ls0 != null) {
            double kSnapDistance = 0.5;

            Coordinate[] cs0 = ls0.getCoordinates();

            @SuppressWarnings("unchecked")
            HashMap<String, Object> data1 = (HashMap<String, Object>) edges.get(1).getEdge().getData();
            LineString ls1 = (LineString) data1.get("geom");
            Coordinate[] cs1 = ls1.getCoordinates();

            // let's isolate vertex number 0:
            Coordinate cs0_vertex_0 = cs0[0];
            Coordinate cs1_vertex_0 = cs1[0];
            Coordinate cs1_vertex_last = cs1[cs1.length - 1];

            if ((cs0_vertex_0.distance(cs1_vertex_0) < kSnapDistance)
                    || (cs0_vertex_0.distance(cs1_vertex_last) < kSnapDistance)) {
                // reverse direction:
                for (int c = cs0.length - 1; c > -1; c--)
                    coordinates.add(cs0[c]);
            } else {
                // we already have the correct direction:
                for (int c = 0; c < cs0.length; c++)
                    coordinates.add(cs0[c]);
            }

            int i = 0;
            for (DirectedEdge e : edges) {
                if (i > 0) { // only from the second edge on
                    @SuppressWarnings("unchecked")
                    HashMap<String, Object> data = (HashMap<String, Object>) e.getEdge().getData();
                    LineString ls = (LineString) data.get("geom");

                    Coordinate[] cc = ls.getCoordinates();
                    int ccl = cc.length;
                    if (ccl > 1) { // cc.length must be >= 2
                        // check linestring direction:
                        if (cc[0].distance(coordinates.get(coordinates.size() - 1)) < kSnapDistance) {
                            for (int c = 1; c < ccl; c++)
                                coordinates.add(cc[c]); // first to last
                        } else {
                            for (int c = ccl - 2; c >= 0; c--)
                                coordinates.add(cc[c]); // reverse direction
                        }
                    }
                }
                i++;
            }

            Coordinate[] coords = new Coordinate[coordinates.size()];
            coordinates.toArray(coords);
            return fact.createLineString(coords);
        } else { // we have only 1 edge
            return ls0;
        }
    }

    /**
     * export the label data to a shape file
     * @param filename the name of the shape file
     * @throws SchemaException
     * @throws IOException
     */
    public void dumpToShapeFile(String filename, int sourceRouteID) throws SchemaException, IOException {

        /*final SimpleFeatureType TYPE = DataUtilities.createType("route",
        "location:LineString:srid=4326," + // <- the geometry attribute: Polyline type
              "name:String," + // <- a String attribute
              "number:Integer" // a number attribute
        );
            
        // 1. build a feature
        ArrayList<SimpleFeature> features = new ArrayList<SimpleFeature>();
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
        for (DirectedEdge l : this.getRouteAsEdges()) {
           @SuppressWarnings("unchecked")
           HashMap<String, Object> data = (HashMap<String, Object>) l.getEdge().getData();
           LineString ls = (LineString) data.get("geometry");
           SimpleFeature feature = featureBuilder.buildFeature(null);   
           feature.setDefaultGeometry(ls);
           features.add(feature);
        }
        SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);//FeatureCollections.newCollection();*/

        // get the LineString and write it to a shapefile
        System.out.println("Writing to shapefile " + filename);
        File newFile = new File(filename);
        try {
            RouteDBExporter.exportLineStringToShapeFile(newFile, getLineString(), sourceRouteID);
        } catch (Exception e) {
            System.out.println("Exception during shapefile export: " + e.toString());
        }
    }

    /**
     * print the edge data of the current route to the console (mostly  for debugging purposes)
     */
    public void printRoute() {
        List<DirectedEdge> edges = this.getRouteAsEdges();
        String resultString = "** ";
        for (DirectedEdge e : edges) {
            resultString += e.getData() + " - ";
        }
        System.out.println(resultString);
    }
}