kopia lustrzana https://github.com/onthegomap/planetiler
242 wiersze
7.9 KiB
Java
242 wiersze
7.9 KiB
Java
package com.onthegomap.planetiler.mbtiles;
|
|
|
|
import static com.onthegomap.planetiler.TestUtils.assertSameJson;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
|
|
import com.google.common.math.IntMath;
|
|
import com.onthegomap.planetiler.TestUtils;
|
|
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
|
|
import com.onthegomap.planetiler.archive.TileEncodingResult;
|
|
import com.onthegomap.planetiler.config.Arguments;
|
|
import com.onthegomap.planetiler.geo.TileCoord;
|
|
import com.onthegomap.planetiler.util.LayerStats;
|
|
import java.io.IOException;
|
|
import java.math.RoundingMode;
|
|
import java.nio.file.Path;
|
|
import java.sql.SQLException;
|
|
import java.sql.Statement;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.OptionalLong;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
import java.util.stream.Collectors;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.junit.jupiter.api.io.TempDir;
|
|
import org.junit.jupiter.params.ParameterizedTest;
|
|
import org.junit.jupiter.params.provider.ValueSource;
|
|
import org.locationtech.jts.geom.CoordinateXY;
|
|
import org.locationtech.jts.geom.Envelope;
|
|
|
|
class MbtilesTest {
|
|
|
|
private static final int MAX_PARAMETERS_IN_PREPARED_STATEMENT = 999;
|
|
private static final int TILES_BATCH = MAX_PARAMETERS_IN_PREPARED_STATEMENT / 4;
|
|
private static final int TILES_SHALLOW_BATCH = MAX_PARAMETERS_IN_PREPARED_STATEMENT / 4;
|
|
private static final int TILES_DATA_BATCH = MAX_PARAMETERS_IN_PREPARED_STATEMENT / 2;
|
|
|
|
|
|
private static void testWriteTiles(Path path, int howMany, boolean skipIndexCreation, boolean optimize,
|
|
boolean compactDb) throws IOException, SQLException {
|
|
var options = Arguments.of("compact", Boolean.toString(compactDb));
|
|
try (
|
|
Mbtiles db = path == null ? Mbtiles.newInMemoryDatabase(options) : Mbtiles.newWriteToFileDatabase(path, options)
|
|
) {
|
|
if (skipIndexCreation) {
|
|
db.createTablesWithoutIndexes();
|
|
} else {
|
|
db.createTablesWithIndexes();
|
|
}
|
|
|
|
assertNull(db.getTile(0, 0, 0));
|
|
Set<Mbtiles.TileEntry> expected = new TreeSet<>();
|
|
try (var writer = db.newTileWriter()) {
|
|
for (int i = 0; i < howMany; i++) {
|
|
var dataHash = i - (i % 2);
|
|
var dataBase = howMany + dataHash;
|
|
var entry = new Mbtiles.TileEntry(TileCoord.ofXYZ(i, i + 1, 14), new byte[]{
|
|
(byte) dataBase,
|
|
(byte) (dataBase >> 8),
|
|
(byte) (dataBase >> 16),
|
|
(byte) (dataBase >> 24)
|
|
});
|
|
writer.write(new TileEncodingResult(entry.tile(), entry.bytes(), OptionalLong.of(dataHash)));
|
|
expected.add(entry);
|
|
}
|
|
}
|
|
|
|
if (optimize) {
|
|
db.vacuumAnalyze();
|
|
}
|
|
var all = TestUtils.getAllTiles(db);
|
|
assertEquals(howMany, all.size());
|
|
assertEquals(expected, all);
|
|
assertEquals(expected.stream().map(Mbtiles.TileEntry::tile).collect(Collectors.toSet()),
|
|
db.getAllTileCoords().stream().collect(Collectors.toSet()));
|
|
for (var expectedEntry : expected) {
|
|
var tile = expectedEntry.tile();
|
|
byte[] data = db.getTile(tile.x(), tile.y(), tile.z());
|
|
assertArrayEquals(expectedEntry.bytes(), data);
|
|
}
|
|
assertEquals(compactDb, TestUtils.isCompactDb(db));
|
|
if (compactDb) {
|
|
assertEquals(IntMath.divide(howMany, 2, RoundingMode.CEILING), TestUtils.getTilesDataCount(db));
|
|
}
|
|
}
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@ValueSource(ints = {0, 1, TILES_BATCH, TILES_BATCH + 1, 2 * TILES_BATCH, 2 * TILES_BATCH + 1})
|
|
void testWriteTilesDifferentSizeInNonCompactMode(int howMany) throws IOException, SQLException {
|
|
testWriteTiles(null, howMany, false, false, false);
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@ValueSource(ints = {0, 1, TILES_DATA_BATCH, TILES_DATA_BATCH + 1, 2 * TILES_DATA_BATCH, 2 * TILES_DATA_BATCH + 1,
|
|
TILES_SHALLOW_BATCH, TILES_SHALLOW_BATCH + 1, 2 * TILES_SHALLOW_BATCH, 2 * TILES_SHALLOW_BATCH + 1})
|
|
void testWriteTilesDifferentSizeInCompactMode(int howMany) throws IOException, SQLException {
|
|
testWriteTiles(null, howMany, false, false, true);
|
|
}
|
|
|
|
@Test
|
|
void testSkipIndexCreation() throws IOException, SQLException {
|
|
testWriteTiles(null, 10, true, false, false);
|
|
}
|
|
|
|
@Test
|
|
void testVacuumAnalyze() throws IOException, SQLException {
|
|
testWriteTiles(null, 10, false, true, false);
|
|
}
|
|
|
|
@Test
|
|
void testWriteToFile(@TempDir Path tmpDir) throws IOException, SQLException {
|
|
testWriteTiles(tmpDir.resolve("archive.mbtiles"), 10, false, false, true);
|
|
}
|
|
|
|
@Test
|
|
void testCustomPragma() throws IOException, SQLException {
|
|
try (
|
|
Mbtiles db = Mbtiles.newInMemoryDatabase(Arguments.of(
|
|
"cache-size", "123",
|
|
"garbage", "456"
|
|
));
|
|
) {
|
|
int result = db.connection().createStatement().executeQuery("pragma cache_size").getInt(1);
|
|
assertEquals(123, result);
|
|
}
|
|
}
|
|
|
|
@ParameterizedTest
|
|
@ValueSource(booleans = {true, false})
|
|
void testManualIndexCreationStatements(boolean compactDb) throws IOException, SQLException {
|
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase(compactDb)) {
|
|
db.createTablesWithoutIndexes();
|
|
|
|
List<String> indexCreationStmts = db.getManualIndexCreationStatements();
|
|
assertFalse(indexCreationStmts.isEmpty());
|
|
for (String indexCreationStmt : indexCreationStmts) {
|
|
try (Statement stmt = db.connection().createStatement()) {
|
|
assertDoesNotThrow(() -> stmt.execute(indexCreationStmt));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void testRoundTripMetadata() throws IOException {
|
|
roundTripMetadata(new TileArchiveMetadata(
|
|
"MyName",
|
|
"MyDescription",
|
|
"MyAttribution",
|
|
"MyVersion",
|
|
"baselayer",
|
|
TileArchiveMetadata.MVT_FORMAT,
|
|
new Envelope(1, 2, 3, 4),
|
|
new CoordinateXY(5, 6),
|
|
7d,
|
|
8,
|
|
9,
|
|
List.of(new LayerStats.VectorLayer("MyLayer", Map.of())),
|
|
Map.of("other key", "other value")
|
|
));
|
|
}
|
|
|
|
@Test
|
|
void testRoundTripMinimalMetadata() throws IOException {
|
|
var empty =
|
|
new TileArchiveMetadata(null, null, null, null, null, null, null, null, null, null, null, null, Map.of());
|
|
roundTripMetadata(empty);
|
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
|
db.createTablesWithoutIndexes();
|
|
assertEquals(empty, db.metadata());
|
|
}
|
|
}
|
|
|
|
private static void roundTripMetadata(TileArchiveMetadata metadata) throws IOException {
|
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
|
db.createTablesWithoutIndexes();
|
|
var metadataTable = db.metadataTable();
|
|
metadataTable.set(metadata);
|
|
assertEquals(metadata, metadataTable.get());
|
|
}
|
|
}
|
|
|
|
private void testMetadataJson(Mbtiles.MetadataJson object, String expected) throws IOException {
|
|
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
|
|
var metadata = db.createTablesWithoutIndexes().metadataTable();
|
|
metadata.setJson(object);
|
|
var actual = metadata.getAll().get("json");
|
|
assertSameJson(expected, actual);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
void testMetadataJsonNoLayers() throws IOException {
|
|
testMetadataJson(new Mbtiles.MetadataJson(), """
|
|
{
|
|
"vector_layers": []
|
|
}
|
|
""");
|
|
}
|
|
|
|
@Test
|
|
void testFullMetadataJson() throws IOException {
|
|
testMetadataJson(new Mbtiles.MetadataJson(
|
|
new LayerStats.VectorLayer(
|
|
"full",
|
|
Map.of(
|
|
"NUMBER_FIELD", LayerStats.FieldType.NUMBER,
|
|
"STRING_FIELD", LayerStats.FieldType.STRING,
|
|
"boolean field", LayerStats.FieldType.BOOLEAN
|
|
)
|
|
).withDescription("full description")
|
|
.withMinzoom(0)
|
|
.withMaxzoom(5),
|
|
new LayerStats.VectorLayer(
|
|
"partial",
|
|
Map.of()
|
|
)
|
|
), """
|
|
{
|
|
"vector_layers": [
|
|
{
|
|
"id": "full",
|
|
"description": "full description",
|
|
"minzoom": 0,
|
|
"maxzoom": 5,
|
|
"fields": {
|
|
"NUMBER_FIELD": "Number",
|
|
"STRING_FIELD": "String",
|
|
"boolean field": "Boolean"
|
|
}
|
|
},
|
|
{
|
|
"id": "partial",
|
|
"fields": {}
|
|
}
|
|
]
|
|
}
|
|
""");
|
|
}
|
|
}
|