UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

242 lines (205 loc) • 6.14 kB
import {Layer} from './Layer.js'; import * as Util from '../core/Util.js'; import {getFeature, asFeature} from './GeoJSONUtil.js'; /** @import {Feature, FeatureCollection, MultiPoint, Position} from 'geojson' */ /** @import {InteractiveLayerEventMap, InteractiveLayerOptions} from '../types.js' */ /** @import {LatLng} from '../geo/LatLng.js' */ /** @import {LatLngBounds} from '../geo/LatLngBounds.js' */ /** @import {LeafletMap} from '../map/Map.js' */ /** @import {Marker} from './marker/Marker.js' */ /** @import {LayerBase} from './LayerBase.js' */ // a layer with the methods that only some layers have, which groups call when present /** * @template {LayerBase} [L=Layer] * @typedef {L & { * setZIndex?(zIndex: number): unknown, * setStyle?(style?: object): unknown, * bringToFront?(): unknown, * bringToBack?(): unknown, * getBounds?(): LatLngBounds, * getLatLng?(): LatLng, * toGeoJSON?(precision?: number | false): Feature | FeatureCollection * }} MemberLayer */ /** * Used to group several layers and handle them as one. If you add it to the map, any layers added * or removed from the group will be added/removed on the map as well. Extends `Layer`. * @example * ```js * new LayerGroup([marker1, marker2]) * .addLayer(polyline) * .addTo(map); * ``` * @template {LayerBase} [L=Layer] * @template {InteractiveLayerOptions} [O=InteractiveLayerOptions] * @template {InteractiveLayerEventMap} [E=InteractiveLayerEventMap] * @extends {Layer<O, E>} */ export class LayerGroup extends Layer { static initHooks = true; /** * Create a layer group, optionally given an initial set of layers and an `options` object. * @param {NoInfer<L>[]} [layers] * @param {NoInfer<Partial<O>>} [options] */ constructor(layers, options) { super(); Util.setOptions(this, options); /** @type {{[id: number]: MemberLayer<L>}} */ this._layers = {}; for (const layer of layers ?? []) { this.addLayer(layer); } this.callInitHooks(); } /** * Adds the given layer to the group. * @param {L} layer */ addLayer(layer) { const id = this.getLayerId(layer); this._layers[id] = layer; this._map?.addLayer(layer); return this; } /** * Removes the given layer, or the layer with the given internal ID, from the group. * @param {L | number} layer */ removeLayer(layer) { const id = typeof layer === 'number' ? layer : this.getLayerId(layer); if (this._map && this._layers[id]) { this._map.removeLayer(this._layers[id]); } delete this._layers[id]; return this; } /** * Returns `true` if the given layer, or the layer with the given internal ID, is currently added to the group. * @param {L | number} layer */ hasLayer(layer) { const layerId = typeof layer === 'number' ? layer : this.getLayerId(layer); return layerId in this._layers; } /** Removes all the layers from the group. */ clearLayers() { return this.eachLayer(this.removeLayer, this); } /** @param {LeafletMap} map */ onAdd(map) { this.eachLayer(map.addLayer, map); } /** @param {LeafletMap} map */ onRemove(map) { this.eachLayer(map.removeLayer, map); } /** * Iterates over the layers of the group, optionally specifying context of the iterator function. * ```js * group.eachLayer(layer => layer.bindPopup('Hello')); * ``` * @param {(layer: L) => void} method * @param {unknown} [context] */ eachLayer(method, context) { for (const layer of Object.values(this._layers)) { method.call(context, layer); } return this; } /** * Returns the layer with the given internal ID. * @param {number} id * @returns {L | undefined} */ getLayer(id) { return this._layers[id]; } /** * Returns an array of all the layers added to the group. * @returns {L[]} */ getLayers() { /** @type {L[]} */ const layers = []; this.eachLayer(layer => layers.push(layer)); return layers; } /** * Calls `setZIndex` on every layer contained in this group, passing the z-index. * @param {number} zIndex */ setZIndex(zIndex) { for (const layer of Object.values(this._layers)) { layer.setZIndex?.(zIndex); } return this; } /** * Returns the internal ID for a layer * @param {L} layer */ getLayerId(layer) { return Util.stamp(layer); } /** * Returns a GeoJSON `MultiPoint` feature with the positions of the group's markers. `precision` is the number of * decimal places for coordinates, as in [`toGeoJSON`](#layergroup-togeojson). * @param {number | false} [precision] * @returns {Feature<MultiPoint>} */ toMultiPoint(precision) { /** @type {Position[]} */ const coords = []; this.eachLayer((layer) => { coords.push(/** @type {Marker} */ (/** @type {LayerBase} */ (layer)).toGeoJSON(precision).geometry.coordinates); }); return getFeature(this, { type: 'MultiPoint', coordinates: coords }); } /** * Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. * Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group * (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). * @param {number | false} [precision] * @returns {Feature | FeatureCollection} */ toGeoJSON(precision) { const type = this.feature?.geometry?.type; if (type === 'MultiPoint') { return this.toMultiPoint(precision); } const isGeometryCollection = type === 'GeometryCollection', /** @type {any[]} */ jsons = []; for (const layer of Object.values(this._layers)) { if (layer.toGeoJSON) { const json = layer.toGeoJSON(precision); if (isGeometryCollection) { jsons.push(/** @type {Feature} */ (json).geometry); } else { const feature = asFeature(json); // Squash nested feature collections if (feature.type === 'FeatureCollection') { jsons.push(...feature.features); } else { jsons.push(feature); } } } } if (isGeometryCollection) { return getFeature(this, { geometries: jsons, type: 'GeometryCollection' }); } return { type: 'FeatureCollection', features: jsons }; } }