org.life.sl.graphs.PathSegmentGraph.java Source code

Java tutorial

Introduction

Here is the source code for org.life.sl.graphs.PathSegmentGraph.java

Source

package org.life.sl.graphs;

/*
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/>.
*/

/*
 * The JTS Topology Suite is a collection of Java classes that
 * implement the fundamental operations required to validate a given
 * geo-spatial data set to a known topological specification.
 *
 * Copyright (C) 2001 Vivid Solutions
 *
 * This library 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 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * For more information, contact:
 *
 *     Vivid Solutions
 *     Suite #1A
 *     2328 Government Street
 *     Victoria BC  V8T 5G5
 *     Canada
 *
 *     (250)385-6040
 *     www.vividsolutions.com
 */

import gnu.trove.TIntProcedure;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.Transaction;
import org.geotools.data.collection.ListFeatureCollection;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernatespatial.criterion.SpatialRestrictions;
import org.life.sl.mapmatching.GPSTrack;
import org.life.sl.orm.HibernateUtil;
import org.life.sl.orm.OSMEdge;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.infomatiq.jsi.Rectangle;
import com.infomatiq.jsi.SpatialIndex;
import com.infomatiq.jsi.test.SpatialIndexFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.Point;

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;
import com.vividsolutions.jump.algorithm.EuclideanDistanceToPoint;
import com.vividsolutions.jump.algorithm.PointPairDistance;

/**
 * A planar graph of edges that is analyzed to sew the edges together. The 
 * <code>marked</code> flag on @{link com.vividsolutions.planargraph.Edge}s 
 * and @{link com.vividsolutions.planargraph.Node}s indicates whether they have been
 * logically deleted from the graph.
 *
 * @version 1.7
 */
public class PathSegmentGraph {

    private static final double SPLITSNAPDISTANCE = 1f;

    private float kNearestEdgeDistance = 100.f; // the larger, the slower

    private double xMin, xMax, yMin, yMax;
    private int sourceRouteID;

    // algorithms
    private AllPairsShortestPath allPairsShortestPath;
    private boolean distancesCalculated;
    private LineMergeGraphH4cked lineMergeGraphH4cked;

    private HashMap<Integer, Edge> edgeId__edge = new HashMap<Integer, Edge>();

    private SpatialIndex si;

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

    private Logger logger = Logger.getRootLogger();

    public HashMap<Node, HashMap<Node, Float>> getAPSDistances() {
        return allPairsShortestPath.getDistances();
    }

    public float[][] getAPSDistancesArr() {
        return allPairsShortestPath.getDistancesArr();
    }

    /**
     * default constructor: sets up an empty graph
     */
    public PathSegmentGraph() {
        super();
        distancesCalculated = false;
        setLineMergeGraphH4cked(new LineMergeGraphH4cked());
        Properties p = new Properties();
        p.setProperty("MinNodeEntries", "1");
        p.setProperty("MaxNodeEntries", "10");
        si = SpatialIndexFactory.newInstance("rtree.RTree", p);
    }

    /**
     * initialize the graph using data read from a shapefile
     * @param shapeFile the shapefile containing the network
     * @throws IOException
     */
    public PathSegmentGraph(String shapeFile) throws IOException {
        this();
        addLineStringsFromShape(shapeFile);
    }

    /**
     * initialize the graph from the database (using the complete network)
     * @param i
     */
    public PathSegmentGraph(int i) {
        this();
        addLineStringsFromDatabase();
    }

    /**
     * initialize the graph from a section of the (database-stored) network enveloping the GPS track 
     * @param track array of points on the track under examination
     */
    public PathSegmentGraph(GPSTrack track, float bufferSize, String dumpFile) {
        this();
        this.sourceRouteID = track.getSourceRouteID();
        addLineStringsFromDatabase(track, bufferSize, dumpFile);
    }

