Map SDK API
Package: @agastyadreamspty/map-sdk
This document is the developer reference for integrating the reusable Map SDK into Angular applications.
It is written for engineers who need to:
- understand the supported public API
- embed the full host or individual widgets
- wire custom adapters or renderers
- control runtime behavior through configuration
- verify which responsibilities belong to the SDK versus the consumer app
The SDK is designed to be composable, so consumers can adopt:
- the full map explorer page
- filters only
- legend only
- parcel search only
- zoom and layer status only
- a custom map renderer
- a custom backend adapter
The package name is SDK-first, and the exported contract names in this package follow the latest SDK surface.
Public Surface
The long-term supported entry points are:
MapFeatureConfigMapFeatureZoomPolicyMapFeatureAdapterMapFeatureAuthAdapterMapFeaturePermissionAdapterMapFeatureRendererAdapterMapFeatureProviderOptionsMapFeatureAngularAdapterMapFeatureRuntimeConfigMapFeatureHttpConfigMapFeatureThemeVarsFeatureEventBusFeatureSdkEventFeatureSdkSelectionEventFeatureSdkLayerVisibilityEventMapFeatureLayerClickEventFeatureErrorFeatureErrorCodeprovideMapSdk(...)provideMapFeatureRuntimeConfig(...)provideMapFeatureHttpConfig(...)provideMapFeatureAdapter(...)provideMapFeatureDataSource(...)provideMapFeatureRendererAdapter(...)MapSdkHostComponentMapSdkHostShellComponentMapSdkFiltersComponentMapSdkLegendPanelComponentMapSdkParcelSearchComponentMapSdkZoomLayerStatusComponentMapSdkServiceLocationServiceMapSdkDirectApiFacadeMapSdkRequestStateTrackerMapSdkRequestViewStateHelperMapSdkVectorTileLayerManagerLeafletParcelTileHelperMapboxGlParcelTileHelperOpenLayersParcelTileHelperOpenLayersTileLayerFactoriesMapSdkParcelClickContextresolveParcelClickContext(...)MapFeatureHttpDataSourceMapSdkDetailDialogDataMapFeatureFilterOptionMapFeatureParcelSearchSuggestionMapFeatureGeometryResponseMapFeatureLegendGroupMapFeatureLegendItemMapLegendResponseMapLegendGuardsMapLegendGroupApiMapLegendEntryApiMapLegendStylingMapLegendMatchingMAP_FEATURE_DATA_SOURCEMAP_FEATURE_RUNTIME_CONFIGMAP_FEATURE_HTTP_CONFIGMAP_SDK_RENDERER
Everything else should be treated as implementation detail unless it is documented here.
API-only consumption
Consumers that only want the data client can import the SDK root and use the exported data-access services directly. No separate entry point is required.
Typical API-only entry points:
MapSdkDirectApiFacadeMapSdkRequestStateTrackerMapSdkRequestViewStateHelperMapSdkVectorTileLayerManagerLeafletParcelTileHelperMapboxGlParcelTileHelperOpenLayersParcelTileHelperOpenLayersTileLayerFactoriesMapSdkParcelClickContextresolveParcelClickContext(...)MapSdkServiceLocationServiceMapFeatureHttpDataSourceMapFeatureDataSourceMapFeatureParcelSearchSuggestionMapFeatureGeometryResponse
The SDK still ships the UI widgets, but they are optional for API-only consumers.
Example: request-staleness helper
When a consumer app owns the canvas, it can use a small token tracker to ignore late responses from an older province, municipality, or search scope:
import { Injectable } from '@angular/core';
import {
MapSdkDirectApiFacade,
MapSdkRequestStateTracker,
MapFeatureParcelSearchSuggestion
} from '@agastyadreamspty/map-sdk';
@Injectable({ providedIn: 'root' })
export class ParcelRequestFacade {
private readonly parcelsRequest = new MapSdkRequestStateTracker();
constructor(private readonly api: MapSdkDirectApiFacade) {}
beginParcelsRequest(): number {
return this.parcelsRequest.begin();
}
isCurrentParcelsRequest(token: number): boolean {
return this.parcelsRequest.isCurrent(token);
}
resetParcelsRequest(): void {
this.parcelsRequest.reset();
}
searchParcels(provinceId: number, municipalityId: number | null, search: string) {
return this.api.loadParcels(provinceId, municipalityId, search, 10);
}
searchParcelsForCurrentScope(scope: ReturnType<MapSdkDirectApiFacade['buildParcelScope']>, search: string) {
return this.api.loadParcelsForScope(scope, search, 10);
}
getParcelGeometry(provinceId: number, suggestion: MapFeatureParcelSearchSuggestion) {
return this.api.getParcelGeometry(provinceId, suggestion);
}
getDistrictBoundaryDetails(provinceId: number, sourceId?: string | null) {
return this.api.getDistrictBoundaryDetails(provinceId, sourceId ?? null);
}
getSuburbBoundaryDetails(provinceId: number, sourceId?: string | null) {
return this.api.getSuburbBoundaryDetails(provinceId, sourceId ?? null);
}
getTownshipBoundaryDetails(provinceId: number, sourceId?: string | null) {
return this.api.getTownshipBoundaryDetails(provinceId, sourceId ?? null);
}
}
The common pattern is:
- call
begin()before the request starts - keep the returned token with that request flow
- call
isCurrent(token)before you write into component state - call
reset()when the user changes province, municipality, or a similar scope
The same facade also gives you district, township, and suburb detail shortcuts when you need to open an admin-boundary popup without calling the generic admin helper directly.
Example: loading / error view-state helper
When the consumer app owns the canvas, it can keep a tiny state helper beside the request tracker. That gives the page a consistent loading, empty, stale, and error model without pushing UI state into the SDK:
import { Injectable } from '@angular/core';
import {
MapFeatureParcelSearchResponse,
MapSdkDirectApiFacade,
MapSdkRequestViewStateHelper
} from '@agastyadreamspty/map-sdk';
@Injectable({ providedIn: 'root' })
export class ParcelBrowserFacade {
private readonly parcelsState = new MapSdkRequestViewStateHelper<MapFeatureParcelSearchResponse>();
constructor(private readonly api: MapSdkDirectApiFacade) {}
loadParcels(provinceId: number, municipalityId: number | null, search: string): void {
const token = this.parcelsState.begin();
this.api.loadParcels(provinceId, municipalityId, search, 10).subscribe({
next: (response) => {
const hasRows = Boolean(response?.suggestions?.length);
if (!this.parcelsState.isCurrent(token)) {
return;
}
if (!hasRows) {
this.parcelsState.markEmpty(token);
return;
}
this.parcelsState.markSuccess(token, response);
},
error: (error) => this.parcelsState.markError(token, error)
});
}
get viewState() {
return this.parcelsState.getState();
}
}
The pattern is:
- call
begin()before you issue the request - keep the returned token with that request flow
- call
markSuccess(...),markEmpty(...), ormarkError(...)only if the token is still current - let the component render the current
statusvalue instead of hard-coding loading logic in multiple places
Example: vendor parcel tile helpers
These helpers wrap the repetitive layer-building code for the three supported map vendors.
Leaflet:
import { LeafletParcelTileHelper } from '@agastyadreamspty/map-sdk';
constructor(private readonly helper: LeafletParcelTileHelper) {}
this.helper.attach(leafletMap);
this.helper.onParcelClick((detail) => this.parcelDetail = detail);
this.helper.setScope({ provinceId: 101, municipalityId: 202, parcelTypes: 'ERF,FARM' });
Mapbox GL:
import { MapboxGlParcelTileHelper } from '@agastyadreamspty/map-sdk';
constructor(private readonly helper: MapboxGlParcelTileHelper) {}
this.helper.attach(mapboxMap);
this.helper.onParcelClick((detail) => this.parcelDetail = detail);
this.helper.setScope({ provinceId: 101, municipalityId: 202 });
OpenLayers:
import {
OpenLayersParcelTileHelper,
OpenLayersTileLayerFactories
} from '@agastyadreamspty/map-sdk';
const factories: OpenLayersTileLayerFactories = {
createBoundaryLayer(layerId, tileUrl, sourceLayer, color) {
return buildYourOpenLayersBoundaryLayer(layerId, tileUrl, sourceLayer, color);
},
createParcelLayer(layerId, tileUrl, sourceLayer) {
return buildYourOpenLayersParcelLayer(layerId, tileUrl, sourceLayer);
}
};
constructor(private readonly helper: OpenLayersParcelTileHelper) {}
this.helper.attach(openLayersMap, factories);
this.helper.onParcelClick((detail) => this.parcelDetail = detail);
this.helper.setScope({ provinceId: 101, municipalityId: 202 });
The helper classes keep the scope handling, tile URL building, and parcel click flow consistent while the consumer app stays in charge of the real map object and its own canvas lifecycle.
Example: direct-API facade
The convenience facade bundles the services that direct-API consumers usually need in one injectable class:
import { Injectable } from '@angular/core';
import {
MapSdkDirectApiFacade,
MapFeatureParcelSearchSuggestion
} from '@agastyadreamspty/map-sdk';
@Injectable({ providedIn: 'root' })
export class ParcelLookupFacade {
constructor(private readonly api: MapSdkDirectApiFacade) {}
getProvinces() {
return this.api.getProvinces();
}
getMunicipalities(provinceId: number) {
return this.api.getMunicipalities(provinceId);
}
searchParcels(provinceId: number, municipalityId: number | null, search: string) {
return this.api.loadParcels(provinceId, municipalityId, search, 10);
}
getParcelGeometry(provinceId: number, suggestion: MapFeatureParcelSearchSuggestion) {
return this.api.getParcelGeometry(provinceId, suggestion);
}
buildParcelTileUrl(provinceId: number, municipalityId: number | null) {
return this.api.buildParcelTileUrl(provinceId, municipalityId);
}
}
If you want to feed a vendor helper with a single object, use
buildParcelScope(...):
const scope = this.api.buildParcelScope(101, 202, 'ERF,FARM');
this.helper.setScope(scope);
The same scope object can also drive parcel search:
const scope = this.api.buildParcelScope(101, 202, 'ERF,FARM');
this.api.loadParcelsForScope(scope, 'MAIN ROAD 12', 10);
Example: API-only Angular facade
This pattern lets a consumer app call the SDK API layer without rendering the full host or any widgets:
import { Injectable } from '@angular/core';
import {
LocationService,
MapFeatureParcelSearchSuggestion,
MapSdkService
} from '@agastyadreamspty/map-sdk';
@Injectable({ providedIn: 'root' })
export class ParcelApiFacade {
constructor(
private readonly mapSdk: MapSdkService,
private readonly locations: LocationService
) {}
search(provinceId: number, municipalityId: number | null, search: string) {
return this.mapSdk.searchParcels(provinceId, municipalityId, search, 10);
}
getGeometry(provinceId: number, suggestion: MapFeatureParcelSearchSuggestion) {
return this.mapSdk.getParcelGeometryBySuggestion(provinceId, suggestion);
}
getProvinces() {
return this.locations.getProvinces();
}
getMunicipalities(provinceId: number) {
return this.locations.getMunicipalities(provinceId);
}
}
To use the facade in an application, provide the SDK runtime config once at app startup:
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
}
})
]
};
If a consumer wants to bypass the MapSdkService wrapper, they can inject
MapFeatureHttpDataSource directly and call the HTTP-backed methods from the
SDK root package. For filter dropdowns, LocationService is the shortest path.
Example: consumer app integration
This shows a minimal Angular app setup that uses the SDK root package, a small facade, and a component that binds provinces, municipalities, and parcel search results end to end:
// app.config.ts
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
}
})
]
};
// parcel-api-facade.ts
import { Injectable } from '@angular/core';
import {
LocationService,
MapFeatureParcelSearchSuggestion,
MapSdkService
} from '@agastyadreamspty/map-sdk';
@Injectable({ providedIn: 'root' })
export class ParcelApiFacade {
constructor(
private readonly map: MapSdkService,
private readonly locations: LocationService
) {}
getProvinces() {
return this.locations.getProvinces();
}
getMunicipalities(provinceId: number) {
return this.locations.getMunicipalities(provinceId);
}
searchParcels(provinceId: number, municipalityId: number | null, term: string) {
return this.map.searchParcels(provinceId, municipalityId, term, 10);
}
getGeometry(provinceId: number, suggestion: MapFeatureParcelSearchSuggestion) {
return this.map.getParcelGeometryBySuggestion(provinceId, suggestion);
}
}
// parcel-search.component.ts
import { CommonModule } from '@angular/common';
import { Component, OnInit, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ParcelApiFacade } from './parcel-api-facade';
@Component({
standalone: true,
imports: [CommonModule, ReactiveFormsModule],
template: `
<select [formControl]="provinceId">
<option [ngValue]="null">Select province</option>
<option *ngFor="let province of provinces" [ngValue]="province.id">
{{ province.name }}
</option>
</select>
<select [formControl]="municipalityId">
<option [ngValue]="null">Select municipality</option>
<option *ngFor="let municipality of municipalities" [ngValue]="municipality.id">
{{ municipality.name }}
</option>
</select>
<input [formControl]="searchTerm" placeholder="Search parcels" />
<ul>
<li *ngFor="let suggestion of suggestions" (click)="openSuggestion(suggestion)">
{{ suggestion.displayLabel }}
</li>
</ul>
`
})
export class ParcelSearchComponent implements OnInit {
private readonly api = inject(ParcelApiFacade);
provinceId = new FormControl<number | null>(null);
municipalityId = new FormControl<number | null>(null);
searchTerm = new FormControl('', { nonNullable: true });
provinces: Array<{ id: number; name: string }> = [];
municipalities: Array<{ id: number; name: string }> = [];
suggestions: Array<{ displayLabel: string }> = [];
ngOnInit(): void {
this.api.getProvinces().subscribe((provinces) => {
this.provinces = provinces;
});
}
loadMunicipalities(): void {
const provinceId = this.provinceId.value;
if (provinceId === null) {
this.municipalities = [];
return;
}
this.api.getMunicipalities(provinceId).subscribe((municipalities) => {
this.municipalities = municipalities;
});
}
runSearch(): void {
const provinceId = this.provinceId.value;
if (provinceId === null) {
this.suggestions = [];
return;
}
this.api.searchParcels(provinceId, this.municipalityId.value, this.searchTerm.value).subscribe((response) => {
this.suggestions = response.suggestions ?? [];
});
}
openSuggestion(suggestion: { displayLabel: string }): void {
void suggestion;
}
}
That pattern keeps the SDK as the source of truth for map API calls while the consumer app owns the page state and rendering.
Package Philosophy
The SDK is responsible for:
- backend data loading
- legend grouping and normalization
- zoom thresholds and layer visibility
- parcel search behavior
- popup data normalization
- theming variables
- common events and callbacks
The consumer app is responsible for:
- the surrounding page layout
- the host app chrome
- any custom map vendor if they do not want the full-page host
- local wrapper components
- app-specific styling overrides
Capability Matrix
Use this table to choose the right SDK mode for your app.
| Capability | Full host | Widgets only | Custom renderer | Headless |
|---|---|---|---|---|
| Province and municipality filters | Yes | Yes | Yes | No |
| Boundary rendering | Yes | Yes | Yes | No |
| Parcel rendering | Yes | Yes | Yes | No |
| Parcel search | Yes | Yes | Yes | No |
| Legend panel | Yes | Yes | Yes | No |
| Built-in Leaflet canvas | Yes | Yes | No | No |
| Custom map vendor | Optional | Optional | Yes | Yes |
| SDK event bus | Yes | Yes | Yes | Yes |
| SDK fetches backend data | Yes | Yes | Yes | Yes |
Typical usage:
- Full host: use when you want the SDK to behave like the warehouse map explorer.
- Widgets only: use when your app owns layout but wants the SDK filters, legend, and search.
- Custom renderer: use when your app already owns the map canvas but wants SDK data and behavior.
- Headless: use when your app wants data, events, and policy logic without any default UI.
Architectural Overview
The SDK sits between the consumer app and the map backend. In the default mode, the SDK owns data loading and rendering while the consumer owns the page shell.
flowchart LR
subgraph Consumer["Consumer App"]
A["Route / Page Shell"]
B["Theme Wrapper"]
C["Event Handlers"]
D["Optional Custom Renderer"]
end
subgraph SDK["Map SDK"]
E["MapSdkHostComponent"]
F["MapSdkHostShellComponent"]
G["Widgets\nFilters - Legend - Search - Status"]
H["Providers\nConfig - Adapter - Renderer"]
I["FeatureEventBus"]
end
subgraph Backend["Map Backend API"]
J["Provinces"]
K["Municipalities"]
L["Legends"]
M["Parcel Search"]
N["Geometry + Popup Data"]
end
A --> E
A --> G
B --> E
C --> I
D --> H
E --> H
G --> H
H --> Backend
Backend --> N
H --> I
Fishbone View
This fishbone-style diagram shows the main responsibility branches that make up the SDK experience.
flowchart LR
SDK["Map SDK"]
Consumer["Consumer Shell\nRoute - Layout - Theme"]
Data["Data Contract\nAPI base URL - adapter - runtime config"]
UI["UI Widgets\nFilters - Legend - Search - Status"]
Render["Rendering\nLeaflet host - custom renderer"]
Events["Events\noutputs - event bus - layer clicks"]
Policy["Policy\nzoom rules - visibility - gating"]
Docs["Docs + Deployment\nAPI - integration - theming"]
Consumer --- SDK
Data --- SDK
UI --- SDK
Render --- SDK
Events --- SDK
Policy --- SDK
Docs --- SDK
Configuration
MapFeatureZoomPolicy
Controls when each boundary or parcel family becomes visible.
export interface MapFeatureZoomPolicy {
provinceBoundaryZoom: number;
districtBoundaryZoom: number;
municipalityBoundaryZoom: number;
townshipBoundaryZoom: number;
suburbBoundaryZoom: number;
parcelZoom: number;
labelZoomByKind: Record<MapLayerKind, number>;
}
Default values:
- province boundary:
7 - district boundary:
8 - municipality boundary:
9 - suburb boundary:
10 - township boundary:
11 - parcel zoom:
15
MapFeatureConfig
This is the top-level host configuration.
export interface MapFeatureConfig {
apiBaseUrl: string;
includeInactive: boolean;
zoomPolicy: Partial<MapFeatureZoomPolicy>;
features: MapFeatureFeatureFlags;
ui: MapFeatureUiConfig;
search: MapFeatureSearchConfig;
legend: MapFeatureLegendConfig;
}
Key fields
apiBaseUrl- Base URL used by the SDK when it talks to the backend.
- Default:
/api - Common runtime examples:
- local:
http://localhost:8080/api - production:
https://api-warehouse.idti.dev/api
- local:
includeInactive- Whether inactive legend entries should be returned or hidden.
zoomPolicy- Partial override of the default zoom thresholds.
features- Feature gate block that turns major SDK behaviors on or off.
ui- Host labels, theming class, container ID, and mount mode.
search- Search prompt, debounce, and limit behavior.
legend- Legend toggles and default collapsed state.
MapFeatureFeatureFlags
These gates let the consumer decide which behaviors are active.
legendsearchprovinceFiltersmunicipalityFiltersselectedMunicipalityOverlayrefreshButtonboundariesparcels
MapFeatureUiConfig
export interface MapFeatureUiConfig {
title: string;
subtitle: string;
themeClass: string;
mapContainerId: string;
showStatusBanner: boolean;
showLoadingOverlay: boolean;
mountMode: MapFeatureMountMode;
}
Supported mountMode values:
full-pagewidgetsheadless
Default UI values:
- title:
Map Explorer - subtitle:
Browse boundaries, parcels, and live legend layers. - theme class:
map-sdk-theme - map container ID:
leaflet-map - mount mode:
full-page
MapFeatureSearchConfig
export interface MapFeatureSearchConfig {
placeholder: string;
minLength: number;
debounceMs: number;
resultLimit: number;
}
Default search values:
- placeholder:
Search parcels by LPI, SG number, or description - minimum length:
2 - debounce:
300ms - result limit:
8
MapFeatureLegendConfig
export interface MapFeatureLegendConfig {
allowVisibilityToggle: boolean;
allowColorEdit: boolean;
defaultCollapsed: boolean;
}
Default legend behavior:
- visibility toggle enabled
- color editing enabled
- legend expanded by default
Data Contracts
MapFeatureAdapter
MapFeatureAdapter is the main backend data contract. It is an alias for MapFeatureDataSource.
Use it when you want the SDK to fetch data from your API, but you need to replace the HTTP layer or adapt to your own backend.
Required method:
loadLegend(provinceId, includeInactive?, municipalityId?)
Optional methods:
loadProvinces()loadMunicipalities(provinceId)getParcelDetail(provinceId, parcelType, sourceId)getProvinceGeometry(provinceId)getMunicipalityGeometry(provinceId, municipalityId)searchParcels(provinceId, municipalityId, search, limit?)getParcelGeometryBySuggestion(provinceId, suggestion)getAdminBoundaryDetails(sourceKind, provinceId, sourceId?, displayName?, locationId?)
Common request intent:
| Method | Purpose |
|---|---|
loadLegend(...) |
Load visible legend groups and colors for the current scope |
loadProvinces() |
Populate province dropdowns |
loadMunicipalities(provinceId) |
Populate municipality dropdowns after a province is selected |
getProvinceGeometry(provinceId) |
Render or refresh the selected province overlay |
getMunicipalityGeometry(provinceId, municipalityId) |
Render or refresh the selected municipality overlay |
searchParcels(...) |
Return parcel suggestions for the search panel |
getParcelGeometryBySuggestion(...) |
Render the selected searched parcel geometry |
getParcelDetail(...) |
Load the popup record for a clicked parcel |
getAdminBoundaryDetails(...) |
Load the popup record for a clicked boundary or administrative feature |
For parcel details, pass the tile feature's parcelType unchanged. Supported
values are ERF, FARM, FARM_PORTION, and PARENT_FARM. The backend combines
that value with sourceId and the province code resolved from provinceId.
MapFeatureHttpDataSource
MapFeatureHttpDataSource is the default HTTP-backed implementation used by the
SDK. Consumers that own their own map canvas can also inject it directly when
they want the SDK's tile URL builders and REST endpoints without using the full UI.
Useful methods for direct canvas integration:
loadProvinces()loadMunicipalities(provinceId)getProvinceGeometry(provinceId)getMunicipalityGeometry(provinceId, municipalityId)searchParcels(provinceId, municipalityId, search, limit?)getParcelGeometryBySuggestion(provinceId, suggestion)getParcelDetail(provinceId, parcelType, sourceId)buildProvinceBoundaryTileUrl(provinceId)buildDistrictBoundaryTileUrl(provinceId)buildMunicipalityBoundaryTileUrl(provinceId)buildTownshipBoundaryTileUrl(provinceId)buildSuburbBoundaryTileUrl(provinceId)buildParcelTileUrl(provinceId, municipalityId, parcelTypes?)
The tile URL builders return .../tiles/{z}/{x}/{y} templates that consumer map
vendors can use directly when the app owns the canvas. This is the recommended
path for province, district, municipality, township, suburb, and parcel MVT layers.
Example adapter sketch:
export class AppMapAdapter implements MapFeatureAdapter {
loadLegend(provinceId: string, includeInactive = false, municipalityId?: string) {
return this.http.get<MapLegendResponse>(
`${this.baseUrl}/map/legend/${provinceId}`,
{ params: { includeInactive, municipalityId: municipalityId ?? '' } }
);
}
searchParcels(provinceId: string, municipalityId: string, search: string, limit = 8) {
return this.http.get<MapFeatureParcelSearchResponse>(
`${this.baseUrl}/map/parcels/search`,
{ params: { provinceId, municipalityId, q: search, limit } }
);
}
getParcelGeometryBySuggestion(provinceId: string, suggestion: MapFeatureParcelSearchSuggestion) {
return this.http.get<MapFeatureGeometryResponse>(
`${this.baseUrl}/map/parcels/${provinceId}/geometry`,
{ params: { lpi: suggestion.lpi, parcelType: suggestion.parcelType } }
);
}
}
MapSdkService
MapSdkService is the main SDK-first data-access service behind the package.
It is what the full host, widgets, and API-only consumers use when they want
the SDK's standard data contract without wiring the lower-level adapter
directly.
It provides the same behavior as the HTTP data source, but it first checks
whether the app supplied a custom MapFeatureDataSource through
MAP_FEATURE_DATA_SOURCE.
Common methods:
loadLegend(provinceId, includeInactive?, municipalityId?)loadProvinces()loadMunicipalities(provinceId)getParcelDetail(provinceId, parcelType, sourceId)getProvinceGeometry(provinceId)getMunicipalityGeometry(provinceId, municipalityId)searchParcels(provinceId, municipalityId, search, limit?)getParcelGeometryBySuggestion(provinceId, suggestion)getAdminBoundaryDetails(sourceKind, provinceId, sourceId?, displayName?, locationId?)normalizeLegendResponse(response, includeInactive?)buildLegendItems(response, includeInactive?)resolveZoomVisibility(zoom, policy?)getBoundaryLabelState(kind, zoom, policy?)getParcelLabelState(kind, zoom, policy?)getBoundaryZoom(kind, policy?)getParcelZoom(kind, policy?)createFallbackLegendColor(key, label, groupKey)
Use this service when you want one injectable surface for legends, filters, boundary details, parcel search, and zoom/label rules.
LocationService
LocationService is the smallest filter-oriented service in the SDK.
It exposes:
getProvinces()getMunicipalities(provinceId)formatMunicipalityLabel(municipality)
Use it when the app only needs province and municipality dropdown data, or when you want a clean helper for formatting municipality labels in a search panel or filter toolbar.
MapFeatureAuthAdapter
export interface MapFeatureAuthAdapter {
getAccessToken?(): Promise<string | null> | string | null;
getUserId?(): string | null;
}
Use this when the SDK needs identity context for future auth-aware features or auditing.
MapFeaturePermissionAdapter
export interface MapFeaturePermissionAdapter {
canAccessFeature?(permission: string): boolean | Promise<boolean>;
}
Use this when the consumer app wants to let the SDK ask whether a feature is allowed.
MapFeatureRendererAdapter
Use this when the consumer app owns the map canvas and wants the SDK to supply data, visibility rules, and parcel behavior while the app handles the actual rendering.
Methods:
mount(container)setViewport(viewport)setZoomPolicy(policy)setLayerVisibility(layerId, visible)setLayerLabelsVisible(layerId, visible)setLayerStyle(layerId, style)renderLayer(layerId, geometry, descriptor?)clearLayer(layerId)clearAll()destroy()- optional
getZoomState()
Typical usage:
- mount the consumer-owned map into the supplied container
- call SDK data services for province, municipality, legend, and parcel data
- render boundary and parcel geometry into the app's canvas with
renderLayer(...) - keep layer visibility and labels in sync with
setLayerVisibility(...)andsetLayerLabelsVisible(...) - clear or redraw parcel layers when the selected province, municipality, or search context changes
For parcel workflows, the consumer usually calls MapSdkService.searchParcels(...)
and MapSdkService.getParcelGeometryBySuggestion(...), then paints the returned
geometry into a stable parcel layer id in its own map vendor.
MapFeatureApiClient
Minimal HTTP abstraction:
get<TResponse>(url)post<TResponse, TBody>(url, body)
MapFeatureProviderOptions
Options accepted by provideMapSdk(...).
export interface MapFeatureProviderOptions {
adapter?: MapFeatureAdapter | null;
rendererAdapter?: MapFeatureRendererAdapter | null;
runtimeConfig?: Partial<MapFeatureRuntimeConfig> | null;
httpConfig?: Partial<MapFeatureHttpConfig> | null;
}
MapFeatureAngularAdapter
Angular-specific adapter surface for auth and permission hooks:
getAccessToken?()getUserId?()canAccessFeature?(permission)
FeatureEventBus
Lightweight typed event channel for SDK-wide signaling.
export class FeatureEventBus {
readonly events$: Observable<FeatureSdkEvent>;
emit(event: FeatureSdkEvent): void;
}
Events
FeatureSdkEvent
Generic event envelope:
export interface FeatureSdkEvent<TPayload = unknown> {
type: string;
payload: TPayload;
}
FeatureSdkSelectionEvent
Selection-specific envelope:
export interface FeatureSdkSelectionEvent<TPayload = unknown> extends FeatureSdkEvent<TPayload> {
type: 'selection';
}
FeatureSdkLayerVisibilityEvent
Emitted when a layer is shown or hidden.
export interface FeatureSdkLayerVisibilityEvent {
layerId: string;
visible: boolean;
reason?: 'legend' | 'zoom' | 'selection' | 'refresh' | 'api' | 'consumer';
}
MapFeatureLayerClickEvent
Emitted when a boundary or parcel feature is clicked.
export interface MapFeatureLayerClickEvent {
layerId: string;
kind: string;
sourceId: string;
displayName: string;
featureId?: string | number | null;
properties: Record<string, unknown>;
latLng?: [number, number] | null;
}
Event Lifecycle
| User action | Main SDK events | Typical UI update |
|---|---|---|
| Select province | selection + visibility updates |
Refresh municipality list, reload legend, refresh overlays |
| Select municipality | selection + visibility updates |
Focus map, redraw selected boundary, reload parcel scope |
| Toggle a legend item | layerVisibility |
Show or hide the target boundary or parcel layer |
| Zoom the map | visibility update reason zoom |
Show or hide zoom-gated layers and labels |
| Search and select a parcel | selection + layer click |
Render parcel geometry and open popup |
| Click a boundary or parcel | layerClick |
Open popup with the normalized record data |
| Refresh map layers | visibility update reason refresh |
Reload current legend and visible geometry |
Angular Providers
provideMapSdk(...)
The top-level Angular helper for environment providers.
provideMapSdk({
runtimeConfig,
httpConfig,
adapter,
rendererAdapter
});
Use it when you want the SDK to be bootstrapped once at the application level.
provideMapFeatureRuntimeConfig(...)
Injects base URL and default legend endpoint values.
provideMapFeatureHttpConfig(...)
Injects the HTTP config override path.
provideMapFeatureAdapter(...)
Registers a custom backend adapter.
provideMapFeatureDataSource(...)
Registers a MapFeatureDataSource directly.
provideMapFeatureRendererAdapter(...)
Registers a custom renderer adapter for headless/custom-map usage.
Injection Tokens
MAP_FEATURE_DATA_SOURCEMAP_FEATURE_RUNTIME_CONFIGMAP_FEATURE_HTTP_CONFIGMAP_SDK_RENDERER
UI Components
MapSdkHostComponent
Selector: dw-map-sdk-host
This is the main full-page host that wraps the explorer experience.
Inputs:
configadapterincludeInactivezoomPolicythemeClassmapContainerIdshowLegendshowSearchdefaultProvinceId
Outputs:
provinceChangemunicipalityChangelayerVisibilityChangelayerClick
Use this component when the SDK should own:
- data loading
- map canvas
- legend rendering
- search rendering
- layer visibility rules
MapSdkHostShellComponent
Selector: dw-map-sdk-host-shell
This is the layout shell used by the host. It is useful when you want to compose around the SDK UI pieces but still keep the default explorer shell structure.
Inputs:
configadapterheaderTitleheaderSubtitlehostThemeClasshostMapContainerIdprovinceOptionsmunicipalityOptionsselectedProvinceIdselectedMunicipalityIdprovinceLoadingmunicipalityLoadingincludeInactivezoomlegendResponsemapLoadingmapLoadingLabelzoomPolicyshowLegendshowSearch
Outputs:
provinceChangemunicipalityChangezoomChangelayerVisibilityChangelayerClick
MapSdkFiltersComponent
Selector: dw-map-sdk-filters
Inputs:
provinceOptionsmunicipalityOptionsselectedProvinceIdselectedMunicipalityIdprovinceLoadingmunicipalityLoadingtitlesubtitle
Outputs:
provinceChangemunicipalityChange
MapSdkLegendPanelComponent
Selector: dw-map-sdk-legend-panel
Inputs:
titlesubtitlegroupsadminMode
Outputs:
legendVisibilityTogglelegendActiveTogglelegendColorChange
MapSdkParcelSearchComponent
Selector: dw-map-sdk-parcel-search
Inputs:
querysuggestionsloadingstatusplaceholderautocompleteOpenactiveSuggestionIndexisFocused
Outputs:
queryChangefocusblurkeydownclearsuggestionSelect
The suggestionSelect event returns:
{
suggestion: MapFeatureParcelSearchSuggestion;
event?: Event | MouseEvent | PointerEvent;
}
MapSdkZoomLayerStatusComponent
Selector: dw-map-sdk-zoom-layer-status
Inputs:
zoompolicysummary
MapSdkDetailDialogData
The detail dialog is currently represented as a simple data contract:
export interface MapSdkDetailDialogData {
title: string;
body: string;
}
Response and DTO Models
These models are part of the public data contract and are useful when the consumer wants to understand the shape of backend responses or typed widget data.
Filter and search models
MapFeatureFilterOption- used for province and municipality dropdowns
- fields:
id,name, optionalcode, optionalparentId MapFeatureParcelSearchSuggestion- represents a parcel suggestion row
- includes
lpi,sgNo,displayLabel,sourceSchema,sourceTable, and location fields MapFeatureParcelSearchResponse- contains
suggestionsand an optionalreason
Geometry models
MapFeatureGeometryResponse- wraps a geometry payload plus status metadata
- includes
layerAvailable,status,reason,minZoom, andfeatureCollection MapFeatureGeometryFeatureCollection- lightweight GeoJSON-style collection wrapper used by the SDK
Legend models
MapLegendResponse- the backend legend response shape
- includes
provinceId: number, optionalguards?: MapLegendGuards, andgroups: MapLegendGroupApi[] MapLegendGuards- pre-calculated server-side guard maps for immediate O(1) active checks across consumer applications:
groups: Record<string, boolean>: active state of legend groups, with support for alias keyslayers: Record<string, boolean>: hierarchical effective active state (groupActive && entryActive) indexed by full keys (layer:province-boundary), stripped keys (province-boundary), canonical aliases (province), and source kinds (PROVINCE_BOUNDARY)
MapLegendGroupApi- backend legend group DTO
- includes
key,label,legendType,sortOrder, optionalglobalActive, optionaldescription, optionalicon, andentries MapLegendEntryApi- backend legend entry DTO
- includes
key,label,globalActive,visibleByDefault,editable,matching, andstyling MapLegendMatching- matching keys that determine when a legend entry applies
MapLegendStyling- backend styling rules for color, opacity, and icon
MapFeatureLegendGroup- normalized group used by the UI widgets, including optional
globalActive MapFeatureLegendItem- normalized legend item used by the UI widgets
Default Runtime Behavior
If a consumer does not override anything, the SDK will:
- load provinces and municipalities from its configured backend
- load legends from the configured backend
- show the full-page explorer host
- apply the default theme token set
- enforce zoom thresholds for boundaries and parcels
- keep parcel rendering gated behind the parcel zoom policy
Default Backend URLs
The SDK uses its own backend config and does not require the consumer app to fetch map payloads directly.
- local:
http://localhost:8080/api - production:
https://api-warehouse.idti.dev/api
Projected Host Slots
The full-page host supports native Angular content projection slots so consumers can add custom chrome around the map without replacing the SDK's host shell.
Supported slot markers:
mapSdkHeadermapSdkSidebarmapSdkFooter
Example:
<dw-map-sdk-host>
<app-map-toolbar mapSdkHeader></app-map-toolbar>
<app-map-filters mapSdkSidebar></app-map-filters>
<app-map-footer mapSdkFooter></app-map-footer>
</dw-map-sdk-host>
This is the supported path for apps that want a custom toolbar, filter rail, or footer but still want the SDK to own the underlying map, parcel, and location logic.
If you do not need projected slots, you can still use the SDK as a pure API package by importing the root services directly.
Theming
Consumers can override the SDK tokens through a wrapper class:
.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;
}
Build and Pack
From H:\workspace\data-warehouse\frontend:
npm run build:lib
npm pack
The package name is:
@agastyadreamspty/map-sdk
Consumer Guidance
Use the full host if you want the SDK to own the whole map explorer experience.
Use the widgets and adapters if you want:
- only filters
- only legend rendering
- only parcel search
- only layer status
- a custom map vendor
- custom event handling
- app-specific layout and chrome
The SDK should remain the source of truth for:
- API fetch behavior
- legend gating
- search gating
- zoom thresholds
- popup data normalization
- layer visibility decisions
- theming tokens
Troubleshooting
Layers do not appear after zooming
- Confirm the active zoom policy still allows the layer to render.
- Confirm the selected province and municipality are valid for the current API response.
- Confirm the consumer is not hiding the layer via legend visibility.
Legend or search appears behind the map
- Put the SDK host and overlay containers in a stacking context above the canvas.
- Make sure the consumer wrapper does not reset the SDK z-index tokens.
- Keep search and legend panels outside any container that clips overflow.
Search suggestions appear but clicking them does nothing
- Confirm the consumer is using the SDK click handler, not a mouse-down override.
- Confirm the adapter implements
getParcelGeometryBySuggestion(...). - Confirm the parcel geometry endpoint returns a non-empty feature collection.
Popup content renders with the wrong province or record
- Confirm the active province and municipality are passed into the adapter request.
- Confirm the backend response matches the current filter scope.
- Confirm the consumer is not reusing cached popup data from a previous selection.