npm.io
1.2.3 • Published 1 week ago

@geosdi/ngx-leaflet-layers-plugin

Licence
MIT
Version
1.2.3
Deps
1
Size
57 kB
Vulns
0
Weekly
0

NgxLeafletLayers

General information

The base layers of a Leaflet map, already wired: a store of ready-made layers, a fluent builder to pick the ones an application offers, and the switch between them.

The service also carries the two things a map UI needs whenever the base layer changes and that no Leaflet control gives you: which logo to show over the map, and which contrast colour the controls on top of it should use — a satellite layer needs white, OpenStreetMap needs black.

It attaches to an L.Map already created by the application; it never creates one.

Installation

npm i @geosdi/ngx-leaflet-layers-plugin --save

It depends on :

"@angular/common": ">=20.0.0",
"@angular/core": ">=20.0.0",
"typescript": ">=5.0.0",
"leaflet": ">=1.9.3",
"leaflet-bing-layer": ">=3.3.1",
"rxjs": ">=7.8.2"

leaflet-bing-layer is imported by the service itself, so it must be installed even by an application that offers none of the Bing layers.

Usage

Everything goes through NgxLeafletLayersPluginService, which is providedIn: "root" and stateful: it holds one set of base layers and one current layer, so an application has one map's worth of state in it.

import { Injectable, inject } from "@angular/core";
import {
  BaseLayerEnum,
  NgxLeafletLayersPluginService,
} from "@geosdi/ngx-leaflet-layers-plugin";

@Injectable({ providedIn: "root" })
export class BootstrapService {
  private layersService: NgxLeafletLayersPluginService = inject(
    NgxLeafletLayersPluginService,
  );

  initBaseMaps(map: L.Map): void {
    this.layersService
      .withBaseLayers([
        BaseLayerEnum.OPEN_STREET_MAP,
        BaseLayerEnum.DARK_BASE_MAP,
        BaseLayerEnum.ORTHO_MAP,
      ])
      .withDefaultBaseLayer(BaseLayerEnum.OPEN_STREET_MAP)
      .withMap(map)
      .build();
  }
}

build() adds the default layer to the map at zIndex 0 and notifies the logo and the contrast. The builder methods can be called in any order, but build() needs both withMap and withBaseLayers: it alerts if either is missing — see the note on alert() under Limitations.

withBaseLayers creates every layer it is given straight away, so the list is the place where the cost is paid, not the switch.

To feed a Leaflet control, or a control of your own, getAllBaseLayers() returns the created BaseLayer objects.

BaseLayer
interface BaseLayer {
  key: string;
  label: string;
  layer: any; // the Leaflet layer, already created
  logo_type: LogoType; // "logoBlack" | "logoWhite"
  contrast: string; // "#000" | "#fff"
}

The base layers

Thirteen keys, in BaseLayerEnum. Status measured on 10 September 2026 by requesting one tile from each — it is the one column here that ages by itself.

key label what it serves status
OPEN_STREET_MAP OpenStreetMap OSM Standard, tile.openstreetmap.org works, no key
DARK_BASE_MAP DarkBaseMap the same OSM tiles, darkened in CSS works, no key — see below
ORTHO_MAP OrthoMap Esri World Imagery works, no key
GOOGLE_NORMAL Google Normal mt{0-3}.google.com/vt?lyrs=m answers, but see the note below
GOOGLE_SATELLITE Google Satellite the same endpoint, lyrs=s answers, but see the note below
GOOGLE_HYBRID Google Hybrid the same endpoint, lyrs=y,h answers, but see the note below
BING_AERIAL Bing Aerial Layer Bing Maps, via leaflet-bing-layer dead: the bundled key is refused
BING_ROAD_LAYER Bing Road Layer same dead: the bundled key is refused
BING_HYBRID Bing Hybrid same dead: the bundled key is refused
METACARTA Metacarta WMS vmap0.tiles.osgeo.org dead: the host answers 404
GEOSDI_BASE geoSdi WMS dpc.geosdi.org, Mappa_di_Base dead: LayerNotDefined
GEOSDI_NULL_BASE geoSdi No Map the same WMS, StratiDiBase:nullMap dead: LayerNotDefined
EMPTY EmptyLayer (c) geoSDI nothing — a WMS layer with no url by design: an empty background

Three things worth knowing before picking from that list.

The Bing layers cannot work as published. The Bing Maps key is a constant inside this library, and Bing answers it with 403 DeniedCredentials — your credentials may be denied or suspended. There is no option to pass your own: offering these three needs a change here, not configuration.