    /**
     * Create a new graph, with linestrings read from a shapefile 
     * @param shapeFile name of the shapefile containing the network data
     * @throws IOException
     */
    public void addLineStringsFromShape(String shapeFile) throws IOException {
        setLineMergeGraphH4cked(new LineMergeGraphH4cked());
        distancesCalculated = false;
        LineStringReader reader = new LineStringReader(shapeFile);

        reader.read();
        boolean first = true;
        int id = 0;
        for (LineString ls : reader.getLineStrings()) {
            if (first == true) {
                xMax = xMin = ls.getCoordinate().x;
                yMax = yMin = ls.getCoordinate().y;
                first = false;
            }

            addLineString(ls, ++id, (short) 0, (short) 0, 0);
        }
    }

    /**
     * Create a new graph, with linestrings read from the database (using the complete table OSMEdge!) 
     */
    public void addLineStringsFromDatabase() {
        addLineStringsFromDatabase(null, 0, "");
    }

    /**
     * Find the edge nearest to a given point
     * @param p point for which to calculate the nearest edge
     * @return the nearest edge to the point
     */
    public Edge findNearestEdge(Coordinate c, float nearestDist) {
        Point p = fact.createPoint(c);
        com.infomatiq.jsi.Point pp = new com.infomatiq.jsi.Point((float) p.getX(), (float) p.getY());

        ReturnArray r = new ReturnArray();
        this.si.nearestNUnsorted(pp, r, 8, nearestDist); // TODO: decide how to choose value for furthestDistance? 10 meters is just a guess.
        double dMin = Double.MAX_VALUE, d = 0;
        Edge e0 = null;
        for (Integer i : r.getResult()) {
            Edge e = (Edge) getEdgeByID(i);
            d = p.distance(((LineMergeEdge) e).getLine());
            if (d < dMin) {
                dMin = d;
                e0 = e;
            }
        }
        return e0;
    }

    public Edge getEdgeByID(int edgeId) {
        return edgeId__edge.get(edgeId);
    }

    /**
     * Split the graph by splitting an Edge at a point into 2 Edges
     * @param c coordinates at which to split the graph 
     * @param iSplit split index (the i-th split point, used to create the new edge IDs)
     */
    public void splitGraphAtPoint(Coordinate c, int iSplit) {
        Edge nearestEdge = null;
        float nearestDist = kNearestEdgeDistance;
        while (nearestEdge == null && nearestDist / kNearestEdgeDistance < 20.) {
            nearestEdge = findNearestEdge(c, nearestDist);
            if (nearestEdge == null) {
                nearestDist *= 2.;
                logger.info("Increasing kNearestEdgeDistance: " + nearestDist);
            }
        }
        // get the projected point on the nearest edge

        PointPairDistance ppd = new PointPairDistance();
        @SuppressWarnings("rawtypes")
        LineString nearestLineString = (LineString) ((HashMap) nearestEdge.getData()).get("geom");
        EuclideanDistanceToPoint.computeDistance(nearestLineString, c, ppd);

        Coordinate resultcoord = null;

        for (Coordinate cc : ppd.getCoordinates()) {
            // System.out.println(cc); 
            if (cc.equals(c) == false) {
                resultcoord = cc;
            }
        }

        // let us loop through the vertices

        Coordinate[] nearestLineStringCoordinates = nearestLineString.getCoordinates();

        ArrayList<Coordinate> fromVertices = new ArrayList<Coordinate>();
        ArrayList<Coordinate> toVertices = new ArrayList<Coordinate>();
        fromVertices.add(nearestLineStringCoordinates[0]);
        boolean isAfter = false;

        for (int i = 1; i < nearestLineStringCoordinates.length; i++) {
            Coordinate pt0 = nearestLineStringCoordinates[i - 1];
            Coordinate pt1 = nearestLineStringCoordinates[i];

            // let us build a segment between 2 nodes

            Coordinate[] segmentCoords = { pt0, pt1 };
            LineString segment = fact.createLineString(segmentCoords);

            // let us check whether the projected point (resultcoord) is on the node

            if (fact.createPoint(resultcoord).distance(segment) < SPLITSNAPDISTANCE) {
                // point within, let us split 
                fromVertices.add(resultcoord);
                toVertices.add(resultcoord);
                isAfter = true;
            } else {
                if (!isAfter) {
                    fromVertices.add(nearestLineStringCoordinates[i]);
                } else {
                    toVertices.add(nearestLineStringCoordinates[i]);
                }
            }
        }
        //if (nearestLineStringCoordinates.length==2) {
        toVertices.add(nearestLineStringCoordinates[nearestLineStringCoordinates.length - 1]);
        //}

        // TODO: generate nice edge ids here
        int id1 = -2 * iSplit - 1;
        int id2 = -2 * iSplit - 2;
        // replace straight lines with lines including vertices
        Coordinate[] fromArray = new Coordinate[fromVertices.size()];
        Coordinate[] toArray = new Coordinate[toVertices.size()];

        fromVertices.toArray(fromArray);
        toVertices.toArray(toArray);

        addLineString(fact.createLineString(fromArray), id1);
        addLineString(fact.createLineString(toArray), id2);

        this.removeEdge(nearestEdge);

        System.out.println("Graph split @ " + resultcoord);
    }

