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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@ndwnu/map

v0.0.1-beta.3

Published

A facade pattern library for MapLibre GL that simplifies the management of complex map sources and layers. Built by NDW (Nationaal Dataportaan Wegverkeer) for easier development with MapLibre.

Readme

@ndwnu/map

A facade pattern library for MapLibre GL that simplifies the management of complex map sources and layers. Built by NDW (Nationaal Dataportaan Wegverkeer) for easier development with MapLibre.

Overview

This library provides a structured approach to managing MapLibre GL maps by introducing the MapElement pattern - a container that groups related sources and layers as a single logical unit. MapElements use a generic type (typically an enum) for identification. Instead of managing individual MapLibre sources and layers, you work with MapElements that can be toggled on/off from the UI perspective while handling multiple underlying sources and layers automatically.

Key Features

  • MapElement Pattern: Groups multiple sources and layers into logical units
  • Visibility Management: Easy show/hide functionality for complex map elements
  • Repository Pattern: Centralized management of all map elements

Installation

npm install @ndwnu/map maplibre-gl

CSS Setup

Add MapLibre GL CSS to your angular.json styles array:

{
  "styles": ["node_modules/maplibre-gl/dist/maplibre-gl.css"]
}

Development Process

1. Create Map Elements

Create a folder structure for your map elements and define an enum for element identification:

// map-element.enum.ts
export enum MapElementEnum {
  MyElement = 'my-element',
  AnotherElement = 'another-element',
}
src/
  map-elements/
    map-element.enum.ts
    my-element/
      my-element.element.ts
      my-element.source.ts
      my-element.layer.ts

Example MapElement:

import { MapElement, MapElementConfig, MapSource } from '@ndwnu/map';
import { MapElementEnum } from '../map-element.enum';

export class MyElement extends MapElement<MapElementEnum> {
  constructor(config: MapElementConfig<MapElementEnum>) {
    super(config);
    this.sources = [new MyElementSource(config)];
  }
}

2. Create Element Repository

Extend the MapElementRepository to manage your elements:

import { Injectable } from '@angular/core';
import { MapElementRepository } from '@ndwnu/map';
import { Map } from 'maplibre-gl';
import { MapElementEnum } from './map-element.enum';

@Injectable({ providedIn: 'root' })
export class MyMapElementRepository extends MapElementRepository<MapElementEnum> {
  registerMapElements(map: Map) {
    const config = {
      map,
      mapElementRepository: this,
      maplibreCursorService: this.maplibreCursorService,
    };

    [
      new MyElement({ ...config, elementId: MapElementEnum.MyElement, elementOrder: 0 }),
      // Add more elements...
    ].forEach((element) => this.addMapElement(element));
  }
}

3. Create Map Component

Extend the base MapComponent and optionally provide configuration:

import { Component, inject } from '@angular/core';
import { MapComponent, MapConfig } from '@ndwnu/map';
import { MyMapElementRepository } from './map-elements/my-map-element.repository';

@Component({
  selector: 'app-map',
  template: `<div class="map-container"><!-- Map renders here --></div>`,
  styleUrls: ['./map.component.scss'],
})
export class MyMapComponent extends MapComponent {
  readonly #repository = inject(MyMapElementRepository);

  protected onLoadMap() {
    this.#repository.registerMapElements(this.map);
    // Show initial elements
    this.#repository.showMapElement(MapElementEnum.MyElement);
  }

  protected onRemoveMap() {
    this.#repository.removeAllMapElements();
  }

  protected onIdle() {
    // Handle map idle events
  }

  toggleElement(elementId: MapElementEnum) {
    this.#repository.toggleMapElement(elementId);
  }
}

4. Configure Map (Optional)

You can customize the map behavior by passing a configuration object:

// In your parent component template
@Component({
  template: ` <app-map [config]="mapConfig"></app-map> `,
})
export class ParentComponent {
  mapConfig: Partial<MapConfig> = {
    maxZoom: 20,
    minZoom: 8,
    dragRotate: true,
    center: [5.387827, 52.155172],
    zoom: 12,
    scrollZoom: false, // Disable scroll zoom
  };
}

