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

@angular-helpers/openlayers

v22.1.0

Published

Modern Angular wrapper for OpenLayers with modular architecture, standalone components, and hybrid template/programmatic API

Readme

@angular-helpers/openlayers

A modern Angular wrapper for OpenLayers with modular architecture, standalone components, and a hybrid template/programmatic API.

Features

  • Standalone Components - No NgModule boilerplate, use directly in your components
  • Signals Integration - Native Angular signals for reactive state management
  • Modular Loading - Import only what you need with AgGrid-style sub-entry points
  • Dual API - Template for UI, Inputs for data, Services for operations
  • Tree-shaking - Unused OpenLayers code is eliminated from your bundle
  • TypeScript First - Full type safety with strict mode support
  • Military Features - True geodesic precision for ellipses and sectors, NATO symbology, and MGRS coordinates
  • Proj4 Integration - Declarative registration of local coordinate systems (like UTM)
  • WebGL Accelerated - Mapbox Vector Tiles (MVT) and GPU raster expressions

Installation

pnpm add @angular-helpers/openlayers ol

Quick Start

1. Configure in your app

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideOpenLayers } from '@angular-helpers/openlayers/core';
import { withLayers } from '@angular-helpers/openlayers/layers';
import { withControls } from '@angular-helpers/openlayers/controls';

export const appConfig: ApplicationConfig = {
  providers: [
    provideOpenLayers(
      withLayers(),
      withControls(),
      // Optional: Register custom coordinate systems with proj4
      // withProjections(proj4, [{ code: 'EPSG:32630', proj4def: '...', extent: [...] }])
    ),
  ],
};

2. Use in your component

// map.component.ts
import { Component, inject, signal } from '@angular/core';
import { OlMapComponent } from '@angular-helpers/openlayers/core';
import { OlTileLayerComponent } from '@angular-helpers/openlayers/layers';
import {
  OlZoomControlComponent,
  OlScaleLineControlComponent,
} from '@angular-helpers/openlayers/controls';
import { OlMapService } from '@angular-helpers/openlayers/core';

@Component({
  selector: 'app-map',
  imports: [
    OlMapComponent,
    OlTileLayerComponent,
    OlZoomControlComponent,
    OlScaleLineControlComponent,
  ],
  template: `
    <ol-map
      [center]="center()"
      [zoom]="zoom()"
      (viewChange)="onViewChange($event)"
      (click)="onMapClick($event)"
      style="display: block; height: 400px;"
    >
      <!-- Base Layer -->
      <ol-tile-layer id="osm" source="osm"></ol-tile-layer>

      <!-- Controls -->
      <ol-zoom-control></ol-zoom-control>
      <ol-scale-line-control unit="metric"></ol-scale-line-control>
    </ol-map>

    <p>Clicked: {{ lastClick()?.coordinate | json }}</p>
  `,
})
export class MapComponent {
  private mapService = inject(OlMapService);

  center = signal<[number, number]>([2.2945, 48.8584]);
  zoom = signal<number>(12);
  lastClick = signal<{ coordinate: [number, number]; pixel: [number, number] } | null>(null);

  onViewChange(state: { center: [number, number]; zoom: number }): void {
    this.center.set(state.center);
    this.zoom.set(state.zoom);
  }

  onMapClick(event: { coordinate: [number, number]; pixel: [number, number] }): void {
    this.lastClick.set(event);
  }

  flyToEiffel() {
    this.mapService.animateView({ center: [2.2945, 48.8584], zoom: 15, duration: 1000 });
  }
}
## Custom Projections & Coordinate Systems

Available since `0.4.1` in `@angular-helpers/openlayers/core` and `@angular-helpers/openlayers/layers`.

When working with local reference systems (like UTM zones), you can register custom projections globally and pass coordinates in those reference systems directly to `[center]` or `[features]` without manual transforms:

### 1. Register custom projections globally

