Skip to content

Map SDK Integration Guide

This guide shows how to consume @agastyadreamspty/map-sdk in an Angular app.

The SDK is built for composability. Pick the smallest integration mode that fits your app.

1. Choose Your Mode

Full-page explorer

Use this when the SDK should own the map page layout, filters, legend, parcel search, and popup behavior.

Best when:

  • you want the default warehouse-style explorer
  • you do not want to build your own shell
  • you want the SDK to fetch and render data itself

Widgets-only

Use the exported widgets if you want to place filters or legend controls into your own page layout.

Best when:

  • you already have your own layout or map page
  • you want to keep only the reusable controls
  • you want the consumer app to control surrounding chrome

Headless / custom renderer

Use the adapter and renderer APIs when your app owns the map vendor.

Best when:

  • you already use Leaflet, OpenStreetMap, Google Maps, Mapbox, or another vendor
  • you want the SDK to provide the map data and rules, not the canvas

2. Install the Package

npm install @agastyadreamspty/map-sdk

If you are developing locally from the warehouse repo:

cd H:\workspace\data-warehouse\frontend
npm run build:lib
npm pack

Then install the generated tarball in the consumer repo.

3. Bootstrap the SDK

The easiest entry point is provideMapSdk(...).

import { ApplicationConfig } from '@angular/core';
import { provideMapSdk } from '@agastyadreamspty/map-sdk';

export const appConfig: ApplicationConfig = {
  providers: [
    provideMapSdk({
      runtimeConfig: {
        apiBaseUrl: 'http://localhost:8080/api',
        legendEndpoint: '/map/legend',
        defaultIncludeInactive: false
      }
    })
  ]
};

When to use the lower-level providers

Use the lower-level providers when you want to override only one concern:

  • provideMapFeatureRuntimeConfig(...)
  • provideMapFeatureHttpConfig(...)
  • provideMapFeatureAdapter(...)
  • provideMapFeatureDataSource(...)
  • provideMapFeatureRendererAdapter(...)

4. Full-Page Host Setup

If you want the complete explorer experience, bind the main host component directly.

import { Component } from '@angular/core';
import { MapFeatureConfig, MapSdkHostComponent } from '@agastyadreamspty/map-sdk';

@Component({
  standalone: true,
  imports: [MapSdkHostComponent],
  templateUrl: './map-page.component.html'
})
export class MapPageComponent {
  mapFeatureConfig: Partial<MapFeatureConfig> = {
    apiBaseUrl: 'http://localhost:8080/api',
    includeInactive: false,
    features: {
      legend: true,
      search: true,
      provinceFilters: true,
      municipalityFilters: true,
      selectedMunicipalityOverlay: true,
      refreshButton: true,
      boundaries: true,
      parcels: true
    },
    ui: {
      title: 'Map Explorer',
      subtitle: 'Browse boundaries, parcels, and live legend layers.',
      themeClass: 'smoc-map-theme',
      mapContainerId: 'smoc-map',
      showStatusBanner: true,
      showLoadingOverlay: true,
      mountMode: 'full-page'
    }
  };
}
<dw-map-sdk-host
  [config]="mapFeatureConfig"
  [showLegend]="true"
  [showSearch]="true"
  (provinceChange)="onProvinceChange($event)"
  (municipalityChange)="onMunicipalityChange($event)"
  (layerClick)="onLayerClick($event)"
  (layerVisibilityChange)="onLayerVisibilityChange($event)"
/>

Suggested event handling

  • use provinceChange to update app state or URL filters
  • use municipalityChange to refresh context
  • use layerVisibilityChange to persist legend state
  • use layerClick to open a side panel or local details drawer

5. Widgets-Only Composition

If the consumer app wants to keep the layout but reuse the controls, compose the widgets manually.

<section class="custom-map-shell">
  <aside class="custom-map-shell__controls">
    <dw-map-sdk-filters
      [provinceOptions]="provinceOptions"
      [municipalityOptions]="municipalityOptions"
      [selectedProvinceId]="selectedProvinceId"
      [selectedMunicipalityId]="selectedMunicipalityId"
      [provinceLoading]="provinceLoading"
      [municipalityLoading]="municipalityLoading"
      (provinceChange)="onProvinceChange($event)"
      (municipalityChange)="onMunicipalityChange($event)"
    />

    <dw-map-sdk-legend-panel
      [groups]="legendGroups"
      [adminMode]="false"
      (legendVisibilityToggle)="onLegendVisibilityToggle($event)"
      (legendColorChange)="onLegendColorChange($event)"
    />

    <dw-map-sdk-zoom-layer-status
      [zoom]="zoom"
      [summary]="zoomSummary"
    />
  </aside>

  <div class="custom-map-shell__canvas">
    <!-- your own map vendor canvas -->
  </div>
</section>

This pattern is ideal when you want the SDK UI but not the full explorer shell.

6. Parcel Search Integration

The parcel search widget is controlled entirely by inputs and outputs.

<dw-map-sdk-parcel-search
  [query]="searchQuery"
  [suggestions]="suggestions"
  [loading]="searchLoading"
  [status]="searchStatus"
  [placeholder]="'Search parcels by LPI, SG number, or description'"
  [autocompleteOpen]="autocompleteOpen"
  [activeSuggestionIndex]="activeSuggestionIndex"
  [isFocused]="searchFocused"
  (queryChange)="onSearchQueryChange($event)"
  (focus)="onSearchFocus()"
  (blur)="onSearchBlur()"
  (keydown)="onSearchKeydown($event)"
  (clear)="onSearchClear()"
  (suggestionSelect)="onSuggestionSelect($event)"
/>

