npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sta-map

v1.14.0

Published

OpenLayers and Leaflet Layer for integrating a SensorThings server

Readme

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/[email protected]/dist/leaflet.css" />
<script src="https://unpkg.com/[email protected]/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/[email protected]/ol.css" />
<script src="https://unpkg.com/[email protected]/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/[email protected]/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):

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.