Skip to content

Commit 3fadac7

Browse files
committed
multipolygon support
1 parent 310256c commit 3fadac7

File tree

5 files changed

+94
-48
lines changed

5 files changed

+94
-48
lines changed

tiles/src/main/java/com/protomaps/basemap/Basemap.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,13 @@ static void run(Arguments args) {
163163
FontRegistry fontRegistry = FontRegistry.getInstance();
164164
fontRegistry.setZipFilePath(pgfEncodingZip.toString());
165165

166-
var bytes = new byte[0];
167-
try {
168-
bytes = Files.readAllBytes(Paths.get("clip.json"));
169-
} catch (IOException e) {
170-
throw new RuntimeException(e.getMessage());
166+
167+
168+
Clip clip = null;
169+
var clipArg = args.getString("clip","File path to GeoJSON Polygon or MultiPolygon geometry to clip tileset.");
170+
if (!clipArg.isEmpty()) {
171+
clip = Clip.fromGeoJSONFile(args.getStats(), clipArg);
171172
}
172-
var clip = Clip.fromGeoJSON(bytes);
173173

174174
fontRegistry.loadFontBundle("NotoSansDevanagari-Regular", "1", "Devanagari");
175175

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.protomaps.basemap;
2+
3+
import com.fasterxml.jackson.databind.node.ArrayNode;
4+
import com.onthegomap.planetiler.geo.GeoUtils;
5+
import org.locationtech.jts.geom.Coordinate;
6+
import com.fasterxml.jackson.databind.JsonNode;
7+
import org.locationtech.jts.geom.Geometry;
8+
import org.locationtech.jts.geom.LinearRing;
9+
import org.locationtech.jts.geom.Polygon;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
public class GeoJSON {
15+
private static Coordinate[] parseCoordinates(ArrayNode coordinateArray) {
16+
Coordinate[] coordinates = new Coordinate[coordinateArray.size()];
17+
for (int i = 0; i < coordinateArray.size(); i++) {
18+
ArrayNode coordinate = (ArrayNode) coordinateArray.get(i);
19+
double x = coordinate.get(0).asDouble();
20+
double y = coordinate.get(1).asDouble();
21+
coordinates[i] = new Coordinate(x, y);
22+
}
23+
return coordinates;
24+
}
25+
26+
private static Polygon coordsToPolygon(JsonNode coords) {
27+
ArrayNode outerRingNode = (ArrayNode) coords.get(0);
28+
Coordinate[] outerRingCoordinates = parseCoordinates(outerRingNode);
29+
LinearRing outerRing = GeoUtils.JTS_FACTORY.createLinearRing(outerRingCoordinates);
30+
31+
LinearRing[] innerRings = new LinearRing[coords.size() - 1];
32+
for (int j = 1; j < coords.size(); j++) {
33+
ArrayNode innerRingNode = (ArrayNode) coords.get(j);
34+
Coordinate[] innerRingCoordinates = parseCoordinates(innerRingNode);
35+
innerRings[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(innerRingCoordinates);
36+
}
37+
return GeoUtils.JTS_FACTORY.createPolygon(outerRing, innerRings);
38+
}
39+
40+
// return a Polygon or MultiPolygon from a GeoJSON geometry object.
41+
public static Geometry parseGeometry(JsonNode jsonGeometry) {
42+
var coords = jsonGeometry.get("coordinates");
43+
if (jsonGeometry.get("type").asText().equals("Polygon")) {
44+
return coordsToPolygon(coords);
45+
} else if (jsonGeometry.get("type").asText().equals("MultiPolygon")) {
46+
List<Polygon> polygons = new ArrayList<>();
47+
for (var polygonCoords : coords) {
48+
polygons.add(coordsToPolygon(polygonCoords));
49+
}
50+
return GeoUtils.createMultiPolygon(polygons);
51+
} else {
52+
throw new IllegalArgumentException();
53+
}
54+
}
55+
}

tiles/src/main/java/com/protomaps/basemap/postprocess/Clip.java

+25-42
Original file line numberDiff line numberDiff line change
@@ -7,83 +7,65 @@
77

88
import com.fasterxml.jackson.databind.JsonNode;
99
import com.fasterxml.jackson.databind.ObjectMapper;
10-
import com.fasterxml.jackson.databind.node.ArrayNode;
1110
import com.onthegomap.planetiler.ForwardingProfile;
1211
import com.onthegomap.planetiler.VectorTile;
1312
import com.onthegomap.planetiler.geo.*;
1413
import com.onthegomap.planetiler.render.TiledGeometry;
1514
import java.io.IOException;
15+
import java.nio.file.Files;
16+
import java.nio.file.Paths;
1617
import java.util.*;
1718

1819
import com.onthegomap.planetiler.stats.Stats;
20+
import com.protomaps.basemap.GeoJSON;
1921
import org.locationtech.jts.geom.*;
2022
import org.locationtech.jts.geom.util.AffineTransformation;
2123
import org.locationtech.jts.operation.overlayng.OverlayNG;
2224
import org.locationtech.jts.operation.overlayng.OverlayNGRobust;
2325

2426
public class Clip implements ForwardingProfile.TilePostProcessor {
25-
26-
private final Map<Integer, Map<TileCoord, List<List<CoordinateSequence>>>> data;
27+
private final Map<Integer, Map<TileCoord, List<List<CoordinateSequence>>>> tiledGeometries;
2728
private final Map<Integer, TiledGeometry.CoveredTiles> coverings;
2829
private final Stats stats;
2930

30-
private static Coordinate[] parseCoordinates(ArrayNode coordinateArray) {
31-
Coordinate[] coordinates = new Coordinate[coordinateArray.size()];
32-
for (int i = 0; i < coordinateArray.size(); i++) {
33-
ArrayNode coordinate = (ArrayNode) coordinateArray.get(i);
34-
double x = coordinate.get(0).asDouble();
35-
double y = coordinate.get(1).asDouble();
36-
coordinates[i] = new Coordinate(x, y);
37-
}
38-
return coordinates;
39-
}
40-
41-
public Clip(Geometry input) {
42-
stats = Stats.inMemory();
31+
public Clip(Stats stats, Geometry input) {
32+
this.stats = stats;
4333
var clipGeometry = latLonToWorldCoords(input).buffer(0.00001);
44-
data = new HashMap<>();
34+
tiledGeometries = new HashMap<>();
4535
coverings = new HashMap<>();
4636
try {
4737
for (var i = 0; i <= 15; i++) {
4838
var extents = TileExtents.computeFromWorldBounds(i, WORLD_BOUNDS);
4939
double scale = 1 << i;
5040
Geometry scaled = AffineTransformation.scaleInstance(scale, scale).transform(clipGeometry);
5141
// var simplified = DouglasPeuckerSimplifier.simplify(scaled, 0.25/256);
52-
this.data.put(i, sliceIntoTiles(scaled, 0, 0.015625, i, extents.getForZoom(i)).getTileData());
42+
this.tiledGeometries.put(i, sliceIntoTiles(scaled, 0, 0.015625, i, extents.getForZoom(i)).getTileData());
5343
this.coverings.put(i, getCoveredTiles(scaled, i, extents.getForZoom(i)));
5444
}
5545
} catch (GeometryException e) {
5646
throw new RuntimeException("Error clipping");
5747
}
5848
}
5949

60-
public static Clip fromGeoJSON(byte[] bytes) {
61-
Geometry clipGeometry;
50+
public static Clip fromGeoJSONFile(Stats stats, String filename) {
51+
try {
52+
return fromGeoJSON(stats, Files.readAllBytes(Paths.get(filename)));
53+
} catch (IOException e) {
54+
throw new IllegalArgumentException("Could not open clip file");
55+
}
56+
}
57+
58+
public static Clip fromGeoJSON(Stats stats, byte[] bytes) {
6259
try {
6360
ObjectMapper mapper = new ObjectMapper();
6461
JsonNode geoJson = mapper.readTree(bytes);
65-
if (geoJson.get("type").asText().equals("Polygon")) {
66-
var coords = geoJson.get("coordinates");
67-
ArrayNode outerRingNode = (ArrayNode) coords.get(0);
68-
Coordinate[] outerRingCoordinates = parseCoordinates(outerRingNode);
69-
LinearRing outerRing = GeoUtils.JTS_FACTORY.createLinearRing(outerRingCoordinates);
70-
71-
LinearRing[] innerRings = new LinearRing[coords.size() - 1];
72-
for (int j = 1; j < coords.size(); j++) {
73-
ArrayNode innerRingNode = (ArrayNode) coords.get(j);
74-
Coordinate[] innerRingCoordinates = parseCoordinates(innerRingNode);
75-
innerRings[j - 1] = GeoUtils.JTS_FACTORY.createLinearRing(innerRingCoordinates);
76-
}
77-
78-
clipGeometry = (GeoUtils.JTS_FACTORY.createPolygon(outerRing, innerRings));
79-
return new Clip(clipGeometry);
80-
}
62+
return new Clip(stats, GeoJSON.parseGeometry(geoJson));
8163
} catch (IOException e) {
82-
throw new RuntimeException(e);
64+
throw new IllegalArgumentException("Clip GeoJSON is invalid");
8365
}
84-
throw new RuntimeException();
8566
}
8667

68+
// Copied from elsewhere in planetiler
8769
private static Polygon reassemblePolygon(List<CoordinateSequence> group) throws GeometryException {
8870
try {
8971
LinearRing first = GeoUtils.JTS_FACTORY.createLinearRing(group.getFirst());
@@ -99,6 +81,7 @@ private static Polygon reassemblePolygon(List<CoordinateSequence> group) throws
9981
}
10082
}
10183

84+
// Copied from elsewhere in Planetiler
10285
static Geometry reassemblePolygons(List<List<CoordinateSequence>> groups) throws GeometryException {
10386
int numGeoms = groups.size();
10487
if (numGeoms == 1) {
@@ -117,10 +100,10 @@ public Map<String, List<VectorTile.Feature>> postProcessTile(TileCoord tileCoord
117100
Map<String, List<VectorTile.Feature>> map) throws GeometryException {
118101
if (this.coverings.containsKey(tileCoord.z()) &&
119102
this.coverings.get(tileCoord.z()).test(tileCoord.x(), tileCoord.y())) {
120-
if (this.data.containsKey(tileCoord.z()) && this.data.get(tileCoord.z()).containsKey(tileCoord)) {
121-
List<List<CoordinateSequence>> coords = data.get(tileCoord.z()).get(tileCoord);
103+
if (this.tiledGeometries.containsKey(tileCoord.z()) && this.tiledGeometries.get(tileCoord.z()).containsKey(tileCoord)) {
104+
List<List<CoordinateSequence>> coords = tiledGeometries.get(tileCoord.z()).get(tileCoord);
122105
var clipGeometry = reassemblePolygons(coords);
123-
var clipGeometry2 = GeoUtils.snapAndFixPolygon(clipGeometry, stats, "render");
106+
var clipGeometry2 = GeoUtils.fixPolygon(clipGeometry);
124107
clipGeometry2.reverse();
125108
Map<String, List<VectorTile.Feature>> output = new HashMap<>();
126109

@@ -131,7 +114,7 @@ public Map<String, List<VectorTile.Feature>> postProcessTile(TileCoord tileCoord
131114
var newGeom = OverlayNGRobust.overlay(feature.geometry().decode(), clipGeometry2, OverlayNG.INTERSECTION);
132115
if (!newGeom.isEmpty() && newGeom.getNumGeometries() > 0) {
133116
if (newGeom instanceof Polygonal) {
134-
newGeom = GeoUtils.snapAndFixPolygon(newGeom, stats, "render");
117+
newGeom = GeoUtils.snapAndFixPolygon(newGeom, stats, "clip");
135118
newGeom = newGeom.reverse();
136119
if (!newGeom.isEmpty() && newGeom.getNumGeometries() > 0) {
137120
if (newGeom instanceof GeometryCollection) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.protomaps.basemap;
2+
3+
public class GeoJSONTest {
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.protomaps.basemap.postprocess;
2+
3+
public class ClipTest {
4+
}

0 commit comments

Comments
 (0)