Recommended behavior:

  • search on typing only after the minimum length is reached
  • select a suggestion on click or keyboard navigation
  • fetch geometry when the consumer confirms the suggestion
  • do not hard-code app state inside the widget

7. Custom Renderer Integration

Use MapFeatureRendererAdapter when your app owns the map canvas.

import { MapFeatureRendererAdapter } from '@agastyadreamspty/map-sdk';

export const customRenderer: MapFeatureRendererAdapter = {
  mount(container) {
    myMap.mount(container);
  },
  setViewport(viewport) {
    myMap.setViewport(viewport);
  },
  setZoomPolicy(policy) {
    myMap.setZoomPolicy(policy);
  },
  setLayerVisibility(layerId, visible) {
    myMap.setLayerVisibility(layerId, visible);
  },
  setLayerLabelsVisible(layerId, visible) {
    myMap.setLayerLabelsVisible(layerId, visible);
  },
  setLayerStyle(layerId, style) {
    myMap.setLayerStyle(layerId, style);
  },
  renderLayer(layerId, geometry, descriptor) {
    myMap.renderLayer(layerId, geometry, descriptor);
  },
  clearLayer(layerId) {
    myMap.clearLayer(layerId);
  },
  clearAll() {
    myMap.clearAll();
  },
  destroy() {
    myMap.destroy();
  }
};

Then provide it:

provideMapSdk({
  rendererAdapter: customRenderer
});

8. Custom Backend Adapter

If the SDK should use your own backend contract, provide a MapFeatureAdapter.

import { MapFeatureAdapter } from '@agastyadreamspty/map-sdk';

export const adapter: MapFeatureAdapter = {
  loadLegend: (provinceId, includeInactive, municipalityId) =>
    api.loadLegend(provinceId, includeInactive, municipalityId),
  loadProvinces: () => api.loadProvinces(),
  loadMunicipalities: (provinceId) => api.loadMunicipalities(provinceId),
  getProvinceGeometry: (provinceId) => api.getProvinceGeometry(provinceId),
  getMunicipalityGeometry: (provinceId, municipalityId) =>
    api.getMunicipalityGeometry(provinceId, municipalityId),
  searchParcels: (provinceId, municipalityId, query, limit) =>
    api.searchParcels(provinceId, municipalityId, query, limit ?? 8),
  getParcelGeometryBySuggestion: (provinceId, suggestion) =>
    api.getParcelGeometryBySuggestion(provinceId, suggestion),
  getAdminBoundaryDetails: (sourceKind, provinceId, sourceId, displayName, locationId) =>
    api.getAdminBoundaryDetails(sourceKind, provinceId, sourceId, displayName, locationId)
};

9. Theme Overrides

The SDK ships with CSS variables that the consumer can override.

.smoc-map-theme {
  --map-feature-surface: #ffffff;
  --map-feature-surface-elevated: #f6f8fc;
  --map-feature-border: rgba(15, 23, 42, 0.12);
  --map-feature-text: #0f172a;
  --map-feature-muted-text: #475569;
  --map-feature-accent: #0f766e;
  --map-feature-accent-strong: #115e59;
  --map-feature-chip-surface: rgba(15, 118, 110, 0.12);
  --map-feature-chip-text: #0f172a;
}

Use the themeClass field in MapFeatureConfig to attach the wrapper class.

10. Events and Callbacks

The SDK exposes Angular outputs and a typed event bus.

Angular outputs

  • provinceChange
  • municipalityChange
  • layerVisibilityChange
  • layerClick

Event bus

import { FeatureEventBus } from '@agastyadreamspty/map-sdk';

constructor(private readonly eventBus: FeatureEventBus) {}

this.eventBus.emit({
  type: 'selection',
  payload: {
    layerId: 'parcel:123'
  }
});

Use the event bus when your app needs a shared SDK event channel instead of one-off component outputs.

11. Template Extension Pattern

The SDK does not currently expose dedicated projected <ng-template> slots.

The supported extension approach is:

  1. wrap the SDK components in your own Angular component
  2. place your own header, footer, or sidebars around them
  3. feed data through inputs
  4. listen to the exported outputs
  5. forward events to your own state, store, or router

Example wrapper:

<app-map-page-shell>
  <app-map-header></app-map-header>
  <dw-map-sdk-filters ...></dw-map-sdk-filters>
  <dw-map-sdk-host ...></dw-map-sdk-host>
  <app-map-footer></app-map-footer>
</app-map-page-shell>

If you need native content projection slots later, treat that as a future SDK enhancement.

For most apps:

  1. install the package
  2. configure provideMapSdk(...)
  3. decide whether you want the full host or widget composition
  4. override the theme class
  5. bind outputs to app state
  6. refresh the package when the warehouse publishes a new version

13. Example Consumer Component

import { Component } from '@angular/core';
import { MapFeatureConfig, MapSdkHostComponent } from '@agastyadreamspty/map-sdk';

@Component({
  standalone: true,
  imports: [MapSdkHostComponent],
  template: `
    <dw-map-sdk-host
      [config]="config"
      [showLegend]="true"
      [showSearch]="true"
      (layerClick)="openDetails($event)"
    />
  `
})
export class ConsumerMapPageComponent {
  config: Partial<MapFeatureConfig> = {
    apiBaseUrl: 'http://localhost:8080/api',
    includeInactive: false,
    ui: {
      title: 'Map Explorer',
      subtitle: 'Browse boundaries, parcels, and live legend layers.',
      themeClass: 'smoc-map-theme',
      mapContainerId: 'smoc-map',
      showStatusBanner: true,
      showLoadingOverlay: true,
      mountMode: 'full-page'
    }
  };

  openDetails(event: unknown): void {
    console.log('layer click', event);
  }
}