Java Geotools: привязка к линии, идентифицирующая линию, к которой была привязана

Я пытаюсь написать программу на Java, которая будет привязывать большую серию GPS-координат к шейп-файлу линии (дорожной сети) и возвращать не только новые координаты, но и уникальный идентификатор для привязанного сегмента линии. Неважно, является ли этот идентификатор FID, «индексом», используемым в других языках (например, где 1 — первая функция и т. д.), или любым столбцом в таблице атрибутов.

Я сделал это в R, используя ссылку maptools::snapPointsToLines, но это не масштабируется, учитывая объемы данных, которые мне нужно обработать, поэтому я ищу Java для обработки данные быстрее для анализа в R.

Мой код (ниже) в настоящее время очень похож на учебник по геоинструментам для привязки, с небольшими отличиями, которые я читаю в CSV (19 миллионов строк) точек GPS вместо их создания, и я записываю CSV результатов. Это нормально и намного быстрее, чем то, что я получал, но я понятия не имею, как определить линию, к которой привязана. Доступная документация, по-видимому, охватывает запросы и фильтрацию наборов функций, которые я не могу сделать применимыми, особенно к объекту строки индекса, который создает этот код, и существующая функция в моем коде toString() возвращает что-то непонятное для моих целей, например com.vividsolutions.jts.linearreff.LocationIndexedLine@74cec793.

По сути, я просто хочу, чтобы поле lineID создавало что-то, что любое другое программное обеспечение или язык ГИС может сопоставить с конкретным сегментом дороги.

package org.geotools.tutorial.quickstart;

import java.io.*;
import java.util.List;
import java.util.Arrays;

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.MultiLineString;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.strtree.STRtree;
import com.vividsolutions.jts.linearref.LinearLocation;
import com.vividsolutions.jts.linearref.LocationIndexedLine;

import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.util.NullProgressListener;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import com.opencsv.*;

public class SnapToLine {

    public static void main(String[] args) throws Exception {

        /*
         * Open a shapefile. You should choose one with line features
         * (LineString or MultiLineString geometry)
         * 
         */
        File file = JFileDataStoreChooser.showOpenFile("shp", null);
        if (file == null) {
            return;
        }

        FileDataStore store = FileDataStoreFinder.getDataStore(file);
        FeatureSource source = store.getFeatureSource();

        // Check that we have line features
        Class<?> geomBinding = source.getSchema().getGeometryDescriptor().getType().getBinding();
        boolean isLine = geomBinding != null 
                && (LineString.class.isAssignableFrom(geomBinding) ||
                    MultiLineString.class.isAssignableFrom(geomBinding));

        if (!isLine) {
            System.out.println("This example needs a shapefile with line features");
            return;
        }
         final SpatialIndex index = new STRtree();
        FeatureCollection features = source.getFeatures();
        //FeatureCollection featurecollection = source.getFeatures(Query.FIDS);
        System.out.println("Slurping in features ...");
        features.accepts(new FeatureVisitor() {

            @Override
            public void visit(Feature feature) {
                SimpleFeature simpleFeature = (SimpleFeature) feature;
                Geometry geom = (MultiLineString) simpleFeature.getDefaultGeometry();
                // Just in case: check for  null or empty geometry
                if (geom != null) {
                    Envelope env = geom.getEnvelopeInternal();
                    if (!env.isNull()) {
                        index.insert(env, new LocationIndexedLine(geom));
                    }
                }
            }
        }, new NullProgressListener());
 /*

 /*
         * We defined the maximum distance that a line can be from a point
         * to be a candidate for snapping 
         */

        ReferencedEnvelope bounds = features.getBounds();
        final double MAX_SEARCH_DISTANCE = bounds.getSpan(0) / 1000.0;



        int pointsProcessed = 0;
        int pointsSnapped = 0;
        long elapsedTime = 0;
        long startTime = System.currentTimeMillis();
        double longiOut;
        double latiOut;
        int moved;
        String lineID   = "NA";

        //Open up the CSVReader. Reading in line by line to avoid memory failure.

        CSVReader csvReader = new CSVReader(new FileReader(new File("fakedata.csv")));
        String[] rowIn;



        //open up the CSVwriter
        String outcsv = "fakedataOUT.csv";
        CSVWriter writer = new CSVWriter(new FileWriter(outcsv));



        while ((rowIn = csvReader.readNext()) != null) {

            // Get point and create search envelope
            pointsProcessed++;
            double longi = Double.parseDouble(rowIn[0]);
            double lati  = Double.parseDouble(rowIn[1]);
            Coordinate pt = new Coordinate(longi, lati);
            Envelope search = new Envelope(pt);
            search.expandBy(MAX_SEARCH_DISTANCE);

            /*
             * Query the spatial index for objects within the search envelope.
             * Note that this just compares the point envelope to the line envelopes
             * so it is possible that the point is actually more distant than
             * MAX_SEARCH_DISTANCE from a line.
             */
            List<LocationIndexedLine> lines = index.query(search);

            // Initialize the minimum distance found to our maximum acceptable
            // distance plus a little bit
            double minDist = MAX_SEARCH_DISTANCE + 1.0e-6;
            Coordinate minDistPoint = null;

            for (LocationIndexedLine line : lines) {
                LinearLocation here = line.project(pt);
                Coordinate point = line.extractPoint(here);
                double dist = point.distance(pt);
                if (dist < minDist) {
                    minDist = dist;
                    minDistPoint = point;
                    lineID = line.toString();
                }
            }


            if (minDistPoint == null) {
                // No line close enough to snap the point to
                System.out.println(pt + "- X");
                longiOut = longi;
                latiOut  = lati;
                moved    = 0;
                lineID   = "NA";
            } else {
                System.out.printf("%s - snapped by moving %.4f\n", 
                        pt.toString(), minDist);
                longiOut = minDistPoint.x;
                latiOut  = minDistPoint.y;
                moved    = 1;        
                pointsSnapped++;
            }
    //write a new row

    String [] rowOut = {Double.toString(longiOut), Double.toString(latiOut), Integer.toString(moved), lineID}; 
    writer.writeNext(rowOut);
        }

        System.out.printf("Processed %d points (%.2f points per second). \n"
                + "Snapped %d points.\n\n",
                pointsProcessed,
                1000.0 * pointsProcessed / elapsedTime,
                pointsSnapped);
        writer.close();
    }
}

Я не только новичок в Java, но и самоучка в предметно-ориентированных языках, таких как R; Я не столько кодер, сколько тот, кто использует код, поэтому, если решение кажется очевидным, мне может не хватать элементарной теории!

p.s. Я знаю, что есть лучшие решения для сопоставления карт (графоппер и т. д.), я просто пытаюсь начать!

Благодарю вас!


person GeoWork    schedule 10.01.2017    source источник


Ответы (2)


Я бы постарался не заходить так далеко в кроличью нору JTS и придерживаться GeoTools (конечно, я разработчик GeoTools, так что я бы так сказал).

Сначала я бы использовал SpatialIndexFeatureCollection для удерживайте мои строки (при условии, что они помещаются в памяти, в противном случае лучше использовать таблицу PostGIS). Это избавляет меня от необходимости создавать собственный индекс.

Затем я бы использовал CSVDataStore, чтобы не анализировать собственные точки. потока GPS (потому что я ленив, и там тоже много чего не так).

Это означает, что основная часть работы сводится к этому циклу, DWITHIN находит все объекты с указанным расстоянием:

try (SimpleFeatureIterator itr = pointFeatures.getFeatures().features()) { 
  while (itr.hasNext()) {
    SimpleFeature f = itr.next();
    Geometry snapee = (Geometry) f.getDefaultGeometry();
    Filter filter = ECQL.toFilter("DWITH(\"the_geom\",'" + writer.write(snapee) + "'," + MAX_SEARCH_DISTANCE + ")");
    SimpleFeatureCollection possibles = indexed.subCollection(filter);
    double minDist = Double.POSITIVE_INFINITY;
    SimpleFeature bestFit = null;
    Coordinate bestPoint = null;
    try (SimpleFeatureIterator pItr = possibles.features()) {
      while (pItr.hasNext()) {
        SimpleFeature p = pItr.next();
        Geometry line = (Geometry) p.getDefaultGeometry();

        double dist = snapee.distance(line);
        if (dist < minDist) {
          minDist = dist;
          bestPoint = DistanceOp.nearestPoints(snapee, line)[1];
          bestFit  = p;
        }
      }
    }

В конце этого цикла вы должны узнать ближайший объект (bestFit) по линиям (включая его идентификатор, имя и т. д.), ближайшую точку (bestPoint) и пройденное расстояние (minDist).

Опять же, я бы, вероятно, использовал CSVDatastore, чтобы записать функции обратно.

Если у вас есть миллионы точек, я бы, вероятно, посмотрел на использование FilterFactory для создания фильтра напрямую вместо использования синтаксического анализатора ECQL.

person Ian Turton    schedule 11.01.2017
comment
Благодарю вас! Это привело меня туда, куда мне нужно было пойти, и сделало вещи немного более понятными. - person GeoWork; 16.01.2017

Основываясь на коде, предоставленном iant в принятом ответе, я изменил свой код следующим образом, который я публикую для всех, кто задает этот вопрос.

Пара вещей: убедитесь, что в синтаксическом анализаторе ECQL есть модуль (я еще не пробовал предложение FilterFactory). CSVDataStore рассматривается по следующей ссылке. Обратите внимание, что по умолчанию долгота и широта жестко запрограммированы как lon и lat соответственно. http://docs.geotools.org/latest/userguide/tutorial/datastore/intro.html

package org.geotools.tutorial.quickstart;

import java.io.*;
import java.util.List;
import java.util.Arrays;

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.MultiLineString;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.strtree.STRtree;
import com.vividsolutions.jts.linearref.LinearLocation;
import com.vividsolutions.jts.linearref.LocationIndexedLine;
import com.vividsolutions.jts.operation.distance.DistanceOp;
import com.vividsolutions.jts.io.WKTWriter;

import org.geotools.data.FeatureSource;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.util.NullProgressListener;
import org.geotools.data.collection.SpatialIndexFeatureCollection;
import org.geotools.data.collection.SpatialIndexFeatureSource;
import org.geotools.filter.text.ecql.ECQL;

import org.opengis.filter.Filter;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import com.opencsv.*;
import com.csvreader.CsvReader;




        public class SnapToLine {

    public static void main(String[] args) throws Exception {

        //input and output files and other parameters
        String inputpoints = "/home/bitre/fakedata.csv";
        String outcsv = "fakedataOUT.csv";

        final double MAX_SEARCH_DISTANCE = 0.5;


        /*
         * Open a shapefile. You should choose one with line features
         * (LineString or MultiLineString geometry)
         * 
         */
        File file = JFileDataStoreChooser.showOpenFile("shp", null);
        if (file == null) {
            return;
        }

        FileDataStore store = FileDataStoreFinder.getDataStore(file);
        SimpleFeatureSource source = store.getFeatureSource();

        // Check that we have line features
        Class<?> geomBinding = source.getSchema().getGeometryDescriptor().getType().getBinding();
        boolean isLine = geomBinding != null 
                && (LineString.class.isAssignableFrom(geomBinding) ||
                    MultiLineString.class.isAssignableFrom(geomBinding));

        if (!isLine) {
            System.out.println("This example needs a shapefile with line features");
            return;
        }



        SimpleFeatureCollection features = source.getFeatures();

        SpatialIndexFeatureCollection indexed = new SpatialIndexFeatureCollection(features);



 /*

 /*
         * We defined the maximum distance that a line can be from a point
         * to be a candidate for snapping 
         */

        ReferencedEnvelope bounds = features.getBounds();




        //open up the CSVwriter

        CSVWriter csvWriter = new CSVWriter(new FileWriter(outcsv));



        //CSVDataStore features for the points


        CSVDataStore pointFeaturesCSV = new CSVDataStore(new File(inputpoints));
        String typeName = pointFeaturesCSV.getTypeNames()[0];
        SimpleFeatureSource pointFeatures = pointFeaturesCSV.getFeatureSource(typeName); 


        double longiOut;
        double latiOut;
        int progress = 0;
        int remn;
        String[] rowOut = new String[4];

                try (SimpleFeatureIterator itr = pointFeatures.getFeatures().features()) { 
           while (itr.hasNext()) {
             SimpleFeature f = itr.next();
             Geometry snapee = (Geometry) f.getDefaultGeometry();
             WKTWriter writer = new WKTWriter();
             Filter filter = ECQL.toFilter("DWITHIN(\"the_geom\",'" + writer.write(snapee) + "'," + MAX_SEARCH_DISTANCE + "," + "kilometers" + ")");
             SimpleFeatureCollection possibles = indexed.subCollection(filter);
             double minDist = Double.POSITIVE_INFINITY;
             SimpleFeature bestFit = null;
             Coordinate bestPoint = null;
             try (SimpleFeatureIterator pItr = possibles.features()) {
               while (pItr.hasNext()) {
                 SimpleFeature p = pItr.next();
                 Geometry line = (Geometry) p.getDefaultGeometry();

                 double dist = snapee.distance(line);
                 if (dist < minDist) {
                   minDist = dist;
                   bestPoint = DistanceOp.nearestPoints(snapee, line)[1]; // google DistanceOp
                   bestFit  = p;
                 }
                 longiOut = bestPoint.x;
                 latiOut = bestPoint.y;
                 rowOut[0] = bestFit.getID();
                 rowOut[1] = Double.toString(minDist);
                 rowOut[2] = Double.toString(longiOut);
                 rowOut[3] = Double.toString(latiOut);

                 //rowOut = {bestFit.getID(), Double.toString(minDist), Double.toString(longiOut), Double.toString(latiOut)};

               }
                 csvWriter.writeNext(rowOut);
                progress ++;
                remn = progress % 1000000;
                if(remn == 0){
                    System.out.println("Just snapped line" + progress);
                }

             }

           }


       }
    }
}
person GeoWork    schedule 16.01.2017