    public void addOSMEdgeToSpatialIndex(OSMEdge osmEdge) {
        // System.out.println("added edge with ID " + osmEdge.getId());
        // counter__edge.put(osmEdge.getId(), osmEdge.getGeometry());   // store edges in the hash map

        Geometry env = osmEdge.getGeometry().getBoundary();
        //  (minx, miny), (maxx, miny), (maxx, maxy), (minx, maxy), (minx, miny).

        if (env.getNumGeometries() > 0) {
            Point p1 = (Point) env.getGeometryN(0);
            Point p2 = (Point) env.getGeometryN(1);

            si.add(new Rectangle((float) p1.getX(), (float) p1.getY(), (float) p2.getX(), (float) p2.getY()),
                    osmEdge.getId());
        }
    }

    /**
     * Create a new graph, with linestrings read from the database (optionally using only a buffer around a given track for the network)
     * @param track GPS track consisting of a list of data points
     * @param bufferSize size of the buffer to select around the track
     * @param dumpFile if not empty, the path of a shapefile to dump the network into.
     */
    public void addLineStringsFromDatabase(ArrayList<Point> track, float bufferSize, String dumpFile) {
        setLineMergeGraphH4cked(new LineMergeGraphH4cked());
        distancesCalculated = false;

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();

        //Query gpsResults = session.createQuery("from" );

        if (track == null) {
            Query result = session.createQuery("from OSMEdge");

            @SuppressWarnings("unchecked")
            Iterator<OSMEdge> iter = result.iterate();
            while (iter.hasNext()) {
                OSMEdge o = iter.next();
                LineString g = o.getGeometry();
                addLineString(g, o.getId(), o.getEnvtype(), o.getCyktype(), o.getGroenm());
                // let us add this thing to the spatial index
                addOSMEdgeToSpatialIndex(o);

            }
        } else {
            // let us join the nodes of the track to a linestring ....

            Iterator<Point> trackIter = track.iterator();
            Coordinate[] coords = new Coordinate[track.size()];
            int i = 0;
            while (trackIter.hasNext()) {
                Point p = trackIter.next();
                coords[i] = p.getCoordinate();
                i++;
            }
            if (i > 1) {
                LineString l = fact.createLineString(coords);

                Criteria testCriteria = session.createCriteria(OSMEdge.class);
                if (bufferSize > 0.) { // ... build a buffer ...
                    Geometry buffer = l.buffer(bufferSize);
                    //            testCriteria.add(SpatialRestrictions.within("geometry", buffer));
                    testCriteria.add(SpatialRestrictions.intersects("geometry", buffer));
                }
                @SuppressWarnings("unchecked")
                List<OSMEdge> result = testCriteria.list();

                logger.info("Spatial query selected " + result.size() + " edges");

                Iterator<OSMEdge> iter = result.iterator();
                i = 0;
                while (iter.hasNext()) {
                    i++;
                    OSMEdge o = iter.next();
                    LineString g = o.getGeometry();
                    addLineString(g, o.getId(), o.getEnvtype(), o.getCyktype(), o.getGroenm());
                    // let us add this thing to the spatial index
                    addOSMEdgeToSpatialIndex(o);
                }

                // if required, dump the graph to a shapefile:
                if (dumpFile != "") {
                    try {
                        this.dumpBuffer(result, dumpFile);
                        logger.info("buffer dumped");
                    } catch (Exception e) {
                        logger.error("error dumping buffer: " + e);
                    }
                }
            } else {
                logger.error("error: track has <2 coordinates");
            }
        }

        session.disconnect();
    }

