This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch raster-processing
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps.git
The following commit(s) were added to refs/heads/raster-processing by this push:
new f94eb342 Add Chaikin smoother and improve hillshade and contour server
f94eb342 is described below
commit f94eb342781b176b6f9be74888372023639b116a
Author: Bertil Chapuis <[email protected]>
AuthorDate: Wed Jul 31 11:31:17 2024 +0200
Add Chaikin smoother and improve hillshade and contour server
---
.../cli/raster/{HillShade.java => Hillshade.java} | 208 +++++++++++++++------
.../org/apache/baremaps/cli/raster/Raster.java | 2 +-
.../org/apache/baremaps/tilestore/TileStore.java | 3 +-
.../openstreetmap/utils/GeometryUtils.java | 22 +++
.../baremaps/raster/elevation/ChaikinSmoother.java | 130 +++++++------
.../baremaps/raster/elevation/ElevationUtils.java | 9 +-
.../baremaps/raster/elevation/RasterUtils.java | 25 ++-
.../raster/elevation/ChaikinSmootherTest.java | 30 +--
.../baremaps/raster/elevation/ContourRenderer.java | 18 +-
.../src/main/resources/raster/hillshade.html | 34 +++-
10 files changed, 316 insertions(+), 165 deletions(-)
diff --git
a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/HillShade.java
b/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Hillshade.java
similarity index 53%
rename from
baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/HillShade.java
rename to
baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Hillshade.java
index d9fcd0ae..0ccc7209 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/HillShade.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Hillshade.java
@@ -31,28 +31,35 @@ import
com.linecorp.armeria.server.annotation.JacksonResponseConverterFunction;
import com.linecorp.armeria.server.annotation.Param;
import com.linecorp.armeria.server.cors.CorsService;
import com.linecorp.armeria.server.docs.DocService;
+import com.linecorp.armeria.server.file.HttpFile;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Supplier;
+import java.util.zip.GZIPOutputStream;
import javax.imageio.ImageIO;
-import org.apache.baremaps.raster.elevation.ElevationUtils;
-import org.apache.baremaps.raster.elevation.HillshadeCalculator;
+import org.apache.baremaps.maplibre.vectortile.*;
+import org.apache.baremaps.raster.elevation.*;
+import org.apache.baremaps.server.TileResource;
import org.apache.baremaps.tilestore.TileCoord;
import org.apache.baremaps.tilestore.TileStore;
import org.apache.baremaps.tilestore.TileStoreException;
+import org.locationtech.jts.geom.util.AffineTransformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
@Command(name = "hillshade", description = "Start a tile server that computes
hillshades.")
-public class HillShade implements Callable<Integer> {
+public class Hillshade implements Callable<Integer> {
@Option(names = {"--host"}, paramLabel = "HOST", description = "The host of
the server.")
private String host = "localhost";
@@ -68,11 +75,22 @@ public class HillShade implements Callable<Integer> {
var objectMapper = objectMapper();
var jsonResponseConverter = new
JacksonResponseConverterFunction(objectMapper);
- var tileStore = new HillShadeTileStore();
- serverBuilder.annotatedService(new HillShadeTileResource(() -> tileStore),
+ LoadingCache<TileCoord, BufferedImage> cache = Caffeine.newBuilder()
+ .maximumSize(1000)
+ .build(this::getImage);
+
+ var rasterHillshadeTileStore = new RasterHillshadeTileStore(cache);
+ serverBuilder.annotatedService(new HillShadeTileResource(() ->
rasterHillshadeTileStore),
+ jsonResponseConverter);
+
+ var contourTileStore = new ContourTileStore(cache);
+ serverBuilder.annotatedService(new TileResource(() -> contourTileStore),
jsonResponseConverter);
+ var index = HttpFile.of(ClassLoader.getSystemClassLoader(),
"/raster/hillshade.html");
+ serverBuilder.service("/", index.asService());
+
serverBuilder.decorator(CorsService.builderForAnyOrigin()
.allowAllRequestHeaders(true)
.allowRequestMethods(
@@ -102,63 +120,59 @@ public class HillShade implements Callable<Integer> {
return 0;
}
- public static class HillShadeTileStore implements TileStore {
+ private String url =
"https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png";
- // private String url =
"https://s3.amazonaws.com/elevation-tiles-prod/geotiff/{z}/{x}/{y}.tif";
- // private String url =
"https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png";
- private String url =
"https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png";
-
- private final LoadingCache<TileCoord, BufferedImage> cache =
Caffeine.newBuilder()
- .maximumSize(1000)
- .build(this::getImage);
+ public BufferedImage getImage(TileCoord tileCoord) throws IOException {
+ var tileUrl = new URL(this.url
+ .replace("{z}", String.valueOf(tileCoord.z()))
+ .replace("{x}", String.valueOf(tileCoord.x()))
+ .replace("{y}", String.valueOf(tileCoord.y())));
+ return ImageIO.read(tileUrl);
+ }
- public HillShadeTileStore() {
- // Default constructor
+ public static BufferedImage getKernel(TileCoord tileCoord,
+ Function<TileCoord, BufferedImage> provider) {
+ BufferedImage z1 =
+ provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y() - 1,
tileCoord.z()));
+ BufferedImage z2 =
+ provider.apply(new TileCoord(tileCoord.x(), tileCoord.y() - 1,
tileCoord.z()));
+ BufferedImage z3 =
+ provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y() - 1,
tileCoord.z()));
+ BufferedImage z4 =
+ provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y(),
tileCoord.z()));
+ BufferedImage z5 = provider.apply(tileCoord);
+ BufferedImage z6 =
+ provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y(),
tileCoord.z()));
+ BufferedImage z7 =
+ provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y() + 1,
tileCoord.z()));
+ BufferedImage z8 =
+ provider.apply(new TileCoord(tileCoord.x(), tileCoord.y() + 1,
tileCoord.z()));
+ BufferedImage z9 =
+ provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y() + 1,
tileCoord.z()));
+ int kernelSize = z5.getWidth() * 3;
+ BufferedImage kernel = new BufferedImage(kernelSize, kernelSize,
z5.getType());
+ for (int y = 0; y < z5.getHeight(); y++) {
+ for (int x = 0; x < z5.getWidth(); x++) {
+ kernel.setRGB(x, y, z1.getRGB(x, y));
+ kernel.setRGB(x + z5.getWidth(), y, z2.getRGB(x, y));
+ kernel.setRGB(x + 2 * z5.getWidth(), y, z3.getRGB(x, y));
+ kernel.setRGB(x, y + z5.getHeight(), z4.getRGB(x, y));
+ kernel.setRGB(x + z5.getWidth(), y + z5.getHeight(), z5.getRGB(x, y));
+ kernel.setRGB(x + 2 * z5.getWidth(), y + z5.getHeight(), z6.getRGB(x,
y));
+ kernel.setRGB(x, y + 2 * z5.getHeight(), z7.getRGB(x, y));
+ kernel.setRGB(x + z5.getWidth(), y + 2 * z5.getHeight(), z8.getRGB(x,
y));
+ kernel.setRGB(x + 2 * z5.getWidth(), y + 2 * z5.getHeight(),
z9.getRGB(x, y));
+ }
}
+ return kernel;
+ }
- public BufferedImage getImage(TileCoord tileCoord) throws IOException {
- var tileUrl = new URL(this.url
- .replace("{z}", String.valueOf(tileCoord.z()))
- .replace("{x}", String.valueOf(tileCoord.x()))
- .replace("{y}", String.valueOf(tileCoord.y())));
- return ImageIO.read(tileUrl);
- }
+ public static class RasterHillshadeTileStore implements TileStore {
- public BufferedImage getKernel(TileCoord tileCoord, Function<TileCoord,
BufferedImage> provider)
- throws IOException {
- BufferedImage z1 =
- provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y() - 1,
tileCoord.z()));
- BufferedImage z2 =
- provider.apply(new TileCoord(tileCoord.x(), tileCoord.y() - 1,
tileCoord.z()));
- BufferedImage z3 =
- provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y() - 1,
tileCoord.z()));
- BufferedImage z4 =
- provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y(),
tileCoord.z()));
- BufferedImage z5 = provider.apply(tileCoord);
- BufferedImage z6 =
- provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y(),
tileCoord.z()));
- BufferedImage z7 =
- provider.apply(new TileCoord(tileCoord.x() - 1, tileCoord.y() + 1,
tileCoord.z()));
- BufferedImage z8 =
- provider.apply(new TileCoord(tileCoord.x(), tileCoord.y() + 1,
tileCoord.z()));
- BufferedImage z9 =
- provider.apply(new TileCoord(tileCoord.x() + 1, tileCoord.y() + 1,
tileCoord.z()));
- int kernelSize = z5.getWidth() * 3;
- BufferedImage kernel = new BufferedImage(kernelSize, kernelSize,
z5.getType());
- for (int y = 0; y < z5.getHeight(); y++) {
- for (int x = 0; x < z5.getWidth(); x++) {
- kernel.setRGB(x, y, z1.getRGB(x, y));
- kernel.setRGB(x + z5.getWidth(), y, z2.getRGB(x, y));
- kernel.setRGB(x + 2 * z5.getWidth(), y, z3.getRGB(x, y));
- kernel.setRGB(x, y + z5.getHeight(), z4.getRGB(x, y));
- kernel.setRGB(x + z5.getWidth(), y + z5.getHeight(), z5.getRGB(x,
y));
- kernel.setRGB(x + 2 * z5.getWidth(), y + z5.getHeight(),
z6.getRGB(x, y));
- kernel.setRGB(x, y + 2 * z5.getHeight(), z7.getRGB(x, y));
- kernel.setRGB(x + z5.getWidth(), y + 2 * z5.getHeight(),
z8.getRGB(x, y));
- kernel.setRGB(x + 2 * z5.getWidth(), y + 2 * z5.getHeight(),
z9.getRGB(x, y));
- }
- }
- return kernel;
+ private final LoadingCache<TileCoord, BufferedImage> cache;
+
+ public RasterHillshadeTileStore(LoadingCache<TileCoord, BufferedImage>
cache) {
+ this.cache = cache;
}
@Override
@@ -174,8 +188,8 @@ public class HillShade implements Callable<Integer> {
image.getHeight() + 2);
var grid = ElevationUtils.imageToGrid(buffer);
- var hillshade =
- new HillshadeCalculator(grid, buffer.getWidth(),
buffer.getHeight(), 1, true)
+ var hillshadeGrid =
+ new HillshadeCalculator(grid, buffer.getWidth(),
buffer.getHeight(), 1, false)
.calculate(45, 315);
// Create an output image
@@ -183,7 +197,7 @@ public class HillShade implements Callable<Integer> {
new BufferedImage(image.getWidth(), image.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
- int value = (int) hillshade[(y + 1) * buffer.getHeight() + x + 1];
+ int value = (int) hillshadeGrid[(y + 1) * buffer.getHeight() + x +
1];
hillshadeImage.setRGB(x, y, new Color(value, value,
value).getRGB());
}
}
@@ -229,7 +243,7 @@ public class HillShade implements Callable<Integer> {
this.tileStoreSupplier = tileStoreSupplier;
}
- @Get("regex:^/tiles/(?<z>[0-9]+)/(?<x>[0-9]+)/(?<y>[0-9]+).png")
+ @Get("regex:^/raster/(?<z>[0-9]+)/(?<x>[0-9]+)/(?<y>[0-9]+).png")
@Blocking
public HttpResponse tile(@Param("z") int z, @Param("x") int x, @Param("y")
int y) {
TileCoord tileCoord = new TileCoord(x, y, z);
@@ -255,4 +269,76 @@ public class HillShade implements Callable<Integer> {
}
}
}
+
+ public static class ContourTileStore implements TileStore {
+
+ private final LoadingCache<TileCoord, BufferedImage> cache;
+
+ public ContourTileStore(LoadingCache<TileCoord, BufferedImage> cache) {
+ this.cache = cache;
+ }
+
+ @Override
+ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
+ var image = cache.get(tileCoord);
+
+ var kernel = getKernel(tileCoord, cache::get);
+
+ image = kernel.getSubimage(
+ image.getWidth() - 4,
+ image.getHeight() - 4,
+ image.getWidth() + 8,
+ image.getHeight() + 8);
+
+ var grid = ElevationUtils.imageToGrid(image);
+
+ var features = new ArrayList<Feature>();
+
+ for (int level = 0; level < 9000; level += 100) {
+ var contours = new ContourTracer(grid, image.getWidth(),
image.getHeight(), false, false)
+ .traceContours(level);
+
+ for (var contour : contours) {
+
+ contour = AffineTransformation
+ .translationInstance(-4, -4)
+ .scaleInstance(16, 16)
+ .transform(contour);
+
+ // contour = new ChaikinSmoother(2, 0.25).transform(contour);
+
+ features.add(new Feature(level, Map.of("level",
String.valueOf(level)), contour));
+ }
+ }
+
+ var layer = new Layer("elevation", 4096, features);
+ var tile = new Tile(List.of(layer));
+ var vectorTile = new VectorTileEncoder().encodeTile(tile);
+ try (var baos = new ByteArrayOutputStream()) {
+ var gzip = new GZIPOutputStream(baos);
+ vectorTile.writeTo(gzip);
+ gzip.close();
+ return ByteBuffer.wrap(baos.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ @Override
+ public void write(TileCoord tileCoord, ByteBuffer blob) throws
TileStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void delete(TileCoord tileCoord) throws TileStoreException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() throws Exception {
+ // Do nothing
+ }
+ }
+
}
diff --git
a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
b/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
index f0dfa288..a4ee5a4a 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
+++ b/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
@@ -23,7 +23,7 @@ import picocli.CommandLine;
import picocli.CommandLine.Command;
@Command(name = "raster", description = "Raster processing commands.",
- subcommands = {HillShade.class},
+ subcommands = {Hillshade.class},
sortOptions = false)
public class Raster implements Runnable {
diff --git
a/baremaps-core/src/main/java/org/apache/baremaps/tilestore/TileStore.java
b/baremaps-core/src/main/java/org/apache/baremaps/tilestore/TileStore.java
index e7f34b11..bd2b13a7 100644
--- a/baremaps-core/src/main/java/org/apache/baremaps/tilestore/TileStore.java
+++ b/baremaps-core/src/main/java/org/apache/baremaps/tilestore/TileStore.java
@@ -19,6 +19,7 @@ package org.apache.baremaps.tilestore;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -42,7 +43,7 @@ public interface TileStore extends AutoCloseable {
* @return the content of the tiles
* @throws TileStoreException
*/
- default List<ByteBuffer> read(List<TileCoord> tileCoords) throws
TileStoreException {
+ default List<ByteBuffer> read(List<TileCoord> tileCoords) throws
TileStoreException, IOException {
var blobs = new ArrayList<ByteBuffer>();
for (var tileCoord : tileCoords) {
blobs.add(read(tileCoord));
diff --git
a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
index e0d6813e..8199306a 100644
---
a/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
+++
b/baremaps-openstreetmap/src/main/java/org/apache/baremaps/openstreetmap/utils/GeometryUtils.java
@@ -21,6 +21,7 @@ import static org.locationtech.jts.io.WKBConstants.wkbNDR;
import org.apache.baremaps.openstreetmap.function.ProjectionTransformer;
import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.util.AffineTransformation;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKBReader;
import org.locationtech.jts.io.WKBWriter;
@@ -93,4 +94,25 @@ public class GeometryUtils {
return new ProjectionTransformer(inputSRID, outputSRID);
}
+ /**
+ * Scales a geometry by a factor.
+ *
+ * @param geometry The geometry to scale
+ * @param factor The factor to scale by
+ * @return The scaled geometry
+ */
+ public static Geometry scale(Geometry geometry, double factor) {
+ AffineTransformation transform =
AffineTransformation.scaleInstance(factor, factor);
+ return transform.transform(geometry);
+ }
+
+ public static Geometry createEnvelope(int xMin, int yMin, int xMax, int
yMax) {
+ return new GeometryFactory().createPolygon(new Coordinate[] {
+ new Coordinate(xMin, yMin),
+ new Coordinate(xMin, yMax),
+ new Coordinate(xMax, yMax),
+ new Coordinate(xMax, yMin),
+ new Coordinate(xMin, yMin)
+ });
+ }
}
diff --git
a/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ChaikinSmoother.java
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ChaikinSmoother.java
index d585c9ae..6376c36a 100644
---
a/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ChaikinSmoother.java
+++
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ChaikinSmoother.java
@@ -17,71 +17,87 @@
package org.apache.baremaps.raster.elevation;
-import java.util.Arrays;
-import org.locationtech.jts.geom.Coordinate;
-
-public class ChaikinSmoother {
-
- private final Coordinate[] coordinates;
- private final double minX;
- private final double minY;
- private final double maxX;
- private final double maxY;
- private final boolean isOpen;
-
- public ChaikinSmoother(Coordinate[] coordinates, double minX, double minY,
double maxX,
- double maxY) {
- this.coordinates = Arrays.copyOf(coordinates, coordinates.length);
- this.minX = minX;
- this.minY = minY;
- this.maxX = maxX;
- this.maxY = maxY;
- this.isOpen = !coordinates[0].equals(coordinates[coordinates.length - 1]);
+import org.locationtech.jts.geom.*;
+import org.locationtech.jts.geom.impl.CoordinateArraySequence;
+import org.locationtech.jts.geom.util.GeometryTransformer;
+
+public class ChaikinSmoother extends GeometryTransformer {
+
+ private final int iterations;
+
+ private final double factor;
+
+ public ChaikinSmoother(int iterations, double factor) {
+ this.iterations = iterations;
+ this.factor = factor;
}
- public Coordinate[] smooth(int iterations, double factor) {
- Coordinate[] result = isOpen
- ? Arrays.copyOf(coordinates, coordinates.length - 1)
- : coordinates;
-
- double f1 = 1 - factor;
- double f2 = factor;
-
- // Apply the algorithm repeatedly
- for (int n = 0; n < iterations; n++) {
- Coordinate[] temp = new Coordinate[isOpen ? 2 * result.length - 2 : 2 *
result.length];
-
- for (int i = 0; i < result.length; i++) {
- if (isOnBoundary(result[i]) || isOnBoundary(result[(i + 1) %
result.length])) {
- temp[2 * i] = result[i];
- temp[2 * i + 1] = result[(i + 1) % result.length];
- } else {
- temp[2 * i] = new Coordinate(
- f1 * result[i].x + f2 * result[(i + 1) % result.length].x,
- f1 * result[i].y + f2 * result[(i + 1) % result.length].y);
- temp[2 * i + 1] = new Coordinate(
- f2 * result[i].x + f1 * result[(i + 1) % result.length].x,
- f2 * result[i].y + f1 * result[(i + 1) % result.length].y);
- }
- }
+ @Override
+ protected CoordinateSequence transformCoordinates(
+ CoordinateSequence coordinateSequence,
+ Geometry parent) {
+ return smooth(coordinateSequence, iterations, factor);
+ }
- if (isOpen) {
- temp[0] = result[0];
- temp[temp.length - 1] = result[result.length - 1];
- }
+ public static LinearRing smooth(LinearRing linearRing, int iterations,
double factor) {
+ CoordinateSequence coordinateSequence =
+ smooth(linearRing.getCoordinateSequence(), iterations, factor);
+ return linearRing.getFactory().createLinearRing(coordinateSequence);
+ }
+
+ public static LineString smooth(LineString lineString, int iterations,
double factor) {
+ CoordinateSequence coordinateSequence =
+ smooth(lineString.getCoordinateSequence(), iterations, factor);
+ return lineString.getFactory().createLineString(coordinateSequence);
+ }
- result = temp;
+ public static Coordinate[] smooth(Coordinate[] coordinates, int iterations,
double factor) {
+ return smooth(new CoordinateArraySequence(coordinates), iterations,
factor).toCoordinateArray();
+ }
+
+ public static CoordinateSequence smooth(CoordinateSequence
coordinateSequence, int iterations,
+ double factor) {
+ if (CoordinateSequences.isRing(coordinateSequence)) {
+ return new
CoordinateArraySequence(chaikin(coordinateSequence.toCoordinateArray(), 2,
0.25));
+ } else {
+ Coordinate[] original = coordinateSequence.toCoordinateArray();
+ Coordinate[] smoothed = chaikin(original, iterations, factor);
+ int sumOfSquares = (iterations * (iterations + 1) * (2 * iterations +
1)) / 6;
+ int trimmedLength = smoothed.length - sumOfSquares;
+ Coordinate[] result = new Coordinate[trimmedLength + 2];
+ result[0] = original[0];
+ System.arraycopy(smoothed, 0, result, 1, trimmedLength);
+ result[trimmedLength + 1] = original[original.length - 1];
+ return new CoordinateArraySequence(result);
}
+ }
- if (!isOpen) {
- result = Arrays.copyOf(result, result.length + 1);
- result[result.length - 1] = result[0];
+ private static Coordinate[] chaikin(Coordinate[] coordinates, int
iterations, double factor) {
+ if (iterations <= 0) {
+ return coordinates;
}
- return result;
- }
+ for (int i = 0; i < iterations; i++) {
+ int l = coordinates.length;
+ double f1 = 1 - factor;
+ double f2 = factor;
- private boolean isOnBoundary(Coordinate coord) {
- return coord.x == minX || coord.x == maxX || coord.y == minY || coord.y ==
maxY;
+ Coordinate[] smoothed = new Coordinate[l * 2];
+ for (int j = 0; j < l; j++) {
+ Coordinate c1 = coordinates[j];
+ Coordinate c2 = coordinates[(j + 1) % l];
+ smoothed[j * 2] = new Coordinate(
+ f1 * c1.getX() + f2 * c2.getX(),
+ f1 * c1.getY() + f2 * c2.getY());
+ smoothed[j * 2 + 1] = new Coordinate(
+ f2 * c1.getX() + f1 * c2.getX(),
+ f2 * c1.getY() + f1 * c2.getY());
+ }
+
+ coordinates = smoothed;
+ }
+
+ return coordinates;
}
+
}
diff --git
a/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ElevationUtils.java
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ElevationUtils.java
index e605fe38..671e2689 100644
---
a/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ElevationUtils.java
+++
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/ElevationUtils.java
@@ -45,7 +45,7 @@ public class ElevationUtils {
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
- grid[y * width + x] = pixelToElevation(image.getRGB(x, y));
+ grid[y * width + x] = pixelToElevationTerrarium(image.getRGB(x, y));
}
}
@@ -80,6 +80,13 @@ public class ElevationUtils {
return (r * ELEVATION_SCALE + g * 256.0 + b) / 10.0 - ELEVATION_OFFSET;
}
+ private static double pixelToElevationTerrarium(int rgb) {
+ int r = (rgb >> 16) & 0xFF;
+ int g = (rgb >> 8) & 0xFF;
+ int b = rgb & 0xFF;
+ return (r * 256 + g + b / 256) - 32768;
+ }
+
private static int elevationToPixel(double elevation) {
int value = (int) ((elevation + ELEVATION_OFFSET) * 10.0);
int r = (value >> 16) & 0xFF;
diff --git
a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/RasterUtils.java
similarity index 58%
copy from baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
copy to
baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/RasterUtils.java
index f0dfa288..fbe06eba 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
+++
b/baremaps-raster/src/main/java/org/apache/baremaps/raster/elevation/RasterUtils.java
@@ -15,20 +15,19 @@
* limitations under the License.
*/
-package org.apache.baremaps.cli.raster;
+package org.apache.baremaps.raster.elevation;
+import java.awt.*;
+import java.awt.image.BufferedImage;
-
-import picocli.CommandLine;
-import picocli.CommandLine.Command;
-
-@Command(name = "raster", description = "Raster processing commands.",
- subcommands = {HillShade.class},
- sortOptions = false)
-public class Raster implements Runnable {
-
- @Override
- public void run() {
- CommandLine.usage(this, System.out);
+public class RasterUtils {
+ public static BufferedImage resizeImage(
+ BufferedImage originalImage, int targetWidth, int targetHeight) {
+ Image resultingImage =
+ originalImage.getScaledInstance(targetWidth, targetHeight,
Image.SCALE_DEFAULT);
+ BufferedImage outputImage =
+ new BufferedImage(targetWidth, targetHeight,
BufferedImage.TYPE_INT_RGB);
+ outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
+ return outputImage;
}
}
diff --git
a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
b/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ChaikinSmootherTest.java
similarity index 54%
copy from baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
copy to
baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ChaikinSmootherTest.java
index f0dfa288..44581357 100644
--- a/baremaps-cli/src/main/java/org/apache/baremaps/cli/raster/Raster.java
+++
b/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ChaikinSmootherTest.java
@@ -15,20 +15,28 @@
* limitations under the License.
*/
-package org.apache.baremaps.cli.raster;
+package org.apache.baremaps.raster.elevation;
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.geom.GeometryFactory;
+import org.locationtech.jts.geom.LineString;
-import picocli.CommandLine;
-import picocli.CommandLine.Command;
+class ChaikinSmootherTest {
-@Command(name = "raster", description = "Raster processing commands.",
- subcommands = {HillShade.class},
- sortOptions = false)
-public class Raster implements Runnable {
-
- @Override
- public void run() {
- CommandLine.usage(this, System.out);
+ @Test
+ void smoothLineString() {
+ LineString lineString = new GeometryFactory().createLineString(new
Coordinate[] {
+ new Coordinate(0, 0),
+ new Coordinate(1, 1),
+ });
+ Geometry smoothedLineString = new ChaikinSmoother(2,
0.25).transform(lineString);
+ System.out.println(smoothedLineString);
}
+
+
+
}
diff --git
a/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ContourRenderer.java
b/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ContourRenderer.java
index f373d904..c578f151 100644
---
a/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ContourRenderer.java
+++
b/baremaps-raster/src/test/java/org/apache/baremaps/raster/elevation/ContourRenderer.java
@@ -18,7 +18,6 @@
package org.apache.baremaps.raster.elevation;
import java.awt.*;
-import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
@@ -40,7 +39,7 @@ public class ContourRenderer {
var image = ImageIO.read(path);
// Downscale the image by a factor of 16
- image = resizeImage(image, 32, 32);
+ image = RasterUtils.resizeImage(image, 32, 32);
// Convert the image to a grid
double[] grid = ElevationUtils.imageToGrid(image);
@@ -50,7 +49,7 @@ public class ContourRenderer {
.traceContours(0, 9000, 100);
// Scale the image back to its original size
- image = resizeImage(image, image.getWidth() * 16, image.getHeight() * 16);
+ image = RasterUtils.resizeImage(image, image.getWidth() * 16,
image.getHeight() * 16);
// Scale the contour back to its original size
contour = contour.stream()
@@ -65,8 +64,7 @@ public class ContourRenderer {
// Smooth the contour with the Chaikin algorithm
contour = contour.stream()
.map(polygon -> {
- var coordinates =
- new ChaikinSmoother(polygon.getCoordinates(), 0, 0, 512,
512).smooth(2, 0.25);
+ var coordinates = ChaikinSmoother.smooth(polygon.getCoordinates(),
2, 0.25);
return (Geometry) new GeometryFactory().createPolygon(coordinates);
})
.toList();
@@ -79,16 +77,6 @@ public class ContourRenderer {
frame.setVisible(true);
}
- private static BufferedImage resizeImage(BufferedImage originalImage, int
targetWidth,
- int targetHeight) {
- Image resultingImage =
- originalImage.getScaledInstance(targetWidth, targetHeight,
Image.SCALE_DEFAULT);
- BufferedImage outputImage =
- new BufferedImage(targetWidth, targetHeight,
BufferedImage.TYPE_INT_RGB);
- outputImage.getGraphics().drawImage(resultingImage, 0, 0, null);
- return outputImage;
- }
-
// Custom Canvas to draw the contours
static class ContourCanvas extends Canvas {
diff --git a/baremaps-server/src/main/resources/raster/hillshade.html
b/baremaps-server/src/main/resources/raster/hillshade.html
index 3aedd2ac..2638580e 100644
--- a/baremaps-server/src/main/resources/raster/hillshade.html
+++ b/baremaps-server/src/main/resources/raster/hillshade.html
@@ -24,6 +24,7 @@
<div id="map"></div>
<script>
const map = (window.map = new maplibregl.Map({
+ antialias: true,
container: 'map',
zoom: 11,
center: [11.5519, 47.2719],
@@ -34,17 +35,38 @@
rasterSource: {
type: 'raster',
'tiles': [
- 'http://localhost:9000/tiles/{z}/{x}/{y}.png'
+ 'http://localhost:9000/raster/{z}/{x}/{y}.png'
],
tileSize: 256
},
+ contourSource: {
+ type: 'vector',
+ tiles: [
+ 'http://localhost:9000/tiles/{z}/{x}/{y}.mvt'
+ ],
+ },
},
layers: [
+ // {
+ // 'id': 'raster',
+ // 'type': 'raster',
+ // 'source': 'rasterSource',
+ // 'paint': {}
+ // },
{
- 'id': 'raster',
- 'type': 'raster',
- 'source': 'rasterSource',
- 'paint': {}
+ 'id': 'contour',
+ 'type': 'line',
+ 'source': 'contourSource',
+ 'source-layer': 'elevation',
+ 'layout': {},
+ 'paint': {
+ 'line-color': '#f00',
+ 'line-width': 1,
+ },
+ layout: {
+ 'line-join': 'round',
+ 'line-cap': 'round',
+ },
},
],
},
@@ -52,6 +74,8 @@
maxPitch: 85
}));
+ // map.showTileBoundaries = true;
+
map.addControl(
new maplibregl.NavigationControl({
visualizePitch: false,