Expose pmtiles writer (#520)

pull/523/head
Michael Barry 2023-03-18 14:38:04 -04:00 zatwierdzone przez GitHub
rodzic 9945ad406e
commit 74db638dbc
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
48 zmienionych plików z 1386 dodań i 664 usunięć

Wyświetl plik

@ -85,7 +85,7 @@ jobs:
run: mv target/*with-deps.jar ./run.jar run: mv target/*with-deps.jar ./run.jar
working-directory: planetiler-examples working-directory: planetiler-examples
- name: Run - name: Run
run: java -jar run.jar --osm-path=../planetiler-core/src/test/resources/monaco-latest.osm.pbf --mbtiles=data/out.mbtiles run: java -jar run.jar --osm-path=../planetiler-core/src/test/resources/monaco-latest.osm.pbf --output=data/out.mbtiles
working-directory: planetiler-examples working-directory: planetiler-examples
- name: Verify - name: Verify
run: java -cp run.jar com.onthegomap.planetiler.mbtiles.Verify data/out.mbtiles run: java -cp run.jar com.onthegomap.planetiler.mbtiles.Verify data/out.mbtiles

Wyświetl plik

@ -22,73 +22,73 @@ jobs:
timeout-minutes: 20 timeout-minutes: 20
continue-on-error: true continue-on-error: true
steps: steps:
- name: 'Cancel previous runs' - name: 'Cancel previous runs'
uses: styfle/cancel-workflow-action@0.11.0 uses: styfle/cancel-workflow-action@0.11.0
with: with:
access_token: ${{ github.token }} access_token: ${{ github.token }}
- name: 'Checkout branch' - name: 'Checkout branch'
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: branch path: branch
submodules: true submodules: true
- name: 'Checkout base' - name: 'Checkout base'
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: base path: base
ref: ${{ github.event.pull_request.base.sha }} ref: ${{ github.event.pull_request.base.sha }}
submodules: true submodules: true
- name: Cache data/sources - name: Cache data/sources
uses: ./branch/.github/cache-sources-action uses: ./branch/.github/cache-sources-action
with: with:
basedir: branch basedir: branch
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
java-version: 17 java-version: 17
distribution: 'temurin' distribution: 'temurin'
cache: 'maven' cache: 'maven'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '14' node-version: '14'
- run: npm install -g strip-ansi-cli@3.0.2 - run: npm install -g strip-ansi-cli@3.0.2
- name: 'Build branch' - name: 'Build branch'
run: ./scripts/build.sh run: ./scripts/build.sh
working-directory: branch working-directory: branch
- name: 'Build base' - name: 'Build base'
run: ./scripts/build.sh run: ./scripts/build.sh
working-directory: base working-directory: base
- name: 'Download data' - name: 'Download data'
run: | run: |
set -eo pipefail set -eo pipefail
cp base/planetiler-dist/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}" cp base/planetiler-dist/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}"
cp branch/planetiler-dist/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}" cp branch/planetiler-dist/target/*with-deps.jar run.jar && java -jar run.jar --only-download --area="${{ env.AREA }}"
- name: 'Store build info' - name: 'Store build info'
run: | run: |
mkdir build-info mkdir build-info
echo "${{ github.event.pull_request.base.sha }}" > build-info/base_sha echo "${{ github.event.pull_request.base.sha }}" > build-info/base_sha
echo "${{ github.sha }}" > build-info/branch_sha echo "${{ github.sha }}" > build-info/branch_sha
echo "${{ github.event.number }}" > build-info/pull_request_number echo "${{ github.event.number }}" > build-info/pull_request_number
- name: 'Run branch' - name: 'Run branch'
run: | run: |
rm -rf data/out.mbtiles data/tmp rm -rf data/out.mbtiles data/tmp
cp branch/planetiler-dist/target/*with-deps.jar run.jar cp branch/planetiler-dist/target/*with-deps.jar run.jar
java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --mbtiles=data/out.mbtiles 2>&1 | tee log java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --output=data/out.mbtiles 2>&1 | tee log
ls -alh run.jar | tee -a log ls -alh run.jar | tee -a log
cat log | strip-ansi > build-info/branchlogs.txt cat log | strip-ansi > build-info/branchlogs.txt
- name: 'Run base' - name: 'Run base'
run: | run: |
rm -rf data/out.mbtiles data/tmp rm -rf data/out.mbtiles data/tmp
cp base/planetiler-dist/target/*with-deps.jar run.jar cp base/planetiler-dist/target/*with-deps.jar run.jar
java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --mbtiles=data/out.mbtiles 2>&1 | tee log java -Xms${{ env.RAM }} -Xmx${{ env.RAM }} -jar run.jar --area="${{ env.AREA }}" "${{ env.BOUNDS_ARG }}" --output=data/out.mbtiles 2>&1 | tee log
ls -alh run.jar | tee -a log ls -alh run.jar | tee -a log
cat log | strip-ansi > build-info/baselogs.txt cat log | strip-ansi > build-info/baselogs.txt
- name: 'Upload build-info' - name: 'Upload build-info'
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: build-info name: build-info
path: ./build-info path: ./build-info

Wyświetl plik

@ -50,7 +50,7 @@ java -Xmx110g \
--download-threads=10 --download-chunk-size-mb=1000 \ --download-threads=10 --download-chunk-size-mb=1000 \
`# Also download name translations from wikidata` \ `# Also download name translations from wikidata` \
--fetch-wikidata \ --fetch-wikidata \
--mbtiles=output.mbtiles \ --output=output.mbtiles \
`# Store temporary node locations in memory` \ `# Store temporary node locations in memory` \
--nodemap-type=array --storage=ram --nodemap-type=array --storage=ram
``` ```
@ -67,7 +67,7 @@ java -Xmx20g \
--download-threads=10 --download-chunk-size-mb=1000 \ --download-threads=10 --download-chunk-size-mb=1000 \
`# Also download name translations from wikidata` \ `# Also download name translations from wikidata` \
--fetch-wikidata \ --fetch-wikidata \
--mbtiles=output.mbtiles \ --output=output.mbtiles \
`# Store temporary node locations at fixed positions in a memory-mapped file` \ `# Store temporary node locations at fixed positions in a memory-mapped file` \
--nodemap-type=array --storage=mmap --nodemap-type=array --storage=mmap
``` ```
@ -103,7 +103,7 @@ java -Xmx100g \
--download-threads=10 --download-chunk-size-mb=1000 \ --download-threads=10 --download-chunk-size-mb=1000 \
`# Also download name translations from wikidata` \ `# Also download name translations from wikidata` \
--fetch-wikidata \ --fetch-wikidata \
--mbtiles=output.mbtiles \ --output=output.mbtiles \
--nodemap-type=sparsearray --nodemap-storage=ram 2>&1 | tee logs.txt --nodemap-type=sparsearray --nodemap-storage=ram 2>&1 | tee logs.txt
``` ```

Wyświetl plik

@ -8,9 +8,10 @@ or database.
Vector tiles contain raw point, line, and polygon geometries that clients like [MapLibre](https://github.com/maplibre) Vector tiles contain raw point, line, and polygon geometries that clients like [MapLibre](https://github.com/maplibre)
can use to render custom maps in the browser, native apps, or on a server. Planetiler packages tiles into can use to render custom maps in the browser, native apps, or on a server. Planetiler packages tiles into
an [MBTiles](https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md) (sqlite) file that can be served using an [MBTiles](https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md) (sqlite)
tools like [TileServer GL](https://github.com/maptiler/tileserver-gl) or even or [PMTiles](https://github.com/protomaps/PMTiles) file that can be served using tools
[queried directly from the browser](https://github.com/phiresky/sql.js-httpvfs). like [TileServer GL](https://github.com/maptiler/tileserver-gl) or [Martin](https://github.com/maplibre/martin) or
even [queried directly from the browser](https://github.com/protomaps/PMTiles/tree/main/js).
See [awesome-vector-tiles](https://github.com/mapbox/awesome-vector-tiles) for more projects that work with data in this See [awesome-vector-tiles](https://github.com/mapbox/awesome-vector-tiles) for more projects that work with data in this
format. format.
@ -87,7 +88,7 @@ Using [Node.js](https://nodejs.org/en/download/):
```bash ```bash
npm install -g tileserver-gl-light npm install -g tileserver-gl-light
tileserver-gl-light --mbtiles data/output.mbtiles tileserver-gl-light data/output.mbtiles
``` ```
Or using [Docker](https://docs.docker.com/get-docker/): Or using [Docker](https://docs.docker.com/get-docker/):
@ -100,6 +101,8 @@ Then open http://localhost:8080 to view tiles.
Some common arguments: Some common arguments:
- `--output` tells planetiler where to write output to, and what format to write it in. For
example `--output=australia.pmtiles` creates a pmtiles archive named `australia.pmtiles`.
- `--download` downloads input sources automatically and `--only-download` exits after downloading - `--download` downloads input sources automatically and `--only-download` exits after downloading
- `--area=monaco` downloads a `.osm.pbf` extract from [Geofabrik](https://download.geofabrik.de/) - `--area=monaco` downloads a `.osm.pbf` extract from [Geofabrik](https://download.geofabrik.de/)
- `--osm-path=path/to/file.osm.pbf` points Planetiler at an existing OSM extract on disk - `--osm-path=path/to/file.osm.pbf` points Planetiler at an existing OSM extract on disk
@ -209,6 +212,8 @@ download regularly-updated tilesets.
OpenStreetMap [.osm.pbf](https://wiki.openstreetmap.org/wiki/PBF_Format), OpenStreetMap [.osm.pbf](https://wiki.openstreetmap.org/wiki/PBF_Format),
[`geopackage`](https://www.geopackage.org/), [`geopackage`](https://www.geopackage.org/),
and [Esri Shapefiles](https://en.wikipedia.org/wiki/Shapefile) data sources and [Esri Shapefiles](https://en.wikipedia.org/wiki/Shapefile) data sources
- Writes to [MBTiles](https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md) or
or [PMTiles](https://github.com/protomaps/PMTiles) output.
- Java-based [Profile API](planetiler-core/src/main/java/com/onthegomap/planetiler/Profile.java) to customize how source - Java-based [Profile API](planetiler-core/src/main/java/com/onthegomap/planetiler/Profile.java) to customize how source
elements map to vector tile features, and post-process generated tiles elements map to vector tile features, and post-process generated tiles
using [JTS geometry utilities](https://github.com/locationtech/jts) using [JTS geometry utilities](https://github.com/locationtech/jts)
@ -328,6 +333,7 @@ Planetiler is made possible by these awesome open source projects:
- [cel-java](https://github.com/projectnessie/cel-java) for the Java implementation of - [cel-java](https://github.com/projectnessie/cel-java) for the Java implementation of
Google's [Common Expression Language](https://github.com/google/cel-spec) that powers dynamic expressions embedded in Google's [Common Expression Language](https://github.com/google/cel-spec) that powers dynamic expressions embedded in
schema config files. schema config files.
- [PMTiles](https://github.com/protomaps/PMTiles) optimized tile storage format
See [NOTICE.md](NOTICE.md) for a full list and license details. See [NOTICE.md](NOTICE.md) for a full list and license details.

Wyświetl plik

@ -66,9 +66,9 @@ public class BenchmarkMbtilesWriter {
for (int repetition = 0; repetition < repetitions; repetition++) { for (int repetition = 0; repetition < repetitions; repetition++) {
Path outputPath = getTempOutputPath(); Path outputPath = getTempOutputPath();
try (var mbtiles = Mbtiles.newWriteToFileDatabase(outputPath, config.compactDb())) { try (var mbtiles = Mbtiles.newWriteToFileDatabase(outputPath, config.arguments())) {
if (config.skipIndexCreation()) { if (mbtiles.skipIndexCreation()) {
mbtiles.createTablesWithoutIndexes(); mbtiles.createTablesWithoutIndexes();
} else { } else {
mbtiles.createTablesWithIndexes(); mbtiles.createTablesWithIndexes();

Wyświetl plik

@ -1,14 +1,15 @@
package com.onthegomap.planetiler; package com.onthegomap.planetiler;
import com.onthegomap.planetiler.archive.TileArchiveConfig;
import com.onthegomap.planetiler.archive.TileArchiveMetadata; import com.onthegomap.planetiler.archive.TileArchiveMetadata;
import com.onthegomap.planetiler.archive.TileArchiveWriter; import com.onthegomap.planetiler.archive.TileArchiveWriter;
import com.onthegomap.planetiler.archive.TileArchives;
import com.onthegomap.planetiler.archive.WriteableTileArchive; import com.onthegomap.planetiler.archive.WriteableTileArchive;
import com.onthegomap.planetiler.collection.FeatureGroup; import com.onthegomap.planetiler.collection.FeatureGroup;
import com.onthegomap.planetiler.collection.LongLongMap; import com.onthegomap.planetiler.collection.LongLongMap;
import com.onthegomap.planetiler.collection.LongLongMultimap; import com.onthegomap.planetiler.collection.LongLongMultimap;
import com.onthegomap.planetiler.config.Arguments; import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
import com.onthegomap.planetiler.reader.GeoPackageReader; import com.onthegomap.planetiler.reader.GeoPackageReader;
import com.onthegomap.planetiler.reader.NaturalEarthReader; import com.onthegomap.planetiler.reader.NaturalEarthReader;
import com.onthegomap.planetiler.reader.ShapefileReader; import com.onthegomap.planetiler.reader.ShapefileReader;
@ -85,7 +86,7 @@ public class Planetiler {
private final PlanetilerConfig config; private final PlanetilerConfig config;
private FeatureGroup featureGroup; private FeatureGroup featureGroup;
private OsmInputFile osmInputFile; private OsmInputFile osmInputFile;
private Path output; private TileArchiveConfig output;
private boolean overwrite = false; private boolean overwrite = false;
private boolean ran = false; private boolean ran = false;
// most common OSM languages // most common OSM languages
@ -151,8 +152,8 @@ public class Planetiler {
* *
* @param name string to use in stats and logs to identify this stage * @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name_path} argument is not set * @param defaultPath path to the input file to use if {@code name_path} argument is not set
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set. As a shortcut, can use "geofabrik:monaco" or * {@code name_url} argument is not set. As a shortcut, can use "geofabrik:monaco" or
* "geofabrik:australia" shorthand to find an extract by name from * "geofabrik:australia" shorthand to find an extract by name from
* <a href="https://download.geofabrik.de/">Geofabrik download site</a> or "aws:latest" to download * <a href="https://download.geofabrik.de/">Geofabrik download site</a> or "aws:latest" to download
* the latest {@code planet.osm.pbf} file from <a href="https://registry.opendata.aws/osm/">AWS * the latest {@code planet.osm.pbf} file from <a href="https://registry.opendata.aws/osm/">AWS
@ -183,10 +184,11 @@ public class Planetiler {
), ),
ifSourceUsed(name, () -> { ifSourceUsed(name, () -> {
var header = osmInputFile.getHeader(); var header = osmInputFile.getHeader();
tileArchiveMetadata.set("planetiler:" + name + ":osmosisreplicationtime", header.instant()); tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationtime", header.instant());
tileArchiveMetadata.set("planetiler:" + name + ":osmosisreplicationseq", tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationseq",
header.osmosisReplicationSequenceNumber()); header.osmosisReplicationSequenceNumber());
tileArchiveMetadata.set("planetiler:" + name + ":osmosisreplicationurl", header.osmosisReplicationBaseUrl()); tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationurl",
header.osmosisReplicationBaseUrl());
try ( try (
var nodeLocations = var nodeLocations =
LongLongMap.from(config.nodeMapType(), config.nodeMapStorage(), nodeDbPath, config.nodeMapMadvise()); LongLongMap.from(config.nodeMapType(), config.nodeMapStorage(), nodeDbPath, config.nodeMapMadvise());
@ -253,8 +255,8 @@ public class Planetiler {
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a * @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file * {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
* containing the shapefile components. * containing the shapefile components.
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set * {@code name_url} argument is not set
* @return this runner instance for chaining * @return this runner instance for chaining
* @see ShapefileReader * @see ShapefileReader
* @see Downloader * @see Downloader
@ -327,8 +329,8 @@ public class Planetiler {
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a * @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file * {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
* containing the shapefile components. * containing the shapefile components.
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set * {@code name_url} argument is not set
* @return this runner instance for chaining * @return this runner instance for chaining
* @see ShapefileReader * @see ShapefileReader
* @see Downloader * @see Downloader
@ -362,8 +364,8 @@ public class Planetiler {
* {@link org.geotools.referencing.CRS#decode(String)} * {@link org.geotools.referencing.CRS#decode(String)}
* @param name string to use in stats and logs to identify this stage * @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments * @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set * {@code name_url} argument is not set
* @return this runner instance for chaining * @return this runner instance for chaining
* @see GeoPackageReader * @see GeoPackageReader
* @see Downloader * @see Downloader
@ -399,8 +401,8 @@ public class Planetiler {
* *
* @param name string to use in stats and logs to identify this stage * @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments * @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set * {@code name_url} argument is not set
* @return this runner instance for chaining * @return this runner instance for chaining
* @see GeoPackageReader * @see GeoPackageReader
* @see Downloader * @see Downloader
@ -415,12 +417,12 @@ public class Planetiler {
* To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to * To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to
* override the download URL set {@code name_url=http://url/of/natural_earth.zip}. * override the download URL set {@code name_url=http://url/of/natural_earth.zip}.
* *
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
* @param name string to use in stats and logs to identify this stage * @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the * @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the
* {@code .sqlite} file or a {@code .zip} file containing the sqlite file. * {@code .sqlite} file or a {@code .zip} file containing the sqlite file.
* @return this runner instance for chaining * @return this runner instance for chaining
* @see NaturalEarthReader * @see NaturalEarthReader
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public Planetiler addNaturalEarthSource(String name, Path defaultPath) { public Planetiler addNaturalEarthSource(String name, Path defaultPath) {
@ -436,16 +438,15 @@ public class Planetiler {
* To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to * To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to
* override the download URL set {@code name_url=http://url/of/natural_earth.zip}. * override the download URL set {@code name_url=http://url/of/natural_earth.zip}.
* *
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
*
* @param name string to use in stats and logs to identify this stage * @param name string to use in stats and logs to identify this stage
* @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the * @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the
* {@code .sqlite} file or a {@code .zip} file containing the sqlite file. * {@code .sqlite} file or a {@code .zip} file containing the sqlite file.
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and {@code * @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
* name_url} argument is not set * {@code name_url} argument is not set
* @return this runner instance for chaining * @return this runner instance for chaining
* @see NaturalEarthReader * @see NaturalEarthReader
* @see Downloader * @see Downloader
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
*/ */
@Deprecated(forRemoval = true) @Deprecated(forRemoval = true)
public Planetiler addNaturalEarthSource(String name, Path defaultPath, String defaultUrl) { public Planetiler addNaturalEarthSource(String name, Path defaultPath, String defaultUrl) {
@ -540,35 +541,69 @@ public class Planetiler {
} }
/** /**
* Sets the location of the output archive to write rendered tiles to. Fails if the archive already exists. * Sets the location of the output archive to write rendered tiles to.
* <p>
* To override the location of the file, set {@code argument=newpath} in the arguments.
* *
* @param argument the argument key to check for an override to {@code fallback} * @deprecated Use {@link #setOutput(String)} instead
* @param fallback the fallback value if {@code argument} is not set in arguments
* @return this runner instance for chaining
* @see TileArchiveWriter
*/ */
@Deprecated(forRemoval = true)
public Planetiler setOutput(String argument, Path fallback) { public Planetiler setOutput(String argument, Path fallback) {
this.output = arguments.file(argument, "output tile archive", fallback); this.output =
TileArchiveConfig
.from(arguments.getString("output|" + argument, "output tile archive path", fallback.toString()));
return this; return this;
} }
/** /**
* Sets the location of the output archive to write rendered tiles to. Overwrites file if it already exists. * Sets the location of the output archive to write rendered tiles to. Fails if the archive already exists.
* <p> * <p>
* To override the location of the file, set {@code argument=newpath} in the arguments. * To override the location of the file, set {@code argument=newpath} in the arguments. To set options for the output
* drive add {@code output.mbtiles?arg=value} or add command-line argument {@code mbtiles_arg=value}.
* *
* @param argument the argument key to check for an override to {@code fallback} * @param defaultOutputUri The default output URI string to write to.
* @param fallback the fallback value if {@code argument} is not set in arguments
* @return this runner instance for chaining * @return this runner instance for chaining
* @see TileArchiveWriter * @see TileArchiveConfig For details on URI string formats and options.
*/ */
public Planetiler setOutput(String defaultOutputUri) {
this.output = TileArchiveConfig.from(arguments.getString("output", "output tile archive URI", defaultOutputUri));
return this;
}
/** Alias for {@link #setOutput(String)} which infers the output type based on extension. */
public Planetiler setOutput(Path path) {
return setOutput(path.toString());
}
/**
* Sets the location of the output archive to write rendered tiles to.
*
* @deprecated Use {@link #overwriteOutput(String)} instead
*/
@Deprecated(forRemoval = true)
public Planetiler overwriteOutput(String argument, Path fallback) { public Planetiler overwriteOutput(String argument, Path fallback) {
this.overwrite = true; this.overwrite = true;
return setOutput(argument, fallback); return setOutput(argument, fallback);
} }
/**
* Sets the location of the output archive to write rendered tiles to. Overwrites if the archive already exists.
* <p>
* To override the location of the file, set {@code argument=newpath} in the arguments. To set options for the output
* drive add {@code output.mbtiles?arg=value} or add command-line argument {@code mbtiles_arg=value}.
*
* @param defaultOutputUri The default output URI string to write to.
* @return this runner instance for chaining
* @see TileArchiveConfig For details on URI string formats and options.
*/
public Planetiler overwriteOutput(String defaultOutputUri) {
this.overwrite = true;
return setOutput(defaultOutputUri);
}
/** Alias for {@link #overwriteOutput(String)} which infers the output type based on extension. */
public Planetiler overwriteOutput(Path defaultOutput) {
return overwriteOutput(defaultOutput.toString());
}
/** /**
* Reads all elements from all sourced that have been added, generates map features according to the profile, and * Reads all elements from all sourced that have been added, generates map features according to the profile, and
* writes the rendered tiles to the output archive. * writes the rendered tiles to the output archive.
@ -600,19 +635,18 @@ public class Planetiler {
throw new IllegalArgumentException("Can only run once"); throw new IllegalArgumentException("Can only run once");
} }
ran = true; ran = true;
tileArchiveMetadata = new TileArchiveMetadata(profile, config.arguments());
if (arguments.getBoolean("help", "show arguments then exit", false)) { if (arguments.getBoolean("help", "show arguments then exit", false)) {
System.exit(0); System.exit(0);
} else if (onlyDownloadSources) { } else if (onlyDownloadSources) {
// don't check files if not generating map // don't check files if not generating map
} else if (overwrite || config.force()) { } else if (overwrite || config.force()) {
FileUtils.deleteFile(output); output.delete();
} else if (Files.exists(output)) { } else if (output.exists()) {
throw new IllegalArgumentException(output + " already exists, use the --force argument to overwrite."); throw new IllegalArgumentException(output.uri() + " already exists, use the --force argument to overwrite.");
} }
LOGGER.info("Building {} profile into {} in these phases:", profile.getClass().getSimpleName(), output); LOGGER.info("Building {} profile into {} in these phases:", profile.getClass().getSimpleName(), output.uri());
if (!toDownload.isEmpty()) { if (!toDownload.isEmpty()) {
LOGGER.info(" download: Download sources {}", toDownload.stream().map(d -> d.id).toList()); LOGGER.info(" download: Download sources {}", toDownload.stream().map(d -> d.id).toList());
@ -635,7 +669,7 @@ public class Planetiler {
// in case any temp files are left from a previous run... // in case any temp files are left from a previous run...
FileUtils.delete(tmpDir, nodeDbPath, featureDbPath, multipolygonPath); FileUtils.delete(tmpDir, nodeDbPath, featureDbPath, multipolygonPath);
Files.createDirectories(tmpDir); Files.createDirectories(tmpDir);
FileUtils.createParentDirectories(nodeDbPath, featureDbPath, multipolygonPath, output); FileUtils.createParentDirectories(nodeDbPath, featureDbPath, multipolygonPath, output.getLocalPath());
if (!toDownload.isEmpty()) { if (!toDownload.isEmpty()) {
download(); download();
@ -661,14 +695,16 @@ public class Planetiler {
} }
bounds.addFallbackProvider(new OsmNodeBoundsProvider(osmInputFile, config, stats)); bounds.addFallbackProvider(new OsmNodeBoundsProvider(osmInputFile, config, stats));
} }
// must construct this after bounds providers are added in order to infer bounds from the input source if not provided
tileArchiveMetadata = new TileArchiveMetadata(profile, config);
try (WriteableTileArchive archive = Mbtiles.newWriteToFileDatabase(output, config.compactDb())) { try (WriteableTileArchive archive = TileArchives.newWriter(output, config)) {
featureGroup = featureGroup =
FeatureGroup.newDiskBackedFeatureGroup(archive.tileOrder(), featureDbPath, profile, config, stats); FeatureGroup.newDiskBackedFeatureGroup(archive.tileOrder(), featureDbPath, profile, config, stats);
stats.monitorFile("nodes", nodeDbPath); stats.monitorFile("nodes", nodeDbPath);
stats.monitorFile("features", featureDbPath); stats.monitorFile("features", featureDbPath);
stats.monitorFile("multipolygons", multipolygonPath); stats.monitorFile("multipolygons", multipolygonPath);
stats.monitorFile("archive", output); stats.monitorFile("archive", output.getLocalPath());
for (Stage stage : stages) { for (Stage stage : stages) {
stage.task.run(); stage.task.run();
@ -685,9 +721,8 @@ public class Planetiler {
featureGroup.prepare(); featureGroup.prepare();
TileArchiveWriter.writeOutput(featureGroup, archive, () -> FileUtils.fileSize(output), tileArchiveMetadata, TileArchiveWriter.writeOutput(featureGroup, archive, output::size, tileArchiveMetadata,
config, config, stats);
stats);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write to " + output, e); throw new IllegalStateException("Unable to write to " + output, e);
} }
@ -716,7 +751,7 @@ public class Planetiler {
readPhase.addDisk(featureDbPath, featureSize, "temporary feature storage"); readPhase.addDisk(featureDbPath, featureSize, "temporary feature storage");
writePhase.addDisk(featureDbPath, featureSize, "temporary feature storage"); writePhase.addDisk(featureDbPath, featureSize, "temporary feature storage");
// output only needed during write phase // output only needed during write phase
writePhase.addDisk(output, outputSize, "archive output"); writePhase.addDisk(output.getLocalPath(), outputSize, "archive output");
// if the user opts to remove an input source after reading to free up additional space for the output... // if the user opts to remove an input source after reading to free up additional space for the output...
for (var input : inputPaths) { for (var input : inputPaths) {
if (input.freeAfterReading()) { if (input.freeAfterReading()) {

Wyświetl plik

@ -40,5 +40,10 @@ public interface ReadableTileArchive extends Closeable {
*/ */
CloseableIterator<TileCoord> getAllTileCoords(); CloseableIterator<TileCoord> getAllTileCoords();
/**
* Returns the metadata stored in this archive.
*/
TileArchiveMetadata metadata();
// TODO access archive metadata // TODO access archive metadata
} }

