Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export(add_geolocate_control)
export(add_globe_control)
export(add_globe_minimap)
export(add_h3j_source)
export(add_h3t_source)
export(add_heatmap_layer)
export(add_image)
export(add_image_source)
Expand Down
81 changes: 81 additions & 0 deletions R/h3j-h3t.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,84 @@ add_h3j_source <- function(map, id, url) {
}


# addH3TSource
#' Add a tiled hexagon source from the H3 geospatial indexing system.
#'
#' Wraps the [h3t](https://github.com/INSPIDE/h3j-h3t) tile protocol, which
#' registers a `h3tiles://` MapLibre protocol and fetches H3J-formatted JSON
#' tiles from a `{z}/{x}/{y}` endpoint. Unlike [add_h3j_source()], which pulls
#' a single H3J file, this source lets the map request only the cells visible
#' in the current viewport — on pan/zoom the protocol handler fires a fresh
#' request per tile.
#'
#' @param map A map object created by `maplibre()` or a `maplibre_proxy`.
#' @param id Unique source ID.
#' @param tiles A character vector of tile URL templates, each using the
#' `h3tiles://` scheme. The tokens `{z}`, `{x}`, `{y}` are substituted by
#' MapLibre on each tile request. Example:
#' `"h3tiles://h3t.example.com/{z}/{x}/{y}.h3t?q=..."`.
#' @param sourcelayer Name of the source layer that downstream `add_fill_layer()`
#' (or similar) calls reference via `source_layer`. Defaults to `id`.
#' @param geometry_type Either `"Polygon"` (hex boundaries) or `"Point"` (cell
#' centroids). Defaults to `"Polygon"`.
#' @param minzoom,maxzoom Zoom bounds for the source (MapLibre semantics).
#' @param promote_id Whether to promote the `h3id` property to the feature ID.
#' @param debug If `TRUE`, the protocol handler logs per-tile timing to the
#' browser console.
#' @references https://github.com/INSPIDE/h3j-h3t
#' @export
#' @examplesIf interactive()
#' maplibre(center = c(-119, 34), zoom = 5) |>
#' add_h3t_source(
#' id = "sardine",
#' tiles = "h3tiles://h3t.example.com/{z}/{x}/{y}.h3t?q=<base64-SELECT>"
#' ) |>
#' add_fill_layer(
#' id = "sardine",
#' source = "sardine",
#' source_layer = "sardine",
#' fill_color = interpolate(
#' column = "value", values = c(0, 100),
#' stops = c("#ffffcc", "#e31a1c")
#' ),
#' fill_opacity = 0.7
#' )
add_h3t_source <- function(map, id, tiles,
sourcelayer = id,
geometry_type = c("Polygon", "Point"),
minzoom = 0,
maxzoom = 14,
promote_id = TRUE,
debug = FALSE) {
geometry_type <- match.arg(geometry_type)
if (is.character(tiles)) tiles <- as.list(tiles)
stopifnot(is.list(tiles), length(tiles) >= 1L)

h3t_source <- list(
id = id,
tiles = tiles,
sourcelayer = sourcelayer,
geometry_type = geometry_type,
minzoom = minzoom,
maxzoom = maxzoom,
promoteId = promote_id,
debug = debug
)

if (inherits(map, "mapboxgl_proxy") || inherits(map, "maplibre_proxy")) {
proxy_class <- if (inherits(map, "mapboxgl_proxy")) "mapboxgl-proxy" else "maplibre-proxy"
map$session$sendCustomMessage(
proxy_class,
list(
id = map$id,
message = list(type = "add_h3t_sources", h3t_sources = list(h3t_source))
)
)
} else {
map$x$h3t_sources <- c(map$x$h3t_sources, list(h3t_source))
}

map
}


4 changes: 2 additions & 2 deletions inst/htmlwidgets/lib/h3j-h3t/h3j_h3t.js

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions inst/htmlwidgets/maplibregl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1309,6 +1309,22 @@ HTMLWidgets.widget({
}
});
}
if (x.h3t_sources) {
x.h3t_sources.forEach(function (source) {
map.addH3TSource(source.id, {
tiles: source.tiles,
sourcelayer: source.sourcelayer,
geometry_type: source.geometry_type,
minzoom: source.minzoom,
maxzoom: source.maxzoom,
promoteId: source.promoteId,
debug: source.debug,
});
if (x.layers) {
x.layers.forEach((layer) => add_my_layers(layer));
}
});
}

