npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geosdi/ngx-leaflet-layers-plugin

v1.2.3

Published

Service to create base layer for Leaflet Map

Readme

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.