Skip to content

Commit

Permalink
Cheap isFill check and add --skip-filled-tiles option (#234)
Browse files Browse the repository at this point in the history
  • Loading branch information
msbarry authored Jun 4, 2022
1 parent b0f634b commit bed2220
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,34 @@ public byte[] encode() {
return tile.build().toByteArray();
}

/**
* Returns true if this tile contains only polygon fills.
*/
public boolean containsOnlyFills() {
return containsOnlyFillsOrEdges(false);
}

/**
* Returns true if this tile contains only polygon fills or horizontal/vertical edges that are likely to be repeated
* across tiles.
*/
public boolean containsOnlyFillsOrEdges() {
return containsOnlyFillsOrEdges(true);
}

private boolean containsOnlyFillsOrEdges(boolean allowEdges) {
boolean empty = true;
for (var layer : layers.values()) {
for (var feature : layer.encodedFeatures) {
empty = false;
if (!feature.geometry.isFillOrEdge(allowEdges)) {
return false;
}
}
}
return !empty;
}

private enum Command {
MOVE_TO(1),
LINE_TO(2),
Expand All @@ -519,12 +547,63 @@ private enum Command {
*/
public record VectorGeometry(int[] commands, GeometryType geomType, int scale) {

private static final int LEFT = 1;
private static final int RIGHT = 1 << 1;
private static final int TOP = 1 << 2;
private static final int BOTTOM = 1 << 3;
private static final int INSIDE = 0;
private static final int ALL = TOP | LEFT | RIGHT | BOTTOM;

public VectorGeometry {
if (scale < 0) {
throw new IllegalArgumentException("scale can not be less than 0, got: " + scale);
}
}

private static int getSide(int x, int y, int extent) {
int result = INSIDE;
if (x < 0) {
result |= LEFT;
} else if (x > extent) {
result |= RIGHT;
}
if (y < 0) {
result |= TOP;
} else if (y > extent) {
result |= BOTTOM;
}
return result;
}

private static boolean slanted(int x1, int y1, int x2, int y2) {
return x1 != x2 && y1 != y2;
}

private static boolean segmentCrossesTile(int x1, int y1, int x2, int y2, int extent) {
return (y1 >= 0 || y2 >= 0) &&
(y1 <= extent || y2 <= extent) &&
(x1 >= 0 || x2 >= 0) &&
(x1 <= extent || x2 <= extent);
}

private static boolean isSegmentInvalid(boolean allowEdges, int x1, int y1, int x2, int y2, int extent) {
boolean crossesTile = segmentCrossesTile(x1, y1, x2, y2, extent);
if (allowEdges) {
return crossesTile && slanted(x1, y1, x2, y2);
} else {
return crossesTile;
}
}


private static boolean visitedEnoughSides(boolean allowEdges, int sides) {
if (allowEdges) {
return ((sides & LEFT) > 0 && (sides & RIGHT) > 0) || ((sides & TOP) > 0 && (sides & BOTTOM) > 0);
} else {
return sides == ALL;
}
}

/** Converts an encoded geometry back to a JTS geometry. */
public Geometry decode() throws GeometryException {
return decodeCommands(geomType, commands, scale);
Expand Down Expand Up @@ -566,6 +645,95 @@ public String toString() {
"], geomType=" + geomType +
" (" + geomType.asByte() + ")]";
}

/** Returns true if the encoded geometry is a polygon fill. */
public boolean isFill() {
return isFillOrEdge(false);
}

/**
* Returns true if the encoded geometry is a polygon fill, rectangle edge, or part of a horizontal/vertical line
* that is likely to be repeated across tiles.
*/
public boolean isFillOrEdge() {
return isFillOrEdge(true);
}

/**
* Returns true if the encoded geometry is a polygon fill, or if {@code allowEdges == true} then also a rectangle
* edge, or part of a horizontal/vertical line that is likely to be repeated across tiles.
*/
public boolean isFillOrEdge(boolean allowEdges) {
if (geomType != GeometryType.POLYGON && (!allowEdges || geomType != GeometryType.LINE)) {
return false;
}

boolean isLine = geomType == GeometryType.LINE;

int extent = EXTENT << scale;
int visited = INSIDE;
int firstX = 0;
int firstY = 0;
int x = 0;
int y = 0;

int geometryCount = commands.length;
int length = 0;
int command = 0;
int i = 0;
while (i < geometryCount) {

if (length <= 0) {
length = commands[i++];
command = length & ((1 << 3) - 1);
length = length >> 3;
if (isLine && length > 2) {
return false;
}
}

if (length > 0) {
if (command == Command.CLOSE_PATH.value) {
if (isSegmentInvalid(allowEdges, x, y, firstX, firstY, extent) ||
!visitedEnoughSides(allowEdges, visited)) {
return false;
}
length--;
continue;
}

int dx = commands[i++];
int dy = commands[i++];

length--;

dx = zigZagDecode(dx);
dy = zigZagDecode(dy);

int nextX = x + dx;
int nextY = y + dy;

if (command == Command.MOVE_TO.value) {
firstX = nextX;
firstY = nextY;
if ((visited = getSide(firstX, firstY, extent)) == INSIDE) {
return false;
}
} else {
if (isSegmentInvalid(allowEdges, x, y, nextX, nextY, extent)) {
return false;
}
visited |= getSide(nextX, nextY, extent);
}
y = nextY;
x = nextX;
}

}

return visitedEnoughSides(allowEdges, visited);
}

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public record PlanetilerConfig(
double simplifyToleranceAtMaxZoom,
double simplifyToleranceBelowMaxZoom,
boolean osmLazyReads,
boolean compactDb
boolean compactDb,
boolean skipFilledTiles
) {

public static final int MIN_MINZOOM = 0;
Expand Down Expand Up @@ -142,7 +143,10 @@ public static PlanetilerConfig from(Arguments arguments) {
false),
arguments.getBoolean("compact_db",
"Reduce the DB size by separating and deduping the tile data",
true)
true),
arguments.getBoolean("skip_filled_tiles",
"Skip writing tiles containing only polygon fills to the output",
false)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ public class MbtilesWriter {
private static final Logger LOGGER = LoggerFactory.getLogger(MbtilesWriter.class);
private static final long MAX_FEATURES_PER_BATCH = 10_000;
private static final long MAX_TILES_PER_BATCH = 1_000;
private static final int MAX_FEATURES_HASHING_THRESHOLD = 5;
private final Counter.Readable featuresProcessed;
private final Counter memoizedTiles;
private final Mbtiles db;
Expand Down Expand Up @@ -258,7 +257,9 @@ private void tileEncoder(Iterable<TileBatch> prev, Consumer<TileBatch> next) thr
*/
byte[] lastBytes = null, lastEncoded = null;
Long lastTileDataHash = null;
boolean lastIsFill = false;
boolean compactDb = config.compactDb();
boolean skipFilled = config.skipFilledTiles();

for (TileBatch batch : prev) {
Queue<TileEncodingResult> result = new ArrayDeque<>(batch.size());
Expand All @@ -270,23 +271,30 @@ private void tileEncoder(Iterable<TileBatch> prev, Consumer<TileBatch> next) thr
byte[] bytes, encoded;
Long tileDataHash;
if (tileFeatures.hasSameContents(last)) {
if (skipFilled && lastIsFill) {
continue;
}
bytes = lastBytes;
encoded = lastEncoded;
tileDataHash = lastTileDataHash;
memoizedTiles.inc();
} else {
VectorTile en = tileFeatures.getVectorTileEncoder();
encoded = en.encode();
bytes = gzip(encoded);
if (skipFilled) {
lastIsFill = en.containsOnlyFills();
if (lastIsFill) {
continue;
}
}
lastEncoded = encoded = en.encode();
lastBytes = bytes = gzip(encoded);
last = tileFeatures;
lastEncoded = encoded;
lastBytes = bytes;
if (encoded.length > 1_000_000) {
LOGGER.warn("{} {}kb uncompressed",
tileFeatures.tileCoord(),
encoded.length / 1024);
}
if (compactDb && tileFeatures.getNumFeaturesToEmit() < MAX_FEATURES_HASHING_THRESHOLD) {
if (compactDb && en.containsOnlyFillsOrEdges()) {
tileDataHash = tileFeatures.generateContentHash();
} else {
tileDataHash = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,25 @@ void testFullWorldPolygon() throws Exception {
)).stream().map(d -> d.geometry().geom().norm()).toList());
}

@Test
void testSkipFill() throws Exception {
var results = runWithReaderFeatures(
Map.of("threads", "1", "skip-filled-tiles", "true"),
List.of(
newReaderFeature(WORLD_POLYGON, Map.of())
),
(in, features) -> features.polygon("layer")
.setZoomRange(0, 6)
.setBufferPixels(4)
);

assertEquals(481, results.tiles.size());
// spot-check one filled tile does not exist
assertNull(results.tiles.get(TileCoord.ofXYZ(
Z4_TILES / 2, Z4_TILES / 2, 4
)));
}

@ParameterizedTest
@CsvSource({
"chesapeake.wkb, 4076",
Expand Down
Loading

0 comments on commit bed2220

Please sign in to comment.