Draw

Draw, select and edit points, lines and polygons, built on terra-draw.

Installation

#

ng-map-draw lives in the optional ng-mapcn/draw entry point, so apps that do not draw never load terra-draw. Install it and its MapLibre adapter, then import from ng-mapcn/draw.

# terra-draw and its MapLibre adapter are optional peer dependencies
npm i terra-draw terra-draw-maplibre-gl-adapter

# Copy-paste distribution: copies the draw sources to <path>/draw and adds the dependencies
ng add ng-mapcn --draw

Basic Example

#

Pick a tool and click the map. Lines and polygons finish by clicking the last vertex again (or Enter); Escape cancels. In select mode drag a shape, drag its vertices, or press Delete to remove it.

import { Component, signal, viewChild } from '@angular/core';
import { MapComponent } from 'ng-mapcn';
import { MapDrawComponent, type MapDrawFeatureCollection, type MapDrawMode } from 'ng-mapcn/draw';

@Component({
  selector: 'app-draw-map',
  standalone: true,
  imports: [MapComponent, MapDrawComponent],
  template: `
    <button (click)="mode.set('point')">Point</button>
    <button (click)="mode.set('line')">Line</button>
    <button (click)="mode.set('polygon')">Polygon</button>
    <button (click)="mode.set('select')">Select</button>
    <button (click)="mode.set('static')">Pan</button>
    <button (click)="draw().clear()">Clear</button>
    <p>{{ features().features.length }} features</p>

    <div class="h-[420px]">
      <ng-map [center]="[-46.6333, -23.5505]" [zoom]="11">
        <ng-map-draw [(mode)]="mode" [(features)]="features" />
      </ng-map>
    </div>
  `,
})
export class DrawMapComponent {
  readonly draw = viewChild.required(MapDrawComponent);
  readonly mode = signal<MapDrawMode>('static');
  readonly features = signal<MapDrawFeatureCollection>({ type: 'FeatureCollection', features: [] });
}

Preloaded features and events

#

features is a two-way GeoJSON FeatureCollection. This geofence editor starts from one polygon in select mode; edits flow back into the model and out through featureEdit.

Last event: none (1 zones)

import { Component, signal } from '@angular/core';
import { MapComponent } from 'ng-mapcn';
import { MapDrawComponent, type MapDrawFeatureCollection, type MapDrawEvent } from 'ng-mapcn/draw';

@Component({
  selector: 'app-geofence-map',
  standalone: true,
  imports: [MapComponent, MapDrawComponent],
  template: `
    <ng-map [center]="[-46.6333, -23.5505]" [zoom]="12">
      <ng-map-draw
        mode="select"
        [modes]="['polygon']"
        [(features)]="zones"
        (drawEnd)="last.set($event)"
        (featureEdit)="last.set($event)"
      />
    </ng-map>
  `,
})
export class GeofenceMapComponent {
  readonly last = signal<MapDrawEvent | null>(null);
  // Features without an id get one; assigning a new collection replaces what is drawn.
  readonly zones = signal<MapDrawFeatureCollection>({
    type: 'FeatureCollection',
    features: [
      {
        type: 'Feature',
        properties: { name: 'Depot' },
        geometry: {
          type: 'Polygon',
          coordinates: [[[-46.66, -23.54], [-46.62, -23.54], [-46.62, -23.56], [-46.66, -23.56], [-46.66, -23.54]]],
        },
      },
    ],
  });
}

Features model

#

features is a GeoJSON FeatureCollection of points, lines and polygons, so it plugs straight into ng-map-geojson or your backend.

readonly features = signal<MapDrawFeatureCollection>({ type: 'FeatureCollection', features: [] });

// <ng-map-draw [(features)]="features" />
//
// - It updates when a draw, edit or delete completes (not on every mouse move).
// - Assigning a collection diffs by feature id: new ids are added, missing ones removed,
//   changed geometry/properties updated in place. Assigning the same data is a no-op.
// - Features without an id get one; unsupported geometries (MultiPolygon, ...) are dropped and
//   reported through (mapError) on <ng-map>.
// - properties.mode is the shape ('point' | 'line' | 'polygon'); other properties are yours.

Modes and editing

#

modes lists the shapes the user can draw and mode is the active tool (two-way). With editable (the default) the select mode drags shapes, reshapes lines and polygons by their vertices, inserts vertices from midpoints and deletes vertices.

<!-- Only polygons can be drawn; features stay selectable and editable -->
<ng-map-draw [modes]="['polygon']" [(mode)]="mode" [(features)]="features" />

<!-- Display only: no select mode, no editing -->
<ng-map-draw [editable]="false" [(features)]="features" />

<!-- mode: 'point' | 'line' | 'polygon' | 'select' | 'static' (default). A mode that is not
     enabled falls back to 'static'. -->

Events

#
// <ng-map-draw
//   (drawEnd)="onDrawEnd($event)"          // a new shape was completed
//   (featureEdit)="onEdit($event)"         // moved, reshaped, vertex added/removed
//   (featureSelect)="onSelect($event)"     // select mode: the Feature
//   (featureDeselect)="onDeselect($event)" // select mode: the feature id
// />

onDrawEnd(event: MapDrawEvent) {
  // { feature, mode: 'polygon', action: 'draw' }
}

Theme and style changes

#

The palette follows the resolved map theme. A theme switch replaces the map style, which removes terra-draw's layers, so the component stops terra-draw and starts it again on the new style with the same features and mode. The same restart happens when modes or editable change; a shape that is still being drawn is lost. Changing color restyles in place.

<!-- Accent color of the drawn shapes (#rrggbb); defaults to a theme-aware blue -->
<ng-map-draw color="#16a34a" [(features)]="features" />

import { buildDrawStyles, defaultDrawPalettes } from 'ng-mapcn/draw';
// buildDrawStyles('dark') -> the terra-draw styles used for each mode