kopia lustrzana https://github.com/onthegomap/planetiler
Support zoom levels up to 15 (#303)
rodzic
a6a8c245b8
commit
621a8ed759
|
@ -14,7 +14,8 @@ To set up your local development environment:
|
|||
- Install Java 16 or later. You can download Java manually from [Adoptium](https://adoptium.net/installation.html) or
|
||||
use:
|
||||
- [Windows installer](https://adoptium.net/installation.html#windows-msi)
|
||||
- [macOS installer](https://adoptium.net/installation.html#macos-pkg) (or `brew install --cask temurin`, or `port install openjdk17-temurin`)
|
||||
- [macOS installer](https://adoptium.net/installation.html#macos-pkg) (or `brew install --cask temurin`,
|
||||
or `port install openjdk17-temurin`)
|
||||
- [Linux installer](https://github.com/adoptium/website-v2/blob/main/src/asciidoc-pages/installation/linux.adoc)
|
||||
(or `apt-get install openjdk-17-jdk`)
|
||||
- Build and run the tests ([mvnw](https://github.com/takari/maven-wrapper) automatically downloads maven the first time
|
||||
|
@ -25,10 +26,10 @@ To set up your local development environment:
|
|||
- to run just one test e.g. `GeoUtilsTest`: `./mvnw -pl planetiler-core -Dtest=GeoUtilsTest test`
|
||||
- to run benchmarks e.g. `BenchmarkTileCoord`:
|
||||
|
||||
```sh
|
||||
./scripts/build.sh
|
||||
java -cp planetiler-dist/target/planetiler-dist-*-with-deps.jar com.onthegomap.planetiler.benchmarks.BenchmarkTileCoord
|
||||
```
|
||||
```sh
|
||||
./scripts/build.sh
|
||||
java -cp planetiler-dist/target/planetiler-dist-*-with-deps.jar com.onthegomap.planetiler.benchmarks.BenchmarkTileCoord
|
||||
```
|
||||
|
||||
GitHub Workflows will run regression tests on any pull request.
|
||||
|
||||
|
|
|
@ -35,6 +35,8 @@ The `planetiler-core` module includes the following software:
|
|||
- `PbfFieldDecoder` from [osmosis](https://github.com/openstreetmap/osmosis) (Public Domain)
|
||||
- `Madvise` from [uppend](https://github.com/upserve/uppend/) (MIT License)
|
||||
- `ArrayLongMinHeap` implementations from [graphhopper](https://github.com/graphhopper/graphhopper) (Apache license)
|
||||
- `Hilbert` implementation
|
||||
from [github.com/rawrunprotected/hilbert_curves](https://github.com/rawrunprotected/hilbert_curves) (Public Domain)
|
||||
|
||||
Additionally, the `planetiler-basemap` module is based on [OpenMapTiles](https://github.com/openmaptiles/openmaptiles):
|
||||
|
||||
|
|
|
@ -52,7 +52,8 @@ public record PlanetilerConfig(
|
|||
) {
|
||||
|
||||
public static final int MIN_MINZOOM = 0;
|
||||
public static final int MAX_MAXZOOM = 14;
|
||||
public static final int MAX_MAXZOOM = 15;
|
||||
private static final int DEFAULT_MAXZOOM = 14;
|
||||
|
||||
public PlanetilerConfig {
|
||||
if (minzoom > maxzoom) {
|
||||
|
@ -104,6 +105,12 @@ public record PlanetilerConfig(
|
|||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
int minzoom = arguments.getInteger("minzoom", "minimum zoom level", MIN_MINZOOM);
|
||||
int maxzoom = arguments.getInteger("maxzoom", "maximum zoom level up to " + MAX_MAXZOOM, DEFAULT_MAXZOOM);
|
||||
int renderMaxzoom =
|
||||
arguments.getInteger("render_maxzoom", "maximum rendering zoom level up to " + MAX_MAXZOOM,
|
||||
Math.max(maxzoom, DEFAULT_MAXZOOM));
|
||||
return new PlanetilerConfig(
|
||||
arguments,
|
||||
bounds,
|
||||
|
@ -113,9 +120,9 @@ public record PlanetilerConfig(
|
|||
arguments.getInteger("feature_read_threads", "number of threads to use when reading features at tile write time",
|
||||
threads < 32 ? 1 : 2),
|
||||
arguments.getDuration("loginterval", "time between logs", "10s"),
|
||||
arguments.getInteger("minzoom", "minimum zoom level", MIN_MINZOOM),
|
||||
arguments.getInteger("maxzoom", "maximum zoom level (limit 14)", MAX_MAXZOOM),
|
||||
arguments.getInteger("render_maxzoom", "maximum rendering zoom level (limit 14)", MAX_MAXZOOM),
|
||||
minzoom,
|
||||
maxzoom,
|
||||
renderMaxzoom,
|
||||
arguments.getBoolean("skip_mbtiles_index_creation", "skip adding index to mbtiles file", false),
|
||||
arguments.getBoolean("optimize_db", "optimize mbtiles after writing", false),
|
||||
arguments.getBoolean("emit_tiles_in_order", "emit tiles in index order", true),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package com.onthegomap.planetiler.geo;
|
||||
|
||||
import static com.onthegomap.planetiler.config.PlanetilerConfig.MAX_MAXZOOM;
|
||||
|
||||
import com.onthegomap.planetiler.util.Format;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
|
@ -21,8 +23,36 @@ import org.locationtech.jts.geom.CoordinateXY;
|
|||
*/
|
||||
@Immutable
|
||||
public record TileCoord(int encoded, int x, int y, int z) implements Comparable<TileCoord> {
|
||||
|
||||
private static final int[] ZOOM_START_INDEX = new int[MAX_MAXZOOM + 1];
|
||||
|
||||
static {
|
||||
int idx = 0;
|
||||
for (int z = 0; z <= MAX_MAXZOOM; z++) {
|
||||
ZOOM_START_INDEX[z] = idx;
|
||||
int count = (1 << z) * (1 << z);
|
||||
if (Integer.MAX_VALUE - idx < count) {
|
||||
throw new IllegalStateException("Too many zoom levels " + MAX_MAXZOOM);
|
||||
}
|
||||
idx += count;
|
||||
}
|
||||
}
|
||||
|
||||
private static int startIndexForZoom(int z) {
|
||||
return ZOOM_START_INDEX[z];
|
||||
}
|
||||
|
||||
private static int zoomForIndex(int idx) {
|
||||
for (int z = MAX_MAXZOOM; z >= 0; z--) {
|
||||
if (ZOOM_START_INDEX[z] <= idx) {
|
||||
return z;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Bad index: " + idx);
|
||||
}
|
||||
|
||||
public TileCoord {
|
||||
assert z <= 15;
|
||||
assert z <= MAX_MAXZOOM;
|
||||
}
|
||||
|
||||
public static TileCoord ofXYZ(int x, int y, int z) {
|
||||
|
@ -30,19 +60,9 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
|
|||
}
|
||||
|
||||
public static TileCoord decode(int encoded) {
|
||||
int acc = 0;
|
||||
int tmpZ = 0;
|
||||
while (true) {
|
||||
int numTiles = (1 << tmpZ) * (1 << tmpZ);
|
||||
if (acc + numTiles > encoded) {
|
||||
int position = encoded - acc;
|
||||
// long xy = hilbertPositionToXY(tmpZ, position);
|
||||
long xy = tmsPositionToXY(tmpZ, position);
|
||||
return new TileCoord(encoded, (int) (xy >>> 32 & 0xFFFFFFFFL), (int) (xy & 0xFFFFFFFFL), tmpZ);
|
||||
}
|
||||
acc += numTiles;
|
||||
tmpZ++;
|
||||
}
|
||||
int z = zoomForIndex(encoded);
|
||||
long xy = tmsPositionToXY(z, encoded - startIndexForZoom(z));
|
||||
return new TileCoord(encoded, (int) (xy >>> 32 & 0xFFFFFFFFL), (int) (xy & 0xFFFFFFFFL), z);
|
||||
}
|
||||
|
||||
/** Returns the tile containing a latitude/longitude coordinate at a given zoom level. */
|
||||
|
@ -54,12 +74,7 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
|
|||
}
|
||||
|
||||
public static int encode(int x, int y, int z) {
|
||||
int acc = 0;
|
||||
for (int tmpZ = 0; tmpZ < z; tmpZ++) {
|
||||
acc += (1 << tmpZ) * (1 << tmpZ);
|
||||
}
|
||||
// return acc + hilbertXYToPosition(z, x, y);
|
||||
return acc + tmsXYToPosition(z, x, y);
|
||||
return startIndexForZoom(z) + tmsXYToPosition(z, x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -86,17 +101,11 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
|
|||
return "{x=" + x + " y=" + y + " z=" + z + '}';
|
||||
}
|
||||
|
||||
public double progressOnLevel() {
|
||||
int acc = 0;
|
||||
int tmpZ = 0;
|
||||
while (true) {
|
||||
int numTiles = (1 << tmpZ) * (1 << tmpZ);
|
||||
if (acc + numTiles > encoded) {
|
||||
return (encoded - acc) / (double) numTiles;
|
||||
}
|
||||
acc += numTiles;
|
||||
tmpZ++;
|
||||
}
|
||||
public double progressOnLevel(TileExtents extents) {
|
||||
// approximate percent complete within a bounding box by computing what % of the way through the columns we are
|
||||
// (for hilbert ordering, we probably won't be able to reflect the bounding box)
|
||||
var zoomBounds = extents.getForZoom(z);
|
||||
return 1d * (x - zoomBounds.minX()) / (zoomBounds.maxX() - zoomBounds.minX());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -141,107 +150,4 @@ public record TileCoord(int encoded, int x, int y, int z) implements Comparable<
|
|||
int dim = 1 << z;
|
||||
return x * dim + (dim - 1 - y);
|
||||
}
|
||||
|
||||
// hilbert implementation (not currently used)
|
||||
// Fast Hilbert curve algorithm by http://threadlocalmutex.com/
|
||||
// Ported from C++ https://github.com/rawrunprotected/hilbert_curves (public domain)
|
||||
private static int deinterleave(int tx) {
|
||||
tx = tx & 0x55555555;
|
||||
tx = (tx | (tx >>> 1)) & 0x33333333;
|
||||
tx = (tx | (tx >>> 2)) & 0x0F0F0F0F;
|
||||
tx = (tx | (tx >>> 4)) & 0x00FF00FF;
|
||||
tx = (tx | (tx >>> 8)) & 0x0000FFFF;
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static int interleave(int tx) {
|
||||
tx = (tx | (tx << 8)) & 0x00FF00FF;
|
||||
tx = (tx | (tx << 4)) & 0x0F0F0F0F;
|
||||
tx = (tx | (tx << 2)) & 0x33333333;
|
||||
tx = (tx | (tx << 1)) & 0x55555555;
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static int prefixScan(int tx) {
|
||||
tx = (tx >>> 8) ^ tx;
|
||||
tx = (tx >>> 4) ^ tx;
|
||||
tx = (tx >>> 2) ^ tx;
|
||||
tx = (tx >>> 1) ^ tx;
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static long hilbertPositionToXY(int z, int pos) {
|
||||
pos = pos << (32 - 2 * z);
|
||||
|
||||
int i0 = deinterleave(pos);
|
||||
int i1 = deinterleave(pos >>> 1);
|
||||
|
||||
int t0 = (i0 | i1) ^ 0xFFFF;
|
||||
int t1 = i0 & i1;
|
||||
|
||||
int prefixT0 = prefixScan(t0);
|
||||
int prefixT1 = prefixScan(t1);
|
||||
|
||||
int a = (((i0 ^ 0xFFFF) & prefixT1) | (i0 & prefixT0));
|
||||
|
||||
int resultX = (a ^ i1) >>> (16 - z);
|
||||
int resultY = (a ^ i0 ^ i1) >>> (16 - z);
|
||||
return ((long) resultX << 32) | resultY;
|
||||
}
|
||||
|
||||
private static int hilbertXYToIndex(int z, int x, int y) {
|
||||
x = x << (16 - z);
|
||||
y = y << (16 - z);
|
||||
|
||||
int hA, hB, hC, hD;
|
||||
|
||||
int a1 = x ^ y;
|
||||
int b1 = 0xFFFF ^ a1;
|
||||
int c1 = 0xFFFF ^ (x | y);
|
||||
int d1 = x & (y ^ 0xFFFF);
|
||||
|
||||
hA = a1 | (b1 >>> 1);
|
||||
hB = (a1 >>> 1) ^ a1;
|
||||
|
||||
hC = ((c1 >>> 1) ^ (b1 & (d1 >>> 1))) ^ c1;
|
||||
hD = ((a1 & (c1 >>> 1)) ^ (d1 >>> 1)) ^ d1;
|
||||
|
||||
int a2 = hA;
|
||||
int b2 = hB;
|
||||
int c2 = hC;
|
||||
int d2 = hD;
|
||||
|
||||
hA = ((a2 & (a2 >>> 2)) ^ (b2 & (b2 >>> 2)));
|
||||
hB = ((a2 & (b2 >>> 2)) ^ (b2 & ((a2 ^ b2) >>> 2)));
|
||||
|
||||
hC ^= ((a2 & (c2 >>> 2)) ^ (b2 & (d2 >>> 2)));
|
||||
hD ^= ((b2 & (c2 >>> 2)) ^ ((a2 ^ b2) & (d2 >>> 2)));
|
||||
|
||||
int a3 = hA;
|
||||
int b3 = hB;
|
||||
int c3 = hC;
|
||||
int d3 = hD;
|
||||
|
||||
hA = ((a3 & (a3 >>> 4)) ^ (b3 & (b3 >>> 4)));
|
||||
hB = ((a3 & (b3 >>> 4)) ^ (b3 & ((a3 ^ b3) >>> 4)));
|
||||
|
||||
hC ^= ((a3 & (c3 >>> 4)) ^ (b3 & (d3 >>> 4)));
|
||||
hD ^= ((b3 & (c3 >>> 4)) ^ ((a3 ^ b3) & (d3 >>> 4)));
|
||||
|
||||
int a4 = hA;
|
||||
int b4 = hB;
|
||||
int c4 = hC;
|
||||
int d4 = hD;
|
||||
|
||||
hC ^= ((a4 & (c4 >>> 8)) ^ (b4 & (d4 >>> 8)));
|
||||
hD ^= ((b4 & (c4 >>> 8)) ^ ((a4 ^ b4) & (d4 >>> 8)));
|
||||
|
||||
int a = hC ^ (hC >>> 1);
|
||||
int b = hD ^ (hD >>> 1);
|
||||
|
||||
int i0 = x ^ y;
|
||||
int i1 = b | (0xFFFF ^ (i0 | a));
|
||||
|
||||
return ((interleave(i1) << 1) | interleave(i0)) >>> (32 - 2 * z);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,9 +203,9 @@ public class MbtilesWriter {
|
|||
if (lastTile == null) {
|
||||
blurb = "n/a";
|
||||
} else {
|
||||
blurb = "%d/%d/%d (z%d %s%%) %s".formatted(
|
||||
blurb = "%d/%d/%d (z%d %s) %s".formatted(
|
||||
lastTile.z(), lastTile.x(), lastTile.y(),
|
||||
lastTile.z(), 100 * lastTile.progressOnLevel(),
|
||||
lastTile.z(), Format.defaultInstance().percent(lastTile.progressOnLevel(config.bounds().tileExtents())),
|
||||
lastTile.getDebugUrl()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package com.onthegomap.planetiler.util;
|
||||
|
||||
/**
|
||||
* Fast hilbert space-filling curve implementation ported from C++
|
||||
* <a href="https://github.com/rawrunprotected/hilbert_curves">github.com/rawrunprotected/hilbert_curves</a> by
|
||||
* <a href="https://threadlocalmutex.com/">threadlocalmutex.com</a> (public domain).
|
||||
*/
|
||||
public class Hilbert {
|
||||
private Hilbert() {
|
||||
throw new IllegalStateException("Utility class");
|
||||
}
|
||||
|
||||
private static int deinterleave(int tx) {
|
||||
tx = tx & 0x55555555;
|
||||
tx = (tx | (tx >>> 1)) & 0x33333333;
|
||||
tx = (tx | (tx >>> 2)) & 0x0F0F0F0F;
|
||||
tx = (tx | (tx >>> 4)) & 0x00FF00FF;
|
||||
tx = (tx | (tx >>> 8)) & 0x0000FFFF;
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static int interleave(int tx) {
|
||||
tx = (tx | (tx << 8)) & 0x00FF00FF;
|
||||
tx = (tx | (tx << 4)) & 0x0F0F0F0F;
|
||||
tx = (tx | (tx << 2)) & 0x33333333;
|
||||
tx = (tx | (tx << 1)) & 0x55555555;
|
||||
return tx;
|
||||
}
|
||||
|
||||
private static int prefixScan(int tx) {
|
||||
tx = (tx >>> 8) ^ tx;
|
||||
tx = (tx >>> 4) ^ tx;
|
||||
tx = (tx >>> 2) ^ tx;
|
||||
tx = (tx >>> 1) ^ tx;
|
||||
return tx;
|
||||
}
|
||||
|
||||
/** Returns the x coordinate extracted from the result of {@link #hilbertPositionToXY(int, int)}. */
|
||||
public static int extractX(long xy) {
|
||||
return (int) (xy >>> 32);
|
||||
}
|
||||
|
||||
/** Returns the y coordinate extracted from the result of {@link #hilbertPositionToXY(int, int)}. */
|
||||
public static int extractY(long xy) {
|
||||
return (int) xy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the x/y coordinates from hilbert index {@code pos} at {@code level} packed into a long.
|
||||
*
|
||||
* Use {@link #extractX(long)} and {@link #extractY(long)} to extract x and y from the result.
|
||||
*/
|
||||
public static long hilbertPositionToXY(int level, int pos) {
|
||||
pos = pos << (32 - 2 * level);
|
||||
|
||||
int i0 = deinterleave(pos);
|
||||
int i1 = deinterleave(pos >>> 1);
|
||||
|
||||
int t0 = (i0 | i1) ^ 0xFFFF;
|
||||
int t1 = i0 & i1;
|
||||
|
||||
int prefixT0 = prefixScan(t0);
|
||||
int prefixT1 = prefixScan(t1);
|
||||
|
||||
int a = (((i0 ^ 0xFFFF) & prefixT1) | (i0 & prefixT0));
|
||||
|
||||
int resultX = (a ^ i1) >>> (16 - level);
|
||||
int resultY = (a ^ i0 ^ i1) >>> (16 - level);
|
||||
return ((long) resultX << 32) | resultY;
|
||||
}
|
||||
|
||||
/** Returns the hilbert index at {@code level} for an x/y coordinate. */
|
||||
public static int hilbertXYToIndex(int level, int x, int y) {
|
||||
x = x << (16 - level);
|
||||
y = y << (16 - level);
|
||||
|
||||
int hA, hB, hC, hD;
|
||||
|
||||
int a1 = x ^ y;
|
||||
int b1 = 0xFFFF ^ a1;
|
||||
int c1 = 0xFFFF ^ (x | y);
|
||||
int d1 = x & (y ^ 0xFFFF);
|
||||
|
||||
hA = a1 | (b1 >>> 1);
|
||||
hB = (a1 >>> 1) ^ a1;
|
||||
|
||||
hC = ((c1 >>> 1) ^ (b1 & (d1 >>> 1))) ^ c1;
|
||||
hD = ((a1 & (c1 >>> 1)) ^ (d1 >>> 1)) ^ d1;
|
||||
|
||||
int a2 = hA;
|
||||
int b2 = hB;
|
||||
int c2 = hC;
|
||||
int d2 = hD;
|
||||
|
||||
hA = ((a2 & (a2 >>> 2)) ^ (b2 & (b2 >>> 2)));
|
||||
hB = ((a2 & (b2 >>> 2)) ^ (b2 & ((a2 ^ b2) >>> 2)));
|
||||
|
||||
hC ^= ((a2 & (c2 >>> 2)) ^ (b2 & (d2 >>> 2)));
|
||||
hD ^= ((b2 & (c2 >>> 2)) ^ ((a2 ^ b2) & (d2 >>> 2)));
|
||||
|
||||
int a3 = hA;
|
||||
int b3 = hB;
|
||||
int c3 = hC;
|
||||
int d3 = hD;
|
||||
|
||||
hA = ((a3 & (a3 >>> 4)) ^ (b3 & (b3 >>> 4)));
|
||||
hB = ((a3 & (b3 >>> 4)) ^ (b3 & ((a3 ^ b3) >>> 4)));
|
||||
|
||||
hC ^= ((a3 & (c3 >>> 4)) ^ (b3 & (d3 >>> 4)));
|
||||
hD ^= ((b3 & (c3 >>> 4)) ^ ((a3 ^ b3) & (d3 >>> 4)));
|
||||
|
||||
int a4 = hA;
|
||||
int b4 = hB;
|
||||
int c4 = hC;
|
||||
int d4 = hD;
|
||||
|
||||
hC ^= ((a4 & (c4 >>> 8)) ^ (b4 & (d4 >>> 8)));
|
||||
hD ^= ((b4 & (c4 >>> 8)) ^ ((a4 ^ b4) & (d4 >>> 8)));
|
||||
|
||||
int a = hC ^ (hC >>> 1);
|
||||
int b = hD ^ (hD >>> 1);
|
||||
|
||||
int i0 = x ^ y;
|
||||
int i1 = b | (0xFFFF ^ (i0 | a));
|
||||
|
||||
return ((interleave(i1) << 1) | interleave(i0)) >>> (32 - 2 * level);
|
||||
}
|
||||
}
|
|
@ -64,6 +64,8 @@ class PlanetilerTests {
|
|||
private static final String TEST_PROFILE_DESCRIPTION = "test description";
|
||||
private static final String TEST_PROFILE_ATTRIBUTION = "test attribution";
|
||||
private static final String TEST_PROFILE_VERSION = "test version";
|
||||
private static final int Z15_TILES = 1 << 15;
|
||||
private static final double Z15_WIDTH = 1d / Z15_TILES;
|
||||
private static final int Z14_TILES = 1 << 14;
|
||||
private static final double Z14_WIDTH = 1d / Z14_TILES;
|
||||
private static final int Z13_TILES = 1 << 13;
|
||||
|
@ -281,43 +283,49 @@ class PlanetilerTests {
|
|||
|
||||
@Test
|
||||
void testSinglePoint() throws Exception {
|
||||
double x = 0.5 + Z14_WIDTH / 2;
|
||||
double y = 0.5 + Z14_WIDTH / 2;
|
||||
double x = 0.5 + Z14_WIDTH / 4;
|
||||
double y = 0.5 + Z14_WIDTH / 4;
|
||||
double lat = GeoUtils.getWorldLat(y);
|
||||
double lng = GeoUtils.getWorldLon(x);
|
||||
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1"),
|
||||
Map.of("threads", "1", "maxzoom", "15"),
|
||||
List.of(
|
||||
newReaderFeature(newPoint(lng, lat), Map.of(
|
||||
"attr", "value"
|
||||
))
|
||||
),
|
||||
(in, features) -> features.point("layer")
|
||||
.setZoomRange(13, 14)
|
||||
.setZoomRange(13, 15)
|
||||
.setAttr("name", "name value")
|
||||
.inheritAttrFromSource("attr")
|
||||
);
|
||||
|
||||
assertSubmap(Map.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15), List.of(
|
||||
feature(newPoint(128, 128), Map.of(
|
||||
"attr", "value",
|
||||
"name", "name value"
|
||||
))
|
||||
),
|
||||
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
feature(newPoint(64, 64), Map.of(
|
||||
"attr", "value",
|
||||
"name", "name value"
|
||||
))
|
||||
),
|
||||
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
||||
feature(newPoint(32, 32), Map.of(
|
||||
"attr", "value",
|
||||
"name", "name value"
|
||||
))
|
||||
)
|
||||
), results.tiles);
|
||||
assertSameJson(
|
||||
"""
|
||||
{
|
||||
"vector_layers": [
|
||||
{"id": "layer", "fields": {"name": "String", "attr": "String"}, "minzoom": 13, "maxzoom": 14}
|
||||
{"id": "layer", "fields": {"name": "String", "attr": "String"}, "minzoom": 13, "maxzoom": 15}
|
||||
]
|
||||
}
|
||||
""",
|
||||
|
@ -656,6 +664,33 @@ class PlanetilerTests {
|
|||
), results.tiles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testZ15Fill() throws Exception {
|
||||
List<Coordinate> outerPoints = z14CoordinateList(
|
||||
-2, -2,
|
||||
2, -2,
|
||||
2, 2,
|
||||
-2, 2,
|
||||
-2, -2
|
||||
);
|
||||
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1", "maxzoom", "15"),
|
||||
List.of(
|
||||
newReaderFeature(newPolygon(
|
||||
outerPoints
|
||||
), Map.of())
|
||||
),
|
||||
(in, features) -> features.polygon("layer")
|
||||
.setZoomRange(15, 15)
|
||||
.setBufferPixels(4)
|
||||
);
|
||||
|
||||
assertEquals(List.of(
|
||||
feature(newPolygon(tileFill(5)), Map.of())
|
||||
), results.tiles.get(TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFullWorldPolygon() throws Exception {
|
||||
var results = runWithReaderFeatures(
|
||||
|
@ -1158,17 +1193,17 @@ class PlanetilerTests {
|
|||
|
||||
@Test
|
||||
void testMergeLineStrings() throws Exception {
|
||||
double y = 0.5 + Z14_WIDTH / 2;
|
||||
double y = 0.5 + Z15_WIDTH / 2;
|
||||
double lat = GeoUtils.getWorldLat(y);
|
||||
|
||||
double x1 = 0.5 + Z14_WIDTH / 4;
|
||||
double x1 = 0.5 + Z15_WIDTH / 4;
|
||||
double lng1 = GeoUtils.getWorldLon(x1);
|
||||
double lng2 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 10d / 256);
|
||||
double lng3 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 20d / 256);
|
||||
double lng4 = GeoUtils.getWorldLon(x1 + Z14_WIDTH * 30d / 256);
|
||||
double lng2 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 10d / 256);
|
||||
double lng3 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 20d / 256);
|
||||
double lng4 = GeoUtils.getWorldLon(x1 + Z15_WIDTH * 30d / 256);
|
||||
|
||||
var results = runWithReaderFeatures(
|
||||
Map.of("threads", "1"),
|
||||
Map.of("threads", "1", "maxzoom", "15"),
|
||||
List.of(
|
||||
// merge at z13 (same "group"):
|
||||
newReaderFeature(newLineString(
|
||||
|
@ -1186,22 +1221,27 @@ class PlanetilerTests {
|
|||
), Map.of("group", "2", "other", "3"))
|
||||
),
|
||||
(in, features) -> features.line("layer")
|
||||
.setZoomRange(13, 14)
|
||||
.setMinZoom(13)
|
||||
.setAttrWithMinzoom("z14attr", in.getTag("other"), 14)
|
||||
.inheritAttrFromSource("group"),
|
||||
(layer, zoom, items) -> FeatureMerge.mergeLineStrings(items, 0, 0, 0)
|
||||
);
|
||||
|
||||
assertSubmap(sortListValues(Map.of(
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
TileCoord.ofXYZ(Z15_TILES / 2, Z15_TILES / 2, 15), List.of(
|
||||
feature(newLineString(64, 128, 74, 128), Map.of("group", "1", "z14attr", "1")),
|
||||
feature(newLineString(74, 128, 84, 128), Map.of("group", "1", "z14attr", "2")),
|
||||
feature(newLineString(84, 128, 94, 128), Map.of("group", "2", "z14attr", "3"))
|
||||
),
|
||||
TileCoord.ofXYZ(Z14_TILES / 2, Z14_TILES / 2, 14), List.of(
|
||||
feature(newLineString(32, 64, 37, 64), Map.of("group", "1", "z14attr", "1")),
|
||||
feature(newLineString(37, 64, 42, 64), Map.of("group", "1", "z14attr", "2")),
|
||||
feature(newLineString(42, 64, 47, 64), Map.of("group", "2", "z14attr", "3"))
|
||||
),
|
||||
TileCoord.ofXYZ(Z13_TILES / 2, Z13_TILES / 2, 13), List.of(
|
||||
// merge 32->37 and 37->42 since they have same attrs
|
||||
feature(newLineString(32, 64, 42, 64), Map.of("group", "1")),
|
||||
feature(newLineString(42, 64, 47, 64), Map.of("group", "2"))
|
||||
feature(newLineString(16, 32, 21, 32), Map.of("group", "1")),
|
||||
feature(newLineString(21, 32, 23.5, 32), Map.of("group", "2"))
|
||||
)
|
||||
)), sortListValues(results.tiles));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.onthegomap.planetiler.geo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
|
@ -45,17 +47,28 @@ class TileCoordTest {
|
|||
assertEquals(decoded.z(), z, "z");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testTileSortOrderRespectZ() {
|
||||
int last = Integer.MIN_VALUE;
|
||||
for (int z = 0; z <= 15; z++) {
|
||||
int encoded = TileCoord.ofXYZ(0, 0, z).encoded();
|
||||
if (encoded < last) {
|
||||
fail("encoded value for z" + (z - 1) + " (" + last + ") is not less than z" + z + " (" + encoded + ")");
|
||||
}
|
||||
last = encoded;
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"0,0,0,0",
|
||||
"0,1,1,0",
|
||||
"0,0,1,0.25",
|
||||
"1,1,1,0.5",
|
||||
"1,0,1,0.75",
|
||||
"0,3,2,0"
|
||||
})
|
||||
void testTileProgressOnLevel(int x, int y, int z, double p) {
|
||||
double progress = TileCoord.ofXYZ(x, y, z).progressOnLevel();
|
||||
double progress =
|
||||
TileCoord.ofXYZ(x, y, z).progressOnLevel(TileExtents.computeFromWorldBounds(15, GeoUtils.WORLD_BOUNDS));
|
||||
assertEquals(p, progress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package com.onthegomap.planetiler.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
class HilbertTest {
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3, 4, 5, 15})
|
||||
void testRoundTrip(int level) {
|
||||
int max = (1 << level) * (1 << level);
|
||||
int step = Math.max(1, max / 100);
|
||||
for (int i = 0; i < max; i += step) {
|
||||
long decoded = Hilbert.hilbertPositionToXY(level, i);
|
||||
int x = Hilbert.extractX(decoded);
|
||||
int y = Hilbert.extractY(decoded);
|
||||
int reEncoded = Hilbert.hilbertXYToIndex(level, x, y);
|
||||
if (reEncoded != i) {
|
||||
fail("x=" + x + ", y=" + y + " index=" + i + " re-encoded=" + reEncoded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"0,0,0,0",
|
||||
|
||||
"1,0,0,0",
|
||||
"1,0,1,1",
|
||||
"1,1,1,2",
|
||||
"1,1,0,3",
|
||||
|
||||
"2,1,1,2",
|
||||
|
||||
"15,0,0,0",
|
||||
"15,0,1,1",
|
||||
"15,1,1,2",
|
||||
"15,1,0,3",
|
||||
"15,32767,0,1073741823",
|
||||
"15,32767,32767,715827882",
|
||||
|
||||
"16,0,0,0",
|
||||
"16,1,0,1",
|
||||
"16,1,1,2",
|
||||
"16,0,1,3",
|
||||
"16,65535,0,-1",
|
||||
"16,65535,65535,-1431655766",
|
||||
})
|
||||
void testEncoding(int level, int x, int y, int encoded) {
|
||||
int actualEncoded = Hilbert.hilbertXYToIndex(level, x, y);
|
||||
assertEquals(encoded, actualEncoded);
|
||||
long decoded = Hilbert.hilbertPositionToXY(level, encoded);
|
||||
assertEquals(x, Hilbert.extractX(decoded));
|
||||
assertEquals(y, Hilbert.extractY(decoded));
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue