Support zoom levels up to 15 (#303)

pull/306/head
Michael Barry 2022-07-26 07:51:31 -04:00 zatwierdzone przez GitHub
rodzic a6a8c245b8
commit 621a8ed759
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
9 zmienionych plików z 321 dodań i 165 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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):

Wyświetl plik

@ -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),

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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()
);
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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));
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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));
}
}