[英]Java Geotools: Snap to line identifiying line that was snapped to
我正在嘗試編寫一個Java程序,它將大量的GPS坐標捕捉到一個行形狀文件(一個道路網絡),不僅返回新的坐標,而且還會返回捕捉到的線段的唯一標識符。 如果此標識符是FID,在其他語言中使用的“索引”(即,其中1是第一個特征等)或屬性表中的任何列,則無關緊要。
我在R中使用maptools :: snapPointsToLines函數完成了這項工作,但考慮到我需要處理的數據量,這是不可擴展的,所以我正在尋找Java來更快地處理數據以便在R中進行分析。
我的代碼(下面)目前與用於捕捉的geotools教程非常相似,我在一個(1900萬行)的GPS點數中讀取的微小差異而不是生成它們,我寫了一個結果的CSV。 它捕捉得很好,比我得到的快得多,但我不知道如何識別捕捉到的線。 可用的文檔似乎涵蓋了對特征集的查詢和過濾,我無法特別適用於此代碼創建的索引行對象,並且我的代碼toString()
的現有函數返回了我無法理解的內容,例如com.vividsolutions.jts.linearreff.LocationIndexedLine@74cec793
。
基本上,我只想讓lineID字段生成任何其他GIS軟件或語言可以匹配特定路段的內容。
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這樣的領域特定語言中接受過自我訓練; 我不是一個使用代碼的人,所以如果解決方案看起來很明顯,我可能缺乏基本理論!
ps我知道有更好的地圖匹配解決方案(graphhopper等),我只是想開始使用eas!
謝謝!
我會盡量避免在JTS兔子洞里走得那么遠,並堅持使用GeoTools(當然我是GeoTools開發者,所以我會說)。
首先,我使用SpatialIndexFeatureCollection
來保存我的線條(假設它們適合內存,否則PostGIS表格就是這樣)。 這使我不得不建立自己的索引。
然后我會使用CSVDataStore
來保存從GPS流中解析自己的點(因為我很懶,而且那里也有很多錯誤)。
這意味着大部分工作歸結為此循環, DWITHIN
找到指定距離內的所有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)(包括其id和名稱等),最近點(bestPoint)和移動距離(minDist)。
我可能再次使用CSVDatastore
來重新編寫功能。
如果你有數百萬點,我可能會考慮使用FilterFactory
直接創建過濾器,而不是使用ECQL
解析器。
根據iant在接受的答案中提供的代碼,我改變了我的代碼,如下所示,我發布給其他人使用Google搜索這類問題。
ECQL
件事:確保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);
}
}
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.