/
SnapToLine.java
170 lines (147 loc) · 6.69 KB
/
SnapToLine.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/*
* GeoTools Sample code and Tutorials by Open Source Geospatial Foundation, and others
* https://docs.geotools.org
*
* To the extent possible under law, the author(s) have dedicated all copyright
* and related and neighboring rights to this software to the public domain worldwide.
* This software is distributed without any warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication along with this
* software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
package org.geotools.jts;
import java.io.File;
import java.util.List;
import java.util.Random;
import org.geotools.api.data.FeatureSource;
import org.geotools.api.data.FileDataStore;
import org.geotools.api.data.FileDataStoreFinder;
import org.geotools.api.feature.Feature;
import org.geotools.api.feature.FeatureVisitor;
import org.geotools.api.feature.simple.SimpleFeature;
import org.geotools.data.util.NullProgressListener;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.util.SuppressFBWarnings;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.MultiLineString;
import org.locationtech.jts.index.SpatialIndex;
import org.locationtech.jts.index.strtree.STRtree;
import org.locationtech.jts.linearref.LinearLocation;
import org.locationtech.jts.linearref.LocationIndexedLine;
@SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE")
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;
}
// load shapefile end (docs marker)
final SpatialIndex index = new STRtree();
FeatureCollection features = source.getFeatures();
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());
// cache features end (docs marker)
/*
* For test data, we generate a large number of points placed randomly
* within the bounding rectangle of the features.
*/
final int NUM_POINTS = 10000;
ReferencedEnvelope bounds = features.getBounds();
Coordinate[] points = new Coordinate[NUM_POINTS];
Random rand = new Random(file.hashCode());
for (int i = 0; i < NUM_POINTS; i++) {
points[i] =
new Coordinate(
bounds.getMinX() + rand.nextDouble() * bounds.getWidth(),
bounds.getMinY() + rand.nextDouble() * bounds.getHeight());
}
// generate points end (docs marker)
/*
* We defined the maximum distance that a line can be from a point
* to be a candidate for snapping (1% of the width of the feature
* bounds for this example).
*/
final double MAX_SEARCH_DISTANCE = bounds.getSpan(0) / 100.0;
// Maximum time to spend running the snapping process (milliseconds)
final long DURATION = 5000;
int pointsProcessed = 0;
int pointsSnapped = 0;
long elapsedTime = 0;
long startTime = System.currentTimeMillis();
while (pointsProcessed < NUM_POINTS
&& (elapsedTime = System.currentTimeMillis() - startTime) < DURATION) {
// Get point and create search envelope
Coordinate pt = points[pointsProcessed++];
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.
*/
@SuppressWarnings("unchecked")
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;
}
}
if (minDistPoint == null) {
// No line close enough to snap the point to
System.out.println(pt + "- X");
} else {
System.out.printf("%s - snapped by moving %.4f\n", pt.toString(), minDist);
pointsSnapped++;
}
}
System.out.printf(
"Processed %d points (%.2f points per second). \n" + "Snapped %d points.\n\n",
pointsProcessed, 1000.0 * pointsProcessed / elapsedTime, pointsSnapped);
}
}