Wyświetl plik

@ -0,0 +1,196 @@
package com.onthegomap.planetiler.archive;
import static com.onthegomap.planetiler.util.LanguageUtils.nullIfEmpty;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.util.FileUtils;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
/**
* Definition for a tileset, parsed from a URI-like string.
* <p>
* {@link #from(String)} can accept:
* <ul>
* <li>A platform-specific absolute or relative path like {@code "./archive.mbtiles"} or
* {@code "C:\root\archive.mbtiles"}</li>
* <li>A URI pointing at a file, like {@code "file:///root/archive.pmtiles"} or
* {@code "file:///C:/root/archive.pmtiles"}</li>
* </ul>
* <p>
* Both of these can also have archive-specific options added to the end, for example
* {@code "output.mbtiles?compact=false&page_size=16384"}.
*
* @param format The {@link Format format} of the archive, either inferred from the filename extension or the
* {@code ?format=} query parameter
* @param scheme Scheme for accessing the archive
* @param uri Full URI including scheme, location, and options
* @param options Parsed query parameters from the definition string
*/
public record TileArchiveConfig(
Format format,
Scheme scheme,
URI uri,
Map<String, String> options
) {
private static TileArchiveConfig.Scheme getScheme(URI uri) {
String scheme = uri.getScheme();
if (scheme == null) {
return Scheme.FILE;
}
for (var value : TileArchiveConfig.Scheme.values()) {
if (value.id().equals(scheme)) {
return value;
}
}
throw new IllegalArgumentException("Unsupported scheme " + scheme + " from " + uri);
}
private static String getExtension(URI uri) {
String path = uri.getPath();
if (path != null && (path.contains("."))) {
return nullIfEmpty(path.substring(path.lastIndexOf(".") + 1));
}
return null;
}
private static Map<String, String> parseQuery(URI uri) {
String query = uri.getRawQuery();
Map<String, String> result = new HashMap<>();
if (query != null) {
for (var part : query.split("&")) {
var split = part.split("=", 2);
result.put(
URLDecoder.decode(split[0], StandardCharsets.UTF_8),
split.length == 1 ? "true" : URLDecoder.decode(split[1], StandardCharsets.UTF_8)
);
}
}
return result;
}
private static TileArchiveConfig.Format getFormat(URI uri) {
String format = parseQuery(uri).get("format");
if (format == null) {
format = getExtension(uri);
}
if (format == null) {
return TileArchiveConfig.Format.MBTILES;
}
for (var value : TileArchiveConfig.Format.values()) {
if (value.id().equals(format)) {
return value;
}
}
throw new IllegalArgumentException("Unsupported format " + format + " from " + uri);
}
/**
* Parses a string definition of a tileset from a URI-like string.
*/
public static TileArchiveConfig from(String string) {
// unix paths parse fine as URIs, but need to explicitly parse windows paths with backslashes
if (string.contains("\\")) {
String[] parts = string.split("\\?", 2);
string = Path.of(parts[0]).toUri().toString();
if (parts.length > 1) {
string += "?" + parts[1];
}
}
return from(URI.create(string));
}
/**
* Parses a string definition of a tileset from a URI.
*/
public static TileArchiveConfig from(URI uri) {
if (uri.getScheme() == null) {
String base = Path.of(uri.getPath()).toAbsolutePath().toUri().normalize().toString();
if (uri.getRawQuery() != null) {
base += "?" + uri.getRawQuery();
}
uri = URI.create(base);
}
return new TileArchiveConfig(
getFormat(uri),
getScheme(uri),
uri,
parseQuery(uri)
);
}
/**
* Returns the local path on disk that this archive reads/writes to, or {@code null} if it is not on disk (ie. an HTTP
* repository).
*/
public Path getLocalPath() {
return scheme == Scheme.FILE ? Path.of(URI.create(uri.toString().replaceAll("\\?.*$", ""))) : null;
}
/**
* Deletes the archive if possible.
*/
public void delete() {
if (scheme == Scheme.FILE) {
FileUtils.delete(getLocalPath());
}
}
/**
* Returns {@code true} if the archive already exists, {@code false} otherwise.
*/
public boolean exists() {
return getLocalPath() != null && Files.exists(getLocalPath());
}
/**
* Returns the current size of this archive.
*/
public long size() {
return getLocalPath() == null ? 0 : FileUtils.size(getLocalPath());
}
/**
* Returns an {@link Arguments} instance that returns the value for options directly from the query parameters in the
* URI, or from {@code arguments} prefixed by {@code "format_"}.
*/
public Arguments applyFallbacks(Arguments arguments) {
return Arguments.of(options).orElse(arguments.withPrefix(format.id));
}
public enum Format {
MBTILES("mbtiles"),
PMTILES("pmtiles");
private final String id;
Format(String id) {
this.id = id;
}
public String id() {
return id;
}
}
public enum Scheme {
FILE("file");
private final String id;
Scheme(String id) {
this.id = id;
}
public String id() {
return id;
}
}
}

Wyświetl plik

