Arcs

Curved great-circle-style lines between coordinate pairs.

Basic Arc

#

ng-map-arc renders smooth Bézier arcs from from/to pairs — here on a globe projection, connecting London to cities around the world.

import { Component } from '@angular/core';
import { MapArcComponent, MapArcDatum, MapComponent } from 'ng-mapcn';

const hub = { name: 'London', lng: -0.1276, lat: 51.5074 };

const destinations = [
  { name: 'New York', lng: -74.006, lat: 40.7128 },
  { name: 'São Paulo', lng: -46.6333, lat: -23.5505 },
  { name: 'Cape Town', lng: 18.4241, lat: -33.9249 },
  { name: 'Dubai', lng: 55.2708, lat: 25.2048 },
  // ...
];

@Component({
  selector: 'app-basic-arc',
  standalone: true,
  imports: [MapComponent, MapArcComponent],
  template: `
    <div class="h-[420px]">
      <ng-map [center]="[-0.1276, 51.5074]" [zoom]="1" [projection]="{ type: 'globe' }">
        <ng-map-arc
          [data]="arcs"
          [paint]="{ 'line-color': '#3b82f6', 'line-dasharray': [2, 2] }"
          [interactive]="false"
        />
      </ng-map>
    </div>
  `,
})
export class BasicArcComponent {
  readonly arcs: MapArcDatum[] = destinations.map((dest) => ({
    id: dest.name,
    from: [hub.lng, hub.lat],
    to: [dest.lng, dest.lat],
  }));
}

Interactive Arcs

#

Extra datum properties drive the paint via expressions, and hoverPaint + arcHover power the popup. Hover a lane to see its details.

Air
Sea
import { Component, signal } from '@angular/core';
import type { ExpressionSpecification } from 'maplibre-gl';
import {
  MapArcComponent,
  MapArcDatum,
  MapArcEvent,
  MapComponent,
  MapPopupComponent,
} from 'ng-mapcn';

interface Lane extends MapArcDatum {
  origin: string;
  destination: string;
  volume: string;
  mode: 'air' | 'sea';
}

const lanes: Lane[] = [
  { id: 'shg-lax', origin: 'Shanghai', destination: 'Los Angeles',
    from: [121.4737, 31.2304], to: [-118.2437, 34.0522],
    volume: '24.8k TEU', mode: 'sea' },
  { id: 'dxb-jfk', origin: 'Dubai', destination: 'New York',
    from: [55.2708, 25.2048], to: [-74.006, 40.7128],
    volume: '980 tons', mode: 'air' },
  // ...
];

// Extra datum properties are exposed to paint expressions via ["get", ...]
const modeColor: ExpressionSpecification = [
  'match', ['get', 'mode'],
  'air', '#a78bfa',
  'sea', '#34d399',
  '#888',
];

@Component({
  selector: 'app-interactive-arc',
  standalone: true,
  imports: [MapComponent, MapArcComponent, MapPopupComponent],
  template: `
    <div class="h-[420px]">
      <ng-map [center]="[20, 20]" [zoom]="0.8">
        <ng-map-arc
          [data]="lanes"
          [paint]="{ 'line-color': modeColor, 'line-width': 1.5 }"
          [hoverPaint]="{ 'line-width': 3, 'line-opacity': 1 }"
          (arcHover)="onHover($event)"
        />
        @if (selected(); as sel) {
          <ng-map-popup
            [longitude]="sel.longitude"
            [latitude]="sel.latitude"
            [offset]="12"
            [closeOnClick]="false"
            className="ng-popup-passthrough"
          >
            {{ sel.lane.origin }} → {{ sel.lane.destination }} · {{ sel.lane.volume }}
          </ng-map-popup>
        }
      </ng-map>
    </div>
  `,
})
export class InteractiveArcComponent {
  readonly lanes = lanes;
  readonly modeColor = modeColor;
  readonly selected = signal<{
    lane: Lane;
    longitude: number;
    latitude: number;
  } | null>(null);

  onHover(event: MapArcEvent<Lane> | null): void {
    this.selected.set(
      event
        ? { lane: event.arc, longitude: event.longitude, latitude: event.latitude }
        : null
    );
  }
}