sMap integration
sMap integration
The map SDKs display Superroute basemaps in websites and apps: five colour schemes, labels in any language, points (clustered or not), lines, areas, and 3D buildings. Every SDK has the same API and reads from the nearest map node.
Version 1.1.0 adds standard style layers, basemap options and saved custom styles, data files (KML, GPX, CSV and others), layers from other map services, raster tiles, and adapters for other map tools.
The standards sMap follows, what each map tool can do in both directions, use cases and limits are described in sMap interoperability.
1. Access and billing
Maps use the capability map.tiles, enabled on the account by a Superroute administrator. Tiles are
billed per thousand (tile_1k). Issuing a map token is not billed.
A map reaches the service with a map key, a map token, or both:
| Map key | Map token | |
|---|---|---|
| Format | smk_… |
sm1.… |
| Created | Once, in the client portal | By your server, per session |
| Placed in | The page or app | Fetched by the page or app at run time |
| Limited by | Its allowlist | Its expiry (60 seconds to 24 hours) |
When both are given, both are sent and the token is checked first.
2. Map keys
Map keys are created on the Map keys page of the Open Platform in the client portal. Each key lists the websites and apps that may use it:
| Allowlist | Entry |
|---|---|
| Websites | One host per line: maps.example.com or *.example.com |
| Android apps | The package name, then the SHA-256 fingerprint of each signing certificate: com.example.app AB:CD:… |
| Apple apps (iOS, macOS) | The bundle id: com.example.app |
| Windows apps | The package family name of a packaged app, else its product name |
The SDKs read the app's identity from the operating system and send it with the key. An app cannot set it itself.
For Android, list every certificate the app is signed with: debug, release, and the store's app signing key when the store re-signs the app.
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android | grep SHA256
apksigner verify --print-certs app-release.apk | grep SHA-256
A key is refused with 401 when it does not exist or is disabled, and with 403 when the site or
app is not on its allowlist.
3. Map tokens
A map token is issued by your server with the Open Platform API token, and handed to the page or app. The API token is never placed in a page or app.
curl -sS https://open.superroute.ca/v1/map/token \
-H "Authorization: Bearer $OPEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ttl_seconds":3600}'
{
"capability": "map.tiles",
"billing": { "unit": "tile_1k", "quantity": 0, "counted": true },
"data": {
"token": "sm1.…",
"expires_at": "2026-10-27T15:00:00Z",
"base_url": "https://tile.srsr.ca",
"subject": "open:1234"
}
}
ttl_seconds is optional (default 3600, from 60 to 86400). The same token is issued by the GraphQL
query openMapToken and the MCP tool open_map_token.
Every SDK accepts either token (a fixed token) or getToken (a function that returns one). With
getToken, the SDK requests a new token two minutes before the current one expires; a failed
request is retried after 30 seconds.
4. SDKs
Version 1.5.12. Swift packages, the Android Maven repository and example apps: github.com/Goodneo-Solutions/smap-sdk-releases.
| Platform | Package | Installation |
|---|---|---|
| Web (JavaScript) | @superrouteca/smap |
npm install @superrouteca/smap |
| React | @superrouteca/smap-react |
npm install @superrouteca/smap-react @superrouteca/smap |
| React Native | @superrouteca/smap-react-native |
npm install @superrouteca/smap-react-native |
| Flutter | smap_flutter |
flutter pub add smap_flutter |
| Android | ca.superroute:smap:1.5.12 |
Maven: https://goodneo-solutions.github.io/smap-sdk-releases/maven |
| iOS | Swift package, product SMap |
https://github.com/Goodneo-Solutions/smap-sdk-releases |
| macOS | Swift package, product SMapMac |
https://github.com/Goodneo-Solutions/smap-sdk-releases |
| Windows (WPF) | Superroute.SMap.Wpf |
dotnet add package Superroute.SMap.Wpf |
| Windows (Windows Forms) | Superroute.SMap.WinForms |
dotnet add package Superroute.SMap.WinForms |
| Leaflet | @superrouteca/smap-leaflet |
npm install @superrouteca/smap-leaflet leaflet |
| OpenLayers | @superrouteca/smap-openlayers |
npm install @superrouteca/smap-openlayers ol |
| Mapbox GL JS | @superrouteca/smap-mapbox |
npm install @superrouteca/smap-mapbox mapbox-gl |
| deck.gl | @superrouteca/smap-deckgl |
npm install @superrouteca/smap-deckgl @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/mapbox |
| ArcGIS Maps SDK for JavaScript | @superrouteca/smap-arcgis |
npm install @superrouteca/smap-arcgis @arcgis/core |
| CesiumJS | @superrouteca/smap-cesium |
npm install @superrouteca/smap-cesium cesium |
The last six are adapters for other map tools (section 12). Each one installs the web SDK @superrouteca/smap with
it.
The web SDK is also served by the Superroute Open Platform, without a bundler:
<link rel="stylesheet" href="https://open.superroute.ca/sdk/smap/v1/smap.css">
<script src="https://open.superroute.ca/sdk/smap/v1/smap.js"></script>
The files are listed under Downloads.
Each package can also be downloaded as a file (https://open.superroute.ca/sdk/smap/<file>) and installed without a package registry:
# Web, React, React Native
npm install ./smap-js-1.5.12.tgz
npm install ./smap-react-1.5.12.tgz ./smap-js-1.5.12.tgz
npm install ./smap-react-native-1.5.12.tgz
# Tool adapters: smap-leaflet, smap-openlayers, smap-mapbox, smap-deckgl, smap-arcgis, smap-cesium
npm install ./smap-leaflet-1.5.12.tgz ./smap-js-1.5.12.tgz
# Flutter (pubspec.yaml: smap_flutter: { path: smap_flutter })
mkdir smap_flutter && tar -xzf smap-flutter-1.5.12.tar.gz -C smap_flutter
# Android (settings.gradle.kts: repositories { maven { url = uri("smap-android-1.5.12") } }, same dependency)
unzip smap-android-1.5.12.zip
# iOS, macOS (Xcode: File > Add Package Dependencies > Add Local…, folder smap-apple-1.5.12)
unzip smap-apple-1.5.12.zip
# Windows (WPF, Windows Forms)
unzip smap-windows-1.5.12.zip
dotnet nuget add source "$PWD/smap-windows-1.5.12" --name smap-local
dotnet add package Superroute.SMap.Wpf
5. Examples
Each example shows a map of Toronto with one clustered point layer. Styles are light, white,
grayscale, dark and black.
Web
<link rel="stylesheet" href="https://open.superroute.ca/sdk/smap/v1/smap.css">
<script src="https://open.superroute.ca/sdk/smap/v1/smap.js"></script>
<div id="map" style="height:480px"></div>
<script>
const map = new sMap.Map({
container: 'map',
apiKey: 'smk_…',
style: 'light',
language: 'en',
center: [-79.38, 43.65],
zoom: 11,
});
map.ready.then(() => {
map.addPoints('stops', [{ lng: -79.38, lat: 43.65, name: 'Depot' }], { cluster: true, label: 'name' });
map.addLines('route', routeGeoJson, { color: '#16a34a', width: 5 });
map.fitLayer('stops');
});
</script>
With a bundler:
import { Map } from '@superrouteca/smap';
import '@superrouteca/smap/smap.css';
const map = new Map({
container: 'map',
getToken: async () => (await fetch('/map-token')).text(),
style: 'dark',
center: [-79.38, 43.65],
zoom: 11,
});
In a server-rendered page, the map is created in the browser only (in an effect or an event handler).
React
import { SMap, Points, Lines } from '@superrouteca/smap-react';
import '@superrouteca/smap/smap.css';
export function Deliveries({ stops, route }) {
return (
<div style={{ height: 480 }}>
<SMap apiKey="smk_…" style="light" language="en" center={[-79.38, 43.65]} zoom={11}
onReady={(map) => map.fitLayer('stops')}>
<Points id="stops" data={stops} options={{ cluster: true, label: 'name' }}
onTap={(properties) => console.log(properties.name)} />
<Lines id="route" data={route} options={{ color: '#16a34a', width: 5 }} />
</SMap>
</div>
);
}
React Native
import { SMap, Points } from '@superrouteca/smap-react-native';
export function Deliveries({ stops }) {
return (
<SMap apiKey="smk_…" style="light" center={[-79.38, 43.65]} zoom={11} containerStyle={{ flex: 1 }}>
<Points id="stops" data={stops} options={{ cluster: true, label: 'name' }} />
</SMap>
);
}
On iOS, add the SDK's post_install line to ios/Podfile as described in the package README, then
run pod install.
Flutter
dependencies:
smap_flutter: ^1.5.12
import 'package:smap_flutter/smap_flutter.dart';
SMap(
options: const SMapOptions(
apiKey: 'smk_…',
style: SMapStyle.light,
center: LatLng(43.6532, -79.3832),
zoom: 11,
),
onReady: (map) async {
await map.addPoints(
'stops',
[const SMapPoint(43.65, -79.38, properties: {'name': 'Depot'})],
const SMapPointOptions(cluster: true, label: 'name'),
);
},
)
Android
// settings.gradle.kts: repositories { maven { url = uri("https://goodneo-solutions.github.io/smap-sdk-releases/maven") } }
// app/build.gradle.kts:
dependencies {
implementation("ca.superroute:smap:1.5.12")
}
map = SMap(this, SMapOptions(
apiKey = "smk_…",
style = SMapFlavor.LIGHT,
center = LatLng(43.6532, -79.3832),
zoom = 11.0,
))
map.onCreate(savedInstanceState)
setContentView(map)
map.addPoints("stops", listOf(SMapPoint(43.65, -79.38, mapOf("name" to "Depot"))),
SMapPointOptions(cluster = SMapCluster(), label = "name"))
The activity forwards its lifecycle to the map (onStart, onResume, onPause, onStop,
onDestroy, onLowMemory, onSaveInstanceState).
iOS
// Package.swift
.package(url: "https://github.com/Goodneo-Solutions/smap-sdk-releases", from: "1.5.12")
// target dependency
.product(name: "SMap", package: "smap-sdk-releases")
import SMap
let map = SMapView(frame: view.bounds, options: SMapOptions(
style: .light,
center: CLLocationCoordinate2D(latitude: 43.6532, longitude: -79.3832),
zoom: 11,
apiKey: "smk_…"
))
view.addSubview(map)
Task {
await map.ready()
try map.addPoints("stops", [SMapPoint(lat: 43.65, lng: -79.38, properties: ["name": "Depot"])],
options: SMapPointOptions(cluster: SMapCluster(), label: "name"))
}
SwiftUI apps use SMapSwiftUIView.
macOS
The same Swift package, product SMapMac. Requires macOS 12; a sandboxed app needs the
Outgoing Connections (Client) capability.
import SMapMac
let map = SMapMacView(frame: window.contentView!.bounds, options: SMapOptions(apiKey: "smk_…"))
window.contentView!.addSubview(map)
SwiftUI apps use SMapMacSwiftUIView.
Windows
Requires .NET 8 and the WebView2 Runtime.
<Window xmlns:smap="clr-namespace:Superroute.SMap.Wpf;assembly=Superroute.SMap.Wpf">
<smap:SMapView x:Name="Map" />
</Window>
using Superroute.SMap;
Map.Options = new SMapOptions
{
ApiKey = "smk_…",
Style = SMapStyle.Light,
Center = new SMapLatLng(43.6532, -79.3832),
Zoom = 11,
};
Map.AddPoints("stops", new[] { new SMapPoint(43.65, -79.38) },
new SMapPointOptions { Cluster = new SMapCluster() });
Windows Forms apps use Superroute.SMap.WinForms.SMapView.
6. Style layers and slots
Version 1.1.0 adds the sources and layers of the Style Specification v8. They are passed to the map unchanged, so a layer copied from Mapbox Studio, Maputnik or a Mapbox GL JS page works as it is. They are kept when the scheme or the language changes.
map.addSource('parcels', { type: 'vector', tiles: ['https://tiles.example/{z}/{x}/{y}.pbf'], attribution: '© Example' });
map.addStyleLayer({ id: 'parcel-lines', type: 'line', source: 'parcels', 'source-layer': 'parcels',
paint: { 'line-color': '#64748b' } }, { slot: 'roads' });
map.setPaint('parcel-lines', 'line-width', 2);
map.setFilter('parcel-lines', ['==', ['get', 'zoning'], 'R1']);
map.removeSource('parcels'); // with the layers that read it
| Call | Does |
|---|---|
addSource(id, source) / removeSource(id) |
Adds or removes a source |
addStyleLayer(layer, { slot, before }) |
Adds a style layer |
setPaint, setLayout, setFilter |
Change a layer, the basemap's own layers included |
getStyleLayer(id) |
Gives a layer as a Style Specification object |
removeLayer(id) |
Removes a layer of any kind |
A slot places a layer relative to the basemap. addPoints, addLines, addPolygons, addData and every
external layer take slot too.
| Slot | Where |
|---|---|
bottom |
Over land and water, under everything else |
roads |
Under the roads |
labels |
Under the labels |
top |
On top |
before: '<layer id>' places a layer under a named layer instead. A layer without a slot or before is drawn as in
1.0: on top, in the order added. Maps built with 1.0 look the same. zIndex orders layers inside one slot.
map.addPolygons('zones', zones, { fill: '#f97316', opacity: 0.4, slot: 'labels' }); // street names stay readable
Every SDK has the same calls (Android, iOS, Flutter, React, React Native, macOS and Windows in their own language).
7. Basemap options and custom styles
setBasemap() shows, hides and recolours the basemap by class:
map.setBasemap({
water: { color: '#9cc0e6' },
roads: { color: '#ffffff', width: 1.2 }, // every roads.* class
'roads.highway': { color: '#f6c26b' }, // a class's own values take precedence over roads
pois: { visible: false },
labels: { halo: '#ffffff' },
});
map.basemap(); // the overrides in force
map.setBasemap({}); // back to the scheme
Classes: land, landcover, parks, landuse, airports, water, roads (roads.highway, roads.major,
roads.minor, roads.paths), rail, buildings, boundaries, labels (labels.places, labels.roads,
labels.water, labels.addresses) and pois.
| Field | Meaning |
|---|---|
visible |
Shown or hidden |
color |
Fill, line or background colour; the text colour for labels |
casing |
The outline colour of roads |
halo |
The halo colour of labels |
opacity |
0 to 1 |
width |
A line width factor, 0.25 to 4, of the scheme's own width |
Each call replaces the overrides set before. They are kept when the scheme or the language changes. An unknown class or field is refused with an error that names it.
// Android
map.setBasemap(mapOf("water" to SMapBasemapOverride(color = "#9cc0e6"), "pois" to SMapBasemapOverride(visible = false)))
// iOS, macOS
try map.setBasemap(["water": ["color": "#9cc0e6"], "pois": ["visible": false]])
// Flutter
await map.setBasemap({'water': {'color': '#9cc0e6'}, 'pois': {'visible': false}});
Saved custom styles
A custom style is one of the five schemes with class overrides, saved under an id (sms_…). Custom styles are kept
on the Map styles page of the Open Platform in the client portal (/open-api/map-styles), with a live preview.
Each save is kept as a new revision; the id never changes. Styles are served only while map.tiles is enabled for
the account.
Every SDK takes the id:
const map = new sMap.Map({ container: 'map', apiKey: 'smk_…', style: 'custom:sms_…' });
map.setStyle('custom:sms_…');
A custom style is served in every label language, with the same map key or token as the five schemes. Other map tools
read it by its style URL (Copy style URL on the Map styles page):
https://tile.srsr.ca/v1/styles/custom/sms_…?lang=en.
Google Maps styles
The Map styles page imports a Google Maps style JSON (featureType, elementType, stylers, as shared by Snazzy
Maps and similar sites). Each entry becomes a class override. Entries without a counterpart are listed with the
reason and are not applied.
8. Data formats
simplestyle
GeoJSON properties of the simplestyle specification (marker-color, marker-size, stroke, stroke-width,
stroke-opacity, fill, fill-opacity, title, description) are drawn when a layer gives no colour of its own.
Files from geojson.io, GitHub or Mapbox look as they do there.
Reading files on the device
addData() reads a file and draws it: points as the layer <id>, lines as <id>:lines, areas as <id>:areas.
await map.addData('trip', file); // a File from <input type="file">
await map.addData('stores', csvText, { format: 'csv', lat: 'Latitude', lng: 'Longitude' });
await map.addData('route', '_p~iF~ps|U_ulLnnqC', { format: 'polyline', width: 5 });
const collection = sMap.formats.read(kmlText); // a FeatureCollection, without a map
| Format | Reads |
|---|---|
kml, kmz |
Google Earth and My Maps files: placemarks, extended data, styles as simplestyle colours, tracks |
gpx |
Waypoints, routes and tracks (with elevation) |
polyline, polyline6 |
Encoded polylines, precision 5 and 6 |
wkt, wkb |
Well-Known Text and Binary (also EWKB and hexadecimal text) |
csv |
Latitude and longitude columns, named or found from common headings |
topojson, geojson |
TopoJSON topologies and GeoJSON (RFC 7946) |
The format is taken from the file name or the content when not given; polylines are always named. Every SDK reads the same file into the same features (Android, iOS and Flutter read files natively).
Converting on the map service
POST /v1/convert converts a file on the map service, with the same formats and the same answer. It is used for
large files (up to 20 MB) and by callers that cannot read a format themselves. Nothing is stored.
curl -sS "https://tile.srsr.ca/v1/convert?from=gpx" -H "Authorization: Bearer sm1.…" \
--data-binary @trip.gpx -o trip.geojson
In the SDKs: sMap.convert(file, { format }) or map.convert(…).
Export
map.toGeoJSON(id) and map.toKML(id) give a layer's features as GeoJSON or as a KML document.
9. External layers
Layers from other services are added as sources. The attribution of each source is shown beside the map's credit.
map.addRasterLayer('osm', { tiles: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', attribution: '© OpenStreetMap', opacity: 0.6 });
map.addRasterLayer('imagery', { url: 'https://tiles.example/imagery.json' }); // TileJSON
map.addWmsLayer('flood', { url: 'https://wms.example/ows', layers: 'flood_zones', slot: 'labels' });
await map.addWmtsLayer('ortho', { url: 'https://wmts.example/WMTSCapabilities.xml', layer: 'ortho' });
map.addVectorTiles('parcels', { pmtiles: 'https://data.example/parcels.pmtiles',
layers: [{ id: 'parcel-fill', type: 'fill', 'source-layer': 'parcels', paint: { 'fill-color': '#fde68a' } }] });
await map.addArcGisLayer('esri', { url: 'https://services.example/arcgis/rest/services/World/MapServer' });
| Call | Reads |
|---|---|
addRasterLayer |
Raster XYZ templates (tiles) or a TileJSON address (url) |
addWmsLayer |
OGC WMS 1.3.0 or 1.1.1, one GetMap request per tile in Web Mercator |
addWmtsLayer |
OGC WMTS from its GetCapabilities address (KVP or REST), or a REST template |
addVectorTiles |
Mapbox Vector Tiles from templates, a TileJSON address or a PMTiles archive, drawn by your style layers |
addArcGisLayer |
ArcGIS REST MapServer and ImageServer (raster tiles) and VectorTileServer |
Every external layer takes slot, before and zIndex. Google and Bing tiles are not offered: their licences do
not allow it, and the SDK refuses their addresses.
10. A map as JSON
map.toJSON() writes the map's basemap (scheme, language, overrides), sources and layers as one JSON document, and
map.loadLayers(json) loads it. Every SDK reads the same document, so a map built on the web opens the same in an app.
const saved = map.toJSON();
// { "type": "smap.layers", "version": 1, "basemap": { … }, "sources": { … }, "layers": [ … ] }
other.loadLayers(saved);
Popups are written as templates (popup: '<b>{name}</b>'). Popup functions, click handlers and deck.gl layers are
code and are not written. A reader that cannot draw a layer skips that layer and loads the rest. The shape and its JSON
Schema are in shared/LAYER-JSON.md of the SDK repository.
11. Raster tiles, TileJSON and WMTS
For tools that do not draw vector tiles, the map service also serves the map as images, drawn from the same styles:
| Address | Gives |
|---|---|
/v1/raster/<style>/{z}/{x}/{y}.png |
A 256 px tile ({y}@2x.png: 512 px) |
/v1/raster/<style>.json |
TileJSON 3 of the raster tiles |
/v1/raster/wmts?SERVICE=WMTS&REQUEST=GetCapabilities |
OGC WMTS 1.0.0 capabilities (each scheme a layer, tile matrix set GoogleMapsCompatible) |
<style> is a scheme or custom:<id>. lang sets the label language. Raster tiles have the same access rules as
vector tiles and are billed in the same way under map.tiles.
sMap.rasterUrl('grayscale', { lang: 'de', retina: true });
// https://tile.srsr.ca/v1/raster/grayscale/{z}/{x}/{y}@2x.png?lang=de
A desktop program sends no website address, so a map key alone is not accepted there. Put a map token in the address
(&token=sm1.…, valid up to 24 hours, section 3), or give the program's Referer field a website that is on the key's
allowlist.
Office software and other programs that read XYZ, TileJSON or WMTS use the same addresses.
12. Other map tools
Each adapter is an npm package that shows sMap in the tool with what the sMap SDK offers there: the five schemes and custom styles, any label language, basemap options, map key or token with renewal, the nearest map node, and the credits. It also works the other way: the tool's layers on an sMap map. Each package README has the full list of calls.
Leaflet
npm install @superrouteca/smap-leaflet leaflet
import L from 'leaflet';
import { smapLayer, smapRasterLayer } from '@superrouteca/smap-leaflet';
import 'leaflet/dist/leaflet.css';
import '@superrouteca/smap/smap.css';
const map = L.map('map').setView([43.65, -79.38], 12);
const basemap = smapLayer({ apiKey: 'smk_…', style: 'light', language: 'en' }).addTo(map);
basemap.addPoints('stops', stops, { marker: { text: { property: 'stop_number' } } });
basemap.setBasemap({ water: { color: '#9cc0e6' } });
// Without WebGL: smapRasterLayer({ apiKey: 'smk_…', style: 'grayscale' }).addTo(map);
Leaflet's zoom levels are one above the sMap map's own. Leaflet-style calls (L.marker, L.polyline, L.geoJSON,
L.tileLayer, bindPopup, …) also run on an sMap map through @superrouteca/smap-leaflet/compat.
OpenLayers
npm install @superrouteca/smap-openlayers ol
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import { fromLonLat } from 'ol/proj.js';
import { smapLayer } from '@superrouteca/smap-openlayers';
const basemap = smapLayer({ apiKey: 'smk_…', style: 'light', language: 'en' });
const map = new Map({ target: 'map', layers: [basemap], view: new View({ center: fromLonLat([-79.38, 43.65]), zoom: 12 }) });
basemap.addLines('routes', routes, { color: { property: 'color' }, width: 5 });
The view must be in Web Mercator (EPSG:3857). smapRasterLayer() gives raster tiles. OpenLayers sources (XYZ,
TileWMS, WMTS, VectorTile, Vector) are added to an sMap map with addOpenLayersLayer() from
@superrouteca/smap-openlayers/on-smap.
Mapbox GL JS
npm install @superrouteca/smap-mapbox mapbox-gl
import mapboxgl from 'mapbox-gl';
import { SMapMapbox } from '@superrouteca/smap-mapbox';
mapboxgl.accessToken = 'pk.…'; // required by Mapbox's own terms
const smap = new SMapMapbox(mapboxgl, {
container: 'map', apiKey: 'smk_…', style: 'light', language: 'en',
center: [-79.38, 43.65], zoom: 12, pitch: 50, buildings3D: true,
});
await smap.ready;
smap.addStyleLayer({ id: 'zones-fill', type: 'fill', source: 'zones', paint: { 'fill-color': '#f97316' } }, { slot: 'labels' });
An existing Mapbox GL JS map changes its basemap to sMap with switchBasemap() and keeps its own layers. Mapbox
styles and layers are loaded on an sMap map with loadMapboxStyle() from @superrouteca/smap-mapbox/on-smap.
deck.gl and kepler.gl
npm install @superrouteca/smap-deckgl @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/mapbox
import { createSMapDeck } from '@superrouteca/smap-deckgl';
import { HexagonLayer } from '@deck.gl/aggregation-layers';
const { map, setLayers } = await createSMapDeck({
container: 'map', apiKey: 'smk_…', style: 'dark', center: [-79.38, 43.65], zoom: 11, pitch: 45,
layers: [new HexagonLayer({ id: 'orders', data, getPosition: (d) => d.position, extruded: true, radius: 200 })],
});
deck.gl layers are drawn among the map's own layers. For an app with its own Deck, createVectorBasemapLayer()
and createRasterBasemapLayer() give sMap as a deck.gl layer. On any sMap map, map.addDeckLayer(layer, { slot })
adds a deck.gl layer.
kepler.gl reads sMap's styles as map styles:
import KeplerGl from '@kepler.gl/components';
import { keplerMapStyles, keplerTransformRequest } from '@superrouteca/smap-deckgl';
const access = { apiKey: 'smk_…' };
const mapStyles = await keplerMapStyles({ ...access, language: 'en', styles: ['light', 'dark', 'custom:sms_…'] });
<KeplerGl id="map" mapStyles={mapStyles} mapStylesReplaceDefault
transformRequest={keplerTransformRequest(access)} width={width} height={height} />;
The styles are named smap-light, smap-dark and smap-custom-sms_….
ArcGIS Maps SDK for JavaScript
npm install @superrouteca/smap-arcgis @arcgis/core
import Map from '@arcgis/core/Map.js';
import MapView from '@arcgis/core/views/MapView.js';
import { createSMapBasemap, addAttribution } from '@superrouteca/smap-arcgis';
const basemap = await createSMapBasemap({ apiKey: 'smk_…', style: 'light', language: 'fr' });
const view = new MapView({ container: 'map', map: new Map({ basemap }), center: [2.35, 48.86], zoom: 12 });
addAttribution(view);
The vector basemap converts sMap's style into what ArcGIS draws; its limits are in Interoperability guide 5.
raster: true gives raster tiles (also draped in a SceneView). An existing ArcGIS app keeps its layers:
map.basemap = await createSMapBasemap({ … }). ArcGIS REST services are added to an sMap map with
addArcGisService() from @superrouteca/smap-arcgis/services.
CesiumJS
npm install @superrouteca/smap-cesium cesium
import { Viewer } from 'cesium';
import { createBaseLayer } from '@superrouteca/smap-cesium';
const viewer = new Viewer('globe', {
baseLayer: createBaseLayer({ apiKey: 'smk_…', style: 'light', language: 'ja' }),
baseLayerPicker: false,
geocoder: false,
});
The globe's imagery is sMap's raster tiles (XYZ, or WMTS with wmts: true); no Cesium ion account is needed for it.
addSMapData() draws sMap's points, lines and areas in the viewer; addCesiumData() shows CZML, KML and GeoJSON on an
sMap map.
QGIS
QGIS reads sMap's raster tiles as an XYZ connection or as WMTS.
XYZ. In the Browser panel, right-click XYZ Tiles > New Connection:
| Field | Value |
|---|---|
| Name | sMap light |
| URL | https://tile.srsr.ca/v1/raster/light/{z}/{x}/{y}.png?lang=en&token=sm1.… |
| Tile Resolution | Standard (256x256 / 96 DPI); for {y}@2x.png, High (512x512 / 192 DPI) |
WMTS. Layer > Add Layer > Add WMS/WMTS Layer, New, with the URL
https://tile.srsr.ca/v1/raster/wmts?SERVICE=WMTS&REQUEST=GetCapabilities&token=sm1.…. Every scheme is listed as a
layer.
The token is valid for its own lifetime (up to 24 hours). When it expires, a new token is issued and the URL is changed in the connection.
Google Maps JavaScript API
deck.gl's Google Maps overlay draws sMap's points, lines and areas on a Google map. The basemap stays Google's, under Google's terms; sMap's basemap is not placed on Google Maps.
npm install @superrouteca/smap-deckgl @deck.gl/core @deck.gl/layers @deck.gl/geo-layers @deck.gl/google-maps
import { GoogleMapsOverlay } from '@deck.gl/google-maps';
import { pointsLayers, linesLayers, popupTooltip } from '@superrouteca/smap-deckgl';
const map = new google.maps.Map(document.getElementById('map'), { center: { lat: 43.65, lng: -79.38 }, zoom: 12, mapId: '…' });
const overlay = new GoogleMapsOverlay({
layers: [
...linesLayers('route', route, { color: '#16a34a', width: 5, casing: true }),
...pointsLayers('stops', stops, { marker: { text: { property: 'seq' } } }),
],
getTooltip: popupTooltip(),
});
overlay.setMap(map);
13. Offline regions
An app on Android or iOS can keep an area of the map on the device and show it without a network. A region is a bounding box, a zoom range, a colour scheme or saved custom style and a label language, downloaded as one file of at most 500 MB. The web, macOS and Windows SDKs have no offline regions.
- Who may download: a map token, or a map key whose Allow offline regions switch is on (on the Map keys page; off by default).
- Billing: the region's tiles are billed as
map.tileswhen it is downloaded, once a day per region and key or token, however often the download resumes. Asking for the size first is free. Showing a region offline costs nothing. - Validity: 30 days from the download; then the region is not shown until it is refreshed. While online, the SDK downloads again the regions in their last 3 days (
offlineAutoRefresh, on by default). - Resuming: calling the download again for the same region continues where it stopped.
- Fallback: when no map node answers, the map shows a downloaded region containing its centre (
offlineFallback, on by default). Your layers stay on the map. - Managing:
list(),get(id),refresh(id),remove(id)andusage()list, refresh and remove the downloaded regions and give the space they use. After the 30 days a region's map data is removed and its definition is kept, sorefresh(id)downloads it again. - iOS: downloads need iOS 15. Regions are excluded from device backups.
| Platform | Downloads | On the map |
|---|---|---|
| Android | SMapOffline(context, options), map.offline |
SMapOptions(offlineRegion, offlineFallback, offlineAutoRefresh), useOfflineRegion, useOnline |
| iOS | SMapOffline(options:), map.offline |
SMapOfflineSettings(region:fallback:autoRefresh:), useOfflineRegion, useOnline |
| Flutter | SMapOffline(options), controller.offline |
SMapOptions(offlineRegion, offlineFallback, offlineAutoRefresh), useOfflineRegion, useOnline |
| React Native | offlineRegions(options) |
<SMap offlineRegion offlineFallback offlineAutoRefresh>, useOfflineRegion, useOnline, offlineRegionInUse |
val region = SMapOfflineRegion("downtown", west = -79.42, south = 43.63, east = -79.35, north = 43.68, maxZoom = 15)
val estimate = map.offline.estimate(region) // tiles, bytes, fits, allowed
if (estimate.allowed && estimate.fits) {
map.offline.download(region) { done, total -> }
map.useOfflineRegion("downtown") // map.useOnline()
}
const offline = offlineRegions({ apiKey: 'smk_…' });
await offline.download(region, (done, total) => setProgress(done / total)); // refresh(id, onProgress) too
14. Map tools
The controls and panels on a map — zoom, compass, scale bar, locate, and your own panels such as a legend or an orders list — can be shown and hidden one by one or all at once. Every SDK has the same calls from 1.1.5:
const map = new sMap.Map({ container: 'map', apiKey: 'smk_…', tools: { hidden: saved } });
map.addTool('zoom', 'zoom');
map.addTool('legend', document.getElementById('legend'), { label: 'Legend', position: 'bottom-left' });
map.addTool('stops', { layer: ['stops', 'stop-labels'] }, { label: 'Stops' }); // data layers, from 1.1.6
map.onToolsChange((e) => save(e.hidden));
addTool(id, kind or view, options)registers a tool: a control the map makes (zoom,compass,scale,locate; on the web alsonavigation,geolocate,fullscreen) or a view of your own, in a corner or where you placed it (position: 'none').setToolVisible(id, visible),setToolsVisible(visible)andtools()switch and read the tools;onToolsChangereports every change.- A tools control (one button) lists every tool with Show all and Hide all;
tools.control: falseleaves it out. - A hidden tool keeps its state. The map data credit and the sMap badge are never tools.
- The map keeps no state: give the starting state in
tools.hiddenand keep the hidden ids where you like. - On each platform: React
<Tool>, React Native<Tool>children of<SMap>, FlutterSMap(tools: [SMapTool …]), Android and iOSaddTool, macOS and WindowsaddTool(your own view stays where you placed it).
14.1 Where the tools control sits
From 1.1.6 the tools control is by default the last button of the map's toolbar: the corner that holds the map's navigation control (the zoom buttons or the compass), top left when the map has none. It stays last when other controls join that corner later. In a bottom corner it is the lowest tool, just above the map data credit and the sMap badge, which keep the map's edge.
- On the web the toolbar is the corner of the navigation control, whether it was added as a tool (
navigation,zoom,compass) or by the page itself. On Android, iOS, Flutter and React Native it is the corner of the firstzoomtool, else of the firstcompasstool, else the top left, and the control follows that tool when it is added, moved or removed. macOS and Windows follow the web SDK. - A corner given by the app places the control there, as in 1.1.5:
tools: { control: 'top-right' }on the web. None (false, or null on the native SDKs) leaves it out. - The value for the default is
'toolbar'(Web, React, React Native),SMapToolPosition.TOOLBAR(Kotlin),.toolbar(Swift),SMapToolPosition.toolbar(Dart) andSMapToolPosition.Toolbar(C#). It is valid for the tools control only;addToolrefuses it. An exhaustiveswitchorwhenover the position values needs the new case. - Nothing on the map covers the control or its panel: the control is drawn above the other tools of its corner, and while its panel is open that corner is drawn above the map's other corners. The panel opens towards the middle of the map.
const map = new sMap.Map({ container: 'map', apiKey: 'smk_…', tools: { control: 'bottom-right' } }); // a corner of your choice
14.2 Data layers as tools
From 1.1.6 an app can register its own data layers as a tool, so that the tools control shows and hides them: the points, lines and polygons of the SDK, addData layers, style layers, external layers, or the basemap's layers, by layer id.
| Platform | Call |
|---|---|
| Web | map.addTool('stops', { layer: ['stops', 'stop-labels'] }, { label: 'Stops' }) |
| React, React Native | <Tool id="stops" label="Stops" layer={['stops', 'stop-labels']} /> |
| Android (Kotlin) | map.addTool("stops", listOf("stops", "stop-labels"), "Stops"), or one layer: addTool(id, layer, label) |
| iOS, macOS (Swift) | try map.addTool("stops", layers: ["stops", "stop-labels"], label: "Stops"), or layer: |
| Flutter | SMapTool.layers('stops', ['stops', 'stop-labels'], label: 'Stops') in the map's tools |
| Windows (C#) | map.AddTool("stops", new[] { "stops", "stop-labels" }, "Stops") |
- Hiding the tool hides every layer it names, and showing it shows them. It has the same label,
setToolVisible,setToolsVisible,tools(), change events and saved hidden ids as any other tool. - One tool may name several layers, such as a layer and its labels; a layer named twice counts once. An
addDatagroup is named by its layers' ids, not by the group's id. - A layer that is not on the map, removed or not added yet, is left alone and is no error. A layer added later under an id of a hidden tool starts hidden; one added under a shown tool keeps its own visibility.
- Removing a hidden data-layer tool shows its layers again.
- A data-layer tool has no view and no corner; a corner given with it is refused.
The plan editor adds its panel as the tool plan-editor.
15. Markers, drawing and more
From 1.2.0 every SDK has the same calls for what an operations map needs:
map.addCustomMarkers('vans', vans, { idProperty: 'id', anchor: 'bottom', draggable: true,
render: (p) => `<div class="van">${p.name}</div>`, onDragEnd: (f, e) => save(f.properties.id, e.lngLat) });
map.addPoints('stops', stops, { tooltip: '{name}', cluster: { ring: 'color' } });
map.addHeatmap('density', stops, { radius: 18 });
const draw = map.drawTool({ onChange: (e) => save(e.features) });
const picked = await map.selectArea({ mode: 'lasso', layers: ['stops'] });
- Custom markers (
addCustomMarkers): your own content at each point — HTML on the web, macOS and Windows, a view on Android and iOS, a widget in Flutter, a component in React and React Native. - Dragging points and markers (
draggable,onDragStart/onDrag/onDragEnd): the map does not pan meanwhile and the layer keeps the new position. On a phone a drag starts after a long press. - Tooltips (
tooltip) on points, lines and areas, labels along lines (label), and clusters drawn as a ring of their points' colours (cluster.ring) or as your own content. - Heat maps (
addHeatmap), glTF 3D models at points (addModels), base imagery (setBaseImagery), relief (setTerrain), the layer switcher (thelayerstool), tiles you paint (addCanvasTiles) and maps that follow each other (syncWith). - Google's base maps (
setGoogleBase) are read straight from Google with a Map Tiles API session your server makes with your own Google key. Other imagery never reads Google or Bing tiles. - Drawing and editing (
drawTool: polygons, rectangles, circles, lines, points and holes, with undo and redo) and area selection (selectArea: a box or a free shape). - Geometry helpers (
sMap.geo,SMapGeo): distances, lengths, bounds, points in areas and the nearest point on a line. - Without WebGL 2 the web map still shows the map and your layers; what it cannot draw (terrain, 3D models) is reported once as unsupported.
16. Map data attribution
The map data is © OpenStreetMap contributors. The credit is shown on every map and must remain visible. In other map tools, the adapters add it to the tool's own attribution.