    public void dumpBuffer(List<OSMEdge> edges, String filename) 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
        SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(TYPE);
        ArrayList<SimpleFeature> features = new ArrayList<SimpleFeature>();

        Iterator<OSMEdge> iter = edges.iterator();
        while (iter.hasNext()) {
            OSMEdge o = iter.next();
            SimpleFeature feature = featureBuilder.buildFeature(null);
            feature.setDefaultGeometry(o.getGeometry());
            features.add(feature);
        }
        SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);//FeatureCollections.newCollection();

        logger.info("Writing to shapefile " + filename);
        File newFile = new File(filename);
        // File newFile = getNewShapeFile(file);

        ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();

        Map<String, Serializable> params = new HashMap<String, Serializable>();
        params.put("url", newFile.toURI().toURL());
        params.put("create spatial index", Boolean.TRUE);

        ShapefileDataStore newDataStore = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
        newDataStore.createSchema(TYPE);

        // You can comment out this line if you are using the createFeatureType method (at end of
        // class file) rather than DataUtilities.createType
        newDataStore.forceSchemaCRS(DefaultGeographicCRS.WGS84);

        Transaction transaction = new DefaultTransaction("create");

        String typeName = newDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);

        if (featureSource instanceof SimpleFeatureStore) {
            SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;

            featureStore.setTransaction(transaction);
            try {
                featureStore.addFeatures(collection);
                transaction.commit();
            } catch (Exception problem) {
                problem.printStackTrace();
                transaction.rollback();
            } finally {
                transaction.close();
            }
            //System.exit(0); // success!
        } else {
            logger.error(typeName + " does not support read/write access");
            //System.exit(1);   // exit program with status 1 (error)
        }
    }

    public void addLineString(LineString lineString, int id) {
        addLineString(lineString, id, (short) 0, (short) 0, 0);
    }

    /**
     * Adds an Edge, DirectedEdges, and Nodes for the given LineString representation
     * of an edge. Snaps all vertices according to GraphParams.GLOBAL_SNAP_DIST
     * @param lineString
     * @param id : the id coming out of OSM
     */
    public void addLineString(LineString lineString, int id, short envType, short cykType, double groenM) {

        distancesCalculated = false;

        if (lineString.isEmpty()) {
            return;
        }
        if (lineString.getCoordinates().length < 2) {
            System.exit(1);
        }

        Coordinate[] coordinates = lineString.getCoordinates();
        modifyEnvelope(coordinates);

        if (GraphParams.getInstance().getSnap()) {
            double sd = GraphParams.getInstance().getSnapDistance();
            for (Coordinate c : coordinates) {
                c.x = c.x - (c.x % sd);
                c.y = c.y - (c.y % sd);
            }
        }

        Edge edge = getLineMergeGraphH4cked().addEdge(lineString);
        edgeId__edge.put(id, edge);
        if (edge != null) { // edge might not have been added because of coinciding coordinates
            if (lineString.getUserData() == null)
                lineString.setUserData(new HashMap<String, Object>(3));
            @SuppressWarnings("unchecked")
            HashMap<String, Object> userdata = (HashMap<String, Object>) lineString.getUserData();
            // HashMap<String, Object> hm = new HashMap<String, Object>();

            userdata.put("id", id);
            userdata.put("et", envType);
            userdata.put("ct", cykType);
            userdata.put("gm", groenM); // groenM
            userdata.put("geom", lineString);
            edge.setData(userdata);

            // add edge data to the spatial index

        }
    }

    private void modifyEnvelope(Coordinate[] coordinates) {
        for (Coordinate c : coordinates) {
            if (c.x < xMin)
                xMin = c.x;
            if (c.x > xMax)
                xMax = c.x;
            if (c.y < yMin)
                yMin = c.y;
            if (c.y > yMax)
                yMax = c.y;
        }
    }

    /**
     * Get the distance from one Node to another Node in the graph.
     * @param from The Node to get the distance from.
     * @param to The Node to get the distance to.
     * @return The distance between two Nodes
     */
    public double getDistance(Node from, Node to) {
        if (!distancesCalculated) {
            allPairsShortestPath = new AllPairsShortestPath(this);
            distancesCalculated = true;
        }
        return allPairsShortestPath.getDistance(from, to);
    }

    public void calculateDistances() {
        if (!distancesCalculated) {
            allPairsShortestPath = new AllPairsShortestPath(this);
            distancesCalculated = true;
        }
    }

    /**
     * Find the node in the graph that is nearest the query coordinate. Implemented as linear search, can be vastly improved using e.g. a kd-tree
     * @param query The Coordinate used for the query.
     * @return The Node in the graph that is nearest the query Coordinate.
     */
    public Node findClosestNode(Coordinate query) {

        double bestDistance = Double.MAX_VALUE;
        Node closestNode = null;
        for (Node n : getNodes()) {
            double currentDistance = n.getCoordinate().distance(query);
            if (currentDistance < bestDistance) {
                closestNode = n;
                bestDistance = currentDistance;
            }
        }
        return closestNode;
    }

    public ArrayList<Node> getNodes() {
        ArrayList<Node> nodes = new ArrayList<Node>();
        for (Object obj : getLineMergeGraphH4cked().getNodes()) {
            nodes.add((Node) obj);
        }
        return nodes;
    }

    public Edge getEdgeByNodes(Node n1, Node n2) {
        @SuppressWarnings("unchecked")
        Iterator<Edge> ei = Node.getEdgesBetween(n1, n2).iterator();
        return (ei.hasNext() ? ei.next() : null);
    }

    public Edge getSingleEdgeAtNode(Node n1) {
        try {
            Object obj = n1.getOutEdges().getEdges().get(0);
            return ((DirectedEdge) obj).getEdge();
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    }

    public Envelope getEnvelope() {
        Envelope env = new Envelope(xMin, xMax, yMin, yMax);
        return env;
    }

    @SuppressWarnings("unchecked")
    public Collection<Edge> getEdges() {
        return (Collection<Edge>) getLineMergeGraphH4cked().getEdges();
    }

    /*
     * removes an edge 
     */
    public void removeEdge(Edge edge) {
        if (!edge.isRemoved()) {
            getLineMergeGraphH4cked().remove(edge);
        }
    }

    public int getSize_Edges() {
        return getLineMergeGraphH4cked().getEdges().size();
    }

    public int getSize_Nodes() {
        return getLineMergeGraphH4cked().getNodes().size();
    }

    public LineMergeGraphH4cked getLineMergeGraphH4cked() {
        return lineMergeGraphH4cked;
    }

    public void setLineMergeGraphH4cked(LineMergeGraphH4cked lineMergeGraphH4cked) {
        this.lineMergeGraphH4cked = lineMergeGraphH4cked;
    }

    public double getMeanDegree() {
        List<Node> nodes = getNodes();
        int n = 0, ns = 0, nMax = 0, nMin = Integer.MAX_VALUE;
        double nd = 0;
        for (Node node : nodes) {
            n = node.getDegree();
            ns += n;
            if (n > nMax)
                nMax = n;
            if (n < nMin)
                nMin = n;
        }
        nd = (double) ns / (double) nodes.size();
        System.out.printf("Graph: %d nodes, d_mean = %2.3f, d_max = %d, d_min = %d\n", nodes.size(), nd, nMax,
                nMin);
        return nd;
    }

    public double getNCombinations() {
        List<Node> nodes = getNodes();
        double nd = 1.;
        int n;
        for (Node node : nodes) {
            n = Math.max(node.getDegree() - 1, 1);
            nd *= n;
        }
        return Math.sqrt(nd);
    }

    public int getSourceRouteID() {
        return sourceRouteID;
    }

    /**
     * Result container; looks like a bit of overkill, but is required by SpatialIndex...
     */
    class ReturnArray implements TIntProcedure {

        private ArrayList<Integer> result = new ArrayList<Integer>();

        public boolean execute(int value) {
            this.result.add(value);
            return true;
        }

        ArrayList<Integer> getResult() {
            return this.result;
        }
    }

    public static void main(String[] args) {
        new PathSegmentGraph();
    }

}