From 58907f7698fef78a9c5769909165ae82bb5d3c72 Mon Sep 17 00:00:00 2001
From: Mike Barry <msb5014@gmail.com>
Date: Wed, 13 Nov 2024 06:42:43 -0500
Subject: [PATCH 1/2] make fill tiles respect buffer size

---
 .../com/onthegomap/planetiler/VectorTile.java  | 18 ++++++++++++++++++
 .../planetiler/render/FeatureRenderer.java     | 15 +++------------
 .../onthegomap/planetiler/VectorTileTest.java  | 12 ++++++++++++
 3 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/VectorTile.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/VectorTile.java
index 452ee6e269..b72e416851 100644
--- a/planetiler-core/src/main/java/com/onthegomap/planetiler/VectorTile.java
+++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/VectorTile.java
@@ -463,6 +463,24 @@ public static int countGeometries(VectorTileProto.Tile.Feature feature) {
     return result;
   }
 
+  /**
+   * Returns the encoded geometry for a polygon that fills an entire tile plus {@code buffer} pixels as a shortcut to
+   * avoid needing to create an extra JTS geometry for encoding.
+   */
+  public static VectorGeometry encodeFill(double buffer) {
+    int min = (int) Math.round(EXTENT * buffer / 256d);
+    int width = EXTENT + min + min;
+    return new VectorGeometry(new int[]{
+      CommandEncoder.commandAndLength(Command.MOVE_TO, 1),
+      zigZagEncode(-min), zigZagEncode(-min),
+      CommandEncoder.commandAndLength(Command.LINE_TO, 3),
+      zigZagEncode(width), 0,
+      0, zigZagEncode(width),
+      zigZagEncode(-width), 0,
+      CommandEncoder.commandAndLength(Command.CLOSE_PATH, 1)
+    }, GeometryType.POLYGON, 0);
+  }
+
   /**
    * Adds features in a layer to this tile.
    *
diff --git a/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java b/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java
index 422f2271c2..95df944180 100644
--- a/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java
+++ b/planetiler-core/src/main/java/com/onthegomap/planetiler/render/FeatureRenderer.java
@@ -28,7 +28,6 @@
 import org.locationtech.jts.geom.Point;
 import org.locationtech.jts.geom.Polygon;
 import org.locationtech.jts.geom.Polygonal;
-import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
 import org.locationtech.jts.geom.util.AffineTransformation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -39,14 +38,6 @@
  */
 public class FeatureRenderer implements Consumer<FeatureCollector.Feature>, Closeable {
   private static final Logger LOGGER = LoggerFactory.getLogger(FeatureRenderer.class);
-  private static final VectorTile.VectorGeometry FILL = VectorTile.encodeGeometry(GeoUtils.JTS_FACTORY
-    .createPolygon(GeoUtils.JTS_FACTORY.createLinearRing(new PackedCoordinateSequence.Double(new double[]{
-      -5, -5,
-      261, -5,
-      261, 261,
-      -5, 261,
-      -5, -5
-    }, 2, 0))));
   private final PlanetilerConfig config;
   private final Consumer<RenderedFeature> consumer;
   private final Stats stats;
@@ -282,13 +273,13 @@ private void writeTileFeatures(int zoom, long id, FeatureCollector.Feature featu
     // polygons that span multiple tiles contain detail about the outer edges separate from the filled tiles, so emit
     // filled tiles now
     if (feature.isPolygon()) {
-      emitted += emitFilledTiles(id, feature, sliced);
+      emitted += emitFilledTiles(zoom, id, feature, sliced);
     }
 
     stats.emittedFeatures(zoom, feature.getLayer(), emitted);
   }
 
-  private int emitFilledTiles(long id, FeatureCollector.Feature feature, TiledGeometry sliced) {
+  private int emitFilledTiles(int zoom, long id, FeatureCollector.Feature feature, TiledGeometry sliced) {
     Optional<RenderedFeature.Group> groupInfo = Optional.empty();
     /*
      * Optimization: large input polygons that generate many filled interior tiles (i.e. the ocean), the encoder avoids
@@ -298,7 +289,7 @@ private int emitFilledTiles(long id, FeatureCollector.Feature feature, TiledGeom
     VectorTile.Feature vectorTileFeature = new VectorTile.Feature(
       feature.getLayer(),
       id,
-      FILL,
+      VectorTile.encodeFill(feature.getBufferPixelsAtZoom(zoom)),
       feature.getAttrsAtZoom(sliced.zoomLevel())
     );
 
diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/VectorTileTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/VectorTileTest.java
index 203ac7e41d..d3f9346958 100644
--- a/planetiler-core/src/test/java/com/onthegomap/planetiler/VectorTileTest.java
+++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/VectorTileTest.java
@@ -692,6 +692,18 @@ void firstCoordinateOfPolygon(double x, double y) {
     }
   }
 
+  @ParameterizedTest
+  @CsvSource({
+    "0, 0, 256",
+    "1, -1, 257",
+    "10, -10, 266",
+  })
+  void testFill(double buffer, double min, double max) throws GeometryException {
+    var geom = VectorTile.encodeFill(buffer);
+    assertSameGeometry(rectangle(min, max), geom.decode());
+    assertArrayEquals(VectorTile.encodeGeometry(rectangle(min, max)).commands(), geom.commands());
+  }
+
   private static void assertArrayEquals(int[] a, int[] b) {
     assertEquals(
       IntStream.of(a).boxed().toList(),

From 389ff39f6083072891c3bb1ec38b79f2583a9c7d Mon Sep 17 00:00:00 2001
From: Mike Barry <msb5014@gmail.com>
Date: Wed, 13 Nov 2024 06:49:35 -0500
Subject: [PATCH 2/2] fix tests

---
 .../java/com/onthegomap/planetiler/PlanetilerTests.java   | 8 ++++----
 .../onthegomap/planetiler/render/FeatureRendererTest.java | 4 ++--
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java
index eb9fb3c303..044822d71d 100644
--- a/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java
+++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/PlanetilerTests.java
@@ -770,7 +770,7 @@ void testPolygonWithHoleSpanningMultipleTiles(boolean anyGeom) throws Exception
         ), Map.of())
       )),
       newTileEntry(Z14_TILES / 2 + 2, Z14_TILES / 2 + 1, 14, List.of(
-        feature(newPolygon(tileFill(5), List.of()), Map.of())
+        feature(newPolygon(tileFill(4), List.of()), Map.of())
       )),
       newTileEntry(Z14_TILES / 2 + 3, Z14_TILES / 2 + 1, 14, List.of(
         feature(tileLeft(4), Map.of())
@@ -814,7 +814,7 @@ void testZ15Fill() throws Exception {
     );
 
     assertEquals(List.of(
-      feature(newPolygon(tileFill(5)), Map.of())
+      feature(newPolygon(tileFill(4)), Map.of())
     ), results.tiles.get(TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15)));
   }
 
@@ -832,7 +832,7 @@ void testFullWorldPolygon() throws Exception {
 
     assertEquals(5461, results.tiles.size());
     // spot-check one filled tile
-    assertEquals(List.of(rectangle(-5, 256 + 5).norm()), results.tiles.get(TileCoord.ofXYZ(
+    assertEquals(List.of(rectangle(-4, 256 + 4).norm()), results.tiles.get(TileCoord.ofXYZ(
       Z4_TILES / 2, Z4_TILES / 2, 4
     )).stream().map(d -> d.geometry().geom().norm()).toList());
   }
@@ -2449,7 +2449,7 @@ private PlanetilerResults runForCompactTest(boolean compactDbEnabled) throws Exc
       ),
       (in, features) -> features.polygon("layer")
         .setZoomRange(0, 2)
-        .setBufferPixels(0)
+        .setBufferPixels(1)
     );
   }
 
diff --git a/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java b/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java
index 315b03c4e9..a1a3dab4dd 100644
--- a/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java
+++ b/planetiler-core/src/test/java/com/onthegomap/planetiler/render/FeatureRendererTest.java
@@ -814,7 +814,7 @@ void testFill() {
         tileRight(1)
       ),
       TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
-        newPolygon(tileFill(5), List.of()) // <<<<---- the filled tile!
+        newPolygon(tileFill(1), List.of()) // <<<<---- the filled tile!
       ),
       TileCoord.ofXYZ(Z14_TILES / 2 + 1, Z14_TILES / 2, 14), List.of(
         tileLeft(1)
@@ -1173,7 +1173,7 @@ void testNestedMultipolygonFill() {
     var rendered = renderGeometry(feature);
     var innerTile = rendered.get(TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14));
     assertEquals(1, innerTile.size());
-    assertEquals(new TestUtils.NormGeometry(rectangle(-5, 256 + 5)),
+    assertEquals(new TestUtils.NormGeometry(rectangle(-1, 256 + 1)),
       new TestUtils.NormGeometry(innerTile.iterator().next()));
   }