// Add layers if provided
if (x.layers) {
Expand Down Expand Up @@ -2568,6 +2584,30 @@ if (HTMLWidgets.shinyMode) {
map.setFilter(message.layer, message.filter);
// Track filter state for layer restoration
layerState.filters[message.layer] = message.filter;
} else if (message.type === "add_h3t_sources") {
(message.h3t_sources || []).forEach(function (source) {
try {
map.addH3TSource(source.id, {
tiles: source.tiles,
sourcelayer: source.sourcelayer,
geometry_type: source.geometry_type,
minzoom: source.minzoom,
maxzoom: source.maxzoom,
promoteId: source.promoteId,
debug: source.debug,
});
} catch (e) {
console.error("addH3TSource failed for", source.id, e);
}
});
} else if (message.type === "add_h3j_sources") {
(message.h3j_sources || []).forEach(async function (source) {
try {
await map.addH3JSource(source.id, { data: source.url });
} catch (e) {
console.error("addH3JSource failed for", source.id, e);
}
});
} else if (message.type === "add_source") {
if (message.source.type === "vector") {
const sourceConfig = {
Expand Down
2 changes: 1 addition & 1 deletion inst/htmlwidgets/maplibregl.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies:
script:
- "pmtiles.js"
- name: h3j-h3t
version: 0.9.2
version: 0.9.7
src: "htmlwidgets/lib/h3j-h3t"
script:
- "h3j_h3t.js"
Expand Down
53 changes: 41 additions & 12 deletions inst/htmlwidgets/maplibregl_compare.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// build marker for bbest/mapgl feat/add-h3t-source — bump on every JS change
window.__mapgl_h3t_build = "2026-04-22g";
console.log("[mapgl] maplibregl_compare.js build:", window.__mapgl_h3t_build);

function evaluateExpression(expression, properties) {
if (!Array.isArray(expression)) {
return expression;
Expand Down Expand Up @@ -3046,6 +3050,20 @@ HTMLWidgets.widget({
});
});
}
// Process H3T (tiled h3j) sources if provided
if (mapData.h3t_sources) {
mapData.h3t_sources.forEach(function (source) {
map.addH3TSource(source.id, {
tiles: source.tiles,
sourcelayer: source.sourcelayer,
geometry_type: source.geometry_type,
minzoom: source.minzoom,
maxzoom: source.maxzoom,
promoteId: source.promoteId,
debug: source.debug,
});
});
}

if (mapData.markers) {
if (!window.maplibreglMarkers) {
Expand Down Expand Up @@ -4227,12 +4245,18 @@ HTMLWidgets.widget({
link.setAttribute("data-layer-ids", JSON.stringify(layerIds));
link.setAttribute("data-layer-type", config.type);

// Check if the first layer's visibility is set to "none" initially
// Check if the first layer's visibility is set to "none" initially.
// In a compare widget each side only has its own layers, but a single
// layers_control can reference ids from both sides (e.g. "sp" and "env")
// so the toggle can fire on both maps. If the first id isn't present on
// this map, fall back to "visible". We pre-check with getLayer() because
// getLayoutProperty() on a missing layer fires an error event (not a
// throw), which try/catch cannot silence.
const firstLayerId = layerIds[0];
const initialVisibility = map.getLayoutProperty(
firstLayerId,
"visibility",
);
let initialVisibility = "visible";
if (map.getLayer(firstLayerId)) {
initialVisibility = map.getLayoutProperty(firstLayerId, "visibility") || "visible";
}
link.className = initialVisibility === "none" ? "" : "active";

// Show or hide layer(s) when the toggle is clicked
Expand All @@ -4244,17 +4268,22 @@ HTMLWidgets.widget({
const layerIds = JSON.parse(
this.getAttribute("data-layer-ids"),
);
// read visibility from whichever map actually has this layer
// (pre-check with getLayer() — getLayoutProperty fires an error
// event on missing layers and try/catch cannot silence it)
const firstLayerId = layerIds[0];
const visibility = map.getLayoutProperty(
firstLayerId,
"visibility",
);

const newVis = visibility === "visible" ? "none" : "visible";
let visibility;
for (const m of [map, beforeMap, afterMap]) {
if (!m || !m.getLayer(firstLayerId)) continue;
visibility = m.getLayoutProperty(firstLayerId, "visibility");
if (visibility !== undefined) break;
}
const newVis = (visibility || "visible") === "visible" ? "none" : "visible";
const allMaps = [beforeMap, afterMap];
layerIds.forEach((layerId) => {
allMaps.forEach((m) => {
try { m.setLayoutProperty(layerId, "visibility", newVis); } catch(err) {}
if (!m || !m.getLayer(layerId)) return;
m.setLayoutProperty(layerId, "visibility", newVis);
});
});
this.className = newVis === "visible" ? "active" : "";
Expand Down
2 changes: 1 addition & 1 deletion inst/htmlwidgets/maplibregl_compare.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ dependencies:
script:
- "pmtiles.js"
- name: h3j-h3t
version: 0.9.2
version: 0.9.7
src: "htmlwidgets/lib/h3j-h3t"
script:
- "h3j_h3t.js"
Expand Down
71 changes: 71 additions & 0 deletions man/add_h3t_source.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.