kopia lustrzana https://github.com/onthegomap/planetiler
rearrange
rodzic
72679b1b36
commit
fcaeb88c40
|
@ -1,5 +1,6 @@
|
|||
package com.onthegomap.flatmap;
|
||||
|
||||
import com.onthegomap.flatmap.render.RenderedFeature;
|
||||
import com.onthegomap.flatmap.write.Mbtiles;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
|
|
@ -100,8 +100,7 @@ public class OpenMapTilesMain {
|
|||
|
||||
stats.time("sort", featureDb::sort);
|
||||
|
||||
stats.time("mbtiles",
|
||||
() -> MbtilesWriter.writeOutput(featureMap, output, profile, config, stats));
|
||||
stats.time("mbtiles", () -> MbtilesWriter.writeOutput(featureMap, output, profile, config, stats));
|
||||
|
||||
stats.stopTimer("import");
|
||||
|
||||
|
|
|
@ -4,10 +4,10 @@ import com.carrotsearch.hppc.LongLongHashMap;
|
|||
import com.graphhopper.coll.GHLongLongHashMap;
|
||||
import com.onthegomap.flatmap.LayerStats;
|
||||
import com.onthegomap.flatmap.Profile;
|
||||
import com.onthegomap.flatmap.RenderedFeature;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.render.RenderedFeature;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.locationtech.jts.geom.GeometryFactory;
|
|||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.MultiPoint;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.TopologyException;
|
||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
|
||||
import org.locationtech.jts.geom.util.GeometryTransformer;
|
||||
import org.locationtech.jts.io.WKBReader;
|
||||
|
@ -198,4 +199,35 @@ public class GeoUtils {
|
|||
public static Geometry createMultiLineString(List<LineString> lineStrings) {
|
||||
return JTS_FACTORY.createMultiLineString(lineStrings.toArray(EMPTY_LINE_STRING_ARRAY));
|
||||
}
|
||||
|
||||
public static Geometry fixPolygon(Geometry geom, int maxAttempts) throws GeometryException {
|
||||
try {
|
||||
int attempts;
|
||||
for (attempts = 0; attempts < maxAttempts && !geom.isValid(); attempts++) {
|
||||
geom = geom.buffer(0);
|
||||
}
|
||||
|
||||
if (attempts == maxAttempts) {
|
||||
throw new GeometryException("Geometry still invalid after 2 buffers");
|
||||
}
|
||||
return geom;
|
||||
} catch (TopologyException e) {
|
||||
throw new GeometryException("Unable to fix polygon: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
private static double wrapDouble(double value, double max) {
|
||||
value %= max;
|
||||
if (value < 0) {
|
||||
value += max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static long labelGridId(int tilesAtZoom, double labelGridTileSize, Coordinate coord) {
|
||||
return GeoUtils.longPair(
|
||||
(int) Math.floor(wrapDouble(coord.getX() * tilesAtZoom, tilesAtZoom) / labelGridTileSize),
|
||||
(int) Math.floor((coord.getY() * tilesAtZoom) / labelGridTileSize)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.onthegomap.flatmap.geo;
|
||||
|
||||
public class GeometryException extends Exception {
|
||||
|
||||
public GeometryException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public GeometryException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public GeometryException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
|
@ -12,7 +12,6 @@ import com.graphhopper.reader.ReaderRelation;
|
|||
import com.graphhopper.reader.ReaderWay;
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.FeatureRenderer;
|
||||
import com.onthegomap.flatmap.MemoryEstimator;
|
||||
import com.onthegomap.flatmap.Profile;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
|
@ -23,6 +22,7 @@ import com.onthegomap.flatmap.collections.LongLongMultimap;
|
|||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.monitoring.ProgressLoggers;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.render.FeatureRenderer;
|
||||
import com.onthegomap.flatmap.worker.Topology;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -2,13 +2,13 @@ package com.onthegomap.flatmap.read;
|
|||
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.FeatureRenderer;
|
||||
import com.onthegomap.flatmap.Profile;
|
||||
import com.onthegomap.flatmap.SourceFeature;
|
||||
import com.onthegomap.flatmap.collections.FeatureGroup;
|
||||
import com.onthegomap.flatmap.collections.FeatureSort;
|
||||
import com.onthegomap.flatmap.monitoring.ProgressLoggers;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.render.FeatureRenderer;
|
||||
import com.onthegomap.flatmap.worker.Topology;
|
||||
import java.io.Closeable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.locationtech.jts.algorithm.Area;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.CoordinateSequences;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryCollection;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
class CoordinateSequenceExtractor {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(CoordinateSequenceExtractor.class);
|
||||
|
||||
static Geometry reassembleLineString(List<List<CoordinateSequence>> geoms) {
|
||||
Geometry geom;
|
||||
List<LineString> lineStrings = new ArrayList<>();
|
||||
for (List<CoordinateSequence> inner : geoms) {
|
||||
for (CoordinateSequence coordinateSequence : inner) {
|
||||
lineStrings.add(GeoUtils.JTS_FACTORY.createLineString(coordinateSequence));
|
||||
}
|
||||
}
|
||||
geom = GeoUtils.createMultiLineString(lineStrings);
|
||||
return geom;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Geometry reassemblePolygon(FeatureCollector.Feature feature, TileCoord tile,
|
||||
List<List<CoordinateSequence>> geoms) {
|
||||
Geometry geom;
|
||||
int numGeoms = geoms.size();
|
||||
Polygon[] polygons = new Polygon[numGeoms];
|
||||
for (int i = 0; i < numGeoms; i++) {
|
||||
List<CoordinateSequence> group = geoms.get(i);
|
||||
LinearRing first = GeoUtils.JTS_FACTORY.createLinearRing(group.get(0));
|
||||
LinearRing[] rest = new LinearRing[group.size() - 1];
|
||||
for (int j = 1; j < group.size(); j++) {
|
||||
CoordinateSequence seq = group.get(j);
|
||||
CoordinateSequences.reverse(seq);
|
||||
rest[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(seq);
|
||||
}
|
||||
polygons[i] = GeoUtils.JTS_FACTORY.createPolygon(first, rest);
|
||||
}
|
||||
geom = GeoUtils.JTS_FACTORY.createMultiPolygon(polygons);
|
||||
return geom;
|
||||
}
|
||||
|
||||
static List<List<CoordinateSequence>> extractGroups(Geometry geom, double minSize) {
|
||||
List<List<CoordinateSequence>> result = new ArrayList<>();
|
||||
extractGroups(geom, result, minSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void extractGroups(Geometry geom, List<List<CoordinateSequence>> groups, double minSize) {
|
||||
if (geom.isEmpty()) {
|
||||
// ignore
|
||||
} else if (geom instanceof GeometryCollection) {
|
||||
for (int i = 0; i < geom.getNumGeometries(); i++) {
|
||||
extractGroups(geom.getGeometryN(i), groups, minSize);
|
||||
}
|
||||
} else if (geom instanceof Polygon polygon) {
|
||||
extractGroupsFromPolygon(groups, minSize, polygon);
|
||||
} else if (geom instanceof LinearRing linearRing) {
|
||||
extractGroups(GeoUtils.JTS_FACTORY.createPolygon(linearRing), groups, minSize);
|
||||
} else if (geom instanceof LineString lineString) {
|
||||
if (lineString.getLength() >= minSize) {
|
||||
groups.add(List.of(lineString.getCoordinateSequence()));
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("unrecognized geometry type: " + geom.getGeometryType());
|
||||
}
|
||||
}
|
||||
|
||||
private static void extractGroupsFromPolygon(List<List<CoordinateSequence>> groups, double minSize, Polygon polygon) {
|
||||
CoordinateSequence outer = polygon.getExteriorRing().getCoordinateSequence();
|
||||
double outerArea = Area.ofRingSigned(outer);
|
||||
if (outerArea > 0) {
|
||||
CoordinateSequences.reverse(outer);
|
||||
}
|
||||
if (Math.abs(outerArea) >= minSize) {
|
||||
List<CoordinateSequence> group = new ArrayList<>(1 + polygon.getNumInteriorRing());
|
||||
groups.add(group);
|
||||
group.add(outer);
|
||||
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
|
||||
CoordinateSequence inner = polygon.getInteriorRingN(i).getCoordinateSequence();
|
||||
double innerArea = Area.ofRingSigned(inner);
|
||||
if (innerArea > 0) {
|
||||
CoordinateSequences.reverse(inner);
|
||||
}
|
||||
if (Math.abs(innerArea) >= minSize) {
|
||||
group.add(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,12 @@
|
|||
package com.onthegomap.flatmap;
|
||||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.TileExtents;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.GeometryException;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -11,16 +15,12 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.locationtech.jts.algorithm.Area;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.CoordinateSequences;
|
||||
import org.locationtech.jts.geom.CoordinateXY;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryCollection;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.MultiLineString;
|
||||
import org.locationtech.jts.geom.MultiPoint;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
|
@ -65,14 +65,6 @@ public class FeatureRenderer {
|
|||
return value;
|
||||
}
|
||||
|
||||
private static double wrapDouble(double value, double max) {
|
||||
value %= max;
|
||||
if (value < 0) {
|
||||
value += max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public void renderFeature(FeatureCollector.Feature feature) {
|
||||
renderGeometry(feature.getGeometry(), feature);
|
||||
}
|
||||
|
@ -113,6 +105,7 @@ public class FeatureRenderer {
|
|||
TileCoord tile = TileCoord.ofXYZ(wrapInt(x, tilesAtZoom), y, zoom);
|
||||
double tileY = worldY - y;
|
||||
Coordinate outCoordinate = new CoordinateXY(tileX * 256, tileY * 256);
|
||||
tilePrecision.makePrecise(outCoordinate);
|
||||
output.computeIfAbsent(tile, t -> new HashSet<>()).add(outCoordinate);
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +113,7 @@ public class FeatureRenderer {
|
|||
|
||||
private void addPointFeature(FeatureCollector.Feature feature, Coordinate... coords) {
|
||||
long id = idGen.incrementAndGet();
|
||||
boolean hasLabelGrid = feature.hasLabelGrid();
|
||||
for (int zoom = feature.getMaxZoom(); zoom >= feature.getMinZoom(); zoom--) {
|
||||
Map<TileCoord, Set<Coordinate>> sliced = new HashMap<>();
|
||||
Map<String, Object> attrs = feature.getAttrsAtZoom(zoom);
|
||||
|
@ -131,12 +125,12 @@ public class FeatureRenderer {
|
|||
}
|
||||
|
||||
RenderedFeature.Group groupInfo = null;
|
||||
if (feature.hasLabelGrid() && coords.length == 1) {
|
||||
if (hasLabelGrid && coords.length == 1) {
|
||||
double labelGridTileSize = feature.getLabelGridPixelSizeAtZoom(zoom) / 256d;
|
||||
groupInfo = labelGridTileSize >= 1d / 4096d ? new RenderedFeature.Group(GeoUtils.longPair(
|
||||
(int) Math.floor(wrapDouble(coords[0].getX() * tilesAtZoom, tilesAtZoom) / labelGridTileSize),
|
||||
(int) Math.floor((coords[0].getY() * tilesAtZoom) / labelGridTileSize)
|
||||
), feature.getLabelGridLimitAtZoom(zoom)) : null;
|
||||
groupInfo = labelGridTileSize < 1d / 4096d ? null : new RenderedFeature.Group(
|
||||
GeoUtils.labelGridId(tilesAtZoom, labelGridTileSize, coords[0]),
|
||||
feature.getLabelGridLimitAtZoom(zoom)
|
||||
);
|
||||
}
|
||||
|
||||
for (var entry : sliced.entrySet()) {
|
||||
|
@ -145,17 +139,13 @@ public class FeatureRenderer {
|
|||
Geometry geom = value.size() == 1 ? GeoUtils.point(value.iterator().next()) : GeoUtils.multiPoint(value);
|
||||
// TODO stats
|
||||
// TODO writeTileFeatures
|
||||
emitFeature(feature, id, attrs, groupInfo, tile, geom);
|
||||
emitFeature(feature, id, attrs, tile, geom, groupInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void emitFeature(FeatureCollector.Feature feature, long id, TileCoord tile, Geometry geom) {
|
||||
emitFeature(feature, id, feature.getAttrsAtZoom(tile.z()), null, tile, geom);
|
||||
}
|
||||
|
||||
private void emitFeature(FeatureCollector.Feature feature, long id, Map<String, Object> attrs,
|
||||
RenderedFeature.Group groupInfo, TileCoord tile, Geometry geom) {
|
||||
private void emitFeature(FeatureCollector.Feature feature, long id, Map<String, Object> attrs, TileCoord tile,
|
||||
Geometry geom, RenderedFeature.Group groupInfo) {
|
||||
consumer.accept(new RenderedFeature(
|
||||
tile,
|
||||
new VectorTileEncoder.Feature(
|
||||
|
@ -206,8 +196,7 @@ public class FeatureRenderer {
|
|||
simplifier.setDistanceTolerance(tolerance);
|
||||
geom = simplifier.getResultGeometry();
|
||||
|
||||
List<List<CoordinateSequence>> groups = new ArrayList<>();
|
||||
extractGroups(geom, groups, minSize);
|
||||
List<List<CoordinateSequence>> groups = CoordinateSequenceExtractor.extractGroups(geom, minSize);
|
||||
double buffer = feature.getBufferPixelsAtZoom(z);
|
||||
TileExtents.ForZoom extents = config.extents().getForZoom(z);
|
||||
TiledGeometry sliced = TiledGeometry.sliceIntoTiles(groups, buffer, area, z, extents);
|
||||
|
@ -216,29 +205,35 @@ public class FeatureRenderer {
|
|||
}
|
||||
|
||||
private void writeTileFeatures(long id, FeatureCollector.Feature feature, TiledGeometry sliced) {
|
||||
Map<String, Object> attrs = feature.getAttrsAtZoom(sliced.zoomLevel());
|
||||
for (var entry : sliced.getTileData()) {
|
||||
TileCoord tile = entry.getKey();
|
||||
List<List<CoordinateSequence>> geoms = entry.getValue();
|
||||
|
||||
Geometry geom;
|
||||
if (feature.area()) {
|
||||
geom = reassemblePolygon(feature, tile, geoms);
|
||||
} else {
|
||||
geom = reassembleLineString(geoms);
|
||||
}
|
||||
|
||||
try {
|
||||
geom = GeometryPrecisionReducer.reduce(geom, tilePrecision);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.warn("Error reducing precision of " + feature + " on " + tile + ": " + e);
|
||||
}
|
||||
List<List<CoordinateSequence>> geoms = entry.getValue();
|
||||
|
||||
if (!geom.isEmpty()) {
|
||||
// JTS utilities "fix" the geometry to be clockwise outer/CCW inner
|
||||
Geometry geom;
|
||||
if (feature.area()) {
|
||||
geom = geom.reverse();
|
||||
geom = CoordinateSequenceExtractor.reassemblePolygon(feature, tile, geoms);
|
||||
geom = GeoUtils.fixPolygon(geom, 2);
|
||||
} else {
|
||||
geom = CoordinateSequenceExtractor.reassembleLineString(geoms);
|
||||
}
|
||||
emitFeature(feature, id, tile, geom);
|
||||
|
||||
try {
|
||||
geom = GeometryPrecisionReducer.reduce(geom, tilePrecision);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new GeometryException("Error reducing precision");
|
||||
}
|
||||
|
||||
if (!geom.isEmpty()) {
|
||||
// JTS utilities "fix" the geometry to be clockwise outer/CCW inner
|
||||
if (feature.area()) {
|
||||
geom = geom.reverse();
|
||||
}
|
||||
emitFeature(feature, id, attrs, tile, geom, null);
|
||||
}
|
||||
} catch (GeometryException e) {
|
||||
LOGGER.warn(e.getMessage() + ": " + tile + " " + feature);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,110 +246,23 @@ public class FeatureRenderer {
|
|||
private void emitFilledTiles(long id, FeatureCollector.Feature feature, TiledGeometry sliced) {
|
||||
/*
|
||||
* Optimization: large input polygons that generate many filled interior tiles (ie. the ocean), the encoder avoids
|
||||
* re-encoding if groupInfo and vector tile feature are == to previous values. The feature can have different
|
||||
* attributes at different zoom levels though, so need to cache each vector tile feature instance by zoom level.
|
||||
* re-encoding if groupInfo and vector tile feature are == to previous values.
|
||||
*/
|
||||
Optional<RenderedFeature.Group> groupInfo = Optional.empty();
|
||||
VectorTileEncoder.Feature cachedFeature = null;
|
||||
int lastZoom = Integer.MIN_VALUE;
|
||||
VectorTileEncoder.Feature vectorTileFeature = new VectorTileEncoder.Feature(
|
||||
feature.getLayer(),
|
||||
id,
|
||||
FILL,
|
||||
feature.getAttrsAtZoom(sliced.zoomLevel())
|
||||
);
|
||||
|
||||
for (TileCoord tile : sliced.getFilledTilesOrderedByZXY()) {
|
||||
int zoom = tile.z();
|
||||
if (zoom != lastZoom) {
|
||||
cachedFeature = new VectorTileEncoder.Feature(feature.getLayer(), id, FILL, feature.getAttrsAtZoom(zoom));
|
||||
lastZoom = zoom;
|
||||
}
|
||||
for (TileCoord tile : sliced.getFilledTiles()) {
|
||||
consumer.accept(new RenderedFeature(
|
||||
tile,
|
||||
cachedFeature,
|
||||
vectorTileFeature,
|
||||
feature.getZorder(),
|
||||
groupInfo
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private Geometry reassembleLineString(List<List<CoordinateSequence>> geoms) {
|
||||
Geometry geom;
|
||||
List<LineString> lineStrings = new ArrayList<>();
|
||||
for (List<CoordinateSequence> inner : geoms) {
|
||||
for (CoordinateSequence coordinateSequence : inner) {
|
||||
lineStrings.add(GeoUtils.JTS_FACTORY.createLineString(coordinateSequence));
|
||||
}
|
||||
}
|
||||
geom = GeoUtils.createMultiLineString(lineStrings);
|
||||
return geom;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Geometry reassemblePolygon(FeatureCollector.Feature feature, TileCoord tile,
|
||||
List<List<CoordinateSequence>> geoms) {
|
||||
Geometry geom;
|
||||
int numGeoms = geoms.size();
|
||||
Polygon[] polygons = new Polygon[numGeoms];
|
||||
for (int i = 0; i < numGeoms; i++) {
|
||||
List<CoordinateSequence> group = geoms.get(i);
|
||||
LinearRing first = GeoUtils.JTS_FACTORY.createLinearRing(group.get(0));
|
||||
LinearRing[] rest = new LinearRing[group.size() - 1];
|
||||
for (int j = 1; j < group.size(); j++) {
|
||||
CoordinateSequence seq = group.get(j);
|
||||
CoordinateSequences.reverse(seq);
|
||||
rest[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(seq);
|
||||
}
|
||||
polygons[i] = GeoUtils.JTS_FACTORY.createPolygon(first, rest);
|
||||
}
|
||||
geom = GeoUtils.JTS_FACTORY.createMultiPolygon(polygons);
|
||||
if (!geom.isValid()) {
|
||||
geom = geom.buffer(0);
|
||||
if (!geom.isValid()) {
|
||||
geom = geom.buffer(0);
|
||||
if (!geom.isValid()) {
|
||||
LOGGER.warn("Geometry still invalid after 2 buffers " + feature + " on " + tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
return geom;
|
||||
}
|
||||
|
||||
private void extractGroups(Geometry geom, List<List<CoordinateSequence>> groups, double minSize) {
|
||||
if (geom.isEmpty()) {
|
||||
// ignore
|
||||
} else if (geom instanceof GeometryCollection) {
|
||||
for (int i = 0; i < geom.getNumGeometries(); i++) {
|
||||
extractGroups(geom.getGeometryN(i), groups, minSize);
|
||||
}
|
||||
} else if (geom instanceof Polygon polygon) {
|
||||
extractGroupsFromPolygon(groups, minSize, polygon);
|
||||
} else if (geom instanceof LinearRing linearRing) {
|
||||
extractGroups(GeoUtils.JTS_FACTORY.createPolygon(linearRing), groups, minSize);
|
||||
} else if (geom instanceof LineString lineString) {
|
||||
if (lineString.getLength() >= minSize) {
|
||||
groups.add(List.of(lineString.getCoordinateSequence()));
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("unrecognized geometry type: " + geom.getGeometryType());
|
||||
}
|
||||
}
|
||||
|
||||
private void extractGroupsFromPolygon(List<List<CoordinateSequence>> groups, double minSize, Polygon polygon) {
|
||||
CoordinateSequence outer = polygon.getExteriorRing().getCoordinateSequence();
|
||||
double outerArea = Area.ofRingSigned(outer);
|
||||
if (outerArea > 0) {
|
||||
CoordinateSequences.reverse(outer);
|
||||
}
|
||||
if (Math.abs(outerArea) >= minSize) {
|
||||
List<CoordinateSequence> group = new ArrayList<>(1 + polygon.getNumInteriorRing());
|
||||
groups.add(group);
|
||||
group.add(outer);
|
||||
for (int i = 0; i < polygon.getNumInteriorRing(); i++) {
|
||||
CoordinateSequence inner = polygon.getInteriorRingN(i).getCoordinateSequence();
|
||||
double innerArea = Area.ofRingSigned(inner);
|
||||
if (innerArea > 0) {
|
||||
CoordinateSequences.reverse(inner);
|
||||
}
|
||||
if (Math.abs(innerArea) >= minSize) {
|
||||
group.add(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.onthegomap.flatmap;
|
||||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import java.util.Optional;
|
||||
|
|
@ -12,12 +12,13 @@
|
|||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
package com.onthegomap.flatmap;
|
||||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import com.carrotsearch.hppc.IntObjectMap;
|
||||
import com.carrotsearch.hppc.cursors.IntCursor;
|
||||
import com.carrotsearch.hppc.cursors.IntObjectCursor;
|
||||
import com.graphhopper.coll.GHIntObjectHashMap;
|
||||
import com.onthegomap.flatmap.TileExtents;
|
||||
import com.onthegomap.flatmap.collections.IntRange;
|
||||
import com.onthegomap.flatmap.collections.MutableCoordinateSequence;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
|
@ -26,10 +27,7 @@ import java.util.EnumSet;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.locationtech.jts.geom.CoordinateSequence;
|
||||
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -39,12 +37,12 @@ import org.slf4j.LoggerFactory;
|
|||
* This class is adapted from the stripe clipping algorithm in https://github.com/mapbox/geojson-vt/ and modified so
|
||||
* that it eagerly produces all sliced tiles at a zoom level for each input geometry.
|
||||
*/
|
||||
public class TiledGeometry {
|
||||
class TiledGeometry {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TiledGeometry.class);
|
||||
|
||||
private final Map<TileCoord, List<List<CoordinateSequence>>> tileContents = new HashMap<>();
|
||||
private final SortedMap<Column, IntRange> filledRanges = new TreeMap<>();
|
||||
private final Map<Integer, IntRange> filledRanges = new HashMap<>();
|
||||
private final TileExtents.ForZoom extents;
|
||||
private final double buffer;
|
||||
private final int z;
|
||||
|
@ -59,6 +57,10 @@ public class TiledGeometry {
|
|||
this.max = 1 << z;
|
||||
}
|
||||
|
||||
public int zoomLevel() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public static TiledGeometry sliceIntoTiles(List<List<CoordinateSequence>> groups, double buffer, boolean area, int z,
|
||||
TileExtents.ForZoom extents) {
|
||||
int worldExtent = 1 << z;
|
||||
|
@ -73,11 +75,10 @@ public class TiledGeometry {
|
|||
return result;
|
||||
}
|
||||
|
||||
public Iterable<TileCoord> getFilledTilesOrderedByZXY() {
|
||||
public Iterable<TileCoord> getFilledTiles() {
|
||||
return () -> filledRanges.entrySet().stream()
|
||||
.<TileCoord>mapMulti((entry, next) -> {
|
||||
Column column = entry.getKey();
|
||||
int x = column.x, z = column.z;
|
||||
int x = entry.getKey();
|
||||
for (int y : entry.getValue()) {
|
||||
TileCoord coord = TileCoord.ofXYZ(x, y, z);
|
||||
if (!tileContents.containsKey(coord)) {
|
||||
|
@ -141,12 +142,12 @@ public class TiledGeometry {
|
|||
overflow.add(Direction.LEFT);
|
||||
} else {
|
||||
for (CoordinateSequence stripeSegment : xCursor.value) {
|
||||
IntRange filled = sliceY(stripeSegment, x, outer, inProgressShapes);
|
||||
if (area && filled != null) {
|
||||
IntRange filledYRange = sliceY(stripeSegment, x, outer, inProgressShapes);
|
||||
if (area && filledYRange != null) {
|
||||
if (outer) {
|
||||
addFilledRange(z, x, filled);
|
||||
addFilledRange(x, filledYRange);
|
||||
} else {
|
||||
removeFilledRange(z, x, filled);
|
||||
removeFilledRange(x, filledYRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -428,38 +429,27 @@ public class TiledGeometry {
|
|||
return rightFilled != null ? rightFilled.intersect(leftFilled) : null;
|
||||
}
|
||||
|
||||
private void addFilledRange(int z, int x, IntRange range) {
|
||||
if (range == null) {
|
||||
private void addFilledRange(int x, IntRange yRange) {
|
||||
if (yRange == null) {
|
||||
return;
|
||||
}
|
||||
Column key = new Column(z, x);
|
||||
IntRange existing = filledRanges.get(key);
|
||||
IntRange existing = filledRanges.get(x);
|
||||
if (existing == null) {
|
||||
filledRanges.put(key, range);
|
||||
filledRanges.put(x, yRange);
|
||||
} else {
|
||||
existing.addAll(range);
|
||||
existing.addAll(yRange);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFilledRange(int z, int x, IntRange range) {
|
||||
if (range == null) {
|
||||
private void removeFilledRange(int x, IntRange yRange) {
|
||||
if (yRange == null) {
|
||||
return;
|
||||
}
|
||||
Column key = new Column(z, x);
|
||||
IntRange existing = filledRanges.get(key);
|
||||
IntRange existing = filledRanges.get(x);
|
||||
if (existing != null) {
|
||||
existing.removeAll(range);
|
||||
existing.removeAll(yRange);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Direction {RIGHT, LEFT}
|
||||
|
||||
private static record Column(int z, int x) implements Comparable<Column> {
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull Column o) {
|
||||
int result = Integer.compare(z, o.z);
|
||||
return result == 0 ? Integer.compare(x, o.x) : result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.render.RenderedFeature;
|
||||
import com.onthegomap.flatmap.write.Mbtiles;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
|
|
@ -29,10 +29,12 @@ import org.locationtech.jts.geom.CoordinateXY;
|
|||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryCollection;
|
||||
import org.locationtech.jts.geom.LineString;
|
||||
import org.locationtech.jts.geom.Lineal;
|
||||
import org.locationtech.jts.geom.MultiPoint;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
import org.locationtech.jts.geom.Puntal;
|
||||
import org.locationtech.jts.geom.util.AffineTransformation;
|
||||
import org.locationtech.jts.geom.util.GeometryTransformer;
|
||||
|
||||
|
@ -228,15 +230,17 @@ public class TestUtils {
|
|||
|
||||
public static Map<String, Object> toMap(FeatureCollector.Feature feature, int zoom) {
|
||||
TreeMap<String, Object> result = new TreeMap<>(feature.getAttrsAtZoom(zoom));
|
||||
Geometry geom = feature.getGeometry();
|
||||
result.put("_minzoom", feature.getMinZoom());
|
||||
result.put("_maxzoom", feature.getMaxZoom());
|
||||
result.put("_buffer", feature.getBufferPixelsAtZoom(zoom));
|
||||
result.put("_layer", feature.getLayer());
|
||||
result.put("_zorder", feature.getZorder());
|
||||
result.put("_geom", new NormGeometry(feature.getGeometry()));
|
||||
result.put("_geom", new NormGeometry(geom));
|
||||
result.put("_labelgrid_limit", feature.getLabelGridLimitAtZoom(zoom));
|
||||
result.put("_labelgrid_size", feature.getLabelGridPixelSizeAtZoom(zoom));
|
||||
result.put("_minpixelsize", feature.getMinPixelSize(zoom));
|
||||
result.put("_type", geom instanceof Puntal ? "point" : geom instanceof Lineal ? "line" : "polygon");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,10 +7,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import com.onthegomap.flatmap.Profile;
|
||||
import com.onthegomap.flatmap.RenderedFeature;
|
||||
import com.onthegomap.flatmap.VectorTileEncoder;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.monitoring.Stats;
|
||||
import com.onthegomap.flatmap.render.RenderedFeature;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package com.onthegomap.flatmap;
|
||||
package com.onthegomap.flatmap.render;
|
||||
|
||||
import static com.onthegomap.flatmap.TestUtils.assertSameNormalizedFeatures;
|
||||
import static com.onthegomap.flatmap.TestUtils.emptyGeometry;
|
||||
|
@ -8,6 +8,8 @@ import static com.onthegomap.flatmap.TestUtils.newPoint;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
import com.onthegomap.flatmap.CommonParams;
|
||||
import com.onthegomap.flatmap.FeatureCollector;
|
||||
import com.onthegomap.flatmap.geo.GeoUtils;
|
||||
import com.onthegomap.flatmap.geo.TileCoord;
|
||||
import com.onthegomap.flatmap.read.ReaderFeature;
|
Ładowanie…
Reference in New Issue