@ -1,42 +1,90 @@
package com.onthegomap.planetiler.archive; package com.onthegomap.planetiler.archive;
import com.onthegomap.planetiler.Profile; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT;
import com.onthegomap.planetiler.config.Arguments; import static com.onthegomap.planetiler.util.Format.joinCoordinates;
import com.onthegomap.planetiler.util.BuildInfo;
import java.util.LinkedHashMap;
import java.util.Map;
/** Controls information that {@link TileArchiveWriter} will write to the archive metadata. */ import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.util.BuildInfo;
import com.onthegomap.planetiler.util.LayerStats;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Metadata associated with a tile archive. */
public record TileArchiveMetadata( public record TileArchiveMetadata(
String name, @JsonProperty(NAME_KEY) String name,
String description, @JsonProperty(DESCRIPTION_KEY) String description,
String attribution, @JsonProperty(ATTRIBUTION_KEY) String attribution,
String version, @JsonProperty(VERSION_KEY) String version,
String type, @JsonProperty(TYPE_KEY) String type,
Map<String, String> planetilerSpecific @JsonProperty(FORMAT_KEY) String format,
@JsonIgnore Envelope bounds,
@JsonIgnore CoordinateXY center,
@JsonProperty(ZOOM_KEY) Double zoom,
@JsonProperty(MINZOOM_KEY) Integer minzoom,
@JsonProperty(MAXZOOM_KEY) Integer maxzoom,
@JsonIgnore List<LayerStats.VectorLayer> vectorLayers,
@JsonAnyGetter Map<String, String> others
) { ) {
public TileArchiveMetadata(Profile profile) { public static final String NAME_KEY = "name";
public static final String DESCRIPTION_KEY = "description";
public static final String ATTRIBUTION_KEY = "attribution";
public static final String VERSION_KEY = "version";
public static final String TYPE_KEY = "type";
public static final String FORMAT_KEY = "format";
public static final String BOUNDS_KEY = "bounds";
public static final String CENTER_KEY = "center";
public static final String ZOOM_KEY = "zoom";
public static final String MINZOOM_KEY = "minzoom";
public static final String MAXZOOM_KEY = "maxzoom";
public static final String VECTOR_LAYERS_KEY = "vector_layers";
public static final String MVT_FORMAT = "pbf";
private static final Logger LOGGER = LoggerFactory.getLogger(TileArchiveMetadata.class);
private static final ObjectMapper mapper = new ObjectMapper()
.registerModules(new Jdk8Module())
.setSerializationInclusion(NON_ABSENT);
public TileArchiveMetadata(Profile profile, PlanetilerConfig config) {
this(profile, config, null);
}
public TileArchiveMetadata(Profile profile, PlanetilerConfig config, List<LayerStats.VectorLayer> vectorLayers) {
this( this(
profile.name(), getString(config, NAME_KEY, profile.name()),
profile.description(), getString(config, DESCRIPTION_KEY, profile.description()),
profile.attribution(), getString(config, ATTRIBUTION_KEY, profile.attribution()),
profile.version(), getString(config, VERSION_KEY, profile.version()),
profile.isOverlay() ? "overlay" : "baselayer", getString(config, TYPE_KEY, profile.isOverlay() ? "overlay" : "baselayer"),
getString(config, FORMAT_KEY, MVT_FORMAT),
config.bounds().latLon(),
new CoordinateXY(config.bounds().latLon().centre()),
GeoUtils.getZoomFromLonLatBounds(config.bounds().latLon()),
config.minzoom(),
config.maxzoom(),
vectorLayers,
mapWithBuildInfo() mapWithBuildInfo()
); );
} }
public TileArchiveMetadata(Profile profile, Arguments args) { private static String getString(PlanetilerConfig config, String key, String fallback) {
this( return config.arguments()
args.getString("mbtiles_name", "'name' attribute for tileset metadata", profile.name()), .getString("archive_" + key + "|mbtiles_" + key, "'" + key + "' attribute for tileset metadata", fallback);
args.getString("mbtiles_description", "'description' attribute for tileset metadata", profile.description()),
args.getString("mbtiles_attribution", "'attribution' attribute for tileset metadata", profile.attribution()),
args.getString("mbtiles_version", "'version' attribute for tileset metadata", profile.version()),
args.getString("mbtiles_type", "'type' attribute for tileset metadata",
profile.isOverlay() ? "overlay" : "baselayer"),
mapWithBuildInfo()
);
} }
private static Map<String, String> mapWithBuildInfo() { private static Map<String, String> mapWithBuildInfo() {
@ -56,20 +104,39 @@ public record TileArchiveMetadata(
return result; return result;
} }
public TileArchiveMetadata set(String key, Object value) { /** Sets an extra metadata entry in {@link #others}. */
public TileArchiveMetadata setExtraMetadata(String key, Object value) {
if (key != null && value != null) { if (key != null && value != null) {
planetilerSpecific.put(key, value.toString()); others.put(key, value.toString());
} }
return this; return this;
} }
public Map<String, String> getAll() { /**
var allKvs = new LinkedHashMap<String, String>(planetilerSpecific); * Returns a map with all key-value pairs from this metadata entry, including {@link #others} hoisted to top-level
allKvs.put("name", this.name); * keys.
allKvs.put("description", this.description); */
allKvs.put("attribution", this.attribution); public Map<String, String> toMap() {
allKvs.put("version", this.version); Map<String, String> result = new LinkedHashMap<>(mapper.convertValue(this, new TypeReference<>() {}));
allKvs.put("type", this.type); if (bounds != null) {
return allKvs; result.put(BOUNDS_KEY, joinCoordinates(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()));
}
if (center != null) {
result.put(CENTER_KEY, joinCoordinates(center.getX(), center.getY()));
}
if (vectorLayers != null) {
try {
result.put(VECTOR_LAYERS_KEY, mapper.writeValueAsString(vectorLayers));
} catch (JsonProcessingException e) {
LOGGER.warn("Error encoding vector_layers as json", e);
}
}
return result;
}
/** Returns a copy of this instance with {@link #vectorLayers} set to {@code layerStats}. */
public TileArchiveMetadata withLayerStats(List<LayerStats.VectorLayer> layerStats) {
return new TileArchiveMetadata(name, description, attribution, version, type, format, bounds, center, zoom, minzoom,
maxzoom, layerStats, others);
} }
} }

Wyświetl plik

@ -15,7 +15,6 @@ import com.onthegomap.planetiler.stats.Timer;
import com.onthegomap.planetiler.util.DiskBacked; import com.onthegomap.planetiler.util.DiskBacked;
import com.onthegomap.planetiler.util.Format; import com.onthegomap.planetiler.util.Format;
import com.onthegomap.planetiler.util.Hashing; import com.onthegomap.planetiler.util.Hashing;
import com.onthegomap.planetiler.util.LayerStats;
import com.onthegomap.planetiler.worker.WorkQueue; import com.onthegomap.planetiler.worker.WorkQueue;
import com.onthegomap.planetiler.worker.Worker; import com.onthegomap.planetiler.worker.Worker;
import com.onthegomap.planetiler.worker.WorkerPipeline; import com.onthegomap.planetiler.worker.WorkerPipeline;
@ -52,7 +51,6 @@ public class TileArchiveWriter {
private final WriteableTileArchive archive; private final WriteableTileArchive archive;
private final PlanetilerConfig config; private final PlanetilerConfig config;
private final Stats stats; private final Stats stats;
private final LayerStats layerStats;
private final Counter.Readable[] tilesByZoom; private final Counter.Readable[] tilesByZoom;
private final Counter.Readable[] totalTileSizesByZoom; private final Counter.Readable[] totalTileSizesByZoom;
private final LongAccumulator[] maxTileSizesByZoom; private final LongAccumulator[] maxTileSizesByZoom;
@ -61,14 +59,12 @@ public class TileArchiveWriter {
private final TileArchiveMetadata tileArchiveMetadata; private final TileArchiveMetadata tileArchiveMetadata;
private TileArchiveWriter(Iterable<FeatureGroup.TileFeatures> inputTiles, WriteableTileArchive archive, private TileArchiveWriter(Iterable<FeatureGroup.TileFeatures> inputTiles, WriteableTileArchive archive,
PlanetilerConfig config, PlanetilerConfig config, TileArchiveMetadata tileArchiveMetadata, Stats stats) {
TileArchiveMetadata tileArchiveMetadata, Stats stats, LayerStats layerStats) {
this.inputTiles = inputTiles; this.inputTiles = inputTiles;
this.archive = archive; this.archive = archive;
this.config = config; this.config = config;
this.tileArchiveMetadata = tileArchiveMetadata; this.tileArchiveMetadata = tileArchiveMetadata;
this.stats = stats; this.stats = stats;
this.layerStats = layerStats;
tilesByZoom = IntStream.rangeClosed(0, config.maxzoom()) tilesByZoom = IntStream.rangeClosed(0, config.maxzoom())
.mapToObj(i -> Counter.newSingleThreadCounter()) .mapToObj(i -> Counter.newSingleThreadCounter())
.toArray(Counter.Readable[]::new); .toArray(Counter.Readable[]::new);
@ -111,8 +107,9 @@ public class TileArchiveWriter {
readWorker = reader.readWorker(); readWorker = reader.readWorker();
} }
TileArchiveWriter writer = new TileArchiveWriter(inputTiles, output, config, tileArchiveMetadata, stats, TileArchiveWriter writer =
features.layerStats()); new TileArchiveWriter(inputTiles, output, config, tileArchiveMetadata.withLayerStats(features.layerStats()
.getTileStats()), stats);
var pipeline = WorkerPipeline.start("archive", stats); var pipeline = WorkerPipeline.start("archive", stats);
@ -231,7 +228,6 @@ public class TileArchiveWriter {
byte[] lastBytes = null, lastEncoded = null; byte[] lastBytes = null, lastEncoded = null;
Long lastTileDataHash = null; Long lastTileDataHash = null;
boolean lastIsFill = false; boolean lastIsFill = false;
boolean compactDb = config.compactDb();
boolean skipFilled = config.skipFilledTiles(); boolean skipFilled = config.skipFilledTiles();
for (TileBatch batch : prev) { for (TileBatch batch : prev) {
@ -265,7 +261,7 @@ public class TileArchiveWriter {
lastEncoded = encoded; lastEncoded = encoded;
lastBytes = bytes; lastBytes = bytes;
last = tileFeatures; last = tileFeatures;
if (compactDb && en.likelyToBeDuplicated() && bytes != null) { if (archive.deduplicates() && en.likelyToBeDuplicated() && bytes != null) {
tileDataHash = generateContentHash(bytes); tileDataHash = generateContentHash(bytes);
} else { } else {
tileDataHash = null; tileDataHash = null;
@ -292,7 +288,8 @@ public class TileArchiveWriter {
private void tileWriter(Iterable<TileBatch> tileBatches) throws ExecutionException, InterruptedException { private void tileWriter(Iterable<TileBatch> tileBatches) throws ExecutionException, InterruptedException {
archive.initialize(config, tileArchiveMetadata, layerStats); archive.initialize(tileArchiveMetadata);
var order = archive.tileOrder();
TileCoord lastTile = null; TileCoord lastTile = null;
Timer time = null; Timer time = null;
@ -303,8 +300,9 @@ public class TileArchiveWriter {
TileEncodingResult encodedTile; TileEncodingResult encodedTile;
while ((encodedTile = encodedTiles.poll()) != null) { while ((encodedTile = encodedTiles.poll()) != null) {
TileCoord tileCoord = encodedTile.coord(); TileCoord tileCoord = encodedTile.coord();
assert lastTile == null || lastTile.compareTo(tileCoord) < 0 : "Tiles out of order %s before %s" assert lastTile == null ||
.formatted(lastTile, tileCoord); order.encode(tileCoord) > order.encode(lastTile) : "Tiles out of order %s before %s"
.formatted(lastTile, tileCoord);
lastTile = encodedTile.coord(); lastTile = encodedTile.coord();
int z = tileCoord.z(); int z = tileCoord.z();
if (z != currentZ) { if (z != currentZ) {
@ -331,7 +329,7 @@ public class TileArchiveWriter {
} }
archive.finish(config); archive.finish(tileArchiveMetadata);
} }
private void printTileStats() { private void printTileStats() {

Wyświetl plik

@ -0,0 +1,75 @@
package com.onthegomap.planetiler.archive;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
import com.onthegomap.planetiler.pmtiles.ReadablePmtiles;
import com.onthegomap.planetiler.pmtiles.WriteablePmtiles;
import java.io.IOException;
import java.nio.file.Path;
/** Utilities for creating {@link ReadableTileArchive} and {@link WriteableTileArchive} instances. */
public class TileArchives {
private TileArchives() {}
/**
* Returns a new {@link WriteableTileArchive} from the string definition in {@code archive} that will be parsed with
* {@link TileArchiveConfig}.
*
* @throws IOException if an error occurs creating the resource.
*/
public static WriteableTileArchive newWriter(String archive, PlanetilerConfig config) throws IOException {
return newWriter(TileArchiveConfig.from(archive), config);
}
/**
* Returns a new {@link ReadableTileArchive} from the string definition in {@code archive} that will be parsed with
* {@link TileArchiveConfig}.
*
* @throws IOException if an error occurs opening the resource.
*/
public static ReadableTileArchive newReader(String archive, PlanetilerConfig config) throws IOException {
return newReader(TileArchiveConfig.from(archive), config);
}
/**
* Returns a new {@link WriteableTileArchive} from the string definition in {@code archive}.
*
* @throws IOException if an error occurs creating the resource.
*/
public static WriteableTileArchive newWriter(TileArchiveConfig archive, PlanetilerConfig config)
throws IOException {
var options = archive.applyFallbacks(config.arguments());
return switch (archive.format()) {
case MBTILES ->
// pass-through legacy arguments for fallback
Mbtiles.newWriteToFileDatabase(archive.getLocalPath(), options.orElse(config.arguments()
.subset(Mbtiles.LEGACY_VACUUM_ANALYZE, Mbtiles.LEGACY_COMPACT_DB, Mbtiles.LEGACY_SKIP_INDEX_CREATION)));
case PMTILES -> WriteablePmtiles.newWriteToFile(archive.getLocalPath());
};
}
/**
* Returns a new {@link ReadableTileArchive} from the string definition in {@code archive}.
*
* @throws IOException if an error occurs opening the resource.
*/
public static ReadableTileArchive newReader(TileArchiveConfig archive, PlanetilerConfig config)
throws IOException {
var options = archive.applyFallbacks(config.arguments());
return switch (archive.format()) {
case MBTILES -> Mbtiles.newReadOnlyDatabase(archive.getLocalPath(), options);
case PMTILES -> ReadablePmtiles.newReadFromFile(archive.getLocalPath());
};
}
/** Alias for {@link #newReader(String, PlanetilerConfig)}. */
public static ReadableTileArchive newReader(Path path, PlanetilerConfig config) throws IOException {
return newReader(path.toString(), config);
}
/** Alias for {@link #newWriter(String, PlanetilerConfig)}. */
public static WriteableTileArchive newWriter(Path path, PlanetilerConfig config) throws IOException {
return newWriter(path.toString(), config);
}
}

Wyświetl plik

@ -2,7 +2,6 @@ package com.onthegomap.planetiler.archive;
import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.geo.TileOrder; import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.util.LayerStats;
import java.io.Closeable; import java.io.Closeable;
import javax.annotation.concurrent.NotThreadSafe; import javax.annotation.concurrent.NotThreadSafe;
@ -15,6 +14,36 @@ import javax.annotation.concurrent.NotThreadSafe;
@NotThreadSafe @NotThreadSafe
public interface WriteableTileArchive extends Closeable { public interface WriteableTileArchive extends Closeable {
/**
* Returns true if this tile archive deduplicates tiles with the same content.
* <p>
* If false, then {@link TileWriter} will skip computing tile hashes.
*/
boolean deduplicates();
/**
* Specify the preferred insertion order for this archive, e.g. {@link TileOrder#TMS} or {@link TileOrder#HILBERT}.
*/
TileOrder tileOrder();
/**
* Called before any tiles are written into {@link TileWriter}. Implementations of TileArchive should set up any
* required state here.
*/
default void initialize(TileArchiveMetadata metadata) {}
/**
* Implementations should return a object that implements {@link TileWriter} The specific TileWriter returned might
* depend on {@link PlanetilerConfig}.
*/
TileWriter newTileWriter();
/**
* Called after all tiles are written into {@link TileWriter}. After this is called, the archive should be complete on
* disk.
*/
default void finish(TileArchiveMetadata tileArchiveMetadata) {}
interface TileWriter extends Closeable { interface TileWriter extends Closeable {
void write(TileEncodingResult encodingResult); void write(TileEncodingResult encodingResult);
@ -30,29 +59,5 @@ public interface WriteableTileArchive extends Closeable {
default void printStats() {} default void printStats() {}
} }
/**
* Specify the preferred insertion order for this archive, e.g. {@link TileOrder#TMS} or {@link TileOrder#HILBERT}.
*/
TileOrder tileOrder();
/**
* Called before any tiles are written into {@link TileWriter}. Implementations of TileArchive should set up any
* required state here.
*/
void initialize(PlanetilerConfig config, TileArchiveMetadata metadata, LayerStats layerStats);
/**
* Implementations should return a object that implements {@link TileWriter} The specific TileWriter returned might
* depend on {@link PlanetilerConfig}.
*/
TileWriter newTileWriter();
/**
* Called after all tiles are written into {@link TileWriter}. After this is called, the archive should be complete on
* disk.
*/
void finish(PlanetilerConfig config);
// TODO update archive metadata // TODO update archive metadata
} }

Wyświetl plik

@ -12,6 +12,8 @@ import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -20,6 +22,7 @@ import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Envelope;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,6 +31,12 @@ import org.slf4j.LoggerFactory;
/** /**
* Lightweight abstraction over ways to provide key/value pair arguments to a program like jvm properties, environmental * Lightweight abstraction over ways to provide key/value pair arguments to a program like jvm properties, environmental
* variables, or a config file. * variables, or a config file.
* <p>
* When looking up a key, tries to find a case-and-separator-insensitive match, for example {@code "CONFIG_OPTION"} will
* match {@code "config-option"} and {@code "config_option"}.
* <p>
* If you replace an option with a new value, you can read a value from the new option and fall back to old one by using
* {@code "new_flag|old_flag"} as the key.
*/ */
public class Arguments { public class Arguments {
@ -42,15 +51,6 @@ public class Arguments {
this.keys = keys; this.keys = keys;
} }
private static Arguments from(UnaryOperator<String> provider, Supplier<? extends Collection<String>> rawKeys,
UnaryOperator<String> forward, UnaryOperator<String> reverse) {
Supplier<List<String>> keys = () -> rawKeys.get().stream().flatMap(key -> {
String reversed = reverse.apply(key);
return key.equalsIgnoreCase(reversed) ? Stream.empty() : Stream.of(reversed);
}).toList();
return new Arguments(key -> provider.apply(forward.apply(key)), keys);
}
/** /**
* Returns arguments from JVM system properties prefixed with {@code planetiler.} * Returns arguments from JVM system properties prefixed with {@code planetiler.}
* <p> * <p>
@ -64,10 +64,7 @@ public class Arguments {
} }
static Arguments fromJvmProperties(UnaryOperator<String> getter, Supplier<? extends Collection<String>> keys) { static Arguments fromJvmProperties(UnaryOperator<String> getter, Supplier<? extends Collection<String>> keys) {
return from(getter, keys, return fromPrefixed(getter, keys, "planetiler", ".", false);
key -> "planetiler." + key.toLowerCase(Locale.ROOT),
key -> key.replaceFirst("^planetiler\\.", "").toLowerCase(Locale.ROOT)
);
} }
/** /**
@ -83,10 +80,7 @@ public class Arguments {
} }
static Arguments fromEnvironment(UnaryOperator<String> getter, Supplier<Set<String>> keys) { static Arguments fromEnvironment(UnaryOperator<String> getter, Supplier<Set<String>> keys) {
return from(getter, keys, return fromPrefixed(getter, keys, "PLANETILER", "_", true);
key -> "PLANETILER_" + key.toUpperCase(Locale.ROOT),
key -> key.replaceFirst("^PLANETILER_", "").toLowerCase(Locale.ROOT)
);
} }
/** /**
@ -191,8 +185,21 @@ public class Arguments {
.orElse(fromEnvironment()); .orElse(fromEnvironment());
} }
private static String normalize(String key, String separator, boolean upperCase) {
String result = key.replaceAll("[._-]", separator);
return upperCase ? result.toUpperCase(Locale.ROOT) : result.toLowerCase(Locale.ROOT);
}
private static String normalize(String key) {
return normalize(key, "_", false);
}
public static Arguments of(Map<String, String> map) { public static Arguments of(Map<String, String> map) {
return new Arguments(map::get, map::keySet); Map<String, String> updated = new LinkedHashMap<>();
for (var entry : map.entrySet()) {
updated.put(normalize(entry.getKey()), entry.getValue());
}
return new Arguments(updated::get, updated::keySet);
} }
/** Shorthand for {@link #of(Map)} which constructs the map from a list of key/value pairs. */ /** Shorthand for {@link #of(Map)} which constructs the map from a list of key/value pairs. */
@ -204,12 +211,36 @@ public class Arguments {
return of(map); return of(map);
} }
private static Arguments from(UnaryOperator<String> provider, Supplier<? extends Collection<String>> rawKeys,
UnaryOperator<String> forward, UnaryOperator<String> reverse) {
Supplier<List<String>> keys = () -> rawKeys.get().stream().flatMap(key -> {
String reversed = reverse.apply(key);
return normalize(key).equals(normalize(reversed)) ? Stream.empty() : Stream.of(reversed);
}).toList();
return new Arguments(key -> provider.apply(forward.apply(key)), keys);
}
private static Arguments fromPrefixed(UnaryOperator<String> provider, Supplier<? extends Collection<String>> keys,
String prefix, String separator, boolean uppperCase) {
var prefixRegex = Pattern.compile("^" + Pattern.quote(normalize(prefix + separator, separator, uppperCase)),
Pattern.CASE_INSENSITIVE);
return from(provider, keys,
key -> normalize(prefix + separator + key, separator, uppperCase),
key -> normalize(prefixRegex.matcher(key).replaceFirst(""))
);
}
private String get(String key) { private String get(String key) {
String value = provider.apply(key); String[] options = key.split("\\|");
if (value == null) { String value = null;
value = provider.apply(key.replace('-', '_')); for (int i = 0; i < options.length; i++) {
if (value == null) { String option = options[i].strip();
value = provider.apply(key.replace('_', '-')); value = provider.apply(normalize(option));
if (value != null) {
if (i != 0) {
LOGGER.warn("Argument '{}' is deprecated", option);
}
break;
} }
} }
return value; return value;
@ -274,8 +305,8 @@ public class Arguments {
} }
protected void logArgValue(String key, String description, Object result) { protected void logArgValue(String key, String description, Object result) {
if (!silent) { if (!silent && LOGGER.isDebugEnabled()) {
LOGGER.debug("argument: {}={} ({})", key, result, description); LOGGER.debug("argument: {}={} ({})", key.replaceFirst("\\|.*$", ""), result, description);
} }
} }
@ -460,7 +491,7 @@ public class Arguments {
public Map<String, String> toMap() { public Map<String, String> toMap() {
Map<String, String> result = new HashMap<>(); Map<String, String> result = new HashMap<>();
for (var key : keys.get()) { for (var key : keys.get()) {
result.put(key, get(key)); result.put(normalize(key), get(key));
} }
return result; return result;
} }
@ -484,4 +515,27 @@ public class Arguments {
public boolean silenced() { public boolean silenced() {
return silent; return silent;
} }
public Arguments copy() {
return new Arguments(provider, keys);
}
/**
* Returns a new arguments instance that translates requests for a {@code "key"} to {@code "prefix_key"}.
*/
public Arguments withPrefix(String prefix) {
return fromPrefixed(provider, keys, prefix, "_", false);
}
/** Returns a view of this instance, that only supports requests for {@code allowedKeys}. */
public Arguments subset(String... allowedKeys) {
Set<String> allowed = new HashSet<>();
for (String key : allowedKeys) {
allowed.add(normalize(key));
}
return new Arguments(
key -> allowed.contains(normalize(key)) ? provider.apply(key) : null,
() -> keys.get().stream().filter(key -> allowed.contains(normalize(key))).toList()
);
}
} }

Wyświetl plik

@ -24,8 +24,6 @@ public record PlanetilerConfig(
int minzoom, int minzoom,
int maxzoom, int maxzoom,
int maxzoomForRendering, int maxzoomForRendering,
boolean skipIndexCreation,
boolean optimizeDb,
boolean force, boolean force,
boolean gzipTempStorage, boolean gzipTempStorage,
boolean mmapTempStorage, boolean mmapTempStorage,
@ -47,7 +45,6 @@ public record PlanetilerConfig(
double simplifyToleranceAtMaxZoom, double simplifyToleranceAtMaxZoom,
double simplifyToleranceBelowMaxZoom, double simplifyToleranceBelowMaxZoom,
boolean osmLazyReads, boolean osmLazyReads,
boolean compactDb,
boolean skipFilledTiles, boolean skipFilledTiles,
int tileWarningSizeBytes, int tileWarningSizeBytes,
Boolean color Boolean color
@ -125,8 +122,6 @@ public record PlanetilerConfig(
minzoom, minzoom,
maxzoom, maxzoom,
renderMaxzoom, renderMaxzoom,
arguments.getBoolean("skip_mbtiles_index_creation", "skip adding index to mbtiles file", false),
arguments.getBoolean("optimize_db", "Vacuum analyze mbtiles after writing", false),
arguments.getBoolean("force", "overwriting output file and ignore disk/RAM warnings", false), arguments.getBoolean("force", "overwriting output file and ignore disk/RAM warnings", false),
arguments.getBoolean("gzip_temp", "gzip temporary feature storage (uses more CPU, but less disk space)", false), arguments.getBoolean("gzip_temp", "gzip temporary feature storage (uses more CPU, but less disk space)", false),
arguments.getBoolean("mmap_temp", "use memory-mapped IO for temp feature files", true), arguments.getBoolean("mmap_temp", "use memory-mapped IO for temp feature files", true),
@ -168,9 +163,6 @@ public record PlanetilerConfig(
arguments.getBoolean("osm_lazy_reads", arguments.getBoolean("osm_lazy_reads",
"Read OSM blocks from disk in worker threads", "Read OSM blocks from disk in worker threads",
true), true),
arguments.getBoolean("compact_db",
"Reduce the DB size by separating and deduping the tile data",
true),
arguments.getBoolean("skip_filled_tiles", arguments.getBoolean("skip_filled_tiles",
"Skip writing tiles containing only polygon fills to the output", "Skip writing tiles containing only polygon fills to the output",
false), false),

Wyświetl plik

@ -1,6 +1,7 @@
package com.onthegomap.planetiler.mbtiles; package com.onthegomap.planetiler.mbtiles;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT;
import static com.onthegomap.planetiler.util.Format.joinCoordinates;
import com.carrotsearch.hppc.LongIntHashMap; import com.carrotsearch.hppc.LongIntHashMap;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -11,14 +12,14 @@ import com.onthegomap.planetiler.archive.ReadableTileArchive;
import com.onthegomap.planetiler.archive.TileArchiveMetadata; import com.onthegomap.planetiler.archive.TileArchiveMetadata;
import com.onthegomap.planetiler.archive.TileEncodingResult; import com.onthegomap.planetiler.archive.TileEncodingResult;
import com.onthegomap.planetiler.archive.WriteableTileArchive; import com.onthegomap.planetiler.archive.WriteableTileArchive;
import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.TileCoord; import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.geo.TileOrder; import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.reader.FileFormatException; import com.onthegomap.planetiler.reader.FileFormatException;
import com.onthegomap.planetiler.util.CloseableIterator; import com.onthegomap.planetiler.util.CloseableIterator;
import com.onthegomap.planetiler.util.Format; import com.onthegomap.planetiler.util.Format;
import com.onthegomap.planetiler.util.LayerStats; import com.onthegomap.planetiler.util.LayerStats;
import com.onthegomap.planetiler.util.Parse;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.sql.Connection; import java.sql.Connection;
@ -27,21 +28,20 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
import java.util.OptionalLong; import java.util.OptionalLong;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Envelope;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -54,6 +54,17 @@ import org.sqlite.SQLiteConfig;
*/ */
public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive { public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive {
// Options that can be set through "file.mbtiles?compact=true" query parameters
// or "file.mbtiles" with "--mbtiles-compact=true" command-line flag
public static final String COMPACT_DB = "compact";
public static final String SKIP_INDEX_CREATION = "no_index";
public static final String VACUUM_ANALYZE = "vacuum_analyze";
public static final String LEGACY_COMPACT_DB = "compact_db";
public static final String LEGACY_SKIP_INDEX_CREATION = "skip_mbtiles_index_creation";
public static final String LEGACY_VACUUM_ANALYZE = "optimize_db";
// https://www.sqlite.org/src/artifact?ci=trunk&filename=magic.txt // https://www.sqlite.org/src/artifact?ci=trunk&filename=magic.txt
private static final int MBTILES_APPLICATION_ID = 0x4d504258; private static final int MBTILES_APPLICATION_ID = 0x4d504258;
@ -93,72 +104,115 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
private final Connection connection; private final Connection connection;
private final boolean compactDb; private final boolean compactDb;
private final boolean skipIndexCreation;
private final boolean vacuumAnalyze;
private PreparedStatement getTileStatement = null; private PreparedStatement getTileStatement = null;
private Mbtiles(Connection connection, boolean compactDb) { private Mbtiles(Connection connection, Arguments arguments) {
this.connection = connection; this.connection = connection;
this.compactDb = compactDb; this.compactDb = arguments.getBoolean(
COMPACT_DB + "|" + LEGACY_COMPACT_DB,
"mbtiles: reduce the DB size by separating and deduping the tile data",
true
);
this.skipIndexCreation = arguments.getBoolean(
SKIP_INDEX_CREATION + "|" + LEGACY_SKIP_INDEX_CREATION,
"mbtiles: skip adding index to sqlite DB",
false
);
this.vacuumAnalyze = arguments.getBoolean(
VACUUM_ANALYZE + "|" + LEGACY_VACUUM_ANALYZE,
"mbtiles: vacuum analyze sqlite DB after writing",
false
);
} }
/** Returns a new mbtiles file that won't get written to disk. Useful for toy use-cases like unit tests. */ /** Returns a new mbtiles file that won't get written to disk. Useful for toy use-cases like unit tests. */
public static Mbtiles newInMemoryDatabase(boolean compactDb) { public static Mbtiles newInMemoryDatabase(boolean compactDb) {
try { return newInMemoryDatabase(Arguments.of(COMPACT_DB, compactDb ? "true" : "false"));
SQLiteConfig config = new SQLiteConfig();
config.setApplicationId(MBTILES_APPLICATION_ID);
return new Mbtiles(DriverManager.getConnection("jdbc:sqlite::memory:", config.toProperties()), compactDb);
} catch (SQLException throwables) {
throw new IllegalStateException("Unable to create in-memory database", throwables);
}
} }
/** @see {@link #newInMemoryDatabase(boolean)} */ /** Returns an in-memory database with extra mbtiles and pragma options set from {@code options}. */
public static Mbtiles newInMemoryDatabase(Arguments options) {
SQLiteConfig config = new SQLiteConfig();
config.setApplicationId(MBTILES_APPLICATION_ID);
return new Mbtiles(newConnection("jdbc:sqlite::memory:", config, options), options);
}
/** Alias for {@link #newInMemoryDatabase(boolean)} */
public static Mbtiles newInMemoryDatabase() { public static Mbtiles newInMemoryDatabase() {
return newInMemoryDatabase(true); return newInMemoryDatabase(true);
} }
/** Returns a new connection to an mbtiles file optimized for fast bulk writes. */ /**
public static Mbtiles newWriteToFileDatabase(Path path, boolean compactDb) { * Returns a new connection to an mbtiles file optimized for fast bulk writes with extra mbtiles and pragma options
try { * set from {@code options}.
SQLiteConfig config = new SQLiteConfig(); */
config.setJournalMode(SQLiteConfig.JournalMode.OFF); public static Mbtiles newWriteToFileDatabase(Path path, Arguments options) {
config.setSynchronous(SQLiteConfig.SynchronousMode.OFF); Objects.requireNonNull(path);
config.setCacheSize(1_000_000); // 1GB SQLiteConfig sqliteConfig = new SQLiteConfig();
config.setLockingMode(SQLiteConfig.LockingMode.EXCLUSIVE); sqliteConfig.setJournalMode(SQLiteConfig.JournalMode.OFF);
config.setTempStore(SQLiteConfig.TempStore.MEMORY); sqliteConfig.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
config.setApplicationId(MBTILES_APPLICATION_ID); sqliteConfig.setCacheSize(1_000_000); // 1GB
return new Mbtiles(DriverManager.getConnection("jdbc:sqlite:" + path.toAbsolutePath(), config.toProperties()), sqliteConfig.setLockingMode(SQLiteConfig.LockingMode.EXCLUSIVE);
compactDb); sqliteConfig.setTempStore(SQLiteConfig.TempStore.MEMORY);
} catch (SQLException throwables) { sqliteConfig.setApplicationId(MBTILES_APPLICATION_ID);
throw new IllegalArgumentException("Unable to open " + path, throwables); var connection = newConnection("jdbc:sqlite:" + path.toAbsolutePath(), sqliteConfig, options);
} return new Mbtiles(connection, options);
} }
/** Returns a new connection to an mbtiles file optimized for reads. */ /** Returns a new connection to an mbtiles file optimized for reads. */
public static Mbtiles newReadOnlyDatabase(Path path) { public static Mbtiles newReadOnlyDatabase(Path path) {
return newReadOnlyDatabase(path, Arguments.of());
}
/**
* Returns a new connection to an mbtiles file optimized for reads with extra mbtiles and pragma options set from
* {@code options}.
*/
public static Mbtiles newReadOnlyDatabase(Path path, Arguments options) {
Objects.requireNonNull(path);
SQLiteConfig config = new SQLiteConfig();
config.setReadOnly(true);
config.setCacheSize(100_000);
config.setLockingMode(SQLiteConfig.LockingMode.EXCLUSIVE);
config.setPageSize(32_768);
// helps with 3 or more threads concurrently accessing:
// config.setOpenMode(SQLiteOpenMode.NOMUTEX);
Connection connection = newConnection("jdbc:sqlite:" + path.toAbsolutePath(), config, options);
return new Mbtiles(connection, options);
}
private static Connection newConnection(String url, SQLiteConfig defaults, Arguments args) {
try { try {
SQLiteConfig config = new SQLiteConfig(); args = args.copy().silence();
config.setReadOnly(true); var config = new SQLiteConfig(defaults.toProperties());
config.setCacheSize(100_000); for (var pragma : SQLiteConfig.Pragma.values()) {
config.setLockingMode(SQLiteConfig.LockingMode.EXCLUSIVE); var value = args.getString(pragma.getPragmaName(), pragma.getPragmaName(), null);
config.setPageSize(32_768); if (value != null) {
// helps with 3 or more threads concurrently accessing: LOGGER.info("Setting custom mbtiles sqlite pragma {}={}", pragma.getPragmaName(), value);
// config.setOpenMode(SQLiteOpenMode.NOMUTEX); config.setPragma(pragma, value);
Connection connection = DriverManager }
.getConnection("jdbc:sqlite:" + path.toAbsolutePath(), config.toProperties()); }
return new Mbtiles(connection, false /* in read-only mode, it's irrelevant if compact or not */); return DriverManager.getConnection(url, config.toProperties());
} catch (SQLException throwables) { } catch (SQLException throwables) {
throw new IllegalArgumentException("Unable to open " + path, throwables); throw new IllegalArgumentException("Unable to open " + url, throwables);
} }
} }
@Override
public boolean deduplicates() {
return compactDb;
}
@Override @Override
public TileOrder tileOrder() { public TileOrder tileOrder() {
return TileOrder.TMS; return TileOrder.TMS;
} }
@Override @Override
public void initialize(PlanetilerConfig config, TileArchiveMetadata tileArchiveMetadata, LayerStats layerStats) { public void initialize(TileArchiveMetadata tileArchiveMetadata) {
if (config.skipIndexCreation()) { if (skipIndexCreation) {
createTablesWithoutIndexes(); createTablesWithoutIndexes();
if (LOGGER.isInfoEnabled()) { if (LOGGER.isInfoEnabled()) {
LOGGER.info("Skipping index creation. Add later by executing: {}", LOGGER.info("Skipping index creation. Add later by executing: {}",
@ -168,26 +222,12 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
createTablesWithIndexes(); createTablesWithIndexes();
} }
var metadata = metadata() metadataTable().set(tileArchiveMetadata);
.setName(tileArchiveMetadata.name())
.setFormat("pbf")
.setDescription(tileArchiveMetadata.description())
.setAttribution(tileArchiveMetadata.attribution())
.setVersion(tileArchiveMetadata.version())
.setType(tileArchiveMetadata.type())
.setBoundsAndCenter(config.bounds().latLon())
.setMinzoom(config.minzoom())
.setMaxzoom(config.maxzoom())
.setJson(new MetadataJson(layerStats.getTileStats()));
for (var entry : tileArchiveMetadata.planetilerSpecific().entrySet()) {
metadata.setMetadata(entry.getKey(), entry.getValue());
}
} }
@Override @Override
public void finish(PlanetilerConfig config) { public void finish(TileArchiveMetadata tileArchiveMetadata) {
if (config.optimizeDb()) { if (vacuumAnalyze) {
vacuumAnalyze(); vacuumAnalyze();
} }
} }
@ -341,9 +381,9 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
return newTileWriter(); return newTileWriter();
} }
/** Returns the contents of the metadata table. */ @Override
public Metadata metadata() { public TileArchiveMetadata metadata() {
return new Metadata(); return new Metadata().get();
} }
/** Returns the contents of the metadata table. */ /** Returns the contents of the metadata table. */
@ -389,12 +429,21 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
return connection; return connection;
} }
public boolean skipIndexCreation() {
return skipIndexCreation;
}
public boolean compactDb() {
return compactDb;
}
/** /**
* Data contained in the {@code json} row of the metadata table * Data contained in the {@code json} row of the metadata table
* *
* @see <a href="https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md#vector-tileset-metadata">MBtiles * @see <a href="https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md#vector-tileset-metadata">MBtiles
* schema</a> * schema</a>
*/ */
// TODO add tilestats
public record MetadataJson( public record MetadataJson(
@JsonProperty("vector_layers") List<LayerStats.VectorLayer> vectorLayers @JsonProperty("vector_layers") List<LayerStats.VectorLayer> vectorLayers
) { ) {
@ -405,7 +454,7 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
public static MetadataJson fromJson(String json) { public static MetadataJson fromJson(String json) {
try { try {
return objectMapper.readValue(json, MetadataJson.class); return json == null ? null : objectMapper.readValue(json, MetadataJson.class);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid metadata json: " + json, e); throw new IllegalStateException("Invalid metadata json: " + json, e);
} }
@ -466,6 +515,7 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
/** Contents of a row of the tiles_data table. */ /** Contents of a row of the tiles_data table. */
private record TileDataEntry(int tileDataId, byte[] tileData) { private record TileDataEntry(int tileDataId, byte[] tileData) {
@Override @Override
public String toString() { public String toString() {
return "TileDataEntry [tileDataId=" + tileDataId + ", tileData=" + Arrays.toString(tileData) + "]"; return "TileDataEntry [tileDataId=" + tileDataId + ", tileData=" + Arrays.toString(tileData) + "]";
@ -494,6 +544,7 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
/** Iterates through tile coordinates one at a time without materializing the entire list in memory. */ /** Iterates through tile coordinates one at a time without materializing the entire list in memory. */
private class TileCoordIterator implements CloseableIterator<TileCoord> { private class TileCoordIterator implements CloseableIterator<TileCoord> {
private final Statement statement; private final Statement statement;
private final ResultSet rs; private final ResultSet rs;
private boolean hasNext = false; private boolean hasNext = false;
@ -568,7 +619,7 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
insertStmtTableName = tableName; insertStmtTableName = tableName;
insertStmtInsertIgnore = insertIgnore; insertStmtInsertIgnore = insertIgnore;
insertStmtValuesPlaceHolder = columns.stream().map(c -> "?").collect(Collectors.joining(",", "(", ")")); insertStmtValuesPlaceHolder = columns.stream().map(c -> "?").collect(Collectors.joining(",", "(", ")"));
insertStmtColumnsCsv = columns.stream().collect(Collectors.joining(",")); insertStmtColumnsCsv = String.join(",", columns);
batchStatement = createBatchInsertPreparedStatement(batchLimit); batchStatement = createBatchInsertPreparedStatement(batchLimit);
} }
@ -779,16 +830,7 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
/** Data contained in the metadata table. */ /** Data contained in the metadata table. */
public class Metadata { public class Metadata {
private static final NumberFormat nf = NumberFormat.getNumberInstance(Locale.US); /** Inserts a row into the metadata table that sets {@code name=value}. */
static {
nf.setMaximumFractionDigits(5);
}
private static String join(double... items) {
return DoubleStream.of(items).mapToObj(nf::format).collect(Collectors.joining(","));
}
public Metadata setMetadata(String name, Object value) { public Metadata setMetadata(String name, Object value) {
if (value != null) { if (value != null) {
String stringValue = value.toString(); String stringValue = value.toString();
@ -810,79 +852,15 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
return this; return this;
} }
public Metadata setName(String value) { /**
return setMetadata("name", value); * Inserts a row into the metadata table that sets the value for {@code "json"} key to {@code value} serialized as a
} * string.
*/
/** Format of the tile data, should always be pbf {@code pbf}. */
public Metadata setFormat(String format) {
return setMetadata("format", format);
}
public Metadata setBounds(double left, double bottom, double right, double top) {
return setMetadata("bounds", join(left, bottom, right, top));
}
public Metadata setBounds(Envelope envelope) {
return setBounds(envelope.getMinX(), envelope.getMinY(), envelope.getMaxX(), envelope.getMaxY());
}
public Metadata setCenter(double longitude, double latitude, double zoom) {
return setMetadata("center", join(longitude, latitude, zoom));
}
public Metadata setBoundsAndCenter(Envelope envelope) {
return setBounds(envelope).setCenter(envelope);
}
/** Estimate a reasonable center for the map to fit an envelope. */
public Metadata setCenter(Envelope envelope) {
Coordinate center = envelope.centre();
double zoom = Math.ceil(GeoUtils.getZoomFromLonLatBounds(envelope));
return setCenter(center.x, center.y, zoom);
}
public Metadata setMinzoom(int value) {
return setMetadata("minzoom", value);
}
public Metadata setMaxzoom(int maxZoom) {
return setMetadata("maxzoom", maxZoom);
}
public Metadata setAttribution(String value) {
return setMetadata("attribution", value);
}
public Metadata setDescription(String value) {
return setMetadata("description", value);
}
/** {@code overlay} or {@code baselayer}. */
public Metadata setType(String value) {
return setMetadata("type", value);
}
public Metadata setTypeIsOverlay() {
return setType("overlay");
}
public Metadata setTypeIsBaselayer() {
return setType("baselayer");
}
public Metadata setVersion(String value) {
return setMetadata("version", value);
}
public Metadata setJson(String value) {
return setMetadata("json", value);
}
public Metadata setJson(MetadataJson value) { public Metadata setJson(MetadataJson value) {
return value == null ? this : setJson(value.toJson()); return value == null ? this : setMetadata("json", value.toJson());
} }
/** Returns all key-value pairs from the metadata table. */
public Map<String, String> getAll() { public Map<String, String> getAll() {
TreeMap<String, String> result = new TreeMap<>(); TreeMap<String, String> result = new TreeMap<>();
try (Statement statement = connection.createStatement()) { try (Statement statement = connection.createStatement()) {
@ -895,10 +873,86 @@ public final class Mbtiles implements WriteableTileArchive, ReadableTileArchive
); );
} }
} catch (SQLException throwables) { } catch (SQLException throwables) {
LOGGER.warn("Error retrieving metadata: " + throwables); LOGGER.warn("Error retrieving metadata: {}", throwables.toString());
LOGGER.trace("Error retrieving metadata details: ", throwables); LOGGER.trace("Error retrieving metadata details: ", throwables);
} }
return result; return result;
} }
/**
* Inserts rows into the metadata table that set all of the well-known metadata keys from
* {@code tileArchiveMetadata} and passes through the raw values of any options not explicitly called out in the
* MBTiles specification.
*
* @see <a href="https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md#content">MBTiles 1.3
* specification</a>
*/
public Metadata set(TileArchiveMetadata tileArchiveMetadata) {
var map = new LinkedHashMap<>(tileArchiveMetadata.toMap());
setMetadata(TileArchiveMetadata.FORMAT_KEY, tileArchiveMetadata.format());
var center = tileArchiveMetadata.center();
var zoom = tileArchiveMetadata.zoom();
if (center != null) {
if (zoom != null) {
setMetadata(TileArchiveMetadata.CENTER_KEY, joinCoordinates(center.x, center.y, Math.ceil(zoom)));
} else {
setMetadata(TileArchiveMetadata.CENTER_KEY, joinCoordinates(center.x, center.y));
}
}
var bounds = tileArchiveMetadata.bounds();
if (bounds != null) {
setMetadata(TileArchiveMetadata.BOUNDS_KEY,
joinCoordinates(bounds.getMinX(), bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()));
}
setJson(new MetadataJson(tileArchiveMetadata.vectorLayers()));
map.remove(TileArchiveMetadata.FORMAT_KEY);
map.remove(TileArchiveMetadata.CENTER_KEY);
map.remove(TileArchiveMetadata.ZOOM_KEY);
map.remove(TileArchiveMetadata.BOUNDS_KEY);
map.remove(TileArchiveMetadata.VECTOR_LAYERS_KEY);
for (var entry : map.entrySet()) {
setMetadata(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Returns a {@link TileArchiveMetadata} instance parsed from all the rows in the metadata table.
*/
public TileArchiveMetadata get() {
Map<String, String> map = new HashMap<>(getAll());
String[] bounds = map.containsKey(TileArchiveMetadata.BOUNDS_KEY) ?
map.remove(TileArchiveMetadata.BOUNDS_KEY).split(",") : null;
String[] center = map.containsKey(TileArchiveMetadata.CENTER_KEY) ?
map.remove(TileArchiveMetadata.CENTER_KEY).split(",") : null;
var metadataJson = MetadataJson.fromJson(map.remove("json"));
return new TileArchiveMetadata(
map.remove(TileArchiveMetadata.NAME_KEY),
map.remove(TileArchiveMetadata.DESCRIPTION_KEY),
map.remove(TileArchiveMetadata.ATTRIBUTION_KEY),
map.remove(TileArchiveMetadata.VERSION_KEY),
map.remove(TileArchiveMetadata.TYPE_KEY),
map.remove(TileArchiveMetadata.FORMAT_KEY),
bounds == null || bounds.length < 4 ? null : new Envelope(
Double.parseDouble(bounds[0]),
Double.parseDouble(bounds[2]),
Double.parseDouble(bounds[1]),
Double.parseDouble(bounds[3])
),
center == null || center.length < 2 ? null : new CoordinateXY(
Double.parseDouble(center[0]),
Double.parseDouble(center[1])
),
center == null || center.length < 3 ? null : Double.parseDouble(center[2]),
Parse.parseIntOrNull(map.remove(TileArchiveMetadata.MINZOOM_KEY)),
Parse.parseIntOrNull(map.remove(TileArchiveMetadata.MAXZOOM_KEY)),
metadataJson == null ? null : metadataJson.vectorLayers,
// any left-overs:
map
);
}
} }
} }

Wyświetl plik

@ -189,7 +189,7 @@ public class Verify {
} }
private void checkBasicStructure() { private void checkBasicStructure() {
check("contains name attribute", () -> mbtiles.metadata().getAll().containsKey("name")); check("contains name attribute", () -> mbtiles.metadata().toMap().containsKey("name"));
check("contains at least one tile", () -> mbtiles.getAllTileCoords().stream().findAny().isPresent()); check("contains at least one tile", () -> mbtiles.getAllTileCoords().stream().findAny().isPresent());
checkWithMessage("all tiles are valid", () -> { checkWithMessage("all tiles are valid", () -> {
List<String> invalidTiles = mbtiles.getAllTileCoords().stream() List<String> invalidTiles = mbtiles.getAllTileCoords().stream()

Wyświetl plik

@ -23,6 +23,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
public class Pmtiles { public class Pmtiles {
public enum Compression { public enum Compression {
@ -201,6 +203,22 @@ public class Pmtiles {
throw new FileFormatException("Failed to read enough bytes for PMTiles header."); throw new FileFormatException("Failed to read enough bytes for PMTiles header.");
} }
} }
public Envelope bounds() {
return new Envelope(
minLonE7 / 1e7,
maxLonE7 / 1e7,
minLatE7 / 1e7,
maxLatE7 / 1e7
);
}
public CoordinateXY center() {
return new CoordinateXY(
centerLonE7 / 1e7,
centerLatE7 / 1e7
);
}
} }
public static final class Entry implements Comparable<Entry> { public static final class Entry implements Comparable<Entry> {
@ -366,7 +384,7 @@ public class Pmtiles {
try { try {
return objectMapper.readValue(bytes, JsonMetadata.class); return objectMapper.readValue(bytes, JsonMetadata.class);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Invalid metadata json: " + bytes, e); throw new IllegalStateException("Invalid metadata json: " + new String(bytes, StandardCharsets.UTF_8), e);
} }
} }
} }

Wyświetl plik

@ -1,13 +1,19 @@
package com.onthegomap.planetiler.pmtiles; package com.onthegomap.planetiler.pmtiles;
import com.onthegomap.planetiler.archive.ReadableTileArchive; import com.onthegomap.planetiler.archive.ReadableTileArchive;
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
import com.onthegomap.planetiler.geo.TileCoord; import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.util.CloseableIterator; import com.onthegomap.planetiler.util.CloseableIterator;
import com.onthegomap.planetiler.util.Gzip; import com.onthegomap.planetiler.util.Gzip;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel; import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -22,6 +28,10 @@ public class ReadablePmtiles implements ReadableTileArchive {
this.header = Pmtiles.Header.fromBytes(getBytes(0, Pmtiles.HEADER_LEN)); this.header = Pmtiles.Header.fromBytes(getBytes(0, Pmtiles.HEADER_LEN));
} }
public static ReadableTileArchive newReadFromFile(Path path) throws IOException {
return new ReadablePmtiles(FileChannel.open(path, StandardOpenOption.READ));
}
private synchronized byte[] getBytes(long start, int length) throws IOException { private synchronized byte[] getBytes(long start, int length) throws IOException {
channel.position(start); channel.position(start);
var buf = ByteBuffer.allocate(length); var buf = ByteBuffer.allocate(length);
@ -103,6 +113,34 @@ public class ReadablePmtiles implements ReadableTileArchive {
return Pmtiles.JsonMetadata.fromBytes(buf); return Pmtiles.JsonMetadata.fromBytes(buf);
} }
@Override
public TileArchiveMetadata metadata() {
try {
var jsonMetadata = getJsonMetadata();
var map = new LinkedHashMap<>(jsonMetadata.otherMetadata());
return new TileArchiveMetadata(
map.remove(TileArchiveMetadata.NAME_KEY),
map.remove(TileArchiveMetadata.DESCRIPTION_KEY),
map.remove(TileArchiveMetadata.ATTRIBUTION_KEY),
map.remove(TileArchiveMetadata.VERSION_KEY),
map.remove(TileArchiveMetadata.TYPE_KEY),
switch (header.tileType()) {
case MVT -> TileArchiveMetadata.MVT_FORMAT;
default -> null;
},
header.bounds(),
header.center(),
(double) header.centerZoom(),
(int) header.minZoom(),
(int) header.maxZoom(),
jsonMetadata.vectorLayers(),
map
);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static class TileCoordIterator implements CloseableIterator<TileCoord> { private static class TileCoordIterator implements CloseableIterator<TileCoord> {
private final Stream<TileCoord> stream; private final Stream<TileCoord> stream;
private final Iterator<TileCoord> iterator; private final Iterator<TileCoord> iterator;

Wyświetl plik

@ -12,7 +12,6 @@ import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.geo.TileOrder; import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.util.Format; import com.onthegomap.planetiler.util.Format;
import com.onthegomap.planetiler.util.Gzip; import com.onthegomap.planetiler.util.Gzip;
import com.onthegomap.planetiler.util.LayerStats;
import com.onthegomap.planetiler.util.SeekableInMemoryByteChannel; import com.onthegomap.planetiler.util.SeekableInMemoryByteChannel;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
@ -24,10 +23,10 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.OptionalLong; import java.util.OptionalLong;
import org.locationtech.jts.geom.Envelope;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -46,8 +45,6 @@ public final class WriteablePmtiles implements WriteableTileArchive {
private long currentOffset = 0; private long currentOffset = 0;
private long numUnhashedTiles = 0; private long numUnhashedTiles = 0;
private long numAddressedTiles = 0; private long numAddressedTiles = 0;
private LayerStats layerStats;
private TileArchiveMetadata tileArchiveMetadata;
private boolean isClustered = true; private boolean isClustered = true;
private WriteablePmtiles(SeekableByteChannel channel) throws IOException { private WriteablePmtiles(SeekableByteChannel channel) throws IOException {
@ -91,7 +88,7 @@ public final class WriteablePmtiles implements WriteableTileArchive {
* @return byte arrays of the root and all leaf directories, and the # of leaves. * @return byte arrays of the root and all leaf directories, and the # of leaves.
* @throws IOException if compression fails * @throws IOException if compression fails
*/ */
protected static Directories makeDirectories(List<Pmtiles.Entry> entries) throws IOException { static Directories makeDirectories(List<Pmtiles.Entry> entries) throws IOException {
int maxEntriesRootOnly = 16384; int maxEntriesRootOnly = 16384;
int attemptNum = 1; int attemptNum = 1;
if (entries.size() < maxEntriesRootOnly) { if (entries.size() < maxEntriesRootOnly) {
@ -124,19 +121,18 @@ public final class WriteablePmtiles implements WriteableTileArchive {
return new WriteablePmtiles(bytes); return new WriteablePmtiles(bytes);
} }
@Override
public boolean deduplicates() {
return true;
}
@Override @Override
public TileOrder tileOrder() { public TileOrder tileOrder() {
return TileOrder.HILBERT; return TileOrder.HILBERT;
} }
@Override @Override
public void initialize(PlanetilerConfig config, TileArchiveMetadata tileArchiveMetadata, LayerStats layerStats) { public void finish(TileArchiveMetadata tileArchiveMetadata) {
this.layerStats = layerStats;
this.tileArchiveMetadata = tileArchiveMetadata;
}
@Override
public void finish(PlanetilerConfig config) {
if (!isClustered) { if (!isClustered) {
LOGGER.info("Tile data was not written in order, sorting entries..."); LOGGER.info("Tile data was not written in order, sorting entries...");
Collections.sort(entries); Collections.sort(entries);
@ -144,10 +140,32 @@ public final class WriteablePmtiles implements WriteableTileArchive {
} }
try { try {
Directories directories = makeDirectories(entries); Directories directories = makeDirectories(entries);
byte[] jsonBytes = new Pmtiles.JsonMetadata(layerStats.getTileStats(), tileArchiveMetadata.getAll()).toBytes(); var otherMetadata = new LinkedHashMap<>(tileArchiveMetadata.toMap());
// exclude keys included in top-level header
otherMetadata.remove(TileArchiveMetadata.CENTER_KEY);
otherMetadata.remove(TileArchiveMetadata.ZOOM_KEY);
otherMetadata.remove(TileArchiveMetadata.BOUNDS_KEY);
otherMetadata.remove(TileArchiveMetadata.FORMAT_KEY);
otherMetadata.remove(TileArchiveMetadata.MINZOOM_KEY);
otherMetadata.remove(TileArchiveMetadata.MAXZOOM_KEY);
otherMetadata.remove(TileArchiveMetadata.VECTOR_LAYERS_KEY);
byte[] jsonBytes =
new Pmtiles.JsonMetadata(tileArchiveMetadata.vectorLayers(), otherMetadata).toBytes();
jsonBytes = Gzip.gzip(jsonBytes); jsonBytes = Gzip.gzip(jsonBytes);
Envelope envelope = config.bounds().latLon(); String formatString = tileArchiveMetadata.format();
var outputFormat =
TileArchiveMetadata.MVT_FORMAT.equals(formatString) ? Pmtiles.TileType.MVT : Pmtiles.TileType.UNKNOWN;
var bounds = tileArchiveMetadata.bounds() == null ? GeoUtils.WORLD_LAT_LON_BOUNDS : tileArchiveMetadata.bounds();
var center = tileArchiveMetadata.center() == null ? bounds.centre() : tileArchiveMetadata.center();
int zoom = (int) Math.ceil(tileArchiveMetadata.zoom() == null ? GeoUtils.getZoomFromLonLatBounds(bounds) :
tileArchiveMetadata.zoom());
int minzoom = tileArchiveMetadata.minzoom() == null ? 0 : tileArchiveMetadata.minzoom();
int maxzoom =
tileArchiveMetadata.maxzoom() == null ? PlanetilerConfig.MAX_MAXZOOM : tileArchiveMetadata.maxzoom();
Pmtiles.Header header = new Pmtiles.Header( Pmtiles.Header header = new Pmtiles.Header(
(byte) 3, (byte) 3,
@ -165,16 +183,16 @@ public final class WriteablePmtiles implements WriteableTileArchive {
isClustered, isClustered,
Pmtiles.Compression.GZIP, Pmtiles.Compression.GZIP,
Pmtiles.Compression.GZIP, Pmtiles.Compression.GZIP,
Pmtiles.TileType.MVT, outputFormat,
(byte) config.minzoom(), (byte) minzoom,
(byte) config.maxzoom(), (byte) maxzoom,
(int) (envelope.getMinX() * 10_000_000), (int) (bounds.getMinX() * 10_000_000),
(int) (envelope.getMinY() * 10_000_000), (int) (bounds.getMinY() * 10_000_000),
(int) (envelope.getMaxX() * 10_000_000), (int) (bounds.getMaxX() * 10_000_000),
(int) (envelope.getMaxY() * 10_000_000), (int) (bounds.getMaxY() * 10_000_000),
(byte) Math.ceil(GeoUtils.getZoomFromLonLatBounds(envelope)), (byte) zoom,
(int) ((envelope.getMinX() + envelope.getMaxX()) / 2 * 10_000_000), (int) center.x * 10_000_000,
(int) ((envelope.getMinY() + envelope.getMaxY()) / 2 * 10_000_000) (int) center.y * 10_000_000
); );
LOGGER.info("Writing metadata and leaf directories..."); LOGGER.info("Writing metadata and leaf directories...");

Wyświetl plik

@ -43,15 +43,17 @@ public interface Stats extends AutoCloseable {
*/ */
default void printSummary() { default void printSummary() {
Format format = Format.defaultInstance(); Format format = Format.defaultInstance();
Logger LOGGER = LoggerFactory.getLogger(getClass()); Logger logger = LoggerFactory.getLogger(getClass());
LOGGER.info(""); if (logger.isInfoEnabled()) {
LOGGER.info("-".repeat(40)); logger.info("");
timers().printSummary(); logger.info("-".repeat(40));
LOGGER.info("-".repeat(40)); timers().printSummary();
for (var entry : monitoredFiles().entrySet()) { logger.info("-".repeat(40));
long size = FileUtils.size(entry.getValue()); for (var entry : monitoredFiles().entrySet()) {
if (size > 0) { long size = FileUtils.size(entry.getValue());
LOGGER.info("\t" + entry.getKey() + "\t" + format.storage(size, false) + "B"); if (size > 0) {
logger.info("\t{}\t{}B", entry.getKey(), format.storage(size, false));
}
} }
} }
} }
@ -110,7 +112,9 @@ public interface Stats extends AutoCloseable {
/** Adds a stat that will track the size of a file or directory located at {@code path}. */ /** Adds a stat that will track the size of a file or directory located at {@code path}. */
default void monitorFile(String name, Path path) { default void monitorFile(String name, Path path) {
monitoredFiles().put(name, path); if (path != null) {
monitoredFiles().put(name, path);
}
} }
/** Adds a stat that will track the estimated in-memory size of {@code object}. */ /** Adds a stat that will track the estimated in-memory size of {@code object}. */

Wyświetl plik

@ -217,17 +217,19 @@ public class FileUtils {
*/ */
public static void createParentDirectories(Path... paths) { public static void createParentDirectories(Path... paths) {
for (var path : paths) { for (var path : paths) {
try { if (path != null) {
if (Files.isDirectory(path) && !Files.exists(path)) { try {
Files.createDirectories(path); if (Files.isDirectory(path) && !Files.exists(path)) {
} else { Files.createDirectories(path);
Path parent = path.getParent(); } else {
if (parent != null && !Files.exists(parent)) { Path parent = path.getParent();
Files.createDirectories(parent); if (parent != null && !Files.exists(parent)) {
Files.createDirectories(parent);
}
} }
} catch (IOException e) {
throw new IllegalStateException("Unable to create parent directories " + path, e);
} }
} catch (IOException e) {
throw new IllegalStateException("Unable to create parent directories " + path, e);
} }
} }
} }

Wyświetl plik

@ -8,6 +8,8 @@ import java.util.NavigableMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.text.StringEscapeUtils;
import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Coordinate;
@ -19,6 +21,26 @@ public class Format {
public static final Locale DEFAULT_LOCALE = Locale.getDefault(Locale.Category.FORMAT); public static final Locale DEFAULT_LOCALE = Locale.getDefault(Locale.Category.FORMAT);
private static final ConcurrentMap<Locale, Format> instances = new ConcurrentHashMap<>(); private static final ConcurrentMap<Locale, Format> instances = new ConcurrentHashMap<>();
@SuppressWarnings("java:S5164")
private static final NumberFormat latLonNF = NumberFormat.getNumberInstance(Locale.US);
private static final NavigableMap<Long, String> STORAGE_SUFFIXES = new TreeMap<>(Map.ofEntries(
Map.entry(1_000L, "k"),
Map.entry(1_000_000L, "M"),
Map.entry(1_000_000_000L, "G"),
Map.entry(1_000_000_000_000L, "T"),
Map.entry(1_000_000_000_000_000L, "P")
));
private static final NavigableMap<Long, String> NUMERIC_SUFFIXES = new TreeMap<>(Map.ofEntries(
Map.entry(1_000L, "k"),
Map.entry(1_000_000L, "M"),
Map.entry(1_000_000_000L, "B"),
Map.entry(1_000_000_000_000L, "T"),
Map.entry(1_000_000_000_000_000L, "Q")
));
static {
latLonNF.setMaximumFractionDigits(5);
}
// `NumberFormat` instances are not thread safe, so we need to wrap them inside a `ThreadLocal`. // `NumberFormat` instances are not thread safe, so we need to wrap them inside a `ThreadLocal`.
// //
@ -49,6 +71,11 @@ public class Format {
}); });
} }
/** Returns a string with {@code items} rounded to 5 decimals and joined with a comma. */
public static synchronized String joinCoordinates(double... items) {
return DoubleStream.of(items).mapToObj(latLonNF::format).collect(Collectors.joining(","));
}
public static Format forLocale(Locale locale) { public static Format forLocale(Locale locale) {
return instances.computeIfAbsent(locale, Format::new); return instances.computeIfAbsent(locale, Format::new);
} }
@ -57,21 +84,6 @@ public class Format {
return forLocale(DEFAULT_LOCALE); return forLocale(DEFAULT_LOCALE);
} }
private static final NavigableMap<Long, String> STORAGE_SUFFIXES = new TreeMap<>(Map.ofEntries(
Map.entry(1_000L, "k"),
Map.entry(1_000_000L, "M"),
Map.entry(1_000_000_000L, "G"),
Map.entry(1_000_000_000_000L, "T"),
Map.entry(1_000_000_000_000_000L, "P")
));
private static final NavigableMap<Long, String> NUMERIC_SUFFIXES = new TreeMap<>(Map.ofEntries(
Map.entry(1_000L, "k"),
Map.entry(1_000_000L, "M"),
Map.entry(1_000_000_000L, "B"),
Map.entry(1_000_000_000_000L, "T"),
Map.entry(1_000_000_000_000_000L, "Q")
));
public static String padRight(String str, int size) { public static String padRight(String str, int size) {
StringBuilder strBuilder = new StringBuilder(str); StringBuilder strBuilder = new StringBuilder(str);
while (strBuilder.length() < size) { while (strBuilder.length() < size) {
@ -88,6 +100,23 @@ public class Format {
return strBuilder.toString(); return strBuilder.toString();
} }
/** Returns Java code that can re-create {@code string}: {@code null} if null, or {@code "contents"} if not empty. */
public static String quote(Object string) {
if (string == null) {
return "null";
}
return '"' + StringEscapeUtils.escapeJava(string.toString()) + '"';
}
/** Returns an openstreetmap.org map link for a lat/lon */
public static String osmDebugUrl(int zoom, Coordinate coord) {
return "https://www.openstreetmap.org/#map=%d/%.5f/%.5f".formatted(
zoom,
coord.y,
coord.x
);
}
/** Returns a number of bytes formatted like "123" "1.2k" "240M", etc. */ /** Returns a number of bytes formatted like "123" "1.2k" "240M", etc. */
public String storage(Number num, boolean pad) { public String storage(Number num, boolean pad) {
return format(num, pad, STORAGE_SUFFIXES); return format(num, pad, STORAGE_SUFFIXES);
@ -161,21 +190,4 @@ public class Format {
} }
return simplified.toString().replace("PT", "").toLowerCase(Locale.ROOT); return simplified.toString().replace("PT", "").toLowerCase(Locale.ROOT);
} }
/** Returns Java code that can re-create {@code string}: {@code null} if null, or {@code "contents"} if not empty. */
public static String quote(Object string) {
if (string == null) {
return "null";
}
return '"' + StringEscapeUtils.escapeJava(string.toString()) + '"';
}
/** Returns an openstreetmap.org map link for a lat/lon */
public static String osmDebugUrl(int zoom, Coordinate coord) {
return "https://www.openstreetmap.org/#map=%d/%.5f/%.5f".formatted(
zoom,
coord.y,
coord.x
);
}
} }

Wyświetl plik

@ -70,7 +70,7 @@ public class ResourceUsage {
/** Requests {@code amount} bytes on the file system that contains {@code path}. */ /** Requests {@code amount} bytes on the file system that contains {@code path}. */
public ResourceUsage addDisk(Path path, long amount, String description) { public ResourceUsage addDisk(Path path, long amount, String description) {
return add(new DiskUsage(path), amount, description); return path == null ? this : add(new DiskUsage(path), amount, description);
} }
/** Requests {@code amount} bytes of RAM in the JVM heap. */ /** Requests {@code amount} bytes of RAM in the JVM heap. */

Wyświetl plik

@ -15,6 +15,7 @@ import com.onthegomap.planetiler.geo.GeometryException;
import com.onthegomap.planetiler.geo.TileCoord; import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.geo.TileOrder; import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.mbtiles.Mbtiles; import com.onthegomap.planetiler.mbtiles.Mbtiles;
import com.onthegomap.planetiler.pmtiles.ReadablePmtiles;
import com.onthegomap.planetiler.reader.SimpleFeature; import com.onthegomap.planetiler.reader.SimpleFeature;
import com.onthegomap.planetiler.reader.SimpleReader; import com.onthegomap.planetiler.reader.SimpleReader;
import com.onthegomap.planetiler.reader.SourceFeature; import com.onthegomap.planetiler.reader.SourceFeature;
@ -141,14 +142,14 @@ class PlanetilerTests {
FeatureGroup featureGroup = FeatureGroup.newInMemoryFeatureGroup(TileOrder.TMS, profile, stats); FeatureGroup featureGroup = FeatureGroup.newInMemoryFeatureGroup(TileOrder.TMS, profile, stats);
runner.run(featureGroup, profile, config); runner.run(featureGroup, profile, config);
featureGroup.prepare(); featureGroup.prepare();
try (Mbtiles db = Mbtiles.newInMemoryDatabase(config.compactDb())) { try (Mbtiles db = Mbtiles.newInMemoryDatabase(config.arguments())) {
TileArchiveWriter.writeOutput(featureGroup, db, () -> 0L, new TileArchiveMetadata(profile, config.arguments()), TileArchiveWriter.writeOutput(featureGroup, db, () -> 0L, new TileArchiveMetadata(profile, config),
config, config,
stats); stats);
var tileMap = TestUtils.getTileMap(db); var tileMap = TestUtils.getTileMap(db);
tileMap.values().forEach(fs -> fs.forEach(f -> f.geometry().validate())); tileMap.values().forEach(fs -> fs.forEach(f -> f.geometry().validate()));
int tileDataCount = config.compactDb() ? TestUtils.getTilesDataCount(db) : 0; int tileDataCount = db.compactDb() ? TestUtils.getTilesDataCount(db) : 0;
return new PlanetilerResults(tileMap, db.metadata().getAll(), tileDataCount); return new PlanetilerResults(tileMap, db.metadata().toMap(), tileDataCount);
} }
} }
@ -248,20 +249,15 @@ class PlanetilerTests {
"format", "pbf", "format", "pbf",
"minzoom", "0", "minzoom", "0",
"maxzoom", "14", "maxzoom", "14",
"center", "0,0,0", "center", "0,0",
"bounds", "-180,-85.05113,180,85.05113" "bounds", "-180,-85.05113,180,85.05113"
), results.metadata); ), results.metadata);
assertSubmap(Map.of( assertSubmap(Map.of(
"planetiler:version", BuildInfo.get().version() "planetiler:version", BuildInfo.get().version()
), results.metadata); ), results.metadata);
assertSameJson( assertSameJson(
""" "[]",
{ results.metadata.get("vector_layers")
"vector_layers": [
]
}
""",
results.metadata.get("json")
); );
} }
@ -269,11 +265,11 @@ class PlanetilerTests {
void testOverrideMetadata() throws Exception { void testOverrideMetadata() throws Exception {
var results = runWithReaderFeatures( var results = runWithReaderFeatures(
Map.of( Map.of(
"mbtiles_name", "override_name", "archive_name", "override_name",
"mbtiles_description", "override_description", "archive_description", "override_description",
"mbtiles_attribution", "override_attribution", "archive_attribution", "override_attribution",
"mbtiles_version", "override_version", "archive_version", "override_version",
"mbtiles_type", "override_type" "archive_type", "override_type"
), ),
List.of(), List.of(),
(sourceFeature, features) -> { (sourceFeature, features) -> {
@ -331,13 +327,11 @@ class PlanetilerTests {
), results.tiles); ), results.tiles);
assertSameJson( assertSameJson(
""" """
{ [
"vector_layers": [ {"id": "layer", "fields": {"name": "String", "attr": "String"}, "minzoom": 13, "maxzoom": 15}
{"id": "layer", "fields": {"name": "String", "attr": "String"}, "minzoom": 13, "maxzoom": 15} ]
]
}
""", """,
results.metadata.get("json") results.metadata.get("vector_layers")
); );
} }
@ -1686,13 +1680,14 @@ class PlanetilerTests {
@ValueSource(strings = { @ValueSource(strings = {
"", "",
"--write-threads=2 --process-threads=2 --feature-read-threads=2 --threads=4", "--write-threads=2 --process-threads=2 --feature-read-threads=2 --threads=4",
"--emit-tiles-in-order=false",
"--free-osm-after-read", "--free-osm-after-read",
"--osm-parse-node-bounds", "--osm-parse-node-bounds",
"--output-format=pmtiles"
}) })
void testPlanetilerRunner(String args) throws Exception { void testPlanetilerRunner(String args) throws Exception {
boolean pmtiles = args.contains("pmtiles");
Path originalOsm = TestUtils.pathToResource("monaco-latest.osm.pbf"); Path originalOsm = TestUtils.pathToResource("monaco-latest.osm.pbf");
Path mbtiles = tempDir.resolve("output.mbtiles"); Path output = tempDir.resolve(pmtiles ? "output.pmtiles" : "output.mbtiles");
Path tempOsm = tempDir.resolve("monaco-temp.osm.pbf"); Path tempOsm = tempDir.resolve("monaco-temp.osm.pbf");
Files.copy(originalOsm, tempOsm); Files.copy(originalOsm, tempOsm);
Planetiler.create(Arguments.fromArgs( Planetiler.create(Arguments.fromArgs(
@ -1710,7 +1705,7 @@ class PlanetilerTests {
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite")) .addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip")) .addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null) .addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
.setOutput("mbtiles", mbtiles) .setOutput(output)
.run(); .run();
// make sure it got deleted after write // make sure it got deleted after write
@ -1718,7 +1713,9 @@ class PlanetilerTests {
assertFalse(Files.exists(tempOsm)); assertFalse(Files.exists(tempOsm));
} }
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) { try (
var db = pmtiles ? ReadablePmtiles.newReadFromFile(output) : Mbtiles.newReadOnlyDatabase(output)
) {
int features = 0; int features = 0;
var tileMap = TestUtils.getTileMap(db); var tileMap = TestUtils.getTileMap(db);
for (var tile : tileMap.values()) { for (var tile : tileMap.values()) {
@ -1735,7 +1732,7 @@ class PlanetilerTests {
"planetiler:osm:osmosisreplicationtime", "2021-04-21T20:21:46Z", "planetiler:osm:osmosisreplicationtime", "2021-04-21T20:21:46Z",
"planetiler:osm:osmosisreplicationseq", "2947", "planetiler:osm:osmosisreplicationseq", "2947",
"planetiler:osm:osmosisreplicationurl", "http://download.geofabrik.de/europe/monaco-updates" "planetiler:osm:osmosisreplicationurl", "http://download.geofabrik.de/europe/monaco-updates"
), db.metadata().getAll()); ), db.metadata().toMap());
} }
} }
@ -1760,7 +1757,7 @@ class PlanetilerTests {
.addShapefileGlobSource("shapefile-glob-zip", resourceDir.resolve("shapefile.zip"), "*.shp") .addShapefileGlobSource("shapefile-glob-zip", resourceDir.resolve("shapefile.zip"), "*.shp")
// Match *.shp within shapefile.zip // Match *.shp within shapefile.zip
.addShapefileSource("shapefile", resourceDir.resolve("shapefile.zip")) .addShapefileSource("shapefile", resourceDir.resolve("shapefile.zip"))
.setOutput("mbtiles", mbtiles) .setOutput(mbtiles)
.run(); .run();
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) { try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) {
@ -1806,7 +1803,7 @@ class PlanetilerTests {
} }
}) })
.addGeoPackageSource("geopackage", TestUtils.pathToResource(inputFile), null) .addGeoPackageSource("geopackage", TestUtils.pathToResource(inputFile), null)
.setOutput("mbtiles", mbtiles) .setOutput(mbtiles)
.run(); .run();
try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) { try (Mbtiles db = Mbtiles.newReadOnlyDatabase(mbtiles)) {
@ -1834,7 +1831,7 @@ class PlanetilerTests {
.addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite")) .addNaturalEarthSource("ne", TestUtils.pathToResource("natural_earth_vector.sqlite"))
.addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip")) .addShapefileSource("shapefile", TestUtils.pathToResource("shapefile.zip"))
.addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null) .addGeoPackageSource("geopackage", TestUtils.pathToResource("geopackage.gpkg.zip"), null)
.setOutput("mbtiles", tempDir.resolve("output.mbtiles")) .setOutput(tempDir.resolve("output.mbtiles"))
.run(); .run();
} }
@ -1909,9 +1906,8 @@ class PlanetilerTests {
private PlanetilerResults runForCompactTest(boolean compactDbEnabled) throws Exception { private PlanetilerResults runForCompactTest(boolean compactDbEnabled) throws Exception {
return runWithReaderFeatures( return runWithReaderFeatures(
Map.of("threads", "1", "compact-db", Boolean.toString(compactDbEnabled)), Map.of("threads", "1", "mbtiles-compact", Boolean.toString(compactDbEnabled)),
List.of( List.of(
newReaderFeature(WORLD_POLYGON, Map.of()) newReaderFeature(WORLD_POLYGON, Map.of())
), ),

Wyświetl plik

@ -15,6 +15,7 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.onthegomap.planetiler.archive.ReadableTileArchive;
import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.geo.GeoUtils;
import com.onthegomap.planetiler.geo.GeometryException; import com.onthegomap.planetiler.geo.GeometryException;
@ -199,7 +200,8 @@ public class TestUtils {
return round(input, 1e5); return round(input, 1e5);
} }
public static Map<TileCoord, List<ComparableFeature>> getTileMap(Mbtiles db) throws SQLException, IOException { public static Map<TileCoord, List<ComparableFeature>> getTileMap(ReadableTileArchive db)
throws IOException {
Map<TileCoord, List<ComparableFeature>> tiles = new TreeMap<>(); Map<TileCoord, List<ComparableFeature>> tiles = new TreeMap<>();
for (var tile : getAllTiles(db)) { for (var tile : getAllTiles(db)) {
var bytes = gunzip(tile.bytes()); var bytes = gunzip(tile.bytes());
@ -218,21 +220,10 @@ public class TestUtils {
} }
} }
public static Set<Mbtiles.TileEntry> getAllTiles(Mbtiles db) throws SQLException { public static Set<Mbtiles.TileEntry> getAllTiles(ReadableTileArchive db) {
Set<Mbtiles.TileEntry> result = new HashSet<>(); return db.getAllTileCoords().stream()
try (Statement statement = db.connection().createStatement()) { .map(coord -> new Mbtiles.TileEntry(coord, db.getTile(coord)))
ResultSet rs = statement.executeQuery("select zoom_level, tile_column, tile_row, tile_data from tiles"); .collect(Collectors.toSet());
while (rs.next()) {
int z = rs.getInt("zoom_level");
int rawy = rs.getInt("tile_row");
int x = rs.getInt("tile_column");
result.add(new Mbtiles.TileEntry(
TileCoord.ofXYZ(x, (1 << z) - 1 - rawy, z),
rs.getBytes("tile_data")
));
}
}
return result;
} }
public static int getTilesDataCount(Mbtiles db) throws SQLException { public static int getTilesDataCount(Mbtiles db) throws SQLException {

Wyświetl plik

@ -0,0 +1,36 @@
package com.onthegomap.planetiler.archive;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.Test;
class TileArchiveConfigTest {
@Test
void testMbtiles() {
var config = TileArchiveConfig.from("output.mbtiles");
assertEquals(TileArchiveConfig.Format.MBTILES, config.format());
assertEquals(TileArchiveConfig.Scheme.FILE, config.scheme());
assertEquals(Map.of(), config.options());
assertEquals(Path.of("output.mbtiles").toAbsolutePath(), config.getLocalPath());
}
@Test
void testMbtilesWithOptions() {
var config = TileArchiveConfig.from("output.mbtiles?compact=true");
assertEquals(TileArchiveConfig.Format.MBTILES, config.format());
assertEquals(TileArchiveConfig.Scheme.FILE, config.scheme());
assertEquals(Map.of("compact", "true"), config.options());
assertEquals(Path.of("output.mbtiles").toAbsolutePath(), config.getLocalPath());
}
@Test
void testPmtiles() {
assertEquals(TileArchiveConfig.Format.PMTILES, TileArchiveConfig.from("output.pmtiles").format());
assertEquals(TileArchiveConfig.Format.PMTILES, TileArchiveConfig.from("output.mbtiles?format=pmtiles").format());
assertEquals(TileArchiveConfig.Format.PMTILES,
TileArchiveConfig.from("file:///output.mbtiles?format=pmtiles").format());
}
}

Wyświetl plik

@ -0,0 +1,67 @@
package com.onthegomap.planetiler.archive;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.geo.GeoUtils;
import java.util.Map;
import java.util.TreeMap;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
class TileArchiveMetadataTest {
@Test
void testAddMetadataWorldBounds() {
var bounds = GeoUtils.WORLD_LAT_LON_BOUNDS;
var metadata = new TileArchiveMetadata(new Profile.NullProfile(), PlanetilerConfig.from(Arguments.of(Map.of(
"bounds", bounds.getMinX() + "," + bounds.getMinY() + "," + bounds.getMaxX() + "," + bounds.getMaxY()
))));
assertEquals(bounds, metadata.bounds());
assertEquals(new CoordinateXY(0, 0), metadata.center());
assertEquals(0d, metadata.zoom().doubleValue());
}
@Test
void testAddMetadataSmallBounds() {
var bounds = new Envelope(-73.6632, -69.7598, 41.1274, 43.0185);
var metadata = new TileArchiveMetadata(new Profile.NullProfile(), PlanetilerConfig.from(Arguments.of(Map.of(
"bounds", "-73.6632,41.1274,-69.7598,43.0185"
))));
assertEquals(bounds, metadata.bounds());
assertEquals(-71.7115, metadata.center().x, 1e-5);
assertEquals(42.07295, metadata.center().y, 1e-5);
assertEquals(7, Math.ceil(metadata.zoom()));
}
@Test
void testToMap() {
var bounds = "-73.6632,41.1274,-69.7598,43.0185";
var metadata = new TileArchiveMetadata(
new Profile.NullProfile(),
PlanetilerConfig.from(Arguments.of(Map.of(
"bounds", bounds
))));
var map = new TreeMap<>(metadata.toMap());
assertNotNull(map.remove("planetiler:version"));
assertNotNull(map.remove("planetiler:githash"));
assertNotNull(map.remove("planetiler:buildtime"));
assertEquals(
new TreeMap<String, String>(Map.of(
"name", "Null",
"type", "baselayer",
"format", "pbf",
"zoom", "6.5271217861412305",
"minzoom", "0",
"maxzoom", "14",
"bounds", "-73.6632,41.1274,-69.7598,43.0185",
"center", "-71.7115,42.07295"
)),
map
);
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Envelope;
@ -293,4 +294,59 @@ class ArgumentsTest {
assertEquals(false, args.getBooleanObject("BOOL_FALSE", "test")); assertEquals(false, args.getBooleanObject("BOOL_FALSE", "test"));
assertEquals(false, args.getBooleanObject("BOOL_NO", "test")); assertEquals(false, args.getBooleanObject("BOOL_NO", "test"));
} }
@Test
void testDeprecatedArgs() {
assertEquals("newvalue",
Arguments.of("oldkey", "oldvalue", "newkey", "newvalue")
.getString("newkey|oldkey", "key", "fallback"));
assertEquals("oldvalue",
Arguments.of("oldkey", "oldvalue")
.getString("newkey|oldkey", "key", "fallback"));
assertEquals("fallback",
Arguments.of()
.getString("newkey|oldkey", "key", "fallback"));
}
@Test
void testWithPrefix() {
var args = Arguments.of("prefix_a", "a_val", "prefix-b", "b_val", "other", "other_val").withPrefix("prefix");
assertEquals("a_val", args.getArg("a"));
assertEquals("b_val", args.getArg("b"));
assertNull(args.getArg("other"));
assertNull(args.getArg("prefix_a"));
assertNull(args.getArg("prefix_b"));
assertNull(args.getArg("prefix_other"));
assertEquals(Set.of("a", "b"), args.toMap().keySet());
}
@Test
void testPrefixFromEnvironment() {
Map<String, String> env = Map.of(
"OTHER", "value",
"PLANETILEROTHER", "VALUE",
"PLANETILER_MBTILES_KEY1", "value1",
"PLANETILER_PMTILES_KEY2", "value2"
);
Arguments args = Arguments.fromEnvironment(env::get, env::keySet).withPrefix("mbtiles");
assertEquals(Map.of(
"key1", "value1"
), args.toMap());
assertEquals("value1", args.getArg("key1"));
}
@Test
void testSubset() {
var args = Arguments.of(Map.of(
"key_1", "val_1",
"key-2", "val_2",
"key-3", "val_3"
)).subset("key-1", "key-2");
assertEquals(Map.of(
"key_1", "val_1",
"key_2", "val_2"
), args.toMap());
assertEquals("val_1", args.getArg("key-1"));
assertNull(args.getArg("key-3"));
}
} }

Wyświetl plik

@ -5,24 +5,27 @@ import static org.junit.jupiter.api.Assertions.*;
import com.google.common.math.IntMath; import com.google.common.math.IntMath;
import com.onthegomap.planetiler.TestUtils; import com.onthegomap.planetiler.TestUtils;
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
import com.onthegomap.planetiler.archive.TileEncodingResult; import com.onthegomap.planetiler.archive.TileEncodingResult;
import com.onthegomap.planetiler.geo.GeoUtils; import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.geo.TileCoord; import com.onthegomap.planetiler.geo.TileCoord;
import com.onthegomap.planetiler.util.LayerStats; import com.onthegomap.planetiler.util.LayerStats;
import java.io.IOException; import java.io.IOException;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.nio.file.Path;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.OptionalLong; import java.util.OptionalLong;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Envelope;
class MbtilesTest { class MbtilesTest {
@ -33,11 +36,12 @@ class MbtilesTest {
private static final int TILES_DATA_BATCH = MAX_PARAMETERS_IN_PREPARED_STATEMENT / 2; private static final int TILES_DATA_BATCH = MAX_PARAMETERS_IN_PREPARED_STATEMENT / 2;
private static final private static void testWriteTiles(Path path, int howMany, boolean skipIndexCreation, boolean optimize,
boolean compactDb) throws IOException, SQLException {
void testWriteTiles(int howMany, boolean skipIndexCreation, boolean optimize, boolean compactDb) var options = Arguments.of("compact", Boolean.toString(compactDb));
throws IOException, SQLException { try (
try (Mbtiles db = Mbtiles.newInMemoryDatabase(compactDb)) { Mbtiles db = path == null ? Mbtiles.newInMemoryDatabase(options) : Mbtiles.newWriteToFileDatabase(path, options)
) {
if (skipIndexCreation) { if (skipIndexCreation) {
db.createTablesWithoutIndexes(); db.createTablesWithoutIndexes();
} else { } else {
@ -84,24 +88,42 @@ class MbtilesTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(ints = {0, 1, TILES_BATCH, TILES_BATCH + 1, 2 * TILES_BATCH, 2 * TILES_BATCH + 1}) @ValueSource(ints = {0, 1, TILES_BATCH, TILES_BATCH + 1, 2 * TILES_BATCH, 2 * TILES_BATCH + 1})
void testWriteTilesDifferentSizeInNonCompactMode(int howMany) throws IOException, SQLException { void testWriteTilesDifferentSizeInNonCompactMode(int howMany) throws IOException, SQLException {
testWriteTiles(howMany, false, false, false); testWriteTiles(null, howMany, false, false, false);
} }
@ParameterizedTest @ParameterizedTest
@ValueSource(ints = {0, 1, TILES_DATA_BATCH, TILES_DATA_BATCH + 1, 2 * TILES_DATA_BATCH, 2 * TILES_DATA_BATCH + 1, @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}) TILES_SHALLOW_BATCH, TILES_SHALLOW_BATCH + 1, 2 * TILES_SHALLOW_BATCH, 2 * TILES_SHALLOW_BATCH + 1})
void testWriteTilesDifferentSizeInCompactMode(int howMany) throws IOException, SQLException { void testWriteTilesDifferentSizeInCompactMode(int howMany) throws IOException, SQLException {
testWriteTiles(howMany, false, false, true); testWriteTiles(null, howMany, false, false, true);
} }
@Test @Test
void testSkipIndexCreation() throws IOException, SQLException { void testSkipIndexCreation() throws IOException, SQLException {
testWriteTiles(10, true, false, false); testWriteTiles(null, 10, true, false, false);
} }
@Test @Test
void testVacuumAnalyze() throws IOException, SQLException { void testVacuumAnalyze() throws IOException, SQLException {
testWriteTiles(10, false, true, false); 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 @ParameterizedTest
@ -121,71 +143,47 @@ class MbtilesTest {
} }
@Test @Test
void testAddMetadata() throws IOException { void testRoundTripMetadata() throws IOException {
Map<String, String> expected = new TreeMap<>(); roundTripMetadata(new TileArchiveMetadata(
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) { "MyName",
var metadata = db.createTablesWithoutIndexes().metadata(); "MyDescription",
metadata.setName("name value"); "MyAttribution",
expected.put("name", "name value"); "MyVersion",
"baselayer",
metadata.setFormat("pbf"); TileArchiveMetadata.MVT_FORMAT,
expected.put("format", "pbf"); new Envelope(1, 2, 3, 4),
new CoordinateXY(5, 6),
metadata.setAttribution("attribution value"); 7d,
expected.put("attribution", "attribution value"); 8,
9,
metadata.setBoundsAndCenter(GeoUtils.toLatLonBoundsBounds(new Envelope(0.25, 0.75, 0.25, 0.75))); List.of(new LayerStats.VectorLayer("MyLayer", Map.of())),
expected.put("bounds", "-90,-66.51326,90,66.51326"); Map.of("other key", "other value")
expected.put("center", "0,0,1"); ));
metadata.setDescription("description value");
expected.put("description", "description value");
metadata.setMinzoom(1);
expected.put("minzoom", "1");
metadata.setMaxzoom(13);
expected.put("maxzoom", "13");
metadata.setVersion("1.2.3");
expected.put("version", "1.2.3");
metadata.setTypeIsBaselayer();
expected.put("type", "baselayer");
assertEquals(expected, metadata.getAll());
}
} }
@Test @Test
void testAddMetadataWorldBounds() throws IOException { void testRoundTripMinimalMetadata() throws IOException {
Map<String, String> expected = new TreeMap<>(); 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()) { try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
var metadata = db.createTablesWithoutIndexes().metadata(); db.createTablesWithoutIndexes();
metadata.setBoundsAndCenter(GeoUtils.WORLD_LAT_LON_BOUNDS); assertEquals(empty, db.metadata());
expected.put("bounds", "-180,-85.05113,180,85.05113");
expected.put("center", "0,0,0");
assertEquals(expected, metadata.getAll());
} }
} }
@Test private static void roundTripMetadata(TileArchiveMetadata metadata) throws IOException {
void testAddMetadataSmallBounds() throws IOException {
Map<String, String> expected = new TreeMap<>();
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) { try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
var metadata = db.createTablesWithoutIndexes().metadata(); db.createTablesWithoutIndexes();
metadata.setBoundsAndCenter(new Envelope(-73.6632, -69.7598, 41.1274, 43.0185)); var metadataTable = db.metadataTable();
expected.put("bounds", "-73.6632,41.1274,-69.7598,43.0185"); metadataTable.set(metadata);
expected.put("center", "-71.7115,42.07295,7"); assertEquals(metadata, metadataTable.get());
assertEquals(expected, metadata.getAll());
} }
} }
private void testMetadataJson(Mbtiles.MetadataJson object, String expected) throws IOException { private void testMetadataJson(Mbtiles.MetadataJson object, String expected) throws IOException {
try (Mbtiles db = Mbtiles.newInMemoryDatabase()) { try (Mbtiles db = Mbtiles.newInMemoryDatabase()) {
var metadata = db.createTablesWithoutIndexes().metadata(); var metadata = db.createTablesWithoutIndexes().metadataTable();
metadata.setJson(object); metadata.setJson(object);
var actual = metadata.getAll().get("json"); var actual = metadata.getAll().get("json");
assertSameJson(expected, actual); assertSameJson(expected, actual);

Wyświetl plik

@ -45,7 +45,7 @@ class VerifyTest {
@Test @Test
void testValidWithNameAndOneTile() throws IOException { void testValidWithNameAndOneTile() throws IOException {
mbtiles.createTablesWithIndexes(); mbtiles.createTablesWithIndexes();
mbtiles.metadata().setName("name"); mbtiles.metadataTable().setMetadata("name", "name");
try (var writer = mbtiles.newTileWriter()) { try (var writer = mbtiles.newTileWriter()) {
VectorTile tile = new VectorTile(); VectorTile tile = new VectorTile();
tile.addLayerFeatures("layer", List.of(new VectorTile.Feature( tile.addLayerFeatures("layer", List.of(new VectorTile.Feature(
@ -62,7 +62,7 @@ class VerifyTest {
@Test @Test
void testInvalidGeometry() throws IOException { void testInvalidGeometry() throws IOException {
mbtiles.createTablesWithIndexes(); mbtiles.createTablesWithIndexes();
mbtiles.metadata().setName("name"); mbtiles.metadataTable().setMetadata("name", "name");
try (var writer = mbtiles.newTileWriter()) { try (var writer = mbtiles.newTileWriter()) {
VectorTile tile = new VectorTile(); VectorTile tile = new VectorTile();
tile.addLayerFeatures("layer", List.of(new VectorTile.Feature( tile.addLayerFeatures("layer", List.of(new VectorTile.Feature(

Wyświetl plik

@ -15,15 +15,16 @@ import com.onthegomap.planetiler.util.SeekableInMemoryByteChannel;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.OptionalLong; import java.util.OptionalLong;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.locationtech.jts.geom.CoordinateXY;
import org.locationtech.jts.geom.Envelope;
class PmtilesTest { class PmtilesTest {
@ -181,11 +182,12 @@ class PmtilesTest {
var in = WriteablePmtiles.newWriteToMemory(bytes); var in = WriteablePmtiles.newWriteToMemory(bytes);
var config = PlanetilerConfig.defaults(); var config = PlanetilerConfig.defaults();
in.initialize(config, new TileArchiveMetadata(new Profile.NullProfile()), new LayerStats()); var metadata = new TileArchiveMetadata(new Profile.NullProfile(), config);
in.initialize(metadata);
var writer = in.newTileWriter(); var writer = in.newTileWriter();
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.empty())); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.empty()));
in.finish(config); in.finish(metadata);
var reader = new ReadablePmtiles(bytes); var reader = new ReadablePmtiles(bytes);
var header = reader.getHeader(); var header = reader.getHeader();
assertEquals(1, header.numAddressedTiles()); assertEquals(1, header.numAddressedTiles());
@ -200,28 +202,59 @@ class PmtilesTest {
} }
@Test @Test
void testWritePmtilesToFileWithMetadata(@TempDir Path tempDir) throws IOException { 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")
));
}
try (var in = WriteablePmtiles.newWriteToFile(tempDir.resolve("tmp.pmtiles"))) { @Test
var config = PlanetilerConfig.defaults(); void testRoundtripMetadataMinimal() throws IOException {
in.initialize(config, roundTripMetadata(
new TileArchiveMetadata("MyName", "MyDescription", "MyAttribution", "MyVersion", "baselayer", new HashMap<>()), new TileArchiveMetadata(null, null, null, null, null, null, null, null, null, null, null, null, Map.of()),
new LayerStats()); new TileArchiveMetadata(null, null, null, null, null, null,
new Envelope(-180, 180, -85.0511287, 85.0511287),
new CoordinateXY(0, 0),
0d,
0,
15,
null,
Map.of()
)
);
}
private static void roundTripMetadata(TileArchiveMetadata metadata) throws IOException {
roundTripMetadata(metadata, metadata);
}
private static void roundTripMetadata(TileArchiveMetadata input, TileArchiveMetadata output) throws IOException {
try (
var channel = new SeekableInMemoryByteChannel(0);
var in = WriteablePmtiles.newWriteToMemory(channel)
) {
in.initialize(input);
var writer = in.newTileWriter(); var writer = in.newTileWriter();
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.empty())); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.empty()));
in.finish(config); in.finish(input);
var reader = new ReadablePmtiles(channel);
assertArrayEquals(new byte[]{0xa, 0x2}, reader.getTile(0, 0, 0));
assertEquals(output, reader.metadata());
} }
var reader = new ReadablePmtiles(FileChannel.open(tempDir.resolve("tmp.pmtiles")));
assertArrayEquals(new byte[]{0xa, 0x2}, reader.getTile(0, 0, 0));
var metadata = reader.getJsonMetadata();
assertEquals("MyName", metadata.otherMetadata().get("name"));
assertEquals("MyDescription", metadata.otherMetadata().get("description"));
assertEquals("MyAttribution", metadata.otherMetadata().get("attribution"));
assertEquals("MyVersion", metadata.otherMetadata().get("version"));
assertEquals("baselayer", metadata.otherMetadata().get("type"));
} }
@Test @Test
@ -250,13 +283,14 @@ class PmtilesTest {
var in = WriteablePmtiles.newWriteToMemory(bytes); var in = WriteablePmtiles.newWriteToMemory(bytes);
var config = PlanetilerConfig.defaults(); var config = PlanetilerConfig.defaults();
in.initialize(config, new TileArchiveMetadata(new Profile.NullProfile()), new LayerStats()); var metadata = new TileArchiveMetadata(new Profile.NullProfile(), config);
in.initialize(metadata);
var writer = in.newTileWriter(); var writer = in.newTileWriter();
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.of(42))); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.of(42)));
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.of(42))); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.of(42)));
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 2), new byte[]{0xa, 0x2}, OptionalLong.of(42))); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 2), new byte[]{0xa, 0x2}, OptionalLong.of(42)));
in.finish(config); in.finish(metadata);
var reader = new ReadablePmtiles(bytes); var reader = new ReadablePmtiles(bytes);
var header = reader.getHeader(); var header = reader.getHeader();
assertEquals(3, header.numAddressedTiles()); assertEquals(3, header.numAddressedTiles());
@ -276,12 +310,13 @@ class PmtilesTest {
var in = WriteablePmtiles.newWriteToMemory(bytes); var in = WriteablePmtiles.newWriteToMemory(bytes);
var config = PlanetilerConfig.defaults(); var config = PlanetilerConfig.defaults();
in.initialize(config, new TileArchiveMetadata(new Profile.NullProfile()), new LayerStats()); var metadata = new TileArchiveMetadata(new Profile.NullProfile(), config);
in.initialize(metadata);
var writer = in.newTileWriter(); var writer = in.newTileWriter();
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.of(42))); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 1), new byte[]{0xa, 0x2}, OptionalLong.of(42)));
writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.of(42))); writer.write(new TileEncodingResult(TileCoord.ofXYZ(0, 0, 0), new byte[]{0xa, 0x2}, OptionalLong.of(42)));
in.finish(config); in.finish(metadata);
var reader = new ReadablePmtiles(bytes); var reader = new ReadablePmtiles(bytes);
var header = reader.getHeader(); var header = reader.getHeader();
assertEquals(2, header.numAddressedTiles()); assertEquals(2, header.numAddressedTiles());
@ -301,7 +336,8 @@ class PmtilesTest {
var in = WriteablePmtiles.newWriteToMemory(bytes); var in = WriteablePmtiles.newWriteToMemory(bytes);
var config = PlanetilerConfig.defaults(); var config = PlanetilerConfig.defaults();
in.initialize(config, new TileArchiveMetadata(new Profile.NullProfile()), new LayerStats()); var metadata = new TileArchiveMetadata(new Profile.NullProfile(), config);
in.initialize(metadata);
var writer = in.newTileWriter(); var writer = in.newTileWriter();
int ENTRIES = 20000; int ENTRIES = 20000;
@ -311,7 +347,7 @@ class PmtilesTest {
OptionalLong.empty())); OptionalLong.empty()));
} }
in.finish(config); in.finish(metadata);
var reader = new ReadablePmtiles(bytes); var reader = new ReadablePmtiles(bytes);
var header = reader.getHeader(); var header = reader.getHeader();
assertEquals(ENTRIES, header.numAddressedTiles()); assertEquals(ENTRIES, header.numAddressedTiles());

Wyświetl plik

@ -151,8 +151,6 @@ cat planetiler-custommap/planetiler.schema.json | jq -r '.properties.args.proper
- `minzoom` - Minimum tile zoom level to emit - `minzoom` - Minimum tile zoom level to emit
- `maxzoom` - Maximum tile zoom level to emit - `maxzoom` - Maximum tile zoom level to emit
- `render_maxzoom` - Maximum rendering zoom level up to - `render_maxzoom` - Maximum rendering zoom level up to
- `skip_mbtiles_index_creation` - Skip adding index to mbtiles file
- `optimize_db` - Vacuum analyze mbtiles file after writing
- `force` - Overwriting output file and ignore warnings - `force` - Overwriting output file and ignore warnings
- `gzip_temp` - Gzip temporary feature storage (uses more CPU, but less disk space) - `gzip_temp` - Gzip temporary feature storage (uses more CPU, but less disk space)
- `mmap_temp` - Use memory-mapped IO for temp feature files - `mmap_temp` - Use memory-mapped IO for temp feature files
@ -175,7 +173,6 @@ cat planetiler-custommap/planetiler.schema.json | jq -r '.properties.args.proper
maximum zoom level to allow for overzooming maximum zoom level to allow for overzooming
- `simplify_tolerance` - Default value for the tile pixel tolerance to use when simplifying features below the maximum - `simplify_tolerance` - Default value for the tile pixel tolerance to use when simplifying features below the maximum
zoom level zoom level
- `compact_db` - Reduce the DB size by separating and deduping the tile data
- `skip_filled_tiles` - Skip writing tiles containing only polygon fills to the output - `skip_filled_tiles` - Skip writing tiles containing only polygon fills to the output
- `tile_warning_size_mb` - Maximum size in megabytes of a tile to emit a warning about - `tile_warning_size_mb` - Maximum size in megabytes of a tile to emit a warning about

Wyświetl plik

@ -138,28 +138,6 @@
"render_maxzoom": { "render_maxzoom": {
"description": "Maximum rendering zoom level up to" "description": "Maximum rendering zoom level up to"
}, },
"skip_mbtiles_index_creation": {
"description": "Skip adding index to mbtiles file",
"anyOf": [
{
"type": "string"
},
{
"type": "boolean"
}
]
},
"optimize_db": {
"description": "Vacuum analyze mbtiles file after writing",
"anyOf": [
{
"type": "string"
},
{
"type": "boolean"
}
]
},
"force": { "force": {
"description": "Overwriting output file and ignore warnings", "description": "Overwriting output file and ignore warnings",
"anyOf": [ "anyOf": [
@ -294,17 +272,6 @@
"simplify_tolerance": { "simplify_tolerance": {
"description": "Default value for the tile pixel tolerance to use when simplifying features below the maximum zoom level" "description": "Default value for the tile pixel tolerance to use when simplifying features below the maximum zoom level"
}, },
"compact_db": {
"description": "Reduce the DB size by separating and deduping the tile data",
"anyOf": [
{
"type": "string"
},
{
"type": "boolean"
}
]
},
"skip_filled_tiles": { "skip_filled_tiles": {
"description": "Skip writing tiles containing only polygon fills to the output", "description": "Skip writing tiles containing only polygon fills to the output",
"anyOf": [ "anyOf": [

Wyświetl plik

@ -10,7 +10,7 @@ import java.nio.file.Path;
/** /**
* Main driver to create maps configured by a YAML file. * Main driver to create maps configured by a YAML file.
* * <p>
* Parses the config file into a {@link ConfiguredProfile}, loads sources into {@link Planetiler} runner and kicks off * Parses the config file into a {@link ConfiguredProfile}, loads sources into {@link Planetiler} runner and kicks off
* the map generation process. * the map generation process.
*/ */
@ -54,7 +54,7 @@ public class ConfiguredMapMain {
configureSource(planetiler, sourcesDir, source); configureSource(planetiler, sourcesDir, source);
} }
planetiler.overwriteOutput("mbtiles", Path.of("data", "output.mbtiles")).run(); planetiler.overwriteOutput(Path.of("data", "output.mbtiles")).run();
} }
private static void configureSource(Planetiler planetiler, Path sourcesDir, Source source) { private static void configureSource(Planetiler planetiler, Path sourcesDir, Source source) {

Wyświetl plik

@ -187,8 +187,6 @@ public class Contexts {
argumentValues.put("minzoom", config.minzoom()); argumentValues.put("minzoom", config.minzoom());
argumentValues.put("maxzoom", config.maxzoom()); argumentValues.put("maxzoom", config.maxzoom());
argumentValues.put("render_maxzoom", config.maxzoomForRendering()); argumentValues.put("render_maxzoom", config.maxzoomForRendering());
argumentValues.put("skip_mbtiles_index_creation", config.skipIndexCreation());
argumentValues.put("optimize_db", config.optimizeDb());
argumentValues.put("force", config.force()); argumentValues.put("force", config.force());
argumentValues.put("gzip_temp", config.gzipTempStorage()); argumentValues.put("gzip_temp", config.gzipTempStorage());
argumentValues.put("mmap_temp", config.mmapTempStorage()); argumentValues.put("mmap_temp", config.mmapTempStorage());
@ -209,7 +207,6 @@ public class Contexts {
argumentValues.put("min_feature_size", config.minFeatureSizeBelowMaxZoom()); argumentValues.put("min_feature_size", config.minFeatureSizeBelowMaxZoom());
argumentValues.put("simplify_tolerance_at_max_zoom", config.simplifyToleranceAtMaxZoom()); argumentValues.put("simplify_tolerance_at_max_zoom", config.simplifyToleranceAtMaxZoom());
argumentValues.put("simplify_tolerance", config.simplifyToleranceBelowMaxZoom()); argumentValues.put("simplify_tolerance", config.simplifyToleranceBelowMaxZoom());
argumentValues.put("compact_db", config.compactDb());
argumentValues.put("skip_filled_tiles", config.skipFilledTiles()); argumentValues.put("skip_filled_tiles", config.skipFilledTiles());
argumentValues.put("tile_warning_size_mb", config.tileWarningSizeBytes()); argumentValues.put("tile_warning_size_mb", config.tileWarningSizeBytes());
builtInArgs = Set.copyOf(argumentValues.keySet()); builtInArgs = Set.copyOf(argumentValues.keySet());

Wyświetl plik

@ -47,7 +47,7 @@ class ConfiguredMapTest {
"--tmp=" + tmpDir, "--tmp=" + tmpDir,
// Override output location // Override output location
"--mbtiles=" + dbPath "--output=" + dbPath
); );
mbtiles = Mbtiles.newReadOnlyDatabase(dbPath); mbtiles = Mbtiles.newReadOnlyDatabase(dbPath);
} }
@ -59,7 +59,7 @@ class ConfiguredMapTest {
@Test @Test
void testMetadata() { void testMetadata() {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadataTable().getAll();
assertEquals("OWG Simple Schema", metadata.get("name")); assertEquals("OWG Simple Schema", metadata.get("name"));
assertEquals("0", metadata.get("minzoom")); assertEquals("0", metadata.get("minzoom"));
assertEquals("14", metadata.get("maxzoom")); assertEquals("14", metadata.get("maxzoom"));

Wyświetl plik

@ -105,7 +105,7 @@ java -cp target/*-with-deps.jar com.onthegomap.planetiler.examples.MyProfile
Then, to inspect the tiles: Then, to inspect the tiles:
```bash ```bash
tileserver-gl-light --mbtiles data/toilets.mbtiles tileserver-gl-light data/toilets.mbtiles
``` ```
Finally, open http://localhost:8080 to see your tiles. Finally, open http://localhost:8080 to see your tiles.
@ -143,7 +143,7 @@ public void integrationTest(@TempDir Path tmpDir) throws Exception {
MyProfile.main( MyProfile.main(
"--osm_path=" + TestUtils.pathToResource("monaco-latest.osm.pbf"), "--osm_path=" + TestUtils.pathToResource("monaco-latest.osm.pbf"),
"--tmp=" + tmpDir, "--tmp=" + tmpDir,
"--mbtiles=" + mbtilesPath, "--output=" + mbtilesPath,
)); ));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(mbtilesPath)) { try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(mbtilesPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadata().getAll();

Wyświetl plik

@ -22,7 +22,7 @@ import java.util.List;
* <li>then build the examples: {@code mvn clean package}</li> * <li>then build the examples: {@code mvn clean package}</li>
* <li>then run this example: * <li>then run this example:
* {@code java -cp target/*-with-deps.jar com.onthegomap.planetiler.examples.BikeRouteOverlay osm_path="path/to/data.osm.pbf" mbtiles="data/output.mbtiles"}</li> * {@code java -cp target/*-with-deps.jar com.onthegomap.planetiler.examples.BikeRouteOverlay osm_path="path/to/data.osm.pbf" mbtiles="data/output.mbtiles"}</li>
* <li>then run the demo tileserver: {@code tileserver-gl-light --mbtiles data/bikeroutes.mbtiles}</li> * <li>then run the demo tileserver: {@code tileserver-gl-light data/bikeroutes.mbtiles}</li>
* <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li> * <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li>
* </ol> * </ol>
*/ */
@ -175,7 +175,7 @@ public class BikeRouteOverlay implements Profile {
// override this default with osm_path="path/to/data.osm.pbf" // override this default with osm_path="path/to/data.osm.pbf"
.addOsmSource("osm", Path.of("data", "sources", area + ".osm.pbf"), "geofabrik:" + area) .addOsmSource("osm", Path.of("data", "sources", area + ".osm.pbf"), "geofabrik:" + area)
// override this default with mbtiles="path/to/output.mbtiles" // override this default with mbtiles="path/to/output.mbtiles"
.overwriteOutput("mbtiles", Path.of("data", "bikeroutes.mbtiles")) .overwriteOutput(Path.of("data", "bikeroutes.mbtiles"))
.run(); .run();
} }
} }

Wyświetl plik

@ -54,7 +54,7 @@ public class OsmQaTiles implements Profile {
Path.of("data", "sources", area + ".osm.pbf"), Path.of("data", "sources", area + ".osm.pbf"),
"planet".equalsIgnoreCase(area) ? "aws:latest" : ("geofabrik:" + area) "planet".equalsIgnoreCase(area) ? "aws:latest" : ("geofabrik:" + area)
) )
.overwriteOutput("mbtiles", Path.of("data", "qa.mbtiles")) .overwriteOutput(Path.of("data", "qa.mbtiles"))
.run(); .run();
} }

Wyświetl plik

@ -19,7 +19,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* <li>then build the examples: {@code mvn clean package}</li> * <li>then build the examples: {@code mvn clean package}</li>
* <li>then run this example: * <li>then run this example:
* {@code java -cp target/*-fatjar.jar com.onthegomap.planetiler.examples.ToiletsOverlay osm_path="path/to/data.osm.pbf" mbtiles="data/output.mbtiles"}</li> * {@code java -cp target/*-fatjar.jar com.onthegomap.planetiler.examples.ToiletsOverlay osm_path="path/to/data.osm.pbf" mbtiles="data/output.mbtiles"}</li>
* <li>then run the demo tileserver: {@code tileserver-gl-light --mbtiles=data/output.mbtiles}</li> * <li>then run the demo tileserver: {@code tileserver-gl-light data/output.mbtiles}</li>
* <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li> * <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li>
* </ol> * </ol>
*/ */
@ -103,7 +103,7 @@ public class ToiletsOverlay implements Profile {
// override this default with osm_path="path/to/data.osm.pbf" // override this default with osm_path="path/to/data.osm.pbf"
.addOsmSource("osm", Path.of("data", "sources", area + ".osm.pbf"), "geofabrik:" + area) .addOsmSource("osm", Path.of("data", "sources", area + ".osm.pbf"), "geofabrik:" + area)
// override this default with mbtiles="path/to/output.mbtiles" // override this default with mbtiles="path/to/output.mbtiles"
.overwriteOutput("mbtiles", Path.of("data", "toilets.mbtiles")) .overwriteOutput(Path.of("data", "toilets.mbtiles"))
.run(); .run();
} }
} }

Wyświetl plik

@ -4,13 +4,14 @@ import com.onthegomap.planetiler.Planetiler;
import com.onthegomap.planetiler.Profile; import com.onthegomap.planetiler.Profile;
import com.onthegomap.planetiler.archive.TileArchiveMetadata; import com.onthegomap.planetiler.archive.TileArchiveMetadata;
import com.onthegomap.planetiler.archive.TileArchiveWriter; import com.onthegomap.planetiler.archive.TileArchiveWriter;
import com.onthegomap.planetiler.archive.TileArchives;
import com.onthegomap.planetiler.archive.WriteableTileArchive;
import com.onthegomap.planetiler.collection.FeatureGroup; import com.onthegomap.planetiler.collection.FeatureGroup;
import com.onthegomap.planetiler.collection.LongLongMap; import com.onthegomap.planetiler.collection.LongLongMap;
import com.onthegomap.planetiler.collection.LongLongMultimap; import com.onthegomap.planetiler.collection.LongLongMultimap;
import com.onthegomap.planetiler.config.Arguments; import com.onthegomap.planetiler.config.Arguments;
import com.onthegomap.planetiler.config.PlanetilerConfig; import com.onthegomap.planetiler.config.PlanetilerConfig;
import com.onthegomap.planetiler.geo.TileOrder; import com.onthegomap.planetiler.geo.TileOrder;
import com.onthegomap.planetiler.mbtiles.Mbtiles;
import com.onthegomap.planetiler.reader.osm.OsmInputFile; import com.onthegomap.planetiler.reader.osm.OsmInputFile;
import com.onthegomap.planetiler.reader.osm.OsmReader; import com.onthegomap.planetiler.reader.osm.OsmReader;
import com.onthegomap.planetiler.stats.Stats; import com.onthegomap.planetiler.stats.Stats;
@ -31,7 +32,7 @@ import org.slf4j.LoggerFactory;
* <li>then build the examples: {@code mvn clean package}</li> * <li>then build the examples: {@code mvn clean package}</li>
* <li>then run this example: * <li>then run this example:
* {@code java -cp target/*-fatjar.jar com.onthegomap.planetiler.examples.ToiletsOverlayLowLevelApi}</li> * {@code java -cp target/*-fatjar.jar com.onthegomap.planetiler.examples.ToiletsOverlayLowLevelApi}</li>
* <li>then run the demo tileserver: {@code tileserver-gl-light --mbtiles=data/toilets.mbtiles}</li> * <li>then run the demo tileserver: {@code tileserver-gl-light data/toilets.mbtiles}</li>
* <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li> * <li>and view the output at <a href="http://localhost:8080">localhost:8080</a></li>
* </ol> * </ol>
*/ */
@ -57,7 +58,7 @@ public class ToiletsOverlayLowLevelApi {
PlanetilerConfig config = PlanetilerConfig.from(Arguments.fromJvmProperties()); PlanetilerConfig config = PlanetilerConfig.from(Arguments.fromJvmProperties());
// extract mbtiles metadata from profile // extract mbtiles metadata from profile
TileArchiveMetadata tileArchiveMetadata = new TileArchiveMetadata(profile); TileArchiveMetadata tileArchiveMetadata = new TileArchiveMetadata(profile, config);
// overwrite output each time // overwrite output each time
FileUtils.deleteFile(output); FileUtils.deleteFile(output);
@ -112,7 +113,7 @@ public class ToiletsOverlayLowLevelApi {
// then process rendered features, grouped by tile, encoding them into binary vector tile format // then process rendered features, grouped by tile, encoding them into binary vector tile format
// and writing to the output mbtiles file. // and writing to the output mbtiles file.
try (Mbtiles db = Mbtiles.newWriteToFileDatabase(output, config.compactDb())) { try (WriteableTileArchive db = TileArchives.newWriter(output, config)) {
TileArchiveWriter.writeOutput(featureGroup, db, () -> FileUtils.fileSize(output), tileArchiveMetadata, config, TileArchiveWriter.writeOutput(featureGroup, db, () -> FileUtils.fileSize(output), tileArchiveMetadata, config,
stats); stats);
} catch (IOException e) { } catch (IOException e) {

Wyświetl plik

@ -98,10 +98,10 @@ class BikeRouteOverlayTest {
// Override temp dir location // Override temp dir location
"tmp", tmpDir.toString(), "tmp", tmpDir.toString(),
// Override output location // Override output location
"mbtiles", dbPath.toString() "output", dbPath.toString()
)); ));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) { try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadataTable().getAll();
assertEquals("Bike Paths Overlay", metadata.get("name")); assertEquals("Bike Paths Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution")); assertContains("openstreetmap.org/copyright", metadata.get("attribution"));

Wyświetl plik

@ -93,10 +93,10 @@ class OsmQaTilesTest {
// Override temp dir location // Override temp dir location
"tmp", tmpDir.toString(), "tmp", tmpDir.toString(),
// Override output location // Override output location
"mbtiles", dbPath.toString() "output", dbPath.toString()
)); ));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) { try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadataTable().getAll();
assertEquals("osm qa", metadata.get("name")); assertEquals("osm qa", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution")); assertContains("openstreetmap.org/copyright", metadata.get("attribution"));

Wyświetl plik

@ -27,7 +27,7 @@ class ToiletsOverlayLowLevelApiTest {
dbPath dbPath
); );
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) { try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadata().toMap();
assertEquals("Toilets Overlay", metadata.get("name")); assertEquals("Toilets Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution")); assertContains("openstreetmap.org/copyright", metadata.get("attribution"));

Wyświetl plik

@ -58,10 +58,10 @@ class ToiletsProfileTest {
// Override temp dir location // Override temp dir location
"tmp", tmpDir.toString(), "tmp", tmpDir.toString(),
// Override output location // Override output location
"mbtiles", dbPath.toString() "output", dbPath.toString()
)); ));
try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) { try (Mbtiles mbtiles = Mbtiles.newReadOnlyDatabase(dbPath)) {
Map<String, String> metadata = mbtiles.metadata().getAll(); Map<String, String> metadata = mbtiles.metadata().toMap();
assertEquals("Toilets Overlay", metadata.get("name")); assertEquals("Toilets Overlay", metadata.get("name"));
assertContains("openstreetmap.org/copyright", metadata.get("attribution")); assertContains("openstreetmap.org/copyright", metadata.get("attribution"));

@ -1 +1 @@
Subproject commit 62de454cf769e5bf2832f32d6b1f707860d442cf Subproject commit 292611de84b69f0ddbd4b603ec1d0f5d13257c33

Wyświetl plik

@ -102,9 +102,10 @@ if [ "$DRY_RUN" == "true" ]; then
fi fi
function run() { function run() {
echo "$ $*" command="${*//&/\&}"
echo "$ $command"
if [ "$DRY_RUN" != "true" ]; then if [ "$DRY_RUN" != "true" ]; then
eval "$*" eval "$command"
fi fi
} }

Wyświetl plik

@ -15,23 +15,23 @@ fi
echo "Test java build" echo "Test java build"
echo "::group::OpenMapTiles monaco (java)" echo "::group::OpenMapTiles monaco (java)"
rm -f data/out.mbtiles rm -f data/out.mbtiles
java -jar planetiler-dist/target/*with-deps.jar --download --area=monaco --mbtiles=data/out.mbtiles java -jar planetiler-dist/target/*with-deps.jar --download --area=monaco --output=data/out.mbtiles
./scripts/check-monaco.sh data/out.mbtiles ./scripts/check-monaco.sh data/out.mbtiles
echo "::endgroup::" echo "::endgroup::"
echo "::group::Example (java)" echo "::group::Example (java)"
rm -f data/out.mbtiles rm -f data/out.mbtiles
java -jar planetiler-dist/target/*with-deps.jar example-toilets --download --area=monaco --mbtiles=data/out.mbtiles java -jar planetiler-dist/target/*with-deps.jar example-toilets --download --area=monaco --output=data/out.mbtiles
./scripts/check-mbtiles.sh data/out.mbtiles ./scripts/check-mbtiles.sh data/out.mbtiles
echo "::endgroup::" echo "::endgroup::"
echo "::endgroup::" echo "::endgroup::"
echo "::group::OpenMapTiles monaco (docker)" echo "::group::OpenMapTiles monaco (docker)"
rm -f data/out.mbtiles rm -f data/out.mbtiles
docker run -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:"${version}" --area=monaco --mbtiles=data/out.mbtiles docker run -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:"${version}" --area=monaco --output=data/out.mbtiles
./scripts/check-monaco.sh data/out.mbtiles ./scripts/check-monaco.sh data/out.mbtiles
echo "::endgroup::" echo "::endgroup::"
echo "::group::Example (docker)" echo "::group::Example (docker)"
rm -f data/out.mbtiles rm -f data/out.mbtiles
docker run -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:"${version}" example-toilets --area=monaco --mbtiles=data/out.mbtiles docker run -v "$(pwd)/data":/data ghcr.io/onthegomap/planetiler:"${version}" example-toilets --area=monaco --output=data/out.mbtiles
./scripts/check-mbtiles.sh data/out.mbtiles ./scripts/check-mbtiles.sh data/out.mbtiles
echo "::endgroup::" echo "::endgroup::"