All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.iakovlev.timeshape.Index Maven / Gradle / Ivy
package net.iakovlev.timeshape;
import com.esri.core.geometry.*;
import net.iakovlev.timeshape.proto.Geojson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PrimitiveIterator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
final class Index implements Serializable {
static final class Entry implements Serializable {
final ZoneId zoneId;
final Geometry geometry;
Entry(ZoneId zoneId, Geometry geometry) {
this.zoneId = zoneId;
this.geometry = geometry;
}
}
private static final int WGS84_WKID = 4326;
private final ArrayList zoneIds;
private static final SpatialReference spatialReference = SpatialReference.create(WGS84_WKID);
private final QuadTree quadTree;
private static final Logger log = LoggerFactory.getLogger(Index.class);
private Index(QuadTree quadTree, ArrayList zoneIds) {
log.info("Initialized index with {} time zones", zoneIds.size());
this.quadTree = quadTree;
this.zoneIds = zoneIds;
}
List getKnownZoneIds() {
return zoneIds.stream().map(e -> e.zoneId).collect(Collectors.toList());
}
List query(double latitude, double longitude) {
ArrayList result = new ArrayList<>(2);
Point point = new Point(longitude, latitude);
OperatorIntersects operator = OperatorIntersects.local();
QuadTree.QuadTreeIterator iterator = quadTree.getIterator(point, 0);
for (int i = iterator.next(); i >= 0; i = iterator.next()) {
int element = quadTree.getElement(i);
Entry entry = zoneIds.get(element);
if(operator.execute(entry.geometry, point, spatialReference, null)) {
result.add(entry.zoneId);
}
}
return result;
}
List queryPolyline(double[] line) {
Polyline polyline = new Polyline();
ArrayList points = new ArrayList<>(line.length / 2);
for (int i = 0; i < line.length - 1; i += 2) {
Point p = new Point(line[i + 1], line[i]);
points.add(p);
}
polyline.startPath(points.get(0));
for (int i = 1; i < points.size(); i += 1) {
polyline.lineTo(points.get(i));
}
QuadTree.QuadTreeIterator iterator = quadTree.getIterator(polyline, 0);
ArrayList potentiallyMatchingEntries = new ArrayList<>();
for (int i = iterator.next(); i >= 0; i = iterator.next()) {
int element = quadTree.getElement(i);
Entry entry = zoneIds.get(element);
potentiallyMatchingEntries.add(entry);
}
ArrayList sameZoneSegments = new ArrayList<>();
List currentEntry = null;
// 1. find next matching geometry or geometries
// 2. for every match, increase the index
// 3. when it doesn't match anymore, save currentSegment to sameZoneSegments and start new one
// 4. goto 1.
int index = 0;
boolean lastWasEmpty = false;
OperatorIntersects operator = OperatorIntersects.local();
while (index < points.size()) {
Point p = points.get(index);
if (currentEntry == null) {
currentEntry = potentiallyMatchingEntries
.stream()
.filter(e -> operator.execute(e.geometry, p, spatialReference, null))
.collect(Collectors.toList());
}
if (currentEntry.isEmpty()) {
currentEntry = null;
lastWasEmpty = true;
index++;
} else {
if (lastWasEmpty) {
lastWasEmpty = false;
sameZoneSegments.add(SameZoneSpan.fromIndexEntries(Collections.emptyList(), (index - 1) * 2 + 1));
continue;
}
if (currentEntry.stream().allMatch(e -> operator.execute(e.geometry, p, spatialReference, null))) {
if (index == points.size() - 1) {
sameZoneSegments.add(SameZoneSpan.fromIndexEntries(currentEntry, index * 2 + 1));
}
index++;
} else {
sameZoneSegments.add(SameZoneSpan.fromIndexEntries(currentEntry, (index - 1) * 2 + 1));
currentEntry = null;
}
}
}
if (lastWasEmpty) {
sameZoneSegments.add(SameZoneSpan.fromIndexEntries(Collections.emptyList(), index * 2 - 1));
}
return sameZoneSegments;
}
private static Polygon buildPoly(Geojson.Polygon from) {
Polygon poly = new Polygon();
from.getCoordinatesList().stream()
.map(Geojson.LineString::getCoordinatesList)
.forEachOrdered(lp -> {
poly.startPath(lp.get(0).getLon(), lp.get(0).getLat());
lp.subList(1, lp.size()).forEach(p -> poly.lineTo(p.getLon(), p.getLat()));
});
return poly;
}
static Index build(Stream features, int size, Envelope boundaries) {
return build(features, size, boundaries, false);
}
private static Stream getPolygons(Geojson.Feature f) {
if (f.getGeometry().hasPolygon()) {
return Stream.of(buildPoly(f.getGeometry().getPolygon()));
} else if (f.getGeometry().hasMultiPolygon()) {
Geojson.MultiPolygon multiPolygonProto = f.getGeometry().getMultiPolygon();
return multiPolygonProto.getCoordinatesList().stream().map(Index::buildPoly);
} else {
throw new RuntimeException("Unknown geometry type");
}
}
static Index build(Stream features, int size, Envelope boundaries, boolean accelerateGeometry) {
Envelope2D boundariesEnvelope = new Envelope2D();
boundaries.queryEnvelope2D(boundariesEnvelope);
QuadTree quadTree = new QuadTree(boundariesEnvelope, 8);
Envelope2D env = new Envelope2D();
ArrayList zoneIds = new ArrayList<>(size);
PrimitiveIterator.OfInt indices = IntStream.iterate(0, i -> i + 1).iterator();
List unknownZones = new ArrayList<>();
OperatorIntersects operatorIntersects = OperatorIntersects.local();
features.forEach(f -> {
String zoneIdName = f.getProperties(0).getValueString();
try {
ZoneId zoneId = ZoneId.of(zoneIdName);
getPolygons(f).forEach(polygon -> {
if (GeometryEngine.contains(boundaries, polygon, spatialReference)) {
log.debug("Adding zone {} to index", zoneIdName);
if (accelerateGeometry) {
operatorIntersects.accelerateGeometry(polygon, spatialReference, Geometry.GeometryAccelerationDegree.enumMild);
}
polygon.queryEnvelope2D(env);
int index = indices.next();
quadTree.insert(index, env);
zoneIds.add(index, new Entry(zoneId, polygon));
} else {
log.debug("Not adding zone {} to index because it's out of provided boundaries", zoneIdName);
}
});
} catch (Exception ex) {
unknownZones.add(zoneIdName);
}
});
if (unknownZones.size() != 0) {
String allUnknownZones = String.join(", ", unknownZones);
log.error(
"Some of the zone ids were not recognized by the Java runtime and will be ignored. " +
"The most probable reason for this is outdated Java runtime version. " +
"The following zones were not recognized: " + allUnknownZones);
}
return new Index(quadTree, zoneIds);
}
}