Skip to content

Commit 2ee536a

Browse files
committed
Improve hillshade and contour map
1 parent 533c32f commit 2ee536a

File tree

15 files changed

+415
-169
lines changed

15 files changed

+415
-169
lines changed

baremaps-cli/src/main/java/org/apache/baremaps/cli/hillshade/Serve.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
import org.apache.baremaps.tilestore.BufferedImageTileCache;
3434
import org.apache.baremaps.tilestore.raster.BufferedImageTileStore;
3535
import org.apache.baremaps.tilestore.raster.ContourTileStore;
36-
import org.apache.baremaps.tilestore.raster.HillshadeTileStore;
36+
import org.apache.baremaps.tilestore.raster.RasterHillshadeTileStore;
37+
import org.apache.baremaps.tilestore.raster.VectorHillshadeTileStore;
3738
import picocli.CommandLine.Command;
3839
import picocli.CommandLine.Option;
3940

@@ -60,19 +61,29 @@ public Integer call() throws Exception {
6061
var bufferedImageTileCache =
6162
new BufferedImageTileCache(bufferedImageTileStore, CaffeineSpec.parse("maximumSize=1000"));
6263
var hillshadeTileStore =
63-
new HillshadeTileStore(bufferedImageTileCache, ElevationUtils::pixelToElevationTerrarium);
64+
new RasterHillshadeTileStore(bufferedImageTileCache,
65+
ElevationUtils::pixelToElevationTerrarium);
66+
6467
var contourTileStore = new ContourTileStore(bufferedImageTileCache);
6568

69+
var vectorHillshadeTileStore = new VectorHillshadeTileStore(bufferedImageTileCache,
70+
ElevationUtils::pixelToElevationTerrarium);
71+
6672
serverBuilder.annotatedService(
6773
"/raster",
6874
new RasterTileResource(() -> hillshadeTileStore),
6975
jsonResponseConverter);
7076

7177
serverBuilder.annotatedService(
72-
"/tiles",
78+
"/contour",
7379
new VectorTileResource(() -> contourTileStore),
7480
jsonResponseConverter);
7581

82+
serverBuilder.annotatedService(
83+
"/tiles",
84+
new VectorTileResource(() -> vectorHillshadeTileStore),
85+
jsonResponseConverter);
86+
7687
var index = HttpFile.of(ClassLoader.getSystemClassLoader(), "/raster/hillshade.html");
7788
serverBuilder.service("/", index.asService());
7889

baremaps-core/src/main/java/org/apache/baremaps/tilestore/raster/ContourTileStore.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
import org.apache.baremaps.maplibre.vectortile.Layer;
2929
import org.apache.baremaps.maplibre.vectortile.Tile;
3030
import org.apache.baremaps.maplibre.vectortile.VectorTileEncoder;
31+
import org.apache.baremaps.raster.ContourTracer;
3132
import org.apache.baremaps.raster.ElevationUtils;
32-
import org.apache.baremaps.raster.PolygonContourTracer;
3333
import org.apache.baremaps.tilestore.TileCoord;
3434
import org.apache.baremaps.tilestore.TileStore;
3535
import org.apache.baremaps.tilestore.TileStoreException;
@@ -55,11 +55,11 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
5555
image.getHeight() + 8);
5656

5757
var grid = ElevationUtils.imageToGrid(image, ElevationUtils::pixelToElevationTerrarium);
58+
5859
var features = new ArrayList<Feature>();
5960
for (int level = -10000; level < 10000; level += 100) {
60-
6161
var contours =
62-
new PolygonContourTracer(grid, image.getWidth(), image.getHeight(), false, true)
62+
new ContourTracer(grid, image.getWidth(), image.getHeight(), false, false)
6363
.traceContours(level);
6464
for (var contour : contours) {
6565
contour = AffineTransformation
@@ -70,7 +70,7 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
7070
}
7171
}
7272

