# @geosdi/ngx-leaflet-layers-plugin

> Service to create base layer for Leaflet Map

Latest version **1.2.3** (published 2026-09-10) · MIT license · 0 weekly downloads

## Install

```sh
npm install @geosdi/ngx-leaflet-layers-plugin
pnpm add @geosdi/ngx-leaflet-layers-plugin
yarn add @geosdi/ngx-leaflet-layers-plugin
bun add @geosdi/ngx-leaflet-layers-plugin
```

## Health

**Score 65/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 1.2.3 |
| Published | 2026-09-10 |
| First published | 2021-11-08 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM |
| Dependencies | 1 |
| Unpacked size | 57 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Salvia Vito |
| Maintainers | vito.salvia |
| Keywords | angular, javascript, typescript, leaflet, map, base layer |

## Links

- npm: https://www.npmjs.com/package/@geosdi/ngx-leaflet-layers-plugin
- npm.io page: https://npm.io/package/@geosdi/ngx-leaflet-layers-plugin

## Dependencies (1)

- [tslib](https://npm.io/package/tslib.md) ^2.3.0

## Alternatives

- [@openai/codex-sdk](https://npm.io/package/@openai/codex-sdk.md) — 731.4K weekly downloads
- [babel-plugin-transform-react-jsx](https://npm.io/package/babel-plugin-transform-react-jsx.md) — 565.0K weekly downloads
- [babel-helper-remove-or-void](https://npm.io/package/babel-helper-remove-or-void.md) — 508.5K weekly downloads
- [@pnpm/store-controller-types](https://npm.io/package/@pnpm/store-controller-types.md) — 186.9K weekly downloads
- [react-native-signature-canvas](https://npm.io/package/react-native-signature-canvas.md) — 155.6K weekly downloads

## Recent versions

- 1.2.3 (latest) — 2026-09-10
- 1.2.2 — 2026-08-27
- 1.2.1 — 2026-05-21
- 1.2.0 — 2026-03-24
- 1.1.10 — 2026-02-09
- 1.1.9 — 2026-02-09
- 1.1.8 — 2026-02-09
- 1.1.7 — 2025-10-20
- 1.1.6 — 2025-03-10
- 1.1.5 — 2024-08-02
- 1.1.4 — 2023-06-05
- 1.1.3 — 2023-06-05
- 1.1.2 — 2023-06-05
- 1.1.1 — 2023-05-21
- 1.1.0 — 2023-05-10
- … 8 more at https://npm.io/package/@geosdi/ngx-leaflet-layers-plugin/versions

## 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.

```typescript
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

```typescript
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.

```css
.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:

```typescript
// 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 `BehaviorSubject`s 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`.

---
_Source: https://npm.io/package/@geosdi/ngx-leaflet-layers-plugin · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
