diff --git a/.run/basemap-dem.run.xml b/.run/basemap-dem.run.xml
new file mode 100644
index 000000000..0b345455d
--- /dev/null
+++ b/.run/basemap-dem.run.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/baremaps-cli/pom.xml b/baremaps-cli/pom.xml
index 7003b2c62..825f6248f 100644
--- a/baremaps-cli/pom.xml
+++ b/baremaps-cli/pom.xml
@@ -43,6 +43,10 @@ limitations under the License.
org.apache.baremapsbaremaps-core
+
+ org.apache.baremaps
+ baremaps-dem
+ org.apache.baremapsbaremaps-server
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/Baremaps.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/Baremaps.java
index 3a0aee76e..abe4dfee0 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/Baremaps.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/Baremaps.java
@@ -25,6 +25,7 @@
import java.util.concurrent.Callable;
import org.apache.baremaps.cli.Baremaps.VersionProvider;
import org.apache.baremaps.cli.database.Database;
+import org.apache.baremaps.cli.dem.DEM;
import org.apache.baremaps.cli.geocoder.Geocoder;
import org.apache.baremaps.cli.iploc.IpLoc;
import org.apache.baremaps.cli.map.Map;
@@ -42,7 +43,14 @@
name = "baremaps",
description = "A toolkit for producing vector tiles.",
versionProvider = VersionProvider.class,
- subcommands = {Workflow.class, Database.class, Map.class, Geocoder.class, IpLoc.class},
+ subcommands = {
+ Workflow.class,
+ Database.class,
+ Map.class,
+ Geocoder.class,
+ IpLoc.class,
+ DEM.class
+ },
sortOptions = false)
@SuppressWarnings("squid:S106")
public class Baremaps implements Callable {
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/DEM.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/DEM.java
new file mode 100644
index 000000000..25373ecf4
--- /dev/null
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/DEM.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.baremaps.cli.dem;
+
+
+
+import picocli.CommandLine;
+import picocli.CommandLine.Command;
+
+/**
+ * A command group that contains commands for processing digital elevation models (DEMs).
+ */
+@Command(name = "dem", description = "Digital Elevation Models (DEMs) processing",
+ subcommands = {
+ Serve.class,
+ VectorTileContours.class
+ },
+ sortOptions = false)
+@SuppressWarnings("squid:S106")
+public class DEM implements Runnable {
+
+ @Override
+ public void run() {
+ CommandLine.usage(this, System.out);
+ }
+}
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/Serve.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/Serve.java
new file mode 100644
index 000000000..91d27f089
--- /dev/null
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/Serve.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.baremaps.cli.dem;
+
+import static org.apache.baremaps.utils.ObjectMapperUtils.objectMapper;
+
+import com.linecorp.armeria.common.*;
+import com.linecorp.armeria.server.Server;
+import com.linecorp.armeria.server.annotation.JacksonResponseConverterFunction;
+import com.linecorp.armeria.server.cors.CorsService;
+import com.linecorp.armeria.server.docs.DocService;
+import com.linecorp.armeria.server.file.HttpFile;
+import java.nio.file.Path;
+import java.util.concurrent.Callable;
+import org.apache.baremaps.dem.ElevationUtils;
+import org.apache.baremaps.server.BufferedImageResource;
+import org.apache.baremaps.server.VectorTileResource;
+import org.apache.baremaps.tilestore.raster.*;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ * A command that starts a tile server to preview elevation data. The server serves raster tiles for
+ * elevation and hillshade data, and vector tiles for contour and hillshade data.
+ */
+@Command(name = "serve", description = "Start a tile server to preview elevation data.")
+public class Serve implements Callable {
+
+ @Option(names = {"--host"}, paramLabel = "HOST", description = "The host of the server.")
+ private String host = "localhost";
+
+ @Option(names = {"--port"}, paramLabel = "PORT", description = "The port of the server.")
+ private int port = 9000;
+
+ @Option(names = {"--path"}, paramLabel = "PATH",
+ description = "The path of a digital elevation model (DEM) file in the geotiff format.")
+ private Path path;
+
+ @Override
+ public Integer call() throws Exception {
+ // Initialize the tile stores
+ var geoTiffReader = new GeoTiffReader(path);
+ var rasterElevationTileStore = new TerrariumTileStore(geoTiffReader);
+ var rasterHillshadeTileStore =
+ new RasterHillshadeTileStore(
+ geoTiffReader,
+ ElevationUtils::terrariumToElevation);
+ var vectorHillshadeTileStore =
+ new VectorHillshadeTileStore(
+ geoTiffReader);
+ var vectorContourTileStore =
+ new VectorContourTileStore(geoTiffReader);
+
+ // Initialize the server
+ var objectMapper = objectMapper();
+ var jsonResponseConverter = new JacksonResponseConverterFunction(objectMapper);
+ var serverBuilder = Server.builder();
+ serverBuilder.http(port);
+
+ // Register the services
+ serverBuilder.annotatedService(
+ "/raster/elevation",
+ new BufferedImageResource(() -> rasterElevationTileStore),
+ jsonResponseConverter);
+ serverBuilder.annotatedService(
+ "/raster/hillshade",
+ new BufferedImageResource(() -> rasterHillshadeTileStore),
+ jsonResponseConverter);
+ serverBuilder.annotatedService(
+ "/vector/contour",
+ new VectorTileResource(() -> vectorContourTileStore),
+ jsonResponseConverter);
+ serverBuilder.annotatedService(
+ "/vector/hillshade",
+ new VectorTileResource(() -> vectorHillshadeTileStore),
+ jsonResponseConverter);
+
+ var index = HttpFile.of(ClassLoader.getSystemClassLoader(), "/dem/index.html");
+ serverBuilder.service("/", index.asService());
+
+ serverBuilder.decorator(CorsService.builderForAnyOrigin()
+ .allowAllRequestHeaders(true)
+ .allowRequestMethods(
+ HttpMethod.GET,
+ HttpMethod.POST,
+ HttpMethod.PUT,
+ HttpMethod.DELETE,
+ HttpMethod.OPTIONS,
+ HttpMethod.HEAD)
+ .allowCredentials()
+ .exposeHeaders(HttpHeaderNames.LOCATION)
+ .newDecorator());
+
+ serverBuilder.serviceUnder("/docs", new DocService());
+
+ serverBuilder.disableServerHeader();
+ serverBuilder.disableDateHeader();
+ var server = serverBuilder.build();
+
+ var startFuture = server.start();
+ startFuture.join();
+
+ var shutdownFuture = server.closeOnJvmShutdown();
+ shutdownFuture.join();
+
+ return 0;
+ }
+}
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/VectorTileContours.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/VectorTileContours.java
new file mode 100644
index 000000000..9328dd796
--- /dev/null
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/dem/VectorTileContours.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.baremaps.cli.dem;
+
+
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.apache.baremaps.maplibre.tileset.Tileset;
+import org.apache.baremaps.maplibre.tileset.TilesetLayer;
+import org.apache.baremaps.openstreetmap.stream.ProgressLogger;
+import org.apache.baremaps.openstreetmap.stream.StreamUtils;
+import org.apache.baremaps.tilestore.TileCoord;
+import org.apache.baremaps.tilestore.TileEntry;
+import org.apache.baremaps.tilestore.TileStoreException;
+import org.apache.baremaps.tilestore.pmtiles.PMTilesStore;
+import org.apache.baremaps.tilestore.raster.*;
+import org.apache.baremaps.workflow.WorkflowException;
+import org.apache.baremaps.workflow.tasks.ExportVectorTiles;
+import org.locationtech.jts.geom.Envelope;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+@Command(name = "vector-contours", description = "Generate vector contours from a DEM.")
+@SuppressWarnings({"squid:S106", "squid:S3864"})
+public class VectorTileContours implements Callable {
+
+ @Option(names = {"--path"}, paramLabel = "PATH", description = "The path of a geoTIFF file.")
+ private Path path;
+
+ @Option(names = {"--repository"}, paramLabel = "REPOSITORY", description = "The tile repository.",
+ required = true)
+ private Path repository;
+
+ @Option(names = {"--format"}, paramLabel = "FORMAT",
+ description = "The format of the repository.")
+ private ExportVectorTiles.Format format = ExportVectorTiles.Format.FILE;
+
+
+ @Override
+ public Integer call() throws Exception {
+ var contourLayer = new TilesetLayer();
+ contourLayer.setId("contours");
+
+ var tileset = new Tileset();
+ tileset.setName("contours");
+ tileset.setMinzoom(2);
+ tileset.setMaxzoom(10);
+ tileset.setCenter(List.of(0d, 0d, 1d));
+ tileset.setBounds(List.of(-180d, -85.0511d, 180d, 85.0511d));
+ tileset.setVectorLayers(List.of(contourLayer));
+
+ // Initialize the tile stores
+ try (var geoTiffReader = new GeoTiffReader(path);
+ var sourceTileStore = new VectorContourTileStore(geoTiffReader);
+ var targetTileStore = new PMTilesStore(repository, tileset);) {
+
+ var envelope = new Envelope(-180, 180, -85.0511, 85.0511);
+ var count = TileCoord.count(envelope, 0, 10);
+
+ var tileCoordIterator =
+ TileCoord.iterator(envelope, 2, 10);
+ var tileCoordStream =
+ StreamUtils.stream(tileCoordIterator).peek(new ProgressLogger<>(count, 1000));
+
+
+ var bufferedTileEntryStream = StreamUtils.bufferInCompletionOrder(tileCoordStream, tile -> {
+
+ try {
+ return new TileEntry<>(tile, sourceTileStore.read(tile));
+ } catch (TileStoreException e) {
+ throw new WorkflowException(e);
+ } finally {
+ System.out.println("Processing tile " + tile);
+ }
+ }, 8);
+
+ var partitionedTileEntryStream = StreamUtils.partition(bufferedTileEntryStream, 8);
+ partitionedTileEntryStream.forEach(batch -> {
+ try {
+ targetTileStore.write(batch);
+ } catch (TileStoreException e) {
+ throw new WorkflowException(e);
+ }
+ });
+ return 0;
+ }
+ }
+}
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
index 13b37356c..9e81c4e56 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/Dev.java
@@ -27,6 +27,7 @@
import com.linecorp.armeria.server.file.FileService;
import com.linecorp.armeria.server.file.HttpFile;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
@@ -37,8 +38,8 @@
import org.apache.baremaps.maplibre.tileset.Tileset;
import org.apache.baremaps.server.ChangeResource;
import org.apache.baremaps.server.StyleResource;
-import org.apache.baremaps.server.TileResource;
import org.apache.baremaps.server.TilesetResource;
+import org.apache.baremaps.server.VectorTileResource;
import org.apache.baremaps.tilestore.TileStore;
import org.apache.baremaps.tilestore.postgres.PostgresTileStore;
import org.apache.baremaps.utils.PostgresUtils;
@@ -96,7 +97,7 @@ public Integer call() throws Exception {
}
};
- var tileStoreSupplier = (Supplier) () -> {
+ var tileStoreSupplier = (Supplier>) () -> {
var tileJSON = tilesetSupplier.get();
return new PostgresTileStore(datasource, tileJSON);
};
@@ -116,7 +117,8 @@ public Integer call() throws Exception {
var jsonResponseConverter = new JacksonResponseConverterFunction(objectMapper);
serverBuilder.annotatedService(new ChangeResource(tilesetPath, stylePath),
jsonResponseConverter);
- serverBuilder.annotatedService(new TileResource(tileStoreSupplier), jsonResponseConverter);
+ serverBuilder.annotatedService("/tiles", new VectorTileResource(tileStoreSupplier),
+ jsonResponseConverter);
serverBuilder.annotatedService(new StyleResource(styleSupplier), jsonResponseConverter);
serverBuilder.annotatedService(new TilesetResource(tilesetSupplier), jsonResponseConverter);
diff --git a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/MBTiles.java b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/MBTiles.java
index 9628169d3..a6f52e6a2 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/MBTiles.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/map/MBTiles.java
@@ -28,6 +28,7 @@
import com.linecorp.armeria.server.docs.DocService;
import com.linecorp.armeria.server.file.FileService;
import com.linecorp.armeria.server.file.HttpFile;
+import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
@@ -36,9 +37,9 @@
import org.apache.baremaps.maplibre.style.Style;
import org.apache.baremaps.maplibre.tilejson.TileJSON;
import org.apache.baremaps.server.*;
-import org.apache.baremaps.tilestore.TileCache;
import org.apache.baremaps.tilestore.TileStore;
import org.apache.baremaps.tilestore.mbtiles.MBTilesStore;
+import org.apache.baremaps.tilestore.vector.VectorTileCache;
import org.apache.baremaps.utils.SqliteUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -83,8 +84,8 @@ public Integer call() throws Exception {
var datasource = SqliteUtils.createDataSource(mbtilesPath, true);
try (var tileStore = new MBTilesStore(datasource);
- var tileCache = new TileCache(tileStore, caffeineSpec)) {
- var tileStoreSupplier = (Supplier) () -> tileCache;
+ var tileCache = new VectorTileCache(tileStore, caffeineSpec)) {
+ var tileStoreSupplier = (Supplier>) () -> tileCache;
var style = objectMapper.readValue(configReader.read(stylePath), Style.class);
var styleSupplier = (Supplier
+
+
+
+
+
+