```typescript
// app.config.ts
import { provideOpenLayers } from '@angular-helpers/openlayers/core';
import { withProjections } from '@angular-helpers/openlayers/core';
import proj4 from 'proj4';

export const appConfig = {
  providers: [
    provideOpenLayers(
      withProjections(proj4, [
        {
          code: 'EPSG:25830', // UTM Zone 30N
          def: '+proj=utm +zone=30 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
          extent: [0, 0, 1000000, 10000000],
        },
      ]),
    ),
  ],
};

2. Pass local coordinates natively to the map

By setting [coordinateProjection] to match the custom projection, the component automatically bypasses longitude/latitude conversion, feeding UTM coordinates directly into OpenLayers:

<ol-map
  [projection]="'EPSG:25830'"
  [coordinateProjection]="'EPSG:25830'"
  [center]="[440291, 4474255]" <!-- Madrid UTM Zone 30 coordinates -->
  [zoom]="12"
  (viewChange)="onViewChange($event)"
>
  <ol-vector-layer
    id="shapes"
    [features]="utmFeatures()"
    [coordinateProjection]="'EPSG:25830'"
  />
</ol-map>

Overlays — popups and tooltips

Available since 0.3.0 from @angular-helpers/openlayers/overlays.

<ol-popup> — declarative popup with content projection

The popup's host element is used directly as the underlying ol/Overlay element, so projected children stay inside Angular's view tree and benefit from change detection without any extra plumbing.

import { OlPopupComponent } from '@angular-helpers/openlayers/overlays';

@Component({
  imports: [OlMapComponent, OlVectorLayerComponent, OlPopupComponent],
  template: `
    <ol-map [center]="[2.17, 41.38]" [zoom]="12">
      <ol-vector-layer id="cities" [features]="cities()" />

      <ol-popup
        [position]="selectedCoord()"
        [closeButton]="true"
        [autoPan]="true"
        (closed)="clearSelection()"
      >
        <h3>{{ selected()?.name }}</h3>
        <p>{{ selected()?.description }}</p>
      </ol-popup>
    </ol-map>
  `,
})
export class MyMap {
  // …
}

Setting [position]="null" hides the popup and emits closed.

[olTooltip] — feature hover tooltip

<ol-vector-layer
  id="cities"
  [features]="cities()"
  [olTooltip]="'name'"
  [olTooltipLayer]="'cities'"
/>

Reads feature.get('name') for any feature on layer cities under the cursor and renders a styled <div role="tooltip"> near the pointer. Use the .ol-tooltip class to override the default look.

OlPopupService — programmatic popups

Three content modes from a service:

const popups = inject(OlPopupService);

// 1) Plain text / HTMLElement
popups.open({
  id: 'simple',
  position: [2.17, 41.38],
  content: 'Hello map',
  positioning: 'bottom-center',
  autoPan: true,
});

// 2) Dynamic Angular component (createComponent + hostElement)
const handle = popups.openComponent({
  id: 'city-popup',
  position: [2.17, 41.38],
  component: CityCardComponent,
  bindings: [
    inputBinding('city', () => selected()),
    outputBinding<void>('closed', () => handle.close()),
  ],
});

open is idempotent by id and updates the existing overlay in place. openComponent always recreates the ComponentRef on a repeated id and disposes the previous one (appRef.detachView + ref.destroy) to avoid CD leaks. Calls made before the map is ready are queued and replayed on OlMapService.onReady.

WebGL Layers — GPU-accelerated rendering

Available since 0.3.0 from @angular-helpers/openlayers/layers.

WebGL layers render directly on the GPU, making them perfect for extremely heavy tile configurations (with real-time styling expressions) and massive coordinate datasets (10,000+ vector points).

<ol-webgl-tile-layer> — Raster style manipulation

Renders tile layers (OSM, XYZ, MVT) via WebGL. Supports the dynamic application of WebGL tile styles (raster expressions) for dynamic, GPU-powered adjustments like brightness, contrast, saturation, and gamma.

<ol-webgl-tile-layer
  id="satellite-webgl"
  source="xyz"
  [url]="'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'"
  [tileStyle]="{
    brightness: 0.1,
    contrast: 0.2,
    saturation: -0.5
  }"
/>

<ol-webgl-vector-layer> — Smooth massive datasets (10k+ features)

Renders points, lines, and polygons using WebGL 2. For peak performance, hit detection is disabled by default, and styling must be declared using FlatStyleLike expressions rather than standard ol/style/Style instances.

<ol-webgl-vector-layer
  id="massive-points"
  [features]="densePoints()"
  [flatStyle]="{
    'circle-radius': 6,
    'circle-fill-color': '#10b981',
    'stroke-color': '#334155',
    'stroke-width': 1
  }"
  [disableHitDetection]="true"
/>

Rigorous cleanup guarantees that WebGL contexts, framebuffers, and active buffers are fully released on destroy (layer.dispose()), preventing GPU leaks.

Time-Series Animation & Playback

Available since 0.5.0 from @angular-helpers/openlayers/core and @angular-helpers/openlayers/controls.

Provides high-performance, GPU-friendly reactive animation controls for time-series geospatial visualization (e.g. weather radar, vehicle tracking, historical paths).

1. OlTimeService — 60FPS Animation Loop

The core service coordinates playback timing. It runs its timing ticks entirely outside the Angular zone (using requestAnimationFrame) to prevent triggering global Angular change detection cycles at 60FPS, while exposing reactive signals for component consumption.

import { inject, Component } from '@angular/core';
import { OlTimeService } from '@angular-helpers/openlayers/core';

@Component({
  template: `<p>Current Time: {{ timeSvc.currentTime() }}</p>`,
})
export class MapAnimation {
  protected timeSvc = inject(OlTimeService);

  constructor() {
    // Start animation loop
    this.timeSvc.play();
  }
}

2. <ol-timeline> — Premium Playback UI Control

A sleek, glassmorphic UI overlay component displaying a timeline scrubber, play/pause toggle, and playback speed multiplier.

<ol-map [center]="[2.17, 41.38]" [zoom]="12">
  <ol-tile-layer source="osm" />

  <ol-timeline
    [startTime]="1700000000000"
    [endTime]="1700086400000"
    [playSpeed]="60"
    [loop]="true"
    position="bottom-center"
    (timeChange)="onTimeChange($event)"
  />
</ol-map>

| Input | Type | Default | Description | | ------------- | -------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------- | | startTime | number | Required | Start bounds of the time-series (Epoch ms) | | endTime | number | Required | End bounds of the time-series (Epoch ms) | | playSpeed | number | 1 | Default speed multiplier (e.g. 1, 5, 10, 60, 3600) | | loop | boolean | false | Loop playback when reaching endTime | | position | TimelinePosition | 'bottom-center' | Control alignment (top-left, top-center, top-right, bottom-left, bottom-center, bottom-right) | | formatLabel | (time: number) => string | new Date(t).toLocaleString() | Custom label formatter for the time display |

| Output | Type | Description | | ----------------- | --------- | --------------------------------------------------------------------------------- | | timeChange | number | Emitted with the current active epoch timestamp when the timeline ticks or scrubs | | playStateChange | boolean | Emitted when playback transitions between playing (true) and paused (false) |


Geodesic Geometry Helpers

Available from @angular-helpers/openlayers/core via OlGeometryService.

Approximates standard shapes in metric space using true geodesic calculations (Vincenty's formulae via ol/sphere). This means your shapes remain mathematically accurate and visually consistent (without map projection scale distortion) across massive global distances.

import { inject, Component, signal } from '@angular/core';
import { OlGeometryService } from '@angular-helpers/openlayers/core';
import type { Feature } from '@angular-helpers/openlayers/core';

@Component({
  imports: [OlMapComponent, OlVectorLayerComponent],
  template: `
    <ol-map [center]="[2.17, 41.38]" [zoom]="8">
      <ol-tile-layer id="osm" source="osm" />
      <ol-vector-layer id="shapes" [features]="features()" />
    </ol-map>
  `,
})
export class GeodesicDemo {
  private geomSvc = inject(OlGeometryService);
  features = signal<Feature[]>([]);

  ngOnInit() {
    const ellipse = this.geomSvc.createEllipse({
      center: [2.17, 41.38],
      semiMajor: 6_000,
      semiMinor: 3_000,
      rotation: Math.PI / 6,
    });
    const sector = this.geomSvc.createSector({
      center: [-0.38, 39.47],
      radius: 8_000,
      startAngle: Math.PI / 6,
      endAngle: Math.PI / 2,
    });
    const donut = this.geomSvc.createDonut({
      center: [-5.99, 37.39],
      innerRadius: 5_000,
      outerRadius: 10_000,
    });
    this.features.set([ellipse, sector, donut]);
  }
}

| Method | Output | Notes | | ----------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------- | | createEllipse(config) | Feature<Polygon> | Optional rotation in radians, configurable segments (default 64) | | createSector(config) | Feature<Polygon> | Pie-slice (apex-arc-apex). startAngle < endAngle ≤ start + 2π | | createDonut(config) | Feature<Polygon> | Two rings: outer CCW, inner CW (right-hand rule). Renders as an annular band with the basemap visible through the hole |


Military Symbology & Tactical Graphics

Available since 0.4.0 from @angular-helpers/openlayers/military.

Exposes NATO MIL-STD-2525 symbol rendering backed by the optional milsymbol peer dependency, plus tactical military graphic components (frontlines, attack vectors).

MIL-STD-2525 Point Symbology (OlMilitaryService)

Lazy-loads the heavy milsymbol package dynamically on demand, returning a styled Feature<Point> so the vector layer renders it natively.

import { inject, Component, signal } from '@angular/core';
import { OlMilitaryService } from '@angular-helpers/openlayers/military';
import type { Feature } from '@angular-helpers/openlayers/core';

@Component({
  imports: [OlMapComponent, OlVectorLayerComponent],
  providers: [OlMilitaryService],
  template: `
    <ol-map [center]="[-3.7, 40.42]" [zoom]="8">
      <ol-vector-layer id="military" [features]="features()" />
    </ol-map>
  `,
})
export class MilDemo {
  private milSvc = inject(OlMilitaryService);
  features = signal<Feature[]>([]);

  async ngOnInit() {
    const symbol = await this.milSvc.createMilSymbol({
      sidc: 'SFGPUCI-----', // Friendly Infantry Unit
      position: [-3.7, 40.42],
      size: 36,
    });
    this.features.set([symbol]);
  }
}

Install the optional peer dependency if utilizing NATO symbology:

pnpm add milsymbol

Three execution strategies:

  • createMilSymbol(config) — async; lazy-loads on first call.
  • createMilSymbolSync(config) — sync; throws if milsymbol is not loaded yet.
  • preloadMilsymbol() — fire-and-forget on app init to make the first symbol render synchronous.

The service throws clearly on non-browser environments (createMilSymbol requires window).

Tactical Graphics (OlTacticalGraphicsService)

Builds advanced multi-point military tactical graphic features (frontlines with directional teeth, attack arrow coordinates) and provides custom styles.

import { OlTacticalGraphicsService } from '@angular-helpers/openlayers/military';

const tacticalSvc = inject(OlTacticalGraphicsService);

// Create a frontline graphic
const frontline = tacticalSvc.createFrontLine(
  [
    [2.1, 41.3],
    [2.2, 41.4],
  ],
  'friendly',
);

// Apply specialized style for frontline teeth
const frontlineStyle = tacticalSvc.createFrontLineStyle('#4f46e5', 'friendly');

Architecture

Data vs UI Separation

| Use Case | Approach | Example | | ------------------------------------ | --------------------------- | ------------------------------------------------------------- | | Data (features, layers, coords) | @Input() | <ol-vector-layer [features]="data"> | | UI (buttons, popups, toolbars) | Template/content projection | <ol-custom-control><button>...</button></ol-custom-control> | | Operations (animations, queries) | Service via inject() | this.ol.animateView({...}) |

Sub-Entry Points

Import only what you need:

// Core only (~45KB gzipped)
import { OlMapComponent, OlMapService } from '@angular-helpers/openlayers';

// Add layers (~35KB additional)
import { OlVectorLayerComponent, withLayers } from '@angular-helpers/openlayers/layers';

// Add controls (~15KB additional)
import { OlCustomControlComponent, withControls } from '@angular-helpers/openlayers/controls';

// Add interactions (~40KB additional)
import {
  OlDrawInteractionComponent,
  withInteractions,
} from '@angular-helpers/openlayers/interactions';

// Add military features — pure-math helpers + lazy-loaded milsymbol
import { OlMilitaryService, withMilitary } from '@angular-helpers/openlayers/military';

API Reference

See the documentation for full API reference and examples.

Comparison with Other Libraries

| Feature | ngx-openlayers | ngx-ol-library | IGO2-lib | This Library | | --------------------- | -------------- | -------------- | -------- | ------------------------ | | Angular 21+ | ❌ | ✅ | ❌ | ✅ | | Standalone Components | ❌ | ✅ | ❌ | ✅ | | Signals | ❌ | ❌ | ❌ | ✅ | | Modular Loading | ❌ | Partial | ❌ | ✅ | | Programmatic API | ❌ | ❌ | Partial | ✅ | | Military Features | ❌ | ❌ | ❌ | ✅ | | Bundle Size | ~180KB | ~200KB | ~500KB | ~145KB (all modules) |

License

MIT

Contributing

See the main repository for contribution guidelines.