Available configuration options:

  • center: Initial map center position
  • zoom: Initial zoom level
  • maxZoom/minZoom: Zoom level constraints
  • bounds: Initial bounds to fit (overrides center/zoom if provided)
  • interactive: Enable/disable all interactions
  • dragRotate: Enable/disable rotation via drag
  • doubleClickZoom: Enable/disable double-click zoom
  • scrollZoom: Enable/disable scroll wheel zoom
  • boxZoom: Enable/disable shift+drag box zoom
  • dragPan: Enable/disable drag to pan
  • keyboard: Enable/disable keyboard navigation
  • touchZoomRotate: Enable/disable touch gestures

Note: If bounds is provided, it will override center and zoom settings.

You can use predefined bounds:

import { COMMON_BOUNDS } from '@ndwnu/map';

mapConfig: Partial<MapConfig> = {
  bounds: COMMON_BOUNDS.NETHERLANDS, // or COMMON_BOUNDS.AMERSFOORT
  maxZoom: 20,
  dragRotate: true,
};

5. Component Styling

Important: Set a height for your map component:

.map-container {
  height: 500px; /* or 100vh for full viewport */
  width: 100%;
}

6. Register Map Elements

In your onLoadMap() method, call registerMapElements() to initialize all map elements:

protected onLoadMap() {
  this.#repository.registerMapElements(this.map);
  // Set initial visibility
  this.#repository.showMapElement(MapElementEnum.MyElement);
}

Filtering in MapLibre

When working with MapLibre, filters need to be applied to each individual layer, while the filter 'shape' (structure) is tied to the source data. To implement filtering with the current architecture:

  1. Provide filter observables to your MapElement
  2. Pass filters to sources during initialization
  3. Apply filters to layers within each source's layer definitions

Example implementation:

// In your MapElement
export class MyElement extends MapElement<MapElementEnum> {
  constructor(
    config: MapElementConfig<MapElementEnum>,
    private filters$: Observable<FilterObject>,
  ) {
    super(config);
    this.sources = [new MyElementSource(config, filters$)];
  }
}

// In your MapSource
export class MyElementSource extends MapSource<MapElementEnum> {
  constructor(
    config: MapElementConfig<MapElementEnum>,
    private filters$: Observable<FilterObject>,
  ) {
    super(config);
    // Subscribe to filter changes and update layers
    this.filters$.subscribe((filters) => this.updateLayerFilters(filters));
  }

  private updateLayerFilters(filters: FilterObject) {
    // Apply filters to each layer in this source
    this.layers.forEach((layer) => {
      layer.applyFilter(filters);
    });
  }
}

Note: Filter management may be included in future versions of this library to provide a more streamlined filtering experience.

Example Usage

See the playground application for a complete implementation example.

API

MapComponent (Abstract)

Base component that provides MapLibre integration.

Methods:

  • resizeMap(): Resize the map to fit container
  • zoomToLevel(level: number, options?): Zoom to specific level

Abstract Methods:

  • onLoadMap(): Called when map is loaded
  • onRemoveMap(): Called before map destruction
  • onIdle(): Called when map becomes idle

MapElementRepository (Abstract)

Manages collection of map elements.

Methods:

  • addMapElement(element): Add element to repository
  • removeMapElement(element): Remove and destroy element
  • showMapElement(id): Make element visible
  • hideMapElement(id): Hide element
  • toggleMapElement(id): Toggle element visibility

MapElement (Abstract)

Container for related sources and layers.

Properties:

  • id: Unique identifier
  • elementOrder: Display order
  • sources: Array of MapSource instances
  • isVisible: Current visibility state

License

MIT

About NDW

NDW (Nationaal Dataportaan Wegverkeer) - Data from and about road traffic are our core business. We collect, monitor quality, enrich data, store it and make it available.