alexdum commited on
Commit
07004f3
·
1 Parent(s): 3ba8423

feat: implement OpenFreeMap vector styles and a hybrid Sentinel-2 basemap with dynamic label visibility, replacing previous basemap options.

Browse files
Files changed (3) hide show
  1. global.R +8 -0
  2. server.R +99 -102
  3. ui.R +4 -7
global.R CHANGED
@@ -10,6 +10,14 @@ library(plotly)
10
  library(writexl)
11
 
12
 
 
 
 
 
 
 
 
 
13
  # Load Data
14
  stations_file <- "data/jma_stations_full.csv"
15
  if (!file.exists(stations_file)) {
 
10
  library(writexl)
11
 
12
 
13
+ # OpenFreeMap Style URLs
14
+ ofm_positron_style <- "https://tiles.openfreemap.org/styles/positron"
15
+ ofm_bright_style <- "https://tiles.openfreemap.org/styles/bright"
16
+
17
+ # EOX Sentinel-2 Cloudless (Free for commercial use with attribution)
18
+ sentinel_url <- "https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2023_3857/default/GoogleMapsCompatible/{z}/{y}/{x}.jpg"
19
+ sentinel_attribution <- '<a href="https://s2maps.eu" target="_blank">Sentinel-2 cloudless - by EOX IT Services GmbH</a> (Contains modified Copernicus Sentinel data 2023)'
20
+
21
  # Load Data
22
  stations_file <- "data/jma_stations_full.csv"
23
  if (!file.exists(stations_file)) {
server.R CHANGED
@@ -267,7 +267,7 @@ server <- function(input, output, session) {
267
 
268
  output$map <- renderMaplibre({
269
  maplibre(
270
- style = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
271
  center = c(138, 36),
272
  zoom = 4
273
  ) %>%
@@ -318,9 +318,66 @@ server <- function(input, output, session) {
318
  }
319
  })
320
 
321
- # --- Basemap Switching ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
322
  observeEvent(input$basemap, {
323
  proxy <- maplibre_proxy("map")
 
324
 
325
  # Explicitly remove any previously added raster layers
326
  old_layers <- isolate(current_raster_layers())
@@ -331,28 +388,21 @@ server <- function(input, output, session) {
331
  current_raster_layers(character(0))
332
  }
333
 
334
- if (input$basemap %in% c("carto_positron", "carto_voyager")) {
335
- # VECTOR LOGIC (Carto-based styles)
336
- style_url <- switch(input$basemap,
337
- "carto_positron" = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
338
- "carto_voyager" = "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
339
  )
340
 
341
  proxy %>%
342
- set_style(style_url)
343
 
344
- # For vector sandwich, we want stations below labels
345
- stations_before_id("watername_ocean")
346
- style_change_trigger(isolate(style_change_trigger()) + 1)
347
- } else if (input$basemap == "esri_imagery") {
348
- # Esri Imagery (Raster + Styles?) - DWD implementation uses Voyager style base + Raster layer
349
- proxy %>%
350
- set_style("https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json")
351
-
352
- stations_before_id("watername_ocean")
353
 
354
  current_session <- shiny::getDefaultReactiveDomain()
355
- selected_basemap <- input$basemap
356
 
357
  later::later(function() {
358
  shiny::withReactiveDomain(current_session, {
@@ -362,61 +412,19 @@ server <- function(input, output, session) {
362
  return()
363
  }
364
 
365
- unique_suffix <- as.numeric(Sys.time()) * 1000
366
- source_id <- paste0("esri_imagery_source_", unique_suffix)
367
- layer_id <- paste0("esri_imagery_layer_", unique_suffix)
368
-
369
- esri_url <- "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}"
370
-
371
- maplibre_proxy("map") %>%
372
- add_raster_source(id = source_id, tiles = c(esri_url), tileSize = 256) %>%
373
- add_layer(
374
- id = layer_id,
375
- type = "raster",
376
- source = source_id,
377
- paint = list("raster-opacity" = 1),
378
- before_id = "watername_ocean"
379
- )
380
-
381
- current_raster_layers(c(layer_id))
382
  style_change_trigger(isolate(style_change_trigger()) + 1)
383
  })
384
  }, delay = 0.5)
385
- } else {
386
- # RASTER LOGIC (Esri Topo, OSM)
387
- tile_url <- if (input$basemap %in% c("osm", "osm_gray")) {
388
- "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
389
- } else {
390
- "https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}"
391
- }
392
-
393
- attribution_text <- if (input$basemap %in% c("osm", "osm_gray")) {
394
- '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
395
- } else {
396
- "Tiles &copy; Esri"
397
- }
398
-
399
- paint_props <- list("raster-opacity" = 1)
400
- if (input$basemap == "osm_gray") {
401
- paint_props[["raster-saturation"]] <- -0.9
402
- paint_props[["raster-contrast"]] <- 0.3
403
- }
404
-
405
- # Use blank style + raster layer
406
- blank_style <- list(
407
- version = 8,
408
- sources = list(),
409
- layers = list(),
410
- metadata = list(timestamp = as.numeric(Sys.time()))
411
- )
412
- json_blank <- jsonlite::toJSON(blank_style, auto_unbox = TRUE)
413
- blank_uri <- paste0("data:application/json,", URLencode(as.character(json_blank), reserved = TRUE))
414
-
415
  proxy %>%
416
- set_style(blank_uri)
417
 
418
  current_session <- shiny::getDefaultReactiveDomain()
419
- selected_basemap <- input$basemap
420
 
421
  later::later(function() {
422
  shiny::withReactiveDomain(current_session, {
@@ -426,19 +434,37 @@ server <- function(input, output, session) {
426
  }
427
 
428
  unique_suffix <- as.numeric(Sys.time()) * 1000
429
- source_id <- paste0("raster_source_", unique_suffix)
430
- layer_id <- paste0("raster_layer_", unique_suffix)
431
 
 
432
  maplibre_proxy("map") %>%
433
- add_raster_source(id = source_id, tiles = c(tile_url), tileSize = 256, attribution = attribution_text) %>%
434
  add_layer(
435
  id = layer_id,
436
  type = "raster",
437
  source = source_id,
438
- paint = paint_props
 
 
 
 
 
 
 
 
 
 
 
 
439
  )
 
 
 
 
440
 
441
- stations_before_id(NULL)
 
442
  current_raster_layers(c(layer_id))
443
  style_change_trigger(isolate(style_change_trigger()) + 1)
444
  })
@@ -449,36 +475,7 @@ server <- function(input, output, session) {
449
  # Toggle Labels visibility
450
  observeEvent(input$show_labels,
451
  {
452
- visibility <- if (input$show_labels) "visible" else "none"
453
-
454
- label_layers <- c(
455
- "place_villages", "place_town", "place_country_2", "place_country_1",
456
- "place_state", "place_continent",
457
- "place_city_r6", "place_city_r5", "place_city_dot_r7", "place_city_dot_r4",
458
- "place_city_dot_r2", "place_city_dot_z7",
459
- "place_capital_dot_z7", "place_capital",
460
- "roadname_minor", "roadname_sec", "roadname_pri", "roadname_major",
461
- "motorway_name",
462
- "watername_ocean", "watername_sea", "watername_lake", "watername_lake_line",
463
- "poi_stadium", "poi_park", "poi_zoo",
464
- "airport_label",
465
- "country-label", "state-label", "settlement-major-label", "settlement-minor-label",
466
- "settlement-subdivision-label", "road-label", "waterway-label", "natural-point-label",
467
- "poi-label", "airport-label"
468
- )
469
-
470
- proxy <- maplibre_proxy("map")
471
-
472
- for (layer_id in label_layers) {
473
- tryCatch(
474
- {
475
- proxy <- proxy %>% set_layout_property(layer_id, "visibility", visibility)
476
- },
477
- error = function(e) {
478
- # Layer may not exist in current style, ignore silently
479
- }
480
- )
481
- }
482
  },
483
  ignoreInit = TRUE
484
  )
 
267
 
268
  output$map <- renderMaplibre({
269
  maplibre(
270
+ style = ofm_positron_style,
271
  center = c(138, 36),
272
  zoom = 4
273
  ) %>%
 
318
  }
319
  })
320
 
321
+ # --- Basemap Switching Logic ---
322
+ label_layer_ids <- c(
323
+ # OpenFreeMap Positron & Bright common labels
324
+ "waterway_line_label", "water_name_point_label", "water_name_line_label",
325
+ "highway-name-path", "highway-name-minor", "highway-name-major",
326
+ "highway-shield-non-us", "highway-shield-us-interstate", "road_shield_us",
327
+ "airport", "label_other", "label_village", "label_town", "label_state",
328
+ "label_city", "label_city_capital", "label_country_3", "label_country_2", "label_country_1",
329
+ # Bright specific labels (POIs & Directions)
330
+ "road_oneway", "road_oneway_opposite", "poi_r20", "poi_r7", "poi_r1", "poi_transit",
331
+ # Dash variants (sometimes used in different versions)
332
+ "waterway-line-label", "water-name-point-label", "water-name-line-label",
333
+ "highway-shield-non-us", "highway-shield-us-interstate", "road-shield-us",
334
+ "label-other", "label-village", "label-town", "label-state",
335
+ "label-city", "label-city-capital", "label-country-3", "label-country-2", "label-country-1",
336
+ # Legacy/Carto/OSM label names (for compatibility)
337
+ "place_villages", "place_town", "place_country_2", "place_country_1",
338
+ "place_state", "place_continent", "place_city_r6", "place_city_r5",
339
+ "place_city_dot_r7", "place_city_dot_r4", "place_city_dot_r2", "place_city_dot_z7",
340
+ "place_capital_dot_z7", "place_capital", "roadname_minor", "roadname_sec",
341
+ "roadname_pri", "roadname_major", "motorway_name", "watername_ocean",
342
+ "watername_sea", "watername_lake", "watername_lake_line", "poi_stadium",
343
+ "poi_park", "poi_zoo", "airport_label", "country-label", "state-label",
344
+ "settlement-major-label", "settlement-minor-label", "settlement-subdivision-label",
345
+ "road-label", "waterway-label", "natural-point-label", "poi-label", "airport-label"
346
+ )
347
+
348
+ # Layers to hide in Satellite (Hybrid) mode to let the raster shows through
349
+ # This includes all non-symbol layers (background, water, roads, boundaries, etc.)
350
+ non_label_layer_ids <- c(
351
+ "background", "park", "water", "landcover_ice_shelf", "landcover_glacier",
352
+ "landuse_residential", "landcover_wood", "waterway", "building",
353
+ "tunnel_motorway_casing", "tunnel_motorway_inner", "aeroway-taxiway",
354
+ "aeroway-runway-casing", "aeroway-area", "aeroway-runway",
355
+ "road_area_pier", "road_pier", "highway_path", "highway_minor",
356
+ "highway_major_casing", "highway_major_inner", "highway_major_subtle",
357
+ "highway_motorway_casing", "highway_motorway_inner", "highway_motorway_subtle",
358
+ "railway_transit", "railway_transit_dashline", "railway_service",
359
+ "railway_service_dashline", "railway", "railway_dashline",
360
+ "highway_motorway_bridge_casing", "highway_motorway_bridge_inner",
361
+ "boundary_3", "boundary_2", "boundary_disputed"
362
+ )
363
+
364
+ apply_label_visibility <- function(proxy, show_labels) {
365
+ visibility <- if (isTRUE(show_labels)) "visible" else "none"
366
+ for (layer_id in label_layer_ids) {
367
+ tryCatch(
368
+ {
369
+ proxy %>% set_layout_property(layer_id, "visibility", visibility)
370
+ },
371
+ error = function(e) {
372
+ # Layer may not exist in current style, ignore silently
373
+ }
374
+ )
375
+ }
376
+ }
377
+
378
  observeEvent(input$basemap, {
379
  proxy <- maplibre_proxy("map")
380
+ basemap <- input$basemap
381
 
382
  # Explicitly remove any previously added raster layers
383
  old_layers <- isolate(current_raster_layers())
 
388
  current_raster_layers(character(0))
389
  }
390
 
391
+ if (basemap %in% c("ofm_positron", "ofm_bright")) {
392
+ # VECTOR LOGIC (OpenFreeMap styles)
393
+ style_url <- switch(basemap,
394
+ "ofm_positron" = ofm_positron_style,
395
+ "ofm_bright" = ofm_bright_style
396
  )
397
 
398
  proxy %>%
399
+ set_style(style_url, preserve_layers = FALSE)
400
 
401
+ # For OpenFreeMap sandwich, we want stations below labels
402
+ stations_before_id("waterway_line_label")
 
 
 
 
 
 
 
403
 
404
  current_session <- shiny::getDefaultReactiveDomain()
405
+ selected_basemap <- basemap
406
 
407
  later::later(function() {
408
  shiny::withReactiveDomain(current_session, {
 
412
  return()
413
  }
414
 
415
+ # Trigger re-render of stations and apply label visibility
416
+ apply_label_visibility(maplibre_proxy("map"), isolate(input$show_labels))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  style_change_trigger(isolate(style_change_trigger()) + 1)
418
  })
419
  }, delay = 0.5)
420
+ } else if (basemap == "sentinel") {
421
+ # HYBRID LOGIC (EOX Sentinel-2 Satellite + OpenFreeMap Labels/Roads)
422
+ # Use Positron as the base for labels/roads
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  proxy %>%
424
+ set_style(ofm_positron_style, preserve_layers = FALSE)
425
 
426
  current_session <- shiny::getDefaultReactiveDomain()
427
+ selected_basemap <- basemap
428
 
429
  later::later(function() {
430
  shiny::withReactiveDomain(current_session, {
 
434
  }
435
 
436
  unique_suffix <- as.numeric(Sys.time()) * 1000
437
+ source_id <- paste0("sentinel_source_", unique_suffix)
438
+ layer_id <- paste0("sentinel_layer_", unique_suffix)
439
 
440
+ # Add Sentinel layer at the very bottom (before 'background')
441
  maplibre_proxy("map") %>%
442
+ add_raster_source(id = source_id, tiles = sentinel_url, tileSize = 256, attribution = sentinel_attribution) %>%
443
  add_layer(
444
  id = layer_id,
445
  type = "raster",
446
  source = source_id,
447
+ paint = list("raster-opacity" = 1),
448
+ before_id = "background"
449
+ )
450
+
451
+ # Hide all non-label vector layers so only satellite + symbols show
452
+ for (layer_id_kill in non_label_layer_ids) {
453
+ tryCatch(
454
+ {
455
+ maplibre_proxy("map") %>% set_layout_property(layer_id_kill, "visibility", "none")
456
+ },
457
+ error = function(e) {
458
+ # Layer might not exist, ignore
459
+ }
460
  )
461
+ }
462
+
463
+ # Ensure labels according to user preference
464
+ apply_label_visibility(maplibre_proxy("map"), isolate(input$show_labels))
465
 
466
+ # Stations should be above the satellite layer
467
+ stations_before_id("waterway_line_label")
468
  current_raster_layers(c(layer_id))
469
  style_change_trigger(isolate(style_change_trigger()) + 1)
470
  })
 
475
  # Toggle Labels visibility
476
  observeEvent(input$show_labels,
477
  {
478
+ apply_label_visibility(maplibre_proxy("map"), input$show_labels)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
479
  },
480
  ignoreInit = TRUE
481
  )
ui.R CHANGED
@@ -97,14 +97,11 @@ ui <- page_navbar(
97
  inputId = "basemap",
98
  label = "Basemap",
99
  choices = c(
100
- "Carto Positron (Light)" = "carto_positron",
101
- "Carto Voyager" = "carto_voyager",
102
- "OpenStreetMap" = "osm",
103
- "OpenStreetMap Gray" = "osm_gray",
104
- "Esri World Topo Map" = "esri_topo",
105
- "Esri World Imagery" = "esri_imagery"
106
  ),
107
- selected = "carto_positron"
108
  ),
109
  hr(style = "margin: 8px 0;"),
110
  checkboxInput(
 
97
  inputId = "basemap",
98
  label = "Basemap",
99
  choices = c(
100
+ "OpenFreeMap Positron" = "ofm_positron",
101
+ "OpenFreeMap Bright" = "ofm_bright",
102
+ "Satellite (Sentinel-2)" = "sentinel"
 
 
 
103
  ),
104
+ selected = "ofm_positron"
105
  ),
106
  hr(style = "margin: 8px 0;"),
107
  checkboxInput(