Skip to content

Repository files navigation

STAM

All Contributors

STAM (SensorThings API Map) displays Things or FeaturesOfInterest from an OGC SensorThings API service on a Leaflet or OpenLayers map. It loads spatial features for the current map view, clusters dense areas, caches fetched data, and can receive MQTT updates.

Requirements

  • A SensorThings API endpoint.
  • A browser map created with Leaflet or OpenLayers.
  • The corresponding map library loaded as a browser global: L for Leaflet or ol for OpenLayers.

The generated bundles externalize Leaflet and OpenLayers. Load the matching library and its CSS before creating a STAM layer.

Usage

Load the map library, then import the matching bundle as an ES module, from a CDN or from your own host:

<script type="module">
  import { STAM } from "https://unpkg.com/sta-map@latest/dist/stam-leaflet.js";
</script>

The two bundles are dist/stam-leaflet.js and dist/stam-openlayers.js. They carry everything except Leaflet and OpenLayers, which the page provides as the globals L and ol. MQTT.js sits in a chunk next to the bundle and is only fetched when mqtt is enabled, so serve the whole dist directory.

Leaflet

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<script type="module">
  import { STAM } from "https://unpkg.com/sta-map@latest/dist/stam-leaflet.js";

  const map = L.map("map").setView([50.27, 7.26], 8);
  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    attribution: "© OpenStreetMap contributors",
  }).addTo(map);

  STAM({
    baseUrl: "https://example.com/v1.1",
    queryObject: { entityType: "Things" },
    cluster: true,
    clusterMin: 5,
  }).addTo(map);
</script>

STAM() returns a Leaflet layer. Add it to the map with .addTo(map).

OpenLayers

<link rel="stylesheet" href="https://unpkg.com/ol@10.10.0/ol.css" />
<script src="https://unpkg.com/ol@10.10.0/dist/ol.js"></script>

<script type="module">
  import { STAM } from "https://unpkg.com/sta-map@latest/dist/stam-openlayers.js";

  const map = new ol.Map({
    target: "map",
    layers: [new ol.layer.Tile({ source: new ol.source.OSM() })],
    view: new ol.View({ center: [808701, 6493627], zoom: 8 }),
  });

  map.addLayer(
    new STAM({
      baseUrl: "https://example.com/v1.1",
      queryObject: { entityType: "Things" },
      cluster: true,
      clusterMin: 5,
      map,
    }),
  );
</script>

The OpenLayers bundle expects the global ol build and the map instance in config.map.

Configuration

Option Description
baseUrl Base URL of the SensorThings API service.
queryObject A query object or zoom-based array of { zoomLevel, query } entries. Supported entity types are Things and FeaturesOfInterest.
cluster Enables clustering. Defaults to enabled when omitted.
clusterMin Minimum feature count for a cluster to remain displayed.
maxMarkersPerTile Entities requested per tile while no count told STAM how many there are. Defaults to 1000.
cachingDuration Cache lifetime in seconds. A falsy value keeps cached data indefinitely.
plot Observation range used by the default popup: { startDate, offset?, endDate? }.
markerStyle Marker color string or function. Supported colors are green, black, blue, grey, violet, orange, red, yellow, and gold.
polygonStyle Style for non-point spatial features.
clusterStyle Circle and polygon styles for clusters, or a function returning them.
fetchOptions Options passed to fetch for SensorThings requests.
maxConcurrentRequests Requests sent per requestDelay. Without a delay the requests are not limited. Defaults to 5.
requestDelay Milliseconds between two waves of requests. Each wave sends up to maxConcurrentRequests. Defaults to 0.
debounceDuration Milliseconds the map has to be still before its data is requested. Defaults to 200.
maxEntities Entities a query without its own top loads at most, over all pages. Defaults to 1000.
pageSize Entities asked for per request, the pages are merged until the limit. Defaults to 1000.
queryParameters Map<string, string> appended to every generated request URL.
mqtt Enables MQTT updates. true uses the defaults derived from baseUrl, an object configures them. See MQTT.
map Required by the OpenLayers bundle; ignored by Leaflet.

