@superrouteca/smap 1.1.7 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.1 — 2026-10-28
4
+
5
+ - Pins: the number and the count badge are drawn into each pin at every zoom, so where pins overlap the upper pin
6
+ covers the lower one with its number and badge, as on Superroute's route map. Before, further out than zoom 13
7
+ numbers and badges were their own layers above every pin. `bakeZoom` (a zoom) keeps the former drawing further out
8
+ than it; `bakeZoom: false` at every zoom.
9
+
10
+ ## 1.2.0 — 2026-10-28
11
+
12
+ - Maps without WebGL: where the browser has no WebGL 2 (old computers, remote desktops, graphics acceleration
13
+ switched off) or refuses a WebGL context, the map is drawn on a 2D canvas instead — sMap's raster tiles of the same
14
+ scheme, with points, route pins, lines, areas, heat maps, popups, tooltips, custom markers, clusters, drawing and
15
+ selection working through the same calls — so a page is written once. Such a map does not tilt or turn and has no
16
+ 3D buildings, terrain, hillshade or 3D models; it says so with an `smap.unsupported` event. `renderer: 'webgl'`
17
+ keeps the WebGL map only, `renderer: 'canvas'` always draws in 2D, and `map.renderer` says which one is in use.
18
+ - Custom markers: `addCustomMarkers(id, data, { render })` puts your own element at each point — any HTML, such as a
19
+ vehicle with its driver's name, a depot icon or a label — above the drawn layers. Elements are made when a point
20
+ first comes into view and are not laid out while out of view, so thousands stay smooth. Anchor, offset, class,
21
+ opacity and z-order per feature, the marker under the pointer brought to the front (`riseOnHover`), clicks and
22
+ popups as on other layers. `setData` keeps the element of every feature still there and renders it again only when
23
+ its properties change; `getMarker(id, key)` gives one marker's element.
24
+ - Dragging: points (`addPoints`) and custom markers take `draggable` (for every feature or per feature) with
25
+ `onDragStart`, `onDrag` and `onDragEnd`. The map does not pan while dragging, the release is not a click, and the
26
+ layer's data holds the new position afterwards (a copy: the objects you gave are not changed). A dragged point of
27
+ a large layer costs two data updates, whatever the layer's size.
28
+ - Tooltips: every layer takes `tooltip` (a template or a function) shown while the pointer is over a feature —
29
+ beside a point or a custom marker, at the pointer on lines and areas — with `direction`, `offset` and `className`.
30
+ - Line labels: `addLines(…, { label: 'name' })` writes a property along each line as a permanent label.
31
+ - Clusters with their own look: `cluster.ring` draws each cluster as its count inside a ring of its points' colours
32
+ (each colour's share of the points, e.g. the drivers whose stops it holds), and `cluster.html(cluster)` draws each
33
+ cluster yourself from its count, its properties and its points. A click still zooms in to where it splits
34
+ (`zoomOnClick: false` turns it off); `maxZoom` is the last zoom with clusters.
35
+ - Controls: a layer switcher (`addTool('layers', 'layers', { options: { base, overlays } })`) chooses one base map at
36
+ a time — a scheme, a custom style or imagery — and turns overlays on and off, reporting each change. `setBaseImagery`
37
+ shows satellite or aerial tiles in place of the street map, with or without the street map's labels on top, your
38
+ layers staying above. `addControl` puts any element or control in a corner without making it a tool. The full
39
+ screen tool takes `{ container }` to make a page element (the map with its side panels) full screen.
40
+ - Drawing and editing: `drawTool()` draws polygons, rectangles, circles, lines and points and edits them — drag a
41
+ vertex, drag a midpoint to add one, move a whole shape, delete a vertex or a shape, draw a hole in a polygon — with
42
+ snapping to other shapes' vertices, undo and redo, keyboard keys and a toolbar whose texts can be translated.
43
+ Rectangles stay rectangles and circles keep their centre and radius. Every change is reported with the shapes as
44
+ GeoJSON.
45
+ - Area selection: `selectArea({ mode: 'box' | 'lasso' })` lets the user draw a box or a free shape and answers the
46
+ features of each layer inside it.
47
+ - Heat maps: `addHeatmap(id, points, { weight, radius, intensity, colors })`.
48
+ - Google base maps: `setGoogleBase({ session, key })` shows Google's road or satellite map read straight from Google
49
+ with a Map Tiles session your server made with your own Google key, with the Google logo and the copyright of the
50
+ area in view; `onFail` is called when Google's tiles do not load.
51
+ - 3D models: `addModels(id, points, { model, size, heading })` places a glTF model at each point, turned to its
52
+ heading, in the map's 3D view (three.js handed over once with `sMap.useThree(THREE, GLTFLoader)`).
53
+ - Terrain: `setTerrain({ tiles, encoding, exaggeration, hillshade })` draws the map over elevation tiles (seen when
54
+ tilted) with hillshading under the roads; kept across scheme and language changes.
55
+ - Synced maps: `syncWith(other, { offsetZoom })` keeps two maps on the same view, both ways (a picture in picture).
56
+ - Drawn tiles: `addCanvasTiles(id, draw)` shows raster tiles your page paints on a canvas.
57
+ - Geometry helpers: `sMap.geo` — `distance`, `length`, `bounds`, `pointInPolygon` (with holes), `nearestPointOnLine`,
58
+ `distanceToLine`, `decodePolyline` and `encodePolyline`.
59
+
3
60
  ## 1.1.7 — 2026-10-28
4
61
 
5
62
  - Plan editor: faster edits through sMap's fast path, with no code change. When your plan editor token answer names a
package/README.md CHANGED
@@ -65,6 +65,23 @@ SDK does not wrap, and `getEngine()` gives the renderer itself.
65
65
  In a server-rendered page (for example Next.js), create the map in an effect or event handler, never while rendering
66
66
  on the server: `new Map()` throws outside a browser. Importing the package is fine anywhere.
67
67
 
68
+ ## Without WebGL
69
+
70
+ Where the browser has no WebGL 2 (old computers, remote desktops, graphics acceleration switched off) or refuses a
71
+ WebGL context, the map is drawn on a 2D canvas: sMap's raster tiles of the same scheme and language, with your
72
+ points, pins, lines, areas, heat maps, popups, tooltips, custom markers, clusters, drawing and selection drawn by the
73
+ SDK through the same calls. Your page needs nothing of its own.
74
+
75
+ ```js
76
+ const map = new Map({ container: 'map', apiKey: 'smk_…', renderer: 'auto' }); // 'webgl' | 'canvas' to choose
77
+ map.renderer; // 'webgl' or 'canvas'
78
+ map.on('smap.unsupported', (e) => console.info('not without WebGL:', e.feature));
79
+ ```
80
+
81
+ A canvas map does not tilt or turn and has no 3D buildings, terrain, hillshade or 3D models; asking for one of them
82
+ fires `smap.unsupported`. Style layers of your own are drawn when they are raster, fill, line, circle, symbol or
83
+ heatmap layers.
84
+
68
85
  ## Popups
69
86
 
70
87
  Give a layer a `popup` and a click on one of its features opens it. A map has one popup: opening another closes the
@@ -201,6 +218,52 @@ map.addPoints('stops', stops, { // {lng, lat, stop_number, status_colo
201
218
  Numbers and badges are drawn over all pins of the layer, so where pins overlap a lower pin's number can show through
202
219
  an upper pin.
203
220
 
221
+ ## Custom markers, dragging and tooltips
222
+
223
+ **Custom markers** put your own element at each point, above the drawn layers. `render` returns an element or
224
+ trusted HTML (inserted as is: escape feature data yourself). An element is made the first time its point comes into
225
+ view and is left out of the page while out of view, so a layer of thousands stays smooth.
226
+
227
+ ```js
228
+ map.addCustomMarkers('vehicles', vehicles, { // [{ lng, lat, id, name, color, heading }, …]
229
+ idProperty: 'id',
230
+ anchor: 'center', // or top, bottom, left, right, top-left, … (default center)
231
+ render: (p) => `<div class="truck" style="--c:${p.color}">${escapeHtml(p.name)}</div>`,
232
+ zIndex: { property: 'priority' }, // per feature: a constant, a function or { property }
233
+ riseOnHover: true,
234
+ popup: '<b>{name}</b>',
235
+ tooltip: '{name}',
236
+ });
237
+ map.setData('vehicles', moved); // elements of features still there are kept (and only moved)
238
+ map.getMarker('vehicles', 'v-17'); // one marker's element
239
+ ```
240
+
241
+ **Dragging**: points and custom markers take `draggable` (true, or per feature) and `onDragStart` / `onDrag` /
242
+ `onDragEnd(feature, { lngLat, point, originalEvent })`. The map does not pan while a feature is dragged, the release
243
+ is not a click, and the layer's data holds the new position afterwards (a copy of your data).
244
+
245
+ ```js
246
+ map.addPoints('stops', stops, {
247
+ marker: { text: { property: 'stop_number' } },
248
+ draggable: (p) => !p.done,
249
+ onDragEnd: (f, e) => save(f.properties.id, e.lngLat),
250
+ });
251
+ ```
252
+
253
+ **Tooltips**: any layer takes `tooltip` — a template (`'{name}'`, each property HTML-escaped) or a function returning
254
+ trusted HTML or a node — shown while the pointer is over a feature: beside a point or custom marker, at the pointer
255
+ on lines and areas. `tooltipOptions`: `direction` (`top` default, `bottom`, `left`, `right`), `offset`, `className`.
256
+ A map shows one tooltip at a time. **Line labels**: `addLines(id, data, { label: 'name' })` writes a property along
257
+ each line (`labelSpacing`, `labelColor`).
258
+
259
+ **Clusters with their own look**: `cluster.ring` draws each cluster as its count inside a ring of its points' colours
260
+ (each colour's share), `cluster.html(cluster)` draws it yourself (`cluster.count`, `cluster.properties`,
261
+ `await cluster.leaves(limit)`). A click zooms in to where the cluster splits (`zoomOnClick: false` turns it off).
262
+
263
+ ```js
264
+ map.addPoints('stops', stops, { marker: { … }, cluster: { radius: 60, maxZoom: 14, ring: 'route_color' } });
265
+ ```
266
+
204
267
  ## Map tools: showing and hiding controls and panels
205
268
 
206
269
  Register what floats on the map, and the map gets a tools control (one button, the last of the map's toolbar) that
@@ -241,6 +304,61 @@ map.tools(); // [{ id: 'zoom', label: 'Zoom', visible: true }, …]
241
304
  like (the page's storage, your server).
242
305
  - The tools control joins the map with the first tool; a map without tools is unchanged.
243
306
 
307
+ ### Layer switcher, imagery and your own controls
308
+
309
+ ```js
310
+ map.addTool('layers', 'layers', { label: 'Layers', options: {
311
+ base: [
312
+ { id: 'streets', label: 'Streets', style: 'light' },
313
+ { id: 'satellite', label: 'Satellite', imagery: { tiles: ['https://imagery.example/{z}/{y}/{x}'], maxzoom: 19, attribution: '© Example' } },
314
+ { id: 'hybrid', label: 'Hybrid', imagery: { tiles: ['https://imagery.example/{z}/{y}/{x}'], labels: true } },
315
+ ],
316
+ overlays: [{ id: 'traffic', label: 'Traffic', layers: ['traffic'], visible: false }],
317
+ onChange: ({ base, overlays }) => save(base, overlays),
318
+ } });
319
+ map.setBaseImagery({ tiles: […], labels: true }); // or null for the street map
320
+ map.addControl(document.getElementById('legend'), 'bottom-left'); // in a corner, not a tool
321
+ map.addTool('fullscreen', 'fullscreen', { options: { container: document.getElementById('stage') } });
322
+ ```
323
+
324
+ Imagery is drawn over the basemap's land, water and roads and under its labels (kept with `labels: true`); your
325
+ layers stay on top. The scheme already shown is not loaded again when only the imagery changes.
326
+
327
+ ## Drawing, editing and selecting
328
+
329
+ ```js
330
+ const draw = map.drawTool({ features: zones, modes: ['polygon', 'rectangle', 'circle'], labels: { polygon: 'Zone' } });
331
+ draw.on('change', (e) => save(e.features)); // created, edited, deleted: every shape as GeoJSON
332
+ draw.start('polygon'); // or from the toolbar
333
+ draw.select(zoneId); draw.delete(); draw.undo();
334
+
335
+ const picked = await map.selectArea({ mode: 'lasso', layers: ['stops'] });
336
+ if (picked) { assign(picked.features.stops); }
337
+ ```
338
+
339
+ Drawing: click to add vertices; a click on the first vertex, a double click or Enter finishes, Backspace removes the
340
+ last vertex, Escape cancels. Rectangles and circles are dragged. A selected shape shows its vertices and midpoints:
341
+ drag a vertex, drag a midpoint to add a vertex, drag the shape to move it, Delete removes the selected vertex or the
342
+ shape, Ctrl/⌘+Z undoes (with Shift redoes). New and moved vertices snap to other shapes' vertices within 10 px.
343
+
344
+ ## Heat maps, terrain, synced maps, drawn tiles and geometry
345
+
346
+ ```js
347
+ map.addHeatmap('density', stops, { weight: { property: 'orders' }, radius: 25, colors: ['#ffffb2', '#fd8d3c', '#bd0026'] });
348
+ map.setTerrain({ tiles: ['https://dem.example/terrarium/{z}/{x}/{y}.png'], encoding: 'terrarium', exaggeration: 1.5 });
349
+ const stop = map.syncWith(overview, { offsetZoom: -4 }); // a picture in picture; stop() ends it
350
+ map.addCanvasTiles('grid', (ctx, z, x, y, { size }) => { ctx.strokeRect(0, 0, size, size); }, { opacity: 0.5 });
351
+
352
+ map.setGoogleBase({ session, key, type: 'satellite', onFail: () => map.setGoogleBase(null) }); // Google's own tiles
353
+ sMap.useThree(THREE, GLTFLoader); // once, for 3D models
354
+ map.addModels('vans', vehicles, { model: '/models/van.glb', size: 1, heading: { property: 'bearing' } });
355
+
356
+ sMap.geo.distance([-79.38, 43.65], [-73.57, 45.5]); // metres
357
+ sMap.geo.pointInPolygon([lng, lat], zone.geometry); // holes and multi-polygons
358
+ sMap.geo.nearestPointOnLine(route.geometry, [lng, lat]); // { point, distance, index, along }
359
+ sMap.geo.decodePolyline(shape, 6); // [[lng, lat], …]
360
+ ```
361
+
244
362
  ## Plan editor: editing routes on the map
245
363
 
246
364
  `map.planEditor(options)` lets a person edit a plan of routes on the map: drag a stop within its route or onto another
package/dist/SOURCE.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
- "sdkVersion": "1.1.7",
2
+ "sdkVersion": "1.2.1",
3
3
  "engine": "maplibre-gl 6.16.0",
4
4
  "files": {
5
- "smap.js": "1e7ba1266c67f860e74a2956521d55f49ea5771e0b1cfa832014433ea0554d25",
6
- "smap.css": "d82adcd1b8e2b5d1a6ebe0ee648bc24eb84471bc956e3c8ac3cc7dc1f447d96f",
7
- "smap-client.js": "f845c1d52a6f13bfb7f85cf8690e70320b09278b203b9e6a2cc874eed734eb10",
5
+ "smap.js": "bbce8821ec3564f5fc7498b0e7973636e98650e956d94a51a2dbd2259b71b89b",
6
+ "smap.css": "9bc4b14821de97da199e6af4f18541eeaf53a2ae91e5831e7224f57fe6b77660",
7
+ "smap-client.js": "5c769f3f9e4e82d3e7c25bfbd8b91f85f106161f65328fe6eaf19eab6561e897",
8
8
  "smap-client.d.ts": "5f0df11bf60c5a9ced34522a284de81260c27871678ab772f16da9ccf6883f58",
9
- "smap.mjs": "ff3859691bfaf62d672ec6991eeda530be3316b22413d85c09a4adb7e11529cb",
10
- "smap.cjs": "74c72a1504a0023d1b3d2613aac1527bc20676a7002c909715e60f798182b5c8",
11
- "smap-client.cjs": "c1d143a0a9ac8a4cf1abdc7c2c82aec0d063bc51188e3eeb572dc48497475ef3",
12
- "smap-client.mjs": "69d3e44f0d3fd39c3edbd8be6e66d4ddb625795f356a583dfeea610025cb7bd5",
9
+ "smap.mjs": "8edb30186a60969440415b75f6b14b04eea317dc83637dc49e1757ded0c9b0de",
10
+ "smap.cjs": "5bfc4ff01ed19e861ffde2b908f5509c774912fe4d305c6f8269cb4d9f7eeb69",
11
+ "smap-client.cjs": "13a379343810b63bdaa060499d63844e133d28c00649811dcfe107b5dfbabb53",
12
+ "smap-client.mjs": "58f6aa8cb2cec09569606b088247b0e7803cdf225d5ceb733ae3115572a36241",
13
13
  "smap-client.d.cts": "e0fe4eb54cd66b831116082f42bd2f7de260f9f12a2741f4004f8cf10c252116",
14
14
  "smap-client.d.mts": "96830d316ddb33b4ff8edb8b6f810337479636114f75c4170d95038ffda74103",
15
15
  "vendor-licenses.txt": "ee5fc05a0677eaf69601d2c7db0d9ecd6cc27c3abc1d0733bc9ed34707cf8ef2"
@@ -1,5 +1,5 @@
1
1
  /* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
2
- /*! sMap JS client 1.1.7 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
2
+ /*! sMap JS client 1.2.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
3
3
  /*
4
4
  * Runs wherever `fetch` exists: browsers, Node.js 18+, workers; elsewhere (mini programs, older runtimes) pass your
5
5
  * own `fetch`. UMD: `window.SMapClient`, `require('smap-client')`, or an AMD module.
@@ -19,7 +19,7 @@
19
19
  }(typeof self !== 'undefined' ? self : this, function () {
20
20
  'use strict';
21
21
 
22
- var VERSION = '1.1.7';
22
+ var VERSION = '1.2.1';
23
23
  var NODE_TTL = 3600 * 1000; // the measured order
24
24
  var KNOWN_TTL = 30 * 86400 * 1000; // the last node list received, used when none can be read
25
25
 
@@ -1,4 +1,4 @@
1
- /*! sMap JS client 1.1.7 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
1
+ /*! sMap JS client 1.2.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
2
2
  /*
3
3
  * Runs wherever `fetch` exists: browsers, Node.js 18+, workers; elsewhere (mini programs, older runtimes) pass your
4
4
  * own `fetch`. UMD: `window.SMapClient`, `require('smap-client')`, or an AMD module.
@@ -18,7 +18,7 @@
18
18
  }(typeof self !== 'undefined' ? self : this, function () {
19
19
  'use strict';
20
20
 
21
- var VERSION = '1.1.7';
21
+ var VERSION = '1.2.1';
22
22
  var NODE_TTL = 3600 * 1000; // the measured order
23
23
  var KNOWN_TTL = 30 * 86400 * 1000; // the last node list received, used when none can be read
24
24
 
@@ -1,7 +1,7 @@
1
1
  /* Generated by js/build.mjs from js/src (see SOURCE.json). Do not edit. */
2
2
  var smapClientModule = { exports: {} };
3
3
  (function (module, exports, define) {
4
- /*! sMap JS client 1.1.7 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
4
+ /*! sMap JS client 1.2.1 — tokens, the nearest node, styles, tiles and raw reads, without a map view. */
5
5
  /*
6
6
  * Runs wherever `fetch` exists: browsers, Node.js 18+, workers; elsewhere (mini programs, older runtimes) pass your
7
7
  * own `fetch`. UMD: `window.SMapClient`, `require('smap-client')`, or an AMD module.
@@ -21,7 +21,7 @@ var smapClientModule = { exports: {} };
21
21
  }(typeof self !== 'undefined' ? self : this, function () {
22
22
  'use strict';
23
23
 
24
- var VERSION = '1.1.7';
24
+ var VERSION = '1.2.1';
25
25
  var NODE_TTL = 3600 * 1000; // the measured order
26
26
  var KNOWN_TTL = 30 * 86400 * 1000; // the last node list received, used when none can be read
27
27