planetiler/planetiler-core/src/main/java/com/onthegomap/planetiler/geo/PointIndex.java

107 wiersze
3.3 KiB
Java
Czysty Zwykły widok Historia

package com.onthegomap.planetiler.geo;
2021-07-14 09:31:54 +00:00
import java.util.ArrayList;
import java.util.List;
2021-07-26 11:27:56 +00:00
import java.util.Objects;
2021-08-11 12:40:49 +00:00
import javax.annotation.concurrent.ThreadSafe;
2021-07-14 09:31:54 +00:00
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryCollection;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.index.strtree.STRtree;
2021-09-10 00:46:20 +00:00
/**
* Index to efficiently query points within a radius from a point.
* <p>
* Writes and reads are thread-safe, but all writes must occur before reads.
*
* @param <T> the type of value associated with each point
*/
2021-08-11 12:40:49 +00:00
@ThreadSafe
2021-07-14 09:31:54 +00:00
public class PointIndex<T> {
private record GeomWithData<T> (Coordinate coord, T data) {}
2021-07-14 09:31:54 +00:00
private final STRtree index = new STRtree();
private PointIndex() {}
2021-07-14 09:31:54 +00:00
public static <T> PointIndex<T> create() {
return new PointIndex<>();
}
2021-07-26 11:27:56 +00:00
private volatile boolean built = false;
private void build() {
if (!built) {
synchronized (this) {
if (!built) {
index.build();
built = true;
}
}
}
}
2021-09-10 00:46:20 +00:00
/** Returns the data associated with all indexed points within a radius from {@code point}. */
2021-07-14 09:31:54 +00:00
public List<T> getWithin(Point point, double threshold) {
2021-07-26 11:27:56 +00:00
build();
2021-07-14 09:31:54 +00:00
Coordinate coord = point.getCoordinate();
2021-09-10 00:46:20 +00:00
// pre-filter by rectangular envelope
2021-07-14 09:31:54 +00:00
Envelope envelope = point.getEnvelopeInternal();
envelope.expandBy(threshold);
List<?> items = index.query(envelope);
List<T> result = new ArrayList<>(items.size());
2021-09-10 00:46:20 +00:00
// then post-filter by circular radius
for (Object item : items) {
if (item instanceof GeomWithData<?> value) {
2021-07-14 09:31:54 +00:00
double distance = value.coord.distance(coord);
if (distance <= threshold) {
@SuppressWarnings("unchecked") T t = (T) value.data;
result.add(t);
}
}
}
return result;
}
2021-09-10 00:46:20 +00:00
/** Returns the data associated with the nearest indexed point to {@code point}, up to a certain distance. */
2021-07-14 09:31:54 +00:00
public T getNearest(Point point, double threshold) {
2021-07-26 11:27:56 +00:00
build();
2021-07-14 09:31:54 +00:00
Coordinate coord = point.getCoordinate();
Envelope envelope = point.getEnvelopeInternal();
envelope.expandBy(threshold);
List<?> items = index.query(envelope);
double nearestDistance = Double.MAX_VALUE;
T nearestValue = null;
for (Object item : items) {
if (item instanceof GeomWithData<?> value) {
2021-07-14 09:31:54 +00:00
double distance = value.coord.distance(coord);
if (distance < nearestDistance) {
@SuppressWarnings("unchecked") T t = (T) value.data;
nearestDistance = distance;
nearestValue = t;
}
}
}
return nearestValue;
}
2021-09-10 00:46:20 +00:00
/** Indexes {@code item} for points contained in {@code geom}. */
2021-07-14 09:31:54 +00:00
public void put(Geometry geom, T item) {
if (geom instanceof Point point && !point.isEmpty()) {
2021-07-26 11:27:56 +00:00
Envelope envelope = Objects.requireNonNull(point.getEnvelopeInternal());
2021-07-27 12:38:43 +00:00
// need to externally synchronize inserts into the STRTree
synchronized (this) {
index.insert(envelope, new GeomWithData<>(point.getCoordinate(), item));
}
2021-07-14 09:31:54 +00:00
} else if (geom instanceof GeometryCollection geoms) {
for (int i = 0; i < geoms.getNumGeometries(); i++) {
put(geoms.getGeometryN(i), item);
}
}
}
2021-08-11 12:40:49 +00:00
2021-07-14 09:31:54 +00:00
}