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
provinceChangeto update app state or URL filters - use
municipalityChangeto refresh context - use
layerVisibilityChangeto persist legend state - use
layerClickto 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
provinceChangemunicipalityChangelayerVisibilityChangelayerClick
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:
- wrap the SDK components in your own Angular component
- place your own header, footer, or sidebars around them
- feed data through inputs
- listen to the exported outputs
- 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.
12. Recommended Consumer Flow
For most apps:
- install the package
- configure
provideMapSdk(...) - decide whether you want the full host or widget composition
- override the theme class
- bind outputs to app state
- 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);
}
}