73-
var layer = new Layer("elevation", 4096, features);
73+
var layer = new Layer("contour", 4096, features);
7474
var tile = new Tile(List.of(layer));
7575
var vectorTile = new VectorTileEncoder().encodeTile(tile);
7676
try (var baos = new ByteArrayOutputStream()) {

baremaps-core/src/main/java/org/apache/baremaps/tilestore/raster/HillshadeTileStore.java renamed to baremaps-core/src/main/java/org/apache/baremaps/tilestore/raster/RasterHillshadeTileStore.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package org.apache.baremaps.tilestore.raster;
1919

20+
import static org.apache.baremaps.raster.HillshadeCalculator.getResolution;
21+
2022
import java.awt.*;
2123
import java.awt.image.BufferedImage;
2224
import java.io.ByteArrayOutputStream;
@@ -30,13 +32,13 @@
3032
import org.apache.baremaps.tilestore.TileStore;
3133
import org.apache.baremaps.tilestore.TileStoreException;
3234

33-
public class HillshadeTileStore implements TileStore<ByteBuffer> {
35+
public class RasterHillshadeTileStore implements TileStore<ByteBuffer> {
3436

3537
private final TileStore<BufferedImage> tileStore;
3638

3739
private final IntToDoubleFunction pixelToElevation;
3840

39-
public HillshadeTileStore(TileStore<BufferedImage> tileStore,
41+
public RasterHillshadeTileStore(TileStore<BufferedImage> tileStore,
4042
IntToDoubleFunction pixelToElevation) {
4143
this.tileStore = tileStore;
4244
this.pixelToElevation = pixelToElevation;
@@ -45,25 +47,24 @@ public HillshadeTileStore(TileStore<BufferedImage> tileStore,
4547
@Override
4648
public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
4749
try {
48-
var image = tileStore.read(tileCoord);
49-
var onion = BufferedImageTileStore.onion(tileStore, tileCoord, 1);
50-
var buffer = onion.getSubimage(
51-
image.getWidth() - 1,
52-
image.getHeight() - 1,
53-
image.getWidth() + 2,
54-
image.getHeight() + 2);
50+
var size = 256;
51+
var buffer = BufferedImageTileStore.onion(tileStore, tileCoord, 1).getSubimage(
52+
size - 1,
53+
size - 1,
54+
size + 2,
55+
size + 2);
5556

56-
var grid = ElevationUtils.imageToGrid(buffer, pixelToElevation);
57-
var hillshadeGrid =
58-
new HillshadeCalculator(grid, buffer.getWidth(), buffer.getHeight(), 1, false)
57+
var grid = new HillshadeCalculator(
58+
ElevationUtils.clampGrid(ElevationUtils.imageToGrid(buffer, pixelToElevation), 0, 10000),
59+
size + 2, size + 2, getResolution(tileCoord.z()))
5960
.calculate(45, 315);
6061

6162
// Create an output image
6263
BufferedImage hillshadeImage =
63-
new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
64-
for (int y = 0; y < image.getHeight(); y++) {
65-
for (int x = 0; x < image.getWidth(); x++) {
66-
int value = (int) hillshadeGrid[(y + 1) * buffer.getHeight() + x + 1];
64+
new BufferedImage(size, size, BufferedImage.TYPE_BYTE_GRAY);
65+
for (int y = 0; y < size; y++) {
66+
for (int x = 0; x < size; x++) {
67+
int value = (int) grid[(y + 1) * buffer.getHeight() + x + 1];
6768
hillshadeImage.setRGB(x, y, new Color(value, value, value).getRGB());
6869
}
6970
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.baremaps.tilestore.raster;
19+
20+
import java.awt.image.BufferedImage;
21+
import java.io.ByteArrayOutputStream;
22+
import java.io.IOException;
23+
import java.nio.ByteBuffer;
24+
import java.util.ArrayList;
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.function.IntToDoubleFunction;
28+
import java.util.zip.GZIPOutputStream;
29+
import org.apache.baremaps.maplibre.vectortile.Feature;
30+
import org.apache.baremaps.maplibre.vectortile.Layer;
31+
import org.apache.baremaps.maplibre.vectortile.Tile;
32+
import org.apache.baremaps.maplibre.vectortile.VectorTileEncoder;
33+
import org.apache.baremaps.raster.ContourTracer;
34+
import org.apache.baremaps.raster.ElevationUtils;
35+
import org.apache.baremaps.raster.HillshadeCalculator;
36+
import org.apache.baremaps.tilestore.TileCoord;
37+
import org.apache.baremaps.tilestore.TileStore;
38+
import org.apache.baremaps.tilestore.TileStoreException;
39+
import org.locationtech.jts.geom.util.AffineTransformation;
40+
import org.locationtech.jts.simplify.TopologyPreservingSimplifier;
41+
42+
public class VectorHillshadeTileStore implements TileStore<ByteBuffer> {
43+
44+
private final TileStore<BufferedImage> tileStore;
45+
46+
private final IntToDoubleFunction pixelToElevation;
47+
48+
public VectorHillshadeTileStore(TileStore<BufferedImage> tileStore,
49+
IntToDoubleFunction pixelToElevation) {
50+
this.tileStore = tileStore;
51+
this.pixelToElevation = pixelToElevation;
52+
}
53+
54+
@Override
55+
public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
56+
try {
57+
58+
var size = 256;
59+
60+
// Read the elevation data
61+
var image = BufferedImageTileStore.onion(tileStore, tileCoord, 1).getSubimage(
62+
size - 16,
63+
size - 16,
64+
size + 32,
65+
size + 32);
66+
67+
var features = new ArrayList<Feature>();
68+
69+
// Calculate the hillshade
70+
var grid = new HillshadeCalculator(
71+
ElevationUtils.clampGrid(ElevationUtils.imageToGrid(image, pixelToElevation), 0, 10000),
72+
size + 32, size + 32, HillshadeCalculator.getResolution(tileCoord.z()))
73+
.calculate(45, 315);
74+
75+
contours(grid, 255 - 16, features, "1");
76+
contours(grid, 255 - 32, features, "2");
77+
78+
grid = ElevationUtils.invertGrid(grid);
79+
contours(grid, 255 - 32, features, "6");
80+
contours(grid, 255 - 64, features, "5");
81+
contours(grid, 255 - 98, features, "4");
82+
contours(grid, 255 - 128, features, "3");
83+
84+
85+
86+
var layer = new Layer("elevation", 4096, features);
87+
var tile = new Tile(List.of(layer));
88+
var vectorTile = new VectorTileEncoder().encodeTile(tile);
89+
try (var baos = new ByteArrayOutputStream()) {
90+
var gzip = new GZIPOutputStream(baos);
91+
vectorTile.writeTo(gzip);
92+
gzip.close();
93+
return ByteBuffer.wrap(baos.toByteArray());
94+
}
95+
} catch (IOException e) {
96+
throw new TileStoreException(e);
97+
}
98+
}
99+
100+
private static void contours(double[] grid, int level, ArrayList<Feature> features, String id) {
101+
var contours =
102+
new ContourTracer(grid, (int) Math.sqrt(grid.length), (int) Math.sqrt(grid.length), false,
103+
true)
104+
.traceContours(level);
105+
for (var contour : contours) {
106+
contour = AffineTransformation
107+
.translationInstance(-16, -16)
108+
.scale(16, 16)
109+
.transform(contour);
110+
111+
contour = TopologyPreservingSimplifier.simplify(contour, 4);
112+
features.add(new Feature(4, Map.of("level", id), contour));
113+
}
114+
}
115+
116+
@Override
117+
public void write(TileCoord tileCoord, ByteBuffer blob) throws TileStoreException {
118+
throw new UnsupportedOperationException();
119+
}
120+
121+
@Override
122+
public void delete(TileCoord tileCoord) throws TileStoreException {
123+
throw new UnsupportedOperationException();
124+
}
125+
126+
@Override
127+
public void close() throws Exception {
128+
// Do nothing
129+
}
130+
}

0 commit comments

Comments
 (0)