@superrouteca/smap-mapbox 0.0.0-stage → 1.1.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 +20 -0
- package/LICENSE +21 -0
- package/README.md +138 -2
- package/examples/index.html +81 -0
- package/package.json +68 -4
- package/src/common/access.js +198 -0
- package/src/common/badge.js +56 -0
- package/src/common/embed.js +166 -0
- package/src/index.js +475 -0
- package/src/on-smap.js +101 -0
- package/types/index.d.ts +165 -0
- package/types/on-smap.d.ts +28 -0
package/src/on-smap.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// @superrouteca/smap-mapbox/on-smap — Mapbox GL JS styles and layers on an sMap map, through the sMap map's own
|
|
2
|
+
// Style Specification calls (addSource, addStyleLayer):
|
|
3
|
+
//
|
|
4
|
+
// import { loadMapboxStyle, copyMapboxLayers } from '@superrouteca/smap-mapbox/on-smap';
|
|
5
|
+
// const report = await loadMapboxStyle(smapMap, myStudioStyleJson); // its own sources and layers, on sMap
|
|
6
|
+
// copyMapboxLayers(mapboxMap, smapMap); // an app's layers, moved over as they are
|
|
7
|
+
//
|
|
8
|
+
// Sources hosted by Mapbox (mapbox:// and api.mapbox.com) need a Mapbox account and its terms, so they are left out
|
|
9
|
+
// and listed; so are the layers reading them, and a style's background / sky / fog (they would cover sMap's basemap).
|
|
10
|
+
// A Mapbox GL JS v3 `slot` becomes an sMap slot (bottom → bottom, middle → labels, top → top); a layer without a slot
|
|
11
|
+
// draws on top, in the order added, as in 1.0.
|
|
12
|
+
|
|
13
|
+
const SLOTS = { bottom: 'bottom', middle: 'labels', top: 'top' };
|
|
14
|
+
const COVERING = ['background', 'sky', 'fog'];
|
|
15
|
+
|
|
16
|
+
function isMapboxHosted(source) {
|
|
17
|
+
if (!source) { return false; }
|
|
18
|
+
const urls = [source.url, source.data].concat(source.tiles || []).filter(function (u) { return typeof u === 'string'; });
|
|
19
|
+
return urls.some(function (u) { return /^mapbox:\/\//.test(u) || /^https?:\/\/([a-z0-9-]+\.)*mapbox\.com\//i.test(u); });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function need(smap, name) {
|
|
23
|
+
if (typeof smap[name] !== 'function') { throw new Error('sMap: this needs @superrouteca/smap 1.1.0 (' + name + ')'); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Adds a Mapbox GL JS style's (or a part of one's) sources and layers to an sMap map.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} smap an sMap map (after `await map.ready`)
|
|
30
|
+
* @param {Object|string} style a style document, or its address (fetched; a mapbox:// address is refused)
|
|
31
|
+
* @param {Object} [options]
|
|
32
|
+
* prefix (put before every source and layer id, default none), layers (ids to take; default all), filter(layer),
|
|
33
|
+
* slot / before (for layers that name no slot of their own), fetch
|
|
34
|
+
* @returns {Promise<{ sources: string[], layers: string[], skipped: Array<{ id: string, reason: string }>, warnings: string[] }>}
|
|
35
|
+
*/
|
|
36
|
+
export async function loadMapboxStyle(smap, style, options) {
|
|
37
|
+
const o = Object.assign({ prefix: '' }, options);
|
|
38
|
+
need(smap, 'addSource');
|
|
39
|
+
need(smap, 'addStyleLayer');
|
|
40
|
+
let doc = style;
|
|
41
|
+
if (typeof style === 'string') {
|
|
42
|
+
if (/^mapbox:\/\//.test(style)) { throw new Error('sMap: a mapbox:// style is hosted by Mapbox and needs a Mapbox account; pass the style document instead'); }
|
|
43
|
+
const f = o.fetch || fetch;
|
|
44
|
+
const res = await f(style);
|
|
45
|
+
if (!res.ok) { throw new Error('sMap: the style could not be read (HTTP ' + res.status + ')'); }
|
|
46
|
+
doc = await res.json();
|
|
47
|
+
}
|
|
48
|
+
if (!doc || !Array.isArray(doc.layers)) { throw new Error('sMap: not a style document (no layers)'); }
|
|
49
|
+
const report = { sources: [], layers: [], skipped: [], warnings: [] };
|
|
50
|
+
const name = function (id) { return o.prefix + id; };
|
|
51
|
+
const sources = doc.sources || {};
|
|
52
|
+
const left = {};
|
|
53
|
+
Object.keys(sources).forEach(function (id) {
|
|
54
|
+
if (isMapboxHosted(sources[id])) { left[id] = true; report.skipped.push({ id: id, reason: 'hosted by Mapbox (needs a Mapbox account)' }); }
|
|
55
|
+
});
|
|
56
|
+
const wanted = function (l) { return (!o.layers || o.layers.indexOf(l.id) >= 0) && (!o.filter || o.filter(l)); };
|
|
57
|
+
const used = {};
|
|
58
|
+
doc.layers.forEach(function (l) { if (wanted(l) && typeof l.source === 'string') { used[l.source] = true; } });
|
|
59
|
+
Object.keys(sources).forEach(function (id) {
|
|
60
|
+
if (left[id] || !used[id]) { return; }
|
|
61
|
+
smap.addSource(name(id), sources[id]);
|
|
62
|
+
report.sources.push(name(id));
|
|
63
|
+
});
|
|
64
|
+
if ((doc.sprite && doc.layers.some(function (l) { return l.layout && l.layout['icon-image']; })) || doc.imports) {
|
|
65
|
+
report.warnings.push('icons of the style\'s own sprite are not loaded: add them with the sMap map\'s images, or draw points with addPoints');
|
|
66
|
+
}
|
|
67
|
+
doc.layers.forEach(function (l) {
|
|
68
|
+
if (!wanted(l)) { return; }
|
|
69
|
+
if (COVERING.indexOf(l.type) >= 0) { report.skipped.push({ id: l.id, reason: 'a ' + l.type + ' layer would cover the sMap basemap' }); return; }
|
|
70
|
+
if (typeof l.source === 'string' && left[l.source]) { report.skipped.push({ id: l.id, reason: 'reads a source hosted by Mapbox' }); return; }
|
|
71
|
+
if (typeof l.source === 'string' && !sources[l.source]) { report.skipped.push({ id: l.id, reason: 'reads a source the style does not have (' + l.source + ')' }); return; }
|
|
72
|
+
const layer = JSON.parse(JSON.stringify(l));
|
|
73
|
+
layer.id = name(l.id);
|
|
74
|
+
if (typeof layer.source === 'string') { layer.source = name(layer.source); }
|
|
75
|
+
const slot = layer.slot ? SLOTS[layer.slot] : undefined;
|
|
76
|
+
delete layer.slot;
|
|
77
|
+
const placement = {};
|
|
78
|
+
if (slot) { placement.slot = slot; }
|
|
79
|
+
else if (o.slot) { placement.slot = o.slot; }
|
|
80
|
+
if (!placement.slot && o.before) { placement.before = o.before; }
|
|
81
|
+
if (Object.keys(placement).length) { smap.addStyleLayer(layer, placement); } else { smap.addStyleLayer(layer); }
|
|
82
|
+
report.layers.push(layer.id);
|
|
83
|
+
});
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Adds Mapbox GL JS layers (style layer objects; inline sources as they are) to an sMap map. */
|
|
88
|
+
export function addMapboxLayers(smap, layers, options) {
|
|
89
|
+
return loadMapboxStyle(smap, { version: 8, sources: (options && options.sources) || {}, layers: layers }, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Moves an existing Mapbox GL JS map's own layers onto an sMap map: every source and layer of its style except the
|
|
94
|
+
* Mapbox-hosted basemap (or those `layers` names / `filter` keeps). The Mapbox GL JS map is left as it is.
|
|
95
|
+
*/
|
|
96
|
+
export function copyMapboxLayers(mapboxMap, smap, options) {
|
|
97
|
+
const style = mapboxMap.getStyle();
|
|
98
|
+
return loadMapboxStyle(smap, style, options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { isMapboxHosted };
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Types of @superrouteca/smap-mapbox.
|
|
2
|
+
import type * as mapboxgl from 'mapbox-gl';
|
|
3
|
+
import type { Map as SMapMap, StyleFlavor, BasemapOverrides, StyleLayer, StyleSource, Slot, PointOptions, LineOptions, PolygonOptions,
|
|
4
|
+
LayerData, PopupOptions, PopupContent, FitOptions, RasterLayerOptions, VectorTilesOptions, WmsLayerOptions, WmtsLayerOptions, ArcGisLayerOptions,
|
|
5
|
+
LayerDescription, DataOptions, FileInput, LngLatLike } from '@superrouteca/smap';
|
|
6
|
+
import type SMapClient from '@superrouteca/smap/client';
|
|
7
|
+
|
|
8
|
+
export type { StyleFlavor };
|
|
9
|
+
/** A colour scheme or a saved custom style (`custom:<id>`). */
|
|
10
|
+
export type SMapStyle = StyleFlavor | `custom:${string}`;
|
|
11
|
+
/** An sMap slot, or Mapbox GL JS' own `middle` (= `labels`). */
|
|
12
|
+
export type AnySlot = Slot | 'middle';
|
|
13
|
+
/** The Mapbox GL JS module (`import mapboxgl from 'mapbox-gl'`): only its Map class is used. */
|
|
14
|
+
export interface MapboxModule { Map: typeof mapboxgl.Map }
|
|
15
|
+
|
|
16
|
+
/** How the adapters reach the service: a map key or a token (renewed through getToken), the nearest node. */
|
|
17
|
+
export interface AccessOptions {
|
|
18
|
+
/** The map service, `https://tile.srsr.ca` by default. */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
/** A map key (`smk_…`) whose allowlist names this site. */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
token?: string;
|
|
23
|
+
/** Called at start and again two minutes before each token expires. */
|
|
24
|
+
getToken?: () => string | Promise<string>;
|
|
25
|
+
/** Node URLs known in advance, used while `baseUrl` is down. */
|
|
26
|
+
nodes?: string[];
|
|
27
|
+
/** Read from the nearest node (default true). */
|
|
28
|
+
nearest?: boolean;
|
|
29
|
+
fetch?: typeof fetch;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** A JS client of your own instead of the options above. */
|
|
32
|
+
client?: SMapClient;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The service access the raster layer uses (token, node order, failover). */
|
|
36
|
+
export declare class SMapAccess {
|
|
37
|
+
constructor(options?: AccessOptions);
|
|
38
|
+
readonly client: SMapClient;
|
|
39
|
+
readonly baseUrl: string;
|
|
40
|
+
start(): Promise<this>;
|
|
41
|
+
refresh(): Promise<string | null>;
|
|
42
|
+
token(): string | null;
|
|
43
|
+
nodes(): string[];
|
|
44
|
+
head(): string;
|
|
45
|
+
isOurs(url: string): boolean;
|
|
46
|
+
headers(url: string): Record<string, string>;
|
|
47
|
+
rewrite(url: string): string;
|
|
48
|
+
failed(url: string): void;
|
|
49
|
+
withKey(path: string): string;
|
|
50
|
+
style(style: SMapStyle, language?: string): Promise<Record<string, unknown>>;
|
|
51
|
+
rasterTile(style: SMapStyle, z: number, x: number, y: number, options?: { lang?: string; retina?: boolean }): Promise<Blob>;
|
|
52
|
+
rasterTemplate(style: SMapStyle, options?: { lang?: string; retina?: boolean }): string;
|
|
53
|
+
on(listener: (type: 'token' | 'nodes', value: unknown) => void): this;
|
|
54
|
+
stop(): void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface SMapMapboxOptions extends AccessOptions, Omit<Partial<mapboxgl.MapOptions>, 'style'> {
|
|
58
|
+
style?: SMapStyle;
|
|
59
|
+
language?: string;
|
|
60
|
+
/** Basemap class overrides (contract §1). */
|
|
61
|
+
basemap?: BasemapOverrides;
|
|
62
|
+
/** 3D buildings from the basemap. */
|
|
63
|
+
buildings3D?: boolean | { color?: string; opacity?: number };
|
|
64
|
+
/** Default popup options of sMap's layers. */
|
|
65
|
+
popup?: PopupOptions;
|
|
66
|
+
popupLoading?: string | Node;
|
|
67
|
+
/** Auto Zoom for sMap's pins (default true). */
|
|
68
|
+
autoZoom?: boolean;
|
|
69
|
+
/** An access of your own (shared with smapTransformRequest). */
|
|
70
|
+
access?: SMapAccess;
|
|
71
|
+
/** A Mapbox GL JS map you made, instead of `container` (see SMapMapbox.attach). */
|
|
72
|
+
map?: mapboxgl.Map;
|
|
73
|
+
/** With `map`: which of its layers stay (default: all but the Mapbox-hosted basemap, background and sky). */
|
|
74
|
+
keep?: (layer: StyleLayer) => boolean;
|
|
75
|
+
/** The sMap Web SDK module to use (default the installed `@superrouteca/smap`). */
|
|
76
|
+
sdk?: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** sMap in Mapbox GL JS: sMap's style drawn by Mapbox GL JS, sMap's own layers on an overlay sMap map. */
|
|
80
|
+
export declare class SMapMapbox {
|
|
81
|
+
constructor(mapbox: MapboxModule, options: SMapMapboxOptions);
|
|
82
|
+
/** Attaches to a Mapbox GL JS map you made: its basemap becomes sMap's, its own layers stay. */
|
|
83
|
+
static attach(mapbox: MapboxModule, map: mapboxgl.Map, options?: SMapMapboxOptions): SMapMapbox;
|
|
84
|
+
/** Resolves once the style has loaded. */
|
|
85
|
+
readonly ready: Promise<SMapMapbox>;
|
|
86
|
+
/** The Mapbox GL JS map (set once `ready` resolves). */
|
|
87
|
+
readonly map: mapboxgl.Map;
|
|
88
|
+
readonly access: SMapAccess;
|
|
89
|
+
setStyle(style: SMapStyle): this;
|
|
90
|
+
setLanguage(language: string): this;
|
|
91
|
+
setBasemap(overrides: BasemapOverrides): this;
|
|
92
|
+
basemap(): BasemapOverrides;
|
|
93
|
+
setBuildings3D(on: boolean, options?: { color?: string; opacity?: number }): this;
|
|
94
|
+
/** The basemap layer a slot begins at (undefined for `top`). */
|
|
95
|
+
slotLayer(slot?: Slot): string | undefined;
|
|
96
|
+
addSource(id: string, source: StyleSource): this;
|
|
97
|
+
removeSource(id: string): this;
|
|
98
|
+
addStyleLayer(layer: StyleLayer & { slot?: AnySlot }, placement?: { slot?: AnySlot; before?: string }): this;
|
|
99
|
+
setPaint(layerId: string, name: string, value: unknown): this;
|
|
100
|
+
setLayout(layerId: string, name: string, value: unknown): this;
|
|
101
|
+
setFilter(layerId: string, filter: unknown[] | null): this;
|
|
102
|
+
addRasterLayer(id: string, options: RasterLayerOptions): this;
|
|
103
|
+
addVectorTiles(id: string, options: VectorTilesOptions): this;
|
|
104
|
+
/** The overlay sMap map (made on first use). */
|
|
105
|
+
overlay(): Promise<SMapMap>;
|
|
106
|
+
/** An sMap map call on the overlay, resolving to its answer (e.g. `call('toGeoJSON', 'stops')`). */
|
|
107
|
+
call<T = unknown>(name: string, ...args: unknown[]): Promise<T>;
|
|
108
|
+
addPoints(id: string, data: LayerData, options?: PointOptions): this;
|
|
109
|
+
addLines(id: string, data: LayerData, options?: LineOptions): this;
|
|
110
|
+
addPolygons(id: string, data: LayerData, options?: PolygonOptions): this;
|
|
111
|
+
addData(id: string, input: FileInput | Blob, options?: DataOptions): this;
|
|
112
|
+
setData(id: string, data: LayerData): this;
|
|
113
|
+
setVisible(id: string, visible: boolean): this;
|
|
114
|
+
removeLayer(id: string): this;
|
|
115
|
+
fitLayer(id: string, options?: FitOptions): this;
|
|
116
|
+
openPopup(lngLat: LngLatLike, content: PopupContent | Promise<PopupContent>, options?: PopupOptions): this;
|
|
117
|
+
closePopup(): this;
|
|
118
|
+
setAutoZoom(on: boolean): this;
|
|
119
|
+
addWmsLayer(id: string, options: WmsLayerOptions): this;
|
|
120
|
+
addWmtsLayer(id: string, options: WmtsLayerOptions): this;
|
|
121
|
+
addArcGisLayer(id: string, options: ArcGisLayerOptions): this;
|
|
122
|
+
addDeckLayer(layer: unknown): this;
|
|
123
|
+
removeDeckLayer(id: string): this;
|
|
124
|
+
loadLayers(json: LayerDescription | string): this;
|
|
125
|
+
remove(): void;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The sMap badge as a Mapbox GL JS control. */
|
|
129
|
+
export declare class SMapBadgeControl implements mapboxgl.IControl {
|
|
130
|
+
onAdd(map: mapboxgl.Map): HTMLElement;
|
|
131
|
+
onRemove(): void;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** A Mapbox GL JS transformRequest for sMap (node, token, map key); other URLs go to `next` or unchanged. */
|
|
135
|
+
export declare function smapTransformRequest(access: SMapAccess, next?: (url: string, resourceType?: string) => { url: string; headers?: Record<string, string> }): (url: string, resourceType?: string) => { url: string; headers?: Record<string, string> };
|
|
136
|
+
export declare function createMap(mapbox: MapboxModule, options: SMapMapboxOptions): Promise<SMapMapbox>;
|
|
137
|
+
export declare function switchBasemap(mapbox: MapboxModule, map: mapboxgl.Map, options?: SMapMapboxOptions): Promise<SMapMapbox>;
|
|
138
|
+
export declare function smapAccess(options?: AccessOptions): SMapAccess;
|
|
139
|
+
export declare function userPart(style: { sources?: Record<string, unknown>; layers?: StyleLayer[] }, basemapDoc: { sources?: Record<string, unknown>; layers?: StyleLayer[] } | null, extraBasemapLayers?: string[]): { sources: Record<string, unknown>; layers: Array<{ layer: StyleLayer; before: string | null }> };
|
|
140
|
+
export declare function mergeUserPart<S extends { sources?: Record<string, unknown>; layers?: StyleLayer[] }>(doc: S, part: ReturnType<typeof userPart>): S;
|
|
141
|
+
export declare function isMapboxSource(source: unknown): boolean;
|
|
142
|
+
export declare function checkStyle(style?: string): SMapStyle;
|
|
143
|
+
export declare function rasterPath(style: SMapStyle, z: number, x: number, y: number, options?: { lang?: string; retina?: boolean }): string;
|
|
144
|
+
export declare function stylePath(style: SMapStyle, lang?: string): string;
|
|
145
|
+
export declare const styles: StyleFlavor[];
|
|
146
|
+
export declare const version: string;
|
|
147
|
+
export declare const DEFAULT_BASE: string;
|
|
148
|
+
export declare const OSM_ATTRIBUTION: string;
|
|
149
|
+
export declare const attribution: string;
|
|
150
|
+
export declare const OVERLAY_CALLS: string[];
|
|
151
|
+
|
|
152
|
+
declare const SMapMapboxModule: {
|
|
153
|
+
version: string;
|
|
154
|
+
styles: StyleFlavor[];
|
|
155
|
+
SMapMapbox: typeof SMapMapbox;
|
|
156
|
+
createMap: typeof createMap;
|
|
157
|
+
switchBasemap: typeof switchBasemap;
|
|
158
|
+
smapAccess: typeof smapAccess;
|
|
159
|
+
smapTransformRequest: typeof smapTransformRequest;
|
|
160
|
+
SMapBadgeControl: typeof SMapBadgeControl;
|
|
161
|
+
SMapAccess: typeof SMapAccess;
|
|
162
|
+
checkStyle: typeof checkStyle;
|
|
163
|
+
rasterPath: typeof rasterPath;
|
|
164
|
+
};
|
|
165
|
+
export default SMapMapboxModule;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Types of @superrouteca/smap-mapbox/on-smap: Mapbox GL JS styles and layers on an sMap map.
|
|
2
|
+
import type { Map as SMapMap, StyleLayer, StyleSource, Slot } from '@superrouteca/smap';
|
|
3
|
+
|
|
4
|
+
export interface LoadOptions {
|
|
5
|
+
/** Put before every source and layer id (default none). */
|
|
6
|
+
prefix?: string;
|
|
7
|
+
/** The layer ids to take (default all). */
|
|
8
|
+
layers?: string[];
|
|
9
|
+
filter?: (layer: StyleLayer) => boolean;
|
|
10
|
+
/** For layers that name no slot of their own. */
|
|
11
|
+
slot?: Slot;
|
|
12
|
+
before?: string;
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LoadReport {
|
|
17
|
+
sources: string[];
|
|
18
|
+
layers: string[];
|
|
19
|
+
skipped: Array<{ id: string; reason: string }>;
|
|
20
|
+
warnings: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type StyleDocument = { version?: number; sources?: Record<string, StyleSource>; layers: Array<StyleLayer & { slot?: string }>; sprite?: unknown; [key: string]: unknown };
|
|
24
|
+
|
|
25
|
+
export declare function loadMapboxStyle(smap: SMapMap, style: StyleDocument | string, options?: LoadOptions): Promise<LoadReport>;
|
|
26
|
+
export declare function addMapboxLayers(smap: SMapMap, layers: Array<StyleLayer & { slot?: string }>, options?: LoadOptions & { sources?: Record<string, StyleSource> }): Promise<LoadReport>;
|
|
27
|
+
export declare function copyMapboxLayers(mapboxMap: { getStyle(): any }, smap: SMapMap, options?: LoadOptions): Promise<LoadReport>;
|
|
28
|
+
export declare function isMapboxHosted(source: unknown): boolean;
|