Callbacks are available for marker and cluster hover/click events, and for popup close events. A markerClick callback may return HTML for the popup. The default popup can request observations through the feature's generated data callbacks:

const observations = await feature.properties.getData[0].getData(
  (query) => {
    query.resultFormat = "dataArray";
    query.orderby = "phenomenonTime asc";
    return query;
  },
  { signal: controller.signal, onPage: (page, rows) => console.log(page, rows) },
);

Requests ask for pageSize entities at a time and merge the pages until the query's own top, or maxEntities, is reached — so no single request asks a service for everything, and a datastream with millions of observations cannot page on forever.

The default popup plots every page as it arrives, growing the trace with Plotly.extendTraces instead of waiting for the last page, and aborts the pending pages when it is closed. Panning or zooming the plot past the observations it holds loads that time range, the way the map loads tiles: STAM listens for plotly_relayout, requests only the part of the view that is not loaded yet (phenomenonTime ge … and le …), and prepends or appends it to the trace. A range that answered with nothing is not requested again.

MQTT

Set mqtt: true. STAM ships MQTT.js and loads it as a separate chunk the first time a map enables MQTT, so pages without MQTT never download it.

STAM then connects to wss://<host of baseUrl>/mqtt and subscribes to <last segment of baseUrl>/<entityType>, e.g. v1.1/Things. Updates are applied to the matching cached features.

Services that deviate from those defaults are configured with an object instead:

STAM({
  baseUrl: "https://sensor.example/v1.1",
  queryObject: { entityType: "Things" },
  mqtt: {
    url: "wss://broker.example:8884/mqtt",
    options: { username: "sta", password: "secret", clientId: "stam-map" },
    topicPrefix: "sta/v1.1",
  },
});
Option Description
url Websocket endpoint of the broker. Defaults to wss://<host of baseUrl>/mqtt, ws for an http baseUrl.
options Passed to the client's connect, typed as the IClientOptions of MQTT.js.
topicPrefix Prefix the entity type is appended to. Defaults to the last path segment of baseUrl. Ignored when topics is set.
topics Topics to subscribe to, replacing the derived <topicPrefix>/<entityType>. Either an array or a callback receiving the current entity type.
client A connected client, or a connect function. Defaults to the bundled MQTT.js.

MqttClient and IClientOptions are re-exported from the bundles, so a page can type its own client without importing mqtt itself. With client the page controls the connection, and the bundled client is never loaded:

<script src="https://unpkg.com/mqtt@5.15.2/dist/mqtt.min.js"></script>

<script type="module">
  import { STAM } from "https://unpkg.com/sta-map@latest/dist/stam-leaflet.js";

  STAM({
    baseUrl: "https://sensor.example/v1.1",
    queryObject: { entityType: "Things" },
    mqtt: { client: mqtt.connect("wss://broker.example/mqtt", { username: "sta" }) },
  });
</script>

Development

pnpm install
pnpm dev

The development server watches the bundles and serves the Leaflet example at http://localhost:3000/. Run pnpm test, pnpm typecheck, and pnpm build for verification.

Contributors

Thanks goes to these wonderful people (emoji key):

TobiasPressler
TobiasPressler

💻 📖
Hylke van der Schaaf
Hylke van der Schaaf

💻

This project follows the all-contributors specification. Contributions of any kind are welcome.

License

BSD 2-Clause License

Copyright (c) 2020, DataCove e.U. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

About

STAM (SensorThings API Map) is a JavaScript library for showing the Things/Features of interest of a SensorThings server on a Leaflet/OpenLayers map.

Resources

Stars

16 stars

Watchers

5 watching

Forks

Releases

Packages

Used by

Contributors

Languages