Display collection-backed layers on map
This example uses one collection schema as the source of truth for map items. The STANDARD layer displays all visible points from the collection, while the ICON layer reuses the same documents and adds icon rules based on the category field.
Define a collection schema with a nested GeoJSON object at
map.geometryfor geographic coordinates.Configure a
STANDARDlayer and anICONlayer both sourced from the same collection viacollectionSource.Share the same
visible_onlyfilter between the map layers and the CollectionTable widget.Handle live map updates: collection create, update, and delete operations push to the same websocket stream without any widget-specific refresh.
git checkout origin/osp-maps-configuration .
git checkout origin/osp-web-configuration .
git checkout origin/osp-collections-configuration .
git checkout origin/osp-scripts-configuration .
git checkout origin/example-maps-collection-dynamic-layers .
Learning goals
How to configure a
COLLECTIONSmap source.How to stream one schema into both a
STANDARDand anICONlayer.How to reuse the same list-based
filterdefinition across the map and the collection table.How to keep the map live with collection updates without changing the widget request contract.
1. Define the collection schema
The collection stores business fields plus the nested GeoJSON object at map.geometry.
{
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"category": {
"type": "string",
"enum": [
"fire",
"maintenance"
]
},
"visibleOnMap": {
"type": "boolean"
},
"map": {
"type": "object",
"properties": {
"geometry": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"Point"
]
},
"coordinates": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "number"
}
}
},
"required": [
"type",
"coordinates"
]
}
},
"required": [
"geometry"
]
}
},
"required": [
"name",
"category",
"visibleOnMap",
"map"
]
},
"filters": [
{
"id": "visible_only",
"name": "Visible points only"
}
]
}
The visible_only filter metadata is declared in schema.ospp and implemented in schema.collections. The map layers and the table both reference it by id.
{
"moduleId": "modules.collections.collections-1",
"collectionName": "dynamic_map_items",
"indexes": [
{
"name": "visibleOnMap",
"index": "{'visibleOnMap': 1}"
},
{
"name": "category",
"index": "{'category': 1}"
}
],
"filters": [
{
"id": "visible_only",
"query": "{visibleOnMap: true}"
}
]
}
2. Create the collection-backed layers
The standard layer streams the collection documents by collectionSource, extracts GeoJSON from map.geometry and applies the list of schema filter ids declared in filter.
{
"name": "Dynamic collection points",
"description": "Points streamed from the collections module",
"layerType": "STANDARD",
"valueType": "POINT",
"clusteringType": "NONE",
"mapSources": [
{
"type": "COLLECTIONS",
"collectionSource": "root.collections.dynamic_map_items",
"geometry": "map.geometry",
"filter": [
"visible_only"
]
}
]
}
The icon layer reuses the same collection, geometry path and filter list, but also declares the allowed icons and displayRules. visibleExpression is evaluated against the streamed document properties.
{
"name": "Dynamic collection icons",
"description": "Point icons streamed from the collections module",
"layerType": "ICON",
"valueType": "POINT",
"icons": [
"fire",
"maintenance"
],
"mapSources": [
{
"type": "COLLECTIONS",
"collectionSource": "root.collections.dynamic_map_items",
"geometry": "map.geometry",
"filter": [
"visible_only"
],
"displayRules": [
{
"iconName": "fire",
"visibleExpression": "category == 'fire'"
},
{
"iconName": "maintenance",
"visibleExpression": "category == 'maintenance'"
}
]
}
]
}
The linked layer.maps and layer.web files only bind those layers to modules.maps.maps-1 and modules.web.web-1.
3. Configure the dashboard widgets and icon assets
The dashboard activates both layer ids and declares the PNG icon assets used by the icon layer, following the same module.resources pattern as the standard icon example.
{
"configuration": [
{
"id": "maps-dynamic-collection-widget",
"type": "Maps",
"title": "Dynamic collection layers",
"mapsWidgetSettings": {
"layerOptions": [
{
"layerId": "root.layers.collection_standard",
"activatedByDefault": true
},
{
"layerId": "root.layers.collection_icons",
"activatedByDefault": true
}
],
"mapStyle": {
"dark": "mapbox://styles/mapbox/dark-v11",
"light": "mapbox://styles/mapbox/light-v11"
},
"icons": [
{
"path": "osp/resources/icons/fire.png",
"name": "fire"
},
{
"path": "osp/resources/icons/maintenance.png",
"name": "maintenance"
}
],
"iconPriority": [
"fire",
"maintenance"
],
"centerPosition": [
7.4474,
46.948
],
"zoomLevel": 8
}
},
{
"id": "dynamic-map-items-table",
"type": "CollectionTable",
"title": "Visible collection entries",
"menuReferences": [
"root.collections.dynamic_map_items.delete.menu"
],
"collectionTableWidgetSettings": {
"defaultSchemas": [
"root.collections.dynamic_map_items"
],
"defaultFilter": "visible_only",
"pageSize": 50,
"pageSizes": [
50,
100,
200
],
"resizeMode": "widget",
"disableToolbarTableRefresh": false,
"disableSchemaUpdate": false,
"disableViewUpdate": false,
"disableFilterUpdate": false,
"disableToolbar": false,
"disableToolbarMenu": false,
"disableSidePanel": false,
"disableToolbarExport": false,
"disableToolbarColumnShowHide": false,
"disableToolbarFilterShowHide": false,
"disableToolbarSummaryShowHide": false,
"disableToolbarSearch": false,
"disableToolbarColumnChooser": false,
"disableToolbarClearFilter": false
}
}
],
"layout": {
"lg": [
{
"w": 12,
"h": 4,
"x": 0,
"y": 0,
"i": "maps-dynamic-collection-widget"
},
{
"w": 12,
"h": 5,
"x": 0,
"y": 4,
"i": "dynamic-map-items-table"
}
]
},
"breakpoints": {
"lg": 1200,
"md": 996,
"sm": 768,
"xs": 480,
"xxs": 0
},
"cols": {
"lg": 12,
"md": 10,
"sm": 6,
"xs": 4,
"xxs": 2
},
"rowHeight": 130
}
{
"resources": [
{
"destination": "osp/resources/icons/fire.png",
"source": "root/layers/assets/fire.png"
},
{
"destination": "osp/resources/icons/maintenance.png",
"source": "root/layers/assets/maintenance.png"
}
]
}
The same dashboard also contains a CollectionTable filtered with visible_only so operators can inspect the same subset as the map.
5. Runtime behavior
When the frontend sends request-maps-layer for root.layers.collection_standard and root.layers.collection_icons:
osp-webopens one backend geojson stream and forwards the current username.osp-mapsopens collection stream requests for the configured schema and filter ids.Each matching collection document is transformed into a GeoJSON feature.
Further collection updates are pushed automatically to the same websocket response stream.
The table and map both point to the visible_only collection filter, so create, update and delete operations affect the same logical subset without any widget-specific refresh request.