31
31
import com .linecorp .armeria .server .annotation .Param ;
32
32
import com .linecorp .armeria .server .cors .CorsService ;
33
33
import com .linecorp .armeria .server .docs .DocService ;
34
+ import com .linecorp .armeria .server .file .HttpFile ;
34
35
import java .awt .*;
35
36
import java .awt .image .BufferedImage ;
36
37
import java .io .ByteArrayOutputStream ;
37
38
import java .io .IOException ;
38
39
import java .net .URL ;
39
40
import java .nio .ByteBuffer ;
41
+ import java .util .ArrayList ;
42
+ import java .util .List ;
43
+ import java .util .Map ;
40
44
import java .util .concurrent .Callable ;
41
45
import java .util .function .Function ;
42
46
import java .util .function .Supplier ;
47
+ import java .util .zip .GZIPOutputStream ;
43
48
import javax .imageio .ImageIO ;
44
- import org .apache .baremaps .raster .elevation .ElevationUtils ;
45
- import org .apache .baremaps .raster .elevation .HillshadeCalculator ;
49
+ import org .apache .baremaps .maplibre .vectortile .*;
50
+ import org .apache .baremaps .raster .elevation .*;
51
+ import org .apache .baremaps .server .TileResource ;
46
52
import org .apache .baremaps .tilestore .TileCoord ;
47
53
import org .apache .baremaps .tilestore .TileStore ;
48
54
import org .apache .baremaps .tilestore .TileStoreException ;
55
+ import org .locationtech .jts .geom .util .AffineTransformation ;
49
56
import org .slf4j .Logger ;
50
57
import org .slf4j .LoggerFactory ;
51
58
import picocli .CommandLine .Command ;
52
59
import picocli .CommandLine .Option ;
53
60
54
61
@ Command (name = "hillshade" , description = "Start a tile server that computes hillshades." )
55
- public class HillShade implements Callable <Integer > {
62
+ public class Hillshade implements Callable <Integer > {
56
63
57
64
@ Option (names = {"--host" }, paramLabel = "HOST" , description = "The host of the server." )
58
65
private String host = "localhost" ;
@@ -68,11 +75,22 @@ public Integer call() throws Exception {
68
75
69
76
var objectMapper = objectMapper ();
70
77
var jsonResponseConverter = new JacksonResponseConverterFunction (objectMapper );
71
- var tileStore = new HillShadeTileStore ();
72
78
73
- serverBuilder .annotatedService (new HillShadeTileResource (() -> tileStore ),
79
+ LoadingCache <TileCoord , BufferedImage > cache = Caffeine .newBuilder ()
80
+ .maximumSize (1000 )
81
+ .build (this ::getImage );
82
+
83
+ var rasterHillshadeTileStore = new RasterHillshadeTileStore (cache );
84
+ serverBuilder .annotatedService (new HillShadeTileResource (() -> rasterHillshadeTileStore ),
85
+ jsonResponseConverter );
86
+
87
+ var contourTileStore = new ContourTileStore (cache );
88
+ serverBuilder .annotatedService (new TileResource (() -> contourTileStore ),
74
89
jsonResponseConverter );
75
90
91
+ var index = HttpFile .of (ClassLoader .getSystemClassLoader (), "/raster/hillshade.html" );
92
+ serverBuilder .service ("/" , index .asService ());
93
+
76
94
serverBuilder .decorator (CorsService .builderForAnyOrigin ()
77
95
.allowAllRequestHeaders (true )
78
96
.allowRequestMethods (
@@ -102,63 +120,59 @@ public Integer call() throws Exception {
102
120
return 0 ;
103
121
}
104
122
105
- public static class HillShadeTileStore implements TileStore {
123
+ private String url = "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png" ;
106
124
107
- // private String url = "https://s3.amazonaws.com/elevation-tiles-prod/geotiff/{z}/{x}/{y}.tif";
108
- // private String url = "https://demotiles.maplibre.org/terrain-tiles/{z}/{x}/{y}.png";
109
- private String url = "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/ {z}/{x}/{y}.png" ;
110
-
111
- private final LoadingCache < TileCoord , BufferedImage > cache = Caffeine . newBuilder ()
112
- . maximumSize ( 1000 )
113
- . build ( this :: getImage );
125
+ public BufferedImage getImage ( TileCoord tileCoord ) throws IOException {
126
+ var tileUrl = new URL ( this . url
127
+ . replace ( " {z}" , String . valueOf ( tileCoord . z ()))
128
+ . replace ( "{x}" , String . valueOf ( tileCoord . x ()))
129
+ . replace ( "{y}" , String . valueOf ( tileCoord . y ())));
130
+ return ImageIO . read ( tileUrl );
131
+ }
114
132
115
- public HillShadeTileStore () {
116
- // Default constructor
133
+ public static BufferedImage getKernel (TileCoord tileCoord ,
134
+ Function <TileCoord , BufferedImage > provider ) {
135
+ BufferedImage z1 =
136
+ provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y () - 1 , tileCoord .z ()));
137
+ BufferedImage z2 =
138
+ provider .apply (new TileCoord (tileCoord .x (), tileCoord .y () - 1 , tileCoord .z ()));
139
+ BufferedImage z3 =
140
+ provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y () - 1 , tileCoord .z ()));
141
+ BufferedImage z4 =
142
+ provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y (), tileCoord .z ()));
143
+ BufferedImage z5 = provider .apply (tileCoord );
144
+ BufferedImage z6 =
145
+ provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y (), tileCoord .z ()));
146
+ BufferedImage z7 =
147
+ provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y () + 1 , tileCoord .z ()));
148
+ BufferedImage z8 =
149
+ provider .apply (new TileCoord (tileCoord .x (), tileCoord .y () + 1 , tileCoord .z ()));
150
+ BufferedImage z9 =
151
+ provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y () + 1 , tileCoord .z ()));
152
+ int kernelSize = z5 .getWidth () * 3 ;
153
+ BufferedImage kernel = new BufferedImage (kernelSize , kernelSize , z5 .getType ());
154
+ for (int y = 0 ; y < z5 .getHeight (); y ++) {
155
+ for (int x = 0 ; x < z5 .getWidth (); x ++) {
156
+ kernel .setRGB (x , y , z1 .getRGB (x , y ));
157
+ kernel .setRGB (x + z5 .getWidth (), y , z2 .getRGB (x , y ));
158
+ kernel .setRGB (x + 2 * z5 .getWidth (), y , z3 .getRGB (x , y ));
159
+ kernel .setRGB (x , y + z5 .getHeight (), z4 .getRGB (x , y ));
160
+ kernel .setRGB (x + z5 .getWidth (), y + z5 .getHeight (), z5 .getRGB (x , y ));
161
+ kernel .setRGB (x + 2 * z5 .getWidth (), y + z5 .getHeight (), z6 .getRGB (x , y ));
162
+ kernel .setRGB (x , y + 2 * z5 .getHeight (), z7 .getRGB (x , y ));
163
+ kernel .setRGB (x + z5 .getWidth (), y + 2 * z5 .getHeight (), z8 .getRGB (x , y ));
164
+ kernel .setRGB (x + 2 * z5 .getWidth (), y + 2 * z5 .getHeight (), z9 .getRGB (x , y ));
165
+ }
117
166
}
167
+ return kernel ;
168
+ }
118
169
119
- public BufferedImage getImage (TileCoord tileCoord ) throws IOException {
120
- var tileUrl = new URL (this .url
121
- .replace ("{z}" , String .valueOf (tileCoord .z ()))
122
- .replace ("{x}" , String .valueOf (tileCoord .x ()))
123
- .replace ("{y}" , String .valueOf (tileCoord .y ())));
124
- return ImageIO .read (tileUrl );
125
- }
170
+ public static class RasterHillshadeTileStore implements TileStore {
126
171
127
- public BufferedImage getKernel (TileCoord tileCoord , Function <TileCoord , BufferedImage > provider )
128
- throws IOException {
129
- BufferedImage z1 =
130
- provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y () - 1 , tileCoord .z ()));
131
- BufferedImage z2 =
132
- provider .apply (new TileCoord (tileCoord .x (), tileCoord .y () - 1 , tileCoord .z ()));
133
- BufferedImage z3 =
134
- provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y () - 1 , tileCoord .z ()));
135
- BufferedImage z4 =
136
- provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y (), tileCoord .z ()));
137
- BufferedImage z5 = provider .apply (tileCoord );
138
- BufferedImage z6 =
139
- provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y (), tileCoord .z ()));
140
- BufferedImage z7 =
141
- provider .apply (new TileCoord (tileCoord .x () - 1 , tileCoord .y () + 1 , tileCoord .z ()));
142
- BufferedImage z8 =
143
- provider .apply (new TileCoord (tileCoord .x (), tileCoord .y () + 1 , tileCoord .z ()));
144
- BufferedImage z9 =
145
- provider .apply (new TileCoord (tileCoord .x () + 1 , tileCoord .y () + 1 , tileCoord .z ()));
146
- int kernelSize = z5 .getWidth () * 3 ;
147
- BufferedImage kernel = new BufferedImage (kernelSize , kernelSize , z5 .getType ());
148
- for (int y = 0 ; y < z5 .getHeight (); y ++) {
149
- for (int x = 0 ; x < z5 .getWidth (); x ++) {
150
- kernel .setRGB (x , y , z1 .getRGB (x , y ));
151
- kernel .setRGB (x + z5 .getWidth (), y , z2 .getRGB (x , y ));
152
- kernel .setRGB (x + 2 * z5 .getWidth (), y , z3 .getRGB (x , y ));
153
- kernel .setRGB (x , y + z5 .getHeight (), z4 .getRGB (x , y ));
154
- kernel .setRGB (x + z5 .getWidth (), y + z5 .getHeight (), z5 .getRGB (x , y ));
155
- kernel .setRGB (x + 2 * z5 .getWidth (), y + z5 .getHeight (), z6 .getRGB (x , y ));
156
- kernel .setRGB (x , y + 2 * z5 .getHeight (), z7 .getRGB (x , y ));
157
- kernel .setRGB (x + z5 .getWidth (), y + 2 * z5 .getHeight (), z8 .getRGB (x , y ));
158
- kernel .setRGB (x + 2 * z5 .getWidth (), y + 2 * z5 .getHeight (), z9 .getRGB (x , y ));
159
- }
160
- }
161
- return kernel ;
172
+ private final LoadingCache <TileCoord , BufferedImage > cache ;
173
+
174
+ public RasterHillshadeTileStore (LoadingCache <TileCoord , BufferedImage > cache ) {
175
+ this .cache = cache ;
162
176
}
163
177
164
178
@ Override
@@ -174,16 +188,16 @@ public ByteBuffer read(TileCoord tileCoord) throws TileStoreException {
174
188
image .getHeight () + 2 );
175
189
176
190
var grid = ElevationUtils .imageToGrid (buffer );
177
- var hillshade =
178
- new HillshadeCalculator (grid , buffer .getWidth (), buffer .getHeight (), 1 , true )
191
+ var hillshadeGrid =
192
+ new HillshadeCalculator (grid , buffer .getWidth (), buffer .getHeight (), 1 , false )
179
193
.calculate (45 , 315 );
180
194
181
195
// Create an output image
182
196
BufferedImage hillshadeImage =
183
197
new BufferedImage (image .getWidth (), image .getHeight (), BufferedImage .TYPE_BYTE_GRAY );
184
198
for (int y = 0 ; y < image .getHeight (); y ++) {
185
199
for (int x = 0 ; x < image .getWidth (); x ++) {
186
- int value = (int ) hillshade [(y + 1 ) * buffer .getHeight () + x + 1 ];
200
+ int value = (int ) hillshadeGrid [(y + 1 ) * buffer .getHeight () + x + 1 ];
187
201
hillshadeImage .setRGB (x , y , new Color (value , value , value ).getRGB ());
188
202
}
189
203
}
@@ -229,7 +243,7 @@ public HillShadeTileResource(Supplier<TileStore> tileStoreSupplier) {
229
243
this .tileStoreSupplier = tileStoreSupplier ;
230
244
}
231
245
232
- @ Get ("regex:^/tiles /(?<z>[0-9]+)/(?<x>[0-9]+)/(?<y>[0-9]+).png" )
246
+ @ Get ("regex:^/raster /(?<z>[0-9]+)/(?<x>[0-9]+)/(?<y>[0-9]+).png" )
233
247
@ Blocking
234
248
public HttpResponse tile (@ Param ("z" ) int z , @ Param ("x" ) int x , @ Param ("y" ) int y ) {
235
249
TileCoord tileCoord = new TileCoord (x , y , z );
@@ -255,4 +269,76 @@ public HttpResponse tile(@Param("z") int z, @Param("x") int x, @Param("y") int y
255
269
}
256
270
}
257
271
}
272
+
273
+ public static class ContourTileStore implements TileStore {
274
+
275
+ private final LoadingCache <TileCoord , BufferedImage > cache ;
276
+
277
+ public ContourTileStore (LoadingCache <TileCoord , BufferedImage > cache ) {
278
+ this .cache = cache ;
279
+ }
280
+
281
+ @ Override
282
+ public ByteBuffer read (TileCoord tileCoord ) throws TileStoreException {
283
+ var image = cache .get (tileCoord );
284
+
285
+ var kernel = getKernel (tileCoord , cache ::get );
286
+
287
+ image = kernel .getSubimage (
288
+ image .getWidth () - 4 ,
289
+ image .getHeight () - 4 ,
290
+ image .getWidth () + 8 ,
291
+ image .getHeight () + 8 );
292
+
293
+ var grid = ElevationUtils .imageToGrid (image );
294
+
295
+ var features = new ArrayList <Feature >();
296
+
297
+ for (int level = 0 ; level < 9000 ; level += 100 ) {
298
+ var contours = new ContourTracer (grid , image .getWidth (), image .getHeight (), false , false )
299
+ .traceContours (level );
300
+
301
+ for (var contour : contours ) {
302
+
303
+ contour = AffineTransformation
304
+ .translationInstance (-4 , -4 )
305
+ .scaleInstance (16 , 16 )
306
+ .transform (contour );
307
+
308
+ // contour = new ChaikinSmoother(2, 0.25).transform(contour);
309
+
310
+ features .add (new Feature (level , Map .of ("level" , String .valueOf (level )), contour ));
311
+ }
312
+ }
313
+
314
+ var layer = new Layer ("elevation" , 4096 , features );
315
+ var tile = new Tile (List .of (layer ));
316
+ var vectorTile = new VectorTileEncoder ().encodeTile (tile );
317
+ try (var baos = new ByteArrayOutputStream ()) {
318
+ var gzip = new GZIPOutputStream (baos );
319
+ vectorTile .writeTo (gzip );
320
+ gzip .close ();
321
+ return ByteBuffer .wrap (baos .toByteArray ());
322
+ } catch (IOException e ) {
323
+ throw new RuntimeException (e );
324
+ }
325
+
326
+ }
327
+
328
+ @ Override
329
+ public void write (TileCoord tileCoord , ByteBuffer blob ) throws TileStoreException {
330
+ throw new UnsupportedOperationException ();
331
+ }
332
+
333
+ @ Override
334
+ public void delete (TileCoord tileCoord ) throws TileStoreException {
335
+ throw new UnsupportedOperationException ();
336
+ }
337
+
338
+ @ Override
339
+ public void close () throws Exception {
340
+ // Do nothing
341
+ }
342
+ }
343
+
258
344
}
0 commit comments