The two geoSDI WMS layers and Metacarta are gone. dpc.geosdi.org is reachable but publishes no layers at all, so both GEOSDI_* layers answer with a LayerNotDefined service exception, and vmap0.tiles.osgeo.org is a 404. All three are also plain http://, which a page served over https blocks as mixed content before the request even leaves the browser.

The Google layers use an undocumented endpoint. mt{0-3}.google.com/vt is not the Maps API: it needs no key, it answers, and it is outside Google's terms of service. It is in this list for historical reasons — an application with a Maps licence should use the official SDK instead.

DARK_BASE_MAP

DARK_BASE_MAP serves the plain OpenStreetMap tiles, darkened by a CSS filter. There is no dark raster style at tile.openstreetmap.org — the dark map on openstreetmap.org is its vector layer, styled client-side by MapLibre — and the filter is what the site itself applies to its own raster layers.

Nothing to install and no API key: the layer injects this rule into <head> the first time it is created, once per page.

.ngx-dark-base-map .leaflet-tile {
  filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(1.5);
}

To darken it differently, override the class with a more specific rule, or replace the injected <style id="ngx-dark-base-map-style">.

The class is set through the Leaflet className option, so it lands on this layer's tile container only. Writing the same filter on .leaflet-tile, as most examples do, would invert every tile layer on the map — the application's WMS overlays included.

Before 1.2.3 this layer was CARTO dark_all. CARTO now stamps API KEY REQUIRED across every tile served without a key, on all of its basemap styles, and that key belongs to whoever deploys the application rather than to this library.

The logo and the contrast

Both come from the layer definition and change with it: logo_type says which of two images to show, contrast which colour to paint the controls sitting on the map.

Two ways to read them — pull, or push:

// pull, whenever needed
const logo: LogoType = this.layersService.getLogo();
const layer: BaseLayer | undefined = this.layersService.currentBaseLayer;

// push, on every change
this.layersService.subjectNotifyLogo$.subscribe(
  (logo: LogoType) => (this.logo = logo),
);
this.layersService.subjectNotifyContrast$.subscribe(
  (contrast: string) => (this.contrast = contrast),
);
  • the two subjects are BehaviorSubjects that start with "", so a subscriber gets one empty value before the first build();
  • getLogo() reads the current layer and therefore throws before build();
  • logo_type is "logoBlack" or "logoWhite": the file names of your images. This library ships no assets — put logoBlack.png and logoWhite.png, or whatever those two values name for you, in the application's assets folder;
  • the LogoType enum is not exported from the package, although BaseLayer.logo_type is typed with it. Until it is, compare against the two string values rather than importing the enum.

Methods

Method action
withBaseLayers(layers: BaseLayerEnum[]) creates the layers an application offers; returns the service, for chaining
withDefaultBaseLayer(type: string | BaseLayerEnum) the one shown first
withMap(map: L.Map) the map to attach to, already created
build() adds the default layer at zIndex 0 and notifies logo and contrast
changeBaseLayer(type: string | BaseLayerEnum, force: boolean) switches: force: true puts the new layer on the map, force: false only notifies and leaves the swap to a control
currentBaseLayer (getter) the BaseLayer in use, or undefined before build()
getAllBaseLayers() every created BaseLayer, to feed a control
createLayer(type: BaseLayerEnum) one layer, created and not registered — for a map that manages its own
getLogo() the current LogoType
applyCurrentLayer() adds the current layer to the map
removeCurrentBaseLayer() removes it

Events

Event action
subjectNotifyLogo$ the LogoType of the new base layer, on every change
subjectNotifyContrast$ the contrast colour of the new base layer, on every change

Both fire on build() and on every changeBaseLayer(), force or not.

Limitations

Three behaviours that are easier to read here than to discover:

  • changeBaseLayer(type, true) does not remove the previous layer. It adds the new one over it, and the old one keeps requesting tiles. Until it is fixed, either switch with force: false and let a Leaflet layers control own the exclusivity, or call removeCurrentBaseLayer() before changing;
  • errors are reported with alert(), not by throwing: a missing map, an empty layer list, an unknown key. A modal dialog blocks the page, and in an automated browser it blocks the session;
  • the service is a singleton with the state of one map. Two maps in the same application share the current layer, the logo and the contrast; the second one to build() wins. For a second map, provide the service again in that component's providers.

Keywords