Skip to content

Commit

Permalink
Merge pull request #75 from trafficonese/layergroupconditional
Browse files Browse the repository at this point in the history
Layergroupconditional
  • Loading branch information
trafficonese authored Jan 12, 2025
2 parents 96697c3 + 5840082 commit 8883192
Show file tree
Hide file tree
Showing 11 changed files with 419 additions and 0 deletions.
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export(addItemContextmenu)
export(addLabelgun)
export(addLatLngMoving)
export(addLayerGroupCollision)
export(addLayerGroupConditional)
export(addLeafletsync)
export(addLeafletsyncDependency)
export(addMapkeyMarkers)
Expand All @@ -36,6 +37,7 @@ export(antpathOptions)
export(arrowheadOptions)
export(clearAntpath)
export(clearArrowhead)
export(clearConditionalLayers)
export(clearFuture)
export(clearHexbin)
export(clearHistory)
Expand Down Expand Up @@ -74,6 +76,7 @@ export(playbackOptions)
export(reachabilityOptions)
export(removeAntpath)
export(removeArrowhead)
export(removeConditionalLayer)
export(removeEasyprint)
export(removeItemContextmenu)
export(removePlayback)
Expand Down
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# leaflet.extras2 (development version)

* Included [LayerGroup.Collision](https://github.com/MazeMap/Leaflet.LayerGroup.Collision) plugin
* Included [LayerGroup.Conditional](https://github.com/Solfisk/Leaflet.LayerGroup.Conditional) plugin
* Included [OSM Buildings](https://osmbuildings.org/documentation/leaflet/) plugin
* New Function `addDivicon` adds `DivIcon` markers to Leaflet maps with support for custom HTML and CSS classes. See the example in `./inst/examples/divicons_html_app.R`
* Added `addClusterCharts` to enable **pie** and **bar** charts in Marker clusters using `Leaflet.markercluster`, `d3` and `L.DivIcon`, with support for customizable category styling and various aggregation methods like **sum, min, max, mean**, and **median**.
Expand Down
89 changes: 89 additions & 0 deletions R/layergroupconditional.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
layerGroupConditionalDependency <- function() {
list(
htmltools::htmlDependency(
"lfx-conditional",
version = "1.0.0",
src = system.file("htmlwidgets/lfx-conditional", package = "leaflet.extras2"),
script = c(
"leaflet.layergroup.conditional.js",
"leaflet.layergroup.conditional-bindings.js"
)
)
)
}

#' addLayerGroupConditional
#'
#' @param map A map widget object created from \code{\link[leaflet]{leaflet}}.
#' @param groups A character vector of layer group names already added to the map.
#' These layer groups will be conditionally displayed based on the specified \code{conditions}.
#' @param conditions A named list where:
#' - Each **name** is a JavaScript function (using \code{\link[leaflet]{JS}}) defining a condition for displaying a layer group.
#' - Each **value** corresponds to a layer group name (or names) from the \code{layers} parameter.
#' Example:
#' \preformatted{
#' condition = list(
#' "(zoomLevel) => zoomLevel < 4" = "group1",
#' "(zoomLevel) => zoomLevel >= 4 && zoomLevel < 6" = "group2",
#' "(zoomLevel) => zoomLevel >= 6" = c("group3", "group4")
#' )
#' }
#'
#' @seealso \url{https://github.com/Solfisk/Leaflet.LayerGroup.Conditional?tab=readme-ov-file} for more details about the plugin.
#' @family LayerGroupConditional Plugin
#' @export
#' @examples
#' library(leaflet)
#' library(sf)
#' library(leaflet.extras2)
#'
#' breweries91 <- st_as_sf(breweries91)
#' lines <- st_as_sf(atlStorms2005)
#' polys <- st_as_sf(leaflet::gadmCHE)
#' groups <- c("atlStorms", "breweries", "gadmCHE")
#'
#' leaflet() %>%
#' addTiles() %>%
#' #leafem::addMouseCoordinates() %>%
#' addPolylines(data = lines, label = ~Name, group = groups[1]) %>%
#' addCircleMarkers(data = breweries91, label = ~brewery, group = groups[2]) %>%
#' addPolygons(data = polys, label = ~NAME_1, group = groups[3]) %>%
#' addLayerGroupConditional(
#' groups = groups,
#' conditions = list(
#' "(zoomLevel) => zoomLevel < 4" = groups[1],
#' "(zoomLevel) => zoomLevel >= 4 & zoomLevel < 6 " = groups[2],
#' "(zoomLevel) => zoomLevel >= 6" = groups[3]
#' )
#' ) %>%
#' hideGroup(groups) %>%
#' addLayersControl(
#' overlayGroups = groups,
#' options = layersControlOptions(collapsed = FALSE)
#' )
#'
addLayerGroupConditional <- function(map, groups = NULL, conditions = NULL) {
map$dependencies <- c(map$dependencies, layerGroupConditionalDependency())
invokeMethod(
map, getMapData(map), "addLayerGroupConditional",
groups, conditions
)
}

#' removeConditionalLayer
#'
#' @inheritParams addLayerGroupConditional
#' @family LayerGroupConditional Plugin
#' @export
removeConditionalLayer <- function(map, groups) {
invokeMethod(map, getMapData(map), "removeConditionalLayer", groups)
}

#' clearConditionalLayers
#'
#' @inheritParams addLayerGroupConditional
#' @family LayerGroupConditional Plugin
#' @export
clearConditionalLayers <- function(map) {
invokeMethod(map, getMapData(map), "clearConditionalLayers")
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ If you need a plugin that is not already implemented create an [issue](https://g
- [History](https://github.com/cscott530/leaflet-history)
- [Labelgun](https://github.com/Geovation/labelgun)
- [LayerGroup.Collision](https://github.com/MazeMap/Leaflet.LayerGroup.Collision)
- [LayerGroup.Conditional](https://github.com/Solfisk/Leaflet.LayerGroup.Conditional)
- [Leaflet.Sync](https://github.com/jieter/Leaflet.Sync)
- [Mapkey Icons](https://github.com/mapshakers/leaflet-mapkey-icon)
- [Moving Markers](https://github.com/ewoken/Leaflet.MovingMarker)
Expand Down
4 changes: 4 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ reference:
- title: LayerGroup.Collision
contents:
- matches("LayerGroupCollision")
- title: LayerGroup.Conditional
contents:
- matches("LayerGroupConditional")
- matches("ConditionalLayer")
- title: Mapkey Icons
contents:
- matches("Mapkey")
Expand Down
44 changes: 44 additions & 0 deletions inst/examples/conditional_app.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
library(shiny)
library(leaflet)
library(sf)
library(leaflet.extras2)

breweries91 <- st_as_sf(breweries91)
lines <- st_as_sf(atlStorms2005)
polys <- st_as_sf(leaflet::gadmCHE)
groups <- c("atlStorms","breweries","gadmCHE")

ui <- fluidPage(
leafletOutput("map", height = 900),
actionButton("remove", "Remove by Group"),
actionButton("clear", "Clear")
)

server <- function(input, output, session) {
output$map <- renderLeaflet({
leaflet() %>%
addTiles() %>%
leafem::addMouseCoordinates() %>%
addPolylines(data = lines, label = ~Name, group = groups[1]) %>%
addCircleMarkers(data = breweries91, label = ~brewery, group = groups[2]) %>%
addPolygons(data = polys, label = ~NAME_1, group = groups[3]) %>%
addLayerGroupConditional(groups = groups,
conditions = list(
"(zoomLevel) => zoomLevel < 4" = groups[1],
"(zoomLevel) => zoomLevel >= 4 & zoomLevel < 6 " = groups[2],
"(zoomLevel) => zoomLevel >= 6" = c(groups[3])
)) %>%
hideGroup(groups) %>%
addLayersControl(overlayGroups = groups,
options = layersControlOptions(collapsed=FALSE))
})
observeEvent(input$clear, {
leafletProxy("map") %>%
clearConditionalLayers()
})
observeEvent(input$remove, {
leafletProxy("map") %>%
removeConditionalLayer(groups=groups[1])
})
}
shinyApp(ui, server)
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* global LeafletWidget, $, L, Shiny, HTMLWidgets */
LeafletWidget.methods.addLayerGroupConditional = function(groups, conditions) {

const map = this;
let conditionalGroup = L.layerGroup.conditional();
map.conditionalGroup = conditionalGroup;

// Loop through each group
groups.forEach(function(group) {
// Loop through conditions for each group
Object.keys(conditions).forEach(function(condition) {
let groupList = conditions[condition];
if (!Array.isArray(groupList)) {
groupList = [groupList];
}
groupList.forEach(function(group) {
let layer = map.layerManager.getLayerGroup(group);
if (!layer) {
console.warn("Layer not found in group " + group);
return;
}
// Add the layer with the associated condition
conditionalGroup.addConditionalLayer(eval(condition), layer);
})
});
});

// Add the conditional group to the map
conditionalGroup.addTo(map)

// Set up the zoom handler to update conditional layers
let zoomHandler = function() {
conditionalGroup.updateConditionalLayers(map.getZoom());
};
map.on("zoomend", zoomHandler);

// Set initial state of conditional layers
setTimeout(function() {
zoomHandler()
}, 500);

};


LeafletWidget.methods.removeConditionalLayer = function(groups) {
const map = this;
if (!Array.isArray(groups)) {
groups = [groups];
}
groups.forEach(function(group) {
let layer = map.layerManager.getLayerGroup(group);
if (layer && map.conditionalGroup) {
map.conditionalGroup.removeConditionalLayer(layer)
} else {
console.warn("Layer not found " + group);
}
})
};

LeafletWidget.methods.clearConditionalLayers = function() {
this.conditionalGroup.clearConditionalLayers()
};
104 changes: 104 additions & 0 deletions inst/htmlwidgets/lfx-conditional/leaflet.layergroup.conditional.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
L.LayerGroup.Conditional = L.LayerGroup.extend({

initialize: function (layers, options) {
this._conditionalLayers = {};
L.LayerGroup.prototype.initialize.call(this, layers, options);
},

// @method addConditionalLayer(conditionFunction: (object) => boolean, layer: Layer): this
// Adds the given layer to the group with a condition.
addConditionalLayer: function (conditionFunction, layer) {
var id = this.getLayerId(layer);

this._conditionalLayers[id] = {
"condition": conditionFunction,
"layer": layer,
"active": false,
}

return this;
},

// @method removeConditionalLayer(layer: Layer): this
// Removes the given conditional layer from the group.
// @alternative
// @method removeConditionalLayer(id: Number): this
// Removes the conditional layer with the given internal ID from the group.
removeConditionalLayer: function (layer) {
var id = layer in this._conditionalLayers ? layer : this.getLayerId(layer);

if (this._conditionalLayers[id] && this._conditionalLayers[id].active) {
this.removeLayer(id);
}

delete this._conditionalLayers[id];

return this;
},

// @method hasConditionalLayer(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the group with a condition, regardless of whether it is active
// @alternative
// @method hasConditionalLayer(id: Number): Boolean
// Returns `true` if the given internal ID is currently added to the group with a condition, regardless of whether it is active.
hasConditionalLayer: function (layer) {
if (!layer) { return false; }
var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
return layerId in this._conditionalLayers;
},

// @method isConditionalLayerActive(layer: Layer): Boolean
// Returns `true` if the given layer is currently added to the group with a condition, and is currently active
// @alternative
// @method hasConditionalLayer(id: Number): Boolean
// Returns `true` if the given internal ID is currently added to the group with a condition, and is currently active.
isConditionalLayerActive: function (layer) {
if (!layer) { return false; }
var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
return (layerId in this._conditionalLayers) && this._conditionalLayers[layerId].active;
},


// @method clearConditionalLayers(): this
// Removes all the conditional layers from the group.
clearConditionalLayers: function () {
for (var i in this._conditionalLayers) {
this.removeConditionalLayer(i);
}
return this;
},

// @method getConditionalLayers(): Layer[]
// Returns an array of all the conditional layers added to the group.
getConditionalLayers: function () {
var layers = [];
for (var i in this._conditionalLayers) {
layers.push(this._conditionalLayers[i].layer);
}
return layers;
},

// @method updateConditionalLayers(o: object): this
// Update the status of all conditional layers, passing an optional argument to each layer's condition function.
updateConditionalLayers: function(o) {
for (var i in this._conditionalLayers) {
var condLayer = this._conditionalLayers[i];
var active = condLayer.condition(o);
if (active && !condLayer.active) {
this.addLayer(condLayer.layer);
} else if (condLayer.active && !active) {
this.removeLayer(condLayer.layer);
}
condLayer.active = active;
}

return this;
}
});


// @factory L.layerGroup.Conditional(layers?: Layer[], options?: Object)
// Create a conditional layer group, optionally given an initial set of non-conditinoal layers and an `options` object.
L.layerGroup.conditional = function (layers, options) {
return new L.LayerGroup.Conditional(layers, options);
};
Loading

0 comments on commit 8883192

Please sign in to comment.