@unmap/deck 0.0.0-stage → 0.25.0
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/LICENSE +21 -0
- package/README.md +71 -2
- package/dist/index.d.ts +212 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maps & Apps Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,72 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @unmap/deck
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
deck.gl layers for unmap maps: 3D ski lifts on MapLibre terrain, drawn in MapLibre's own WebGL
|
|
4
|
+
context (deck.gl interleaved mode).
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npm i @unmap/deck @unmap/maps maplibre-gl @deck.gl/core @deck.gl/layers @deck.gl/mesh-layers @deck.gl/maplibre @luma.gl/core
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createMap } from "@unmap/maps";
|
|
12
|
+
import { createDeckOverlay, deckMapOptions, fetchLifts3d, liftCablesLayer } from "@unmap/deck";
|
|
13
|
+
|
|
14
|
+
const key = "um_live_...";
|
|
15
|
+
const map = createMap({ key, container: "#map", style: "winter", terrain: true, maplibre: deckMapOptions });
|
|
16
|
+
map.on("load", async () => {
|
|
17
|
+
const lifts = await fetchLifts3d({ key, areaId: "osm-way-123" });
|
|
18
|
+
const exaggeration = map.getTerrain()?.exaggeration ?? 1;
|
|
19
|
+
createDeckOverlay(map, { layers: [liftCablesLayer(lifts, { exaggeration })] });
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What it exports
|
|
24
|
+
|
|
25
|
+
- `createDeckOverlay(map, opts?)` adds an interleaved `MapLibreOverlay` and returns
|
|
26
|
+
`{ overlay, setLayers(layers), remove() }`. It is always interleaved: one WebGL context, with
|
|
27
|
+
depth shared with the terrain.
|
|
28
|
+
- `deckMapOptions` is `{ canvasContextAttributes: { antialias: true } }`. MapLibre creates its
|
|
29
|
+
context with antialiasing off, which gives cables hard edges, and the setting can only be
|
|
30
|
+
chosen when the map is created. Pass it as `createMap`'s `maplibre` option.
|
|
31
|
+
`createDeckOverlay` warns once per map when it is missing.
|
|
32
|
+
- `liftCablesLayer(lifts, opts?)` is a `PathLayer` of each lift's cable at its baked height,
|
|
33
|
+
coloured by `aerialway` (`LIFT_COLORS`, overridable with `colors`). Width is 2 px by
|
|
34
|
+
default; pass `width` and `widthUnits: "meters"` for real size.
|
|
35
|
+
- `liftModelsLayer(lifts, { models })` returns one `ScenegraphLayer` per kind (`pylon`,
|
|
36
|
+
`station`) that has a glTF URL in `models`. Each model is placed at a tower or station and
|
|
37
|
+
turned to face along the line. No models ship with this package. glTF models are Y-up: they
|
|
38
|
+
are rolled 90° onto deck.gl's Z-up axis and yawed by `-bearing`. If a model faces another way,
|
|
39
|
+
correct it with `yawOffset`. `liftModelInstances(lifts)` returns the same placements as plain
|
|
40
|
+
data.
|
|
41
|
+
- `fetchLifts3d({ key, areaId, gateway?, fetch?, signal? })` calls
|
|
42
|
+
`GET /ski/areas/{id}/lifts-3d` and returns the typed `Lifts3dDocument`. It throws
|
|
43
|
+
`Lifts3dError` (an `UnmapError`) when the response is not 2xx, for example `403` with
|
|
44
|
+
`addon_required` when the key has no ski add-on. It also throws, with code
|
|
45
|
+
`schema_mismatch`, when the body is not an `unmap.ski-lifts-3d.v1` document. The key goes
|
|
46
|
+
in the gateway URL only.
|
|
47
|
+
- `bakedZ(point, exaggeration = 1)` gives the height to draw a baked point at.
|
|
48
|
+
|
|
49
|
+
## Heights and terrain exaggeration
|
|
50
|
+
|
|
51
|
+
deck.gl layers do not drape on MapLibre terrain. Every z comes from the lifts file: metres
|
|
52
|
+
above sea level, sampled from `terrain_release`, the same terrain release the style's
|
|
53
|
+
`terrain=on` draws. Nothing is queried at render time.
|
|
54
|
+
|
|
55
|
+
MapLibre draws terrain at the DEM height times `exaggeration`, measured from sea level. A
|
|
56
|
+
baked z has to be multiplied by the same factor to stay on that terrain. So every layer takes
|
|
57
|
+
`exaggeration`, with a default of 1. Read it from the map with
|
|
58
|
+
`map.getTerrain()?.exaggeration ?? 1`. Rebuild the layers if it changes. Layer ids stay the
|
|
59
|
+
same, so deck.gl updates the layers in place.
|
|
60
|
+
|
|
61
|
+
The file stores one z per point, not ground height plus height above ground. So the part above
|
|
62
|
+
the ground is scaled too. At 1.5×, a tower's foot stays on the ground, but the tower draws
|
|
63
|
+
1.5 times as tall. At 1× the heights are exact.
|
|
64
|
+
|
|
65
|
+
## Bundle cost
|
|
66
|
+
|
|
67
|
+
`@unmap/maps` never imports this package, so a plain map does not load deck.gl. deck.gl is
|
|
68
|
+
a peer dependency (`~9.4.0`) and is never bundled. `fetchLifts3d`, `bakedZ` and
|
|
69
|
+
`deckMapOptions` import no renderer at runtime, and the package is `sideEffects: false`.
|
|
70
|
+
Load the layers with a dynamic `import("@unmap/deck")` when the user turns 3D on.
|
|
71
|
+
|
|
72
|
+
The lift data needs the `ski` add-on. The renderers are free.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { UnmapError } from '@unmap/core';
|
|
2
|
+
export { UnmapError } from '@unmap/core';
|
|
3
|
+
import { PathLayer } from '@deck.gl/layers';
|
|
4
|
+
import { ScenegraphLayer } from '@deck.gl/mesh-layers';
|
|
5
|
+
import { MapLibreOverlay, MapLibreOverlayProps } from '@deck.gl/maplibre';
|
|
6
|
+
import { LayersList } from '@deck.gl/core';
|
|
7
|
+
import { Map } from 'maplibre-gl';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `[longitude, latitude, z]`, with z in metres above sea level, sampled by the pipeline from the
|
|
11
|
+
* same terrain release the style's `terrain=on` draws. Longitude first, everywhere in unmap.
|
|
12
|
+
*/
|
|
13
|
+
type LngLatZ = [number, number, number];
|
|
14
|
+
/** One aerial lift, baked in 3D. */
|
|
15
|
+
interface Lift3d {
|
|
16
|
+
/** Stable OSM-derived id, such as `way-123` (the OSM way). */
|
|
17
|
+
id: string;
|
|
18
|
+
name: string | null;
|
|
19
|
+
/** The OSM `aerialway=*` value: `chair_lift`, `gondola`, `t-bar`, `cable_car`, … */
|
|
20
|
+
aerialway: string;
|
|
21
|
+
/** Bottom and top station. */
|
|
22
|
+
stations: [LngLatZ, LngLatZ];
|
|
23
|
+
/** Towers along the line. */
|
|
24
|
+
pylons: LngLatZ[];
|
|
25
|
+
/** The cable as an ordered path from bottom to top, sag included. */
|
|
26
|
+
cable: LngLatZ[];
|
|
27
|
+
}
|
|
28
|
+
/** The `unmap.ski-lifts-3d.v1` document served by `GET /ski/areas/{id}/lifts-3d`. */
|
|
29
|
+
interface Lifts3dDocument {
|
|
30
|
+
schema: "unmap.ski-lifts-3d.v1";
|
|
31
|
+
/** The ski data release the file was built from. */
|
|
32
|
+
release: string;
|
|
33
|
+
/**
|
|
34
|
+
* The terrain release the z values were sampled from. The heights match the draped terrain only
|
|
35
|
+
* while the style serves this same release; a newer one means a rebuilt file.
|
|
36
|
+
*/
|
|
37
|
+
terrain_release: string;
|
|
38
|
+
area_id: string;
|
|
39
|
+
lifts: Lift3d[];
|
|
40
|
+
}
|
|
41
|
+
/** RGBA, 0–255 per channel, as deck.gl takes colours. */
|
|
42
|
+
type RGBA = [number, number, number, number];
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The height to draw a baked point at, on a map whose terrain has this vertical exaggeration.
|
|
46
|
+
*
|
|
47
|
+
* MapLibre draws terrain at `DEM height × exaggeration`, measured from sea level (no offset), and
|
|
48
|
+
* deck.gl layers are not draped on that terrain: they sit at whatever z they are given. A baked z
|
|
49
|
+
* therefore has to be scaled by the same factor to stay on the terrain the map draws. At the
|
|
50
|
+
* default exaggeration of 1 the baked z is returned as is.
|
|
51
|
+
*
|
|
52
|
+
* The whole height is scaled, including the part that is above the ground (a tower, the cable's
|
|
53
|
+
* clearance), because the lifts document carries one z per point, not ground plus offset. At 1.5×
|
|
54
|
+
* a 12 m tower draws 18 m tall: still on the ground at its foot, just taller. Read the map's own
|
|
55
|
+
* value with `map.getTerrain()?.exaggeration ?? 1`.
|
|
56
|
+
*/
|
|
57
|
+
declare function bakedZ(point: LngLatZ, exaggeration?: number): number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* MapLibre options `@unmap/deck` needs at construction time. Pass them as `createMap`'s
|
|
61
|
+
* `maplibre` option (or spread them into `new maplibregl.Map({...})`).
|
|
62
|
+
*
|
|
63
|
+
* MapLibre creates its WebGL context with `antialias: false`, which gives deck.gl's lift cables
|
|
64
|
+
* hard, stair-stepped edges. Antialiasing is a context attribute, so it can only be chosen when
|
|
65
|
+
* the map is created; `createDeckOverlay` cannot turn it on later and warns instead.
|
|
66
|
+
*/
|
|
67
|
+
declare const deckMapOptions: {
|
|
68
|
+
canvasContextAttributes: {
|
|
69
|
+
antialias: true;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** The only document schema this package reads. */
|
|
74
|
+
declare const LIFTS_3D_SCHEMA = "unmap.ski-lifts-3d.v1";
|
|
75
|
+
/**
|
|
76
|
+
* A lifts-3D failure: a non-2xx answer from the gateway (`status` and `code` from it, such as
|
|
77
|
+
* `403 addon_required` for a key without the ski add-on), or a body that is not a
|
|
78
|
+
* `unmap.ski-lifts-3d.v1` document (`code: "schema_mismatch"`).
|
|
79
|
+
*
|
|
80
|
+
* Extends `UnmapError`, so one `instanceof UnmapError` catches it with every other unmap error.
|
|
81
|
+
*/
|
|
82
|
+
declare class Lifts3dError extends UnmapError {
|
|
83
|
+
constructor(status: number, message: string, code?: string, retryAfterSeconds?: number);
|
|
84
|
+
}
|
|
85
|
+
interface FetchLifts3dOptions {
|
|
86
|
+
/** An unmap key with the `ski` add-on. It is sent to `gateway` and nowhere else. */
|
|
87
|
+
key: string;
|
|
88
|
+
/** A ski area id from `/ski/areas`, such as `osm-way-123`. */
|
|
89
|
+
areaId: string;
|
|
90
|
+
/** Gateway origin; defaults to `https://api.unmap.dev`. */
|
|
91
|
+
gateway?: string;
|
|
92
|
+
/** A `fetch` to use instead of the global one. */
|
|
93
|
+
fetch?: typeof fetch;
|
|
94
|
+
signal?: AbortSignal;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Fetch an area's baked 3D lifts (`GET /ski/areas/{id}/lifts-3d`).
|
|
98
|
+
*
|
|
99
|
+
* The data sits behind the `ski` add-on; the renderers in this package do not. The document's z
|
|
100
|
+
* values are metres above sea level from `terrain_release`, ready for `liftCablesLayer` and
|
|
101
|
+
* `liftModelsLayer`.
|
|
102
|
+
*/
|
|
103
|
+
declare function fetchLifts3d(opts: FetchLifts3dOptions): Promise<Lifts3dDocument>;
|
|
104
|
+
|
|
105
|
+
/** A lifts document, or just its `lifts`. */
|
|
106
|
+
type LiftsInput = Lifts3dDocument | readonly Lift3d[];
|
|
107
|
+
/** Known OSM `aerialway=*` values, plus `default` for anything else. */
|
|
108
|
+
type LiftColorKey = "cable_car" | "gondola" | "mixed_lift" | "chair_lift" | "drag_lift" | "t-bar" | "j-bar" | "platter" | "rope_tow" | "magic_carpet" | "zip_line" | "goods" | "default";
|
|
109
|
+
/**
|
|
110
|
+
* Cable colour by aerialway. Dark, cool and unsaturated so a cable reads as steel against the
|
|
111
|
+
* winter style's snow; enclosed cabins (gondola, cable car) in blue, surface lifts in brown.
|
|
112
|
+
* Magenta is the interface accent and stays out.
|
|
113
|
+
*/
|
|
114
|
+
declare const LIFT_COLORS: Readonly<Record<LiftColorKey, RGBA>>;
|
|
115
|
+
interface LiftCablesLayerOptions {
|
|
116
|
+
/** Layer id. Defaults to `unmap-lift-cables`; keep it stable so deck.gl diffs instead of rebuilding. */
|
|
117
|
+
id?: string;
|
|
118
|
+
/**
|
|
119
|
+
* The map's terrain exaggeration (`map.getTerrain()?.exaggeration ?? 1`). Every baked z is
|
|
120
|
+
* multiplied by it, from sea level, as MapLibre multiplies the DEM; see `bakedZ`. Default 1.
|
|
121
|
+
*/
|
|
122
|
+
exaggeration?: number;
|
|
123
|
+
/** Cable width in `widthUnits`. Default 2. */
|
|
124
|
+
width?: number;
|
|
125
|
+
/** `pixels` (the default) keeps cables legible at every zoom; `meters` makes them real-sized. */
|
|
126
|
+
widthUnits?: "pixels" | "meters" | "common";
|
|
127
|
+
/** Colours per aerialway, merged over `LIFT_COLORS`. */
|
|
128
|
+
colors?: Partial<Record<string, RGBA>>;
|
|
129
|
+
/** Pick lifts on hover and click. Default false. */
|
|
130
|
+
pickable?: boolean;
|
|
131
|
+
/** MapLibre layer id to draw beneath (interleaved mode), e.g. the first label layer. */
|
|
132
|
+
beforeId?: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* A deck.gl `PathLayer` drawing each lift's `cable` in 3D at its baked height.
|
|
136
|
+
*
|
|
137
|
+
* Heights are baked by the pipeline and never queried at render time: deck.gl layers do not drape
|
|
138
|
+
* on MapLibre terrain, so the z in the file is the only thing that puts a cable on the mountain.
|
|
139
|
+
*/
|
|
140
|
+
declare function liftCablesLayer(input: LiftsInput, opts?: LiftCablesLayerOptions): PathLayer<Lift3d>;
|
|
141
|
+
/** What a model stands for. `station` is both terminals; `pylon` is every tower. */
|
|
142
|
+
type LiftModelKind = "pylon" | "station";
|
|
143
|
+
/** One placed model: a station or a pylon of one lift. */
|
|
144
|
+
interface LiftModelInstance {
|
|
145
|
+
/** `<liftId>:<kind>:<index>`, stable across releases while OSM ids are. */
|
|
146
|
+
id: string;
|
|
147
|
+
liftId: string;
|
|
148
|
+
aerialway: string;
|
|
149
|
+
kind: LiftModelKind;
|
|
150
|
+
index: number;
|
|
151
|
+
/** Baked position with z already scaled by the exaggeration. */
|
|
152
|
+
position: LngLatZ;
|
|
153
|
+
/** Direction of the line at this point, degrees clockwise from north. */
|
|
154
|
+
bearing: number;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Every station and pylon as a model instance, oriented along its lift. Exposed so a caller can
|
|
158
|
+
* build their own layer (or a label layer) over the same points `liftModelsLayer` draws.
|
|
159
|
+
*/
|
|
160
|
+
declare function liftModelInstances(input: LiftsInput, exaggeration?: number): LiftModelInstance[];
|
|
161
|
+
interface LiftModelsLayerOptions {
|
|
162
|
+
/**
|
|
163
|
+
* glTF/GLB URL per kind. Supplied by the caller; this package bundles no models. A kind with no
|
|
164
|
+
* URL is not drawn.
|
|
165
|
+
*/
|
|
166
|
+
models: Partial<Record<LiftModelKind, string>>;
|
|
167
|
+
/** Id prefix; each layer is `<id>-<kind>`. Defaults to `unmap-lift-models`. */
|
|
168
|
+
id?: string;
|
|
169
|
+
/** The map's terrain exaggeration, as for `liftCablesLayer`. Default 1. */
|
|
170
|
+
exaggeration?: number;
|
|
171
|
+
/** Model units to metres. Default 1 (models authored in metres). */
|
|
172
|
+
sizeScale?: number;
|
|
173
|
+
/** Extra yaw in degrees, for a model authored facing another way. Default 0. */
|
|
174
|
+
yawOffset?: number;
|
|
175
|
+
pickable?: boolean;
|
|
176
|
+
beforeId?: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* deck.gl `ScenegraphLayer`s placing a model at each pylon and station, one layer per kind that
|
|
180
|
+
* has a URL in `models` (a ScenegraphLayer draws one scenegraph).
|
|
181
|
+
*
|
|
182
|
+
* The model origin goes at the baked point. Models are glTF (Y up), so they are rolled 90° onto
|
|
183
|
+
* deck.gl's Z up, and yawed by `-bearing` so the model's forward axis runs up the line.
|
|
184
|
+
*/
|
|
185
|
+
declare function liftModelsLayer(input: LiftsInput, opts: LiftModelsLayerOptions): ScenegraphLayer<LiftModelInstance>[];
|
|
186
|
+
|
|
187
|
+
/** The map `createDeckOverlay` takes: a MapLibre `Map`, such as `createMap` returns. */
|
|
188
|
+
type DeckOverlayMap = Map;
|
|
189
|
+
/** Overlay props, less `interleaved`: this package always shares MapLibre's WebGL context. */
|
|
190
|
+
type DeckOverlayOptions = Omit<MapLibreOverlayProps, "interleaved">;
|
|
191
|
+
interface DeckOverlay {
|
|
192
|
+
/** The underlying `MapLibreOverlay`, for picking (`pickObject`) or any other deck.gl prop. */
|
|
193
|
+
readonly overlay: MapLibreOverlay;
|
|
194
|
+
/** Replace the drawn layers. Keep layer ids stable so deck.gl diffs rather than rebuilds. */
|
|
195
|
+
setLayers(layers: LayersList): void;
|
|
196
|
+
/** Take the overlay off the map and release its GPU resources. Safe to call twice. */
|
|
197
|
+
remove(): void;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Add a deck.gl overlay to a MapLibre map, interleaved: deck.gl draws into MapLibre's own WebGL
|
|
201
|
+
* context and layer stack (one context, depth shared with the terrain), never a second canvas.
|
|
202
|
+
*
|
|
203
|
+
* Create the map with `deckMapOptions` for smooth cables; antialiasing cannot be switched on after
|
|
204
|
+
* the map exists, so this warns once per map when it is off.
|
|
205
|
+
*
|
|
206
|
+
* const deck = createDeckOverlay(map, { layers: [liftCablesLayer(lifts)] });
|
|
207
|
+
* deck.setLayers([...]);
|
|
208
|
+
* deck.remove();
|
|
209
|
+
*/
|
|
210
|
+
declare function createDeckOverlay(map: DeckOverlayMap, opts?: DeckOverlayOptions): DeckOverlay;
|
|
211
|
+
|
|
212
|
+
export { type DeckOverlay, type DeckOverlayMap, type DeckOverlayOptions, type FetchLifts3dOptions, LIFTS_3D_SCHEMA, LIFT_COLORS, type Lift3d, type LiftCablesLayerOptions, type LiftColorKey, type LiftModelInstance, type LiftModelKind, type LiftModelsLayerOptions, type Lifts3dDocument, Lifts3dError, type LiftsInput, type LngLatZ, type RGBA, bakedZ, createDeckOverlay, deckMapOptions, fetchLifts3d, liftCablesLayer, liftModelInstances, liftModelsLayer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// src/geometry.ts
|
|
2
|
+
function bakedZ(point, exaggeration = 1) {
|
|
3
|
+
if (!(Number.isFinite(exaggeration) && exaggeration > 0)) {
|
|
4
|
+
throw new RangeError(`exaggeration must be a positive number, got ${exaggeration}`);
|
|
5
|
+
}
|
|
6
|
+
return (point[2] ?? 0) * exaggeration;
|
|
7
|
+
}
|
|
8
|
+
function scaled(point, exaggeration) {
|
|
9
|
+
return [point[0], point[1], bakedZ(point, exaggeration)];
|
|
10
|
+
}
|
|
11
|
+
var RAD = Math.PI / 180;
|
|
12
|
+
function bearing(a, b) {
|
|
13
|
+
const [lng1 = 0, lat1 = 0] = a;
|
|
14
|
+
const [lng2 = 0, lat2 = 0] = b;
|
|
15
|
+
const \u03C61 = lat1 * RAD;
|
|
16
|
+
const \u03C62 = lat2 * RAD;
|
|
17
|
+
const \u0394\u03BB = (lng2 - lng1) * RAD;
|
|
18
|
+
const y = Math.sin(\u0394\u03BB) * Math.cos(\u03C62);
|
|
19
|
+
const x = Math.cos(\u03C61) * Math.sin(\u03C62) - Math.sin(\u03C61) * Math.cos(\u03C62) * Math.cos(\u0394\u03BB);
|
|
20
|
+
return (Math.atan2(y, x) / RAD + 360) % 360;
|
|
21
|
+
}
|
|
22
|
+
function segmentDistance2(p, a, b) {
|
|
23
|
+
const k = Math.cos((p[1] ?? 0) * RAD);
|
|
24
|
+
const px = (p[0] ?? 0) * k, py = p[1] ?? 0;
|
|
25
|
+
const ax = (a[0] ?? 0) * k, ay = a[1] ?? 0;
|
|
26
|
+
const bx = (b[0] ?? 0) * k, by = b[1] ?? 0;
|
|
27
|
+
const dx = bx - ax, dy = by - ay;
|
|
28
|
+
const len2 = dx * dx + dy * dy;
|
|
29
|
+
const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len2));
|
|
30
|
+
const cx = ax + t * dx - px, cy = ay + t * dy - py;
|
|
31
|
+
return cx * cx + cy * cy;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/options.ts
|
|
35
|
+
var deckMapOptions = {
|
|
36
|
+
canvasContextAttributes: { antialias: true }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/fetch.ts
|
|
40
|
+
import { DEFAULT_GATEWAY, UnmapError, parseRetryAfter } from "@unmap/core";
|
|
41
|
+
var LIFTS_3D_SCHEMA = "unmap.ski-lifts-3d.v1";
|
|
42
|
+
var Lifts3dError = class extends UnmapError {
|
|
43
|
+
constructor(status, message, code, retryAfterSeconds) {
|
|
44
|
+
super(status, message, code, retryAfterSeconds);
|
|
45
|
+
this.name = "Lifts3dError";
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
async function fetchLifts3d(opts) {
|
|
49
|
+
if (!opts.key) throw new Lifts3dError(400, "fetchLifts3d needs a key");
|
|
50
|
+
if (!opts.areaId) throw new Lifts3dError(400, "fetchLifts3d needs an areaId");
|
|
51
|
+
const base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\/+$/, "");
|
|
52
|
+
const url = `${base}/ski/areas/${encodeURIComponent(opts.areaId)}/lifts-3d?${new URLSearchParams({ key: opts.key })}`;
|
|
53
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
54
|
+
const res = await fetchImpl(url, { signal: opts.signal, headers: { accept: "application/json" } });
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
const fallback = `lifts-3d ${res.status}`;
|
|
57
|
+
let body2;
|
|
58
|
+
try {
|
|
59
|
+
body2 = await res.json();
|
|
60
|
+
} catch {
|
|
61
|
+
body2 = void 0;
|
|
62
|
+
}
|
|
63
|
+
const reason = typeof body2?.error === "string" ? body2.error : void 0;
|
|
64
|
+
const code = typeof body2?.code === "string" ? body2.code : void 0;
|
|
65
|
+
const retryAfter = parseRetryAfter(res.headers?.get("retry-after") ?? null) ?? void 0;
|
|
66
|
+
throw new Lifts3dError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);
|
|
67
|
+
}
|
|
68
|
+
let body;
|
|
69
|
+
try {
|
|
70
|
+
body = await res.json();
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Lifts3dError(res.status, "lifts-3d answered with a body that is not JSON", "schema_mismatch");
|
|
73
|
+
}
|
|
74
|
+
const envelope = body;
|
|
75
|
+
const data = envelope && typeof envelope === "object" ? envelope.data : void 0;
|
|
76
|
+
const flat = data ? { ...data, release: envelope?.meta?.data_version } : void 0;
|
|
77
|
+
if (!isLifts3dDocument(flat)) {
|
|
78
|
+
const got = data && "schema" in data ? String(data.schema) : "no schema";
|
|
79
|
+
throw new Lifts3dError(res.status, `lifts-3d answered with ${got}; this client reads ${LIFTS_3D_SCHEMA}`, "schema_mismatch");
|
|
80
|
+
}
|
|
81
|
+
return { schema: flat.schema, release: flat.release, terrain_release: flat.terrain_release, area_id: flat.area_id, lifts: flat.lifts };
|
|
82
|
+
}
|
|
83
|
+
function isLifts3dDocument(body) {
|
|
84
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return false;
|
|
85
|
+
const doc = body;
|
|
86
|
+
return doc.schema === LIFTS_3D_SCHEMA && Array.isArray(doc.lifts) && typeof doc.release === "string" && typeof doc.area_id === "string" && typeof doc.terrain_release === "string";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/layers.ts
|
|
90
|
+
import { PathLayer } from "@deck.gl/layers";
|
|
91
|
+
import { ScenegraphLayer } from "@deck.gl/mesh-layers";
|
|
92
|
+
var liftsOf = (input) => Array.isArray(input) ? input : input.lifts;
|
|
93
|
+
var LIFT_COLORS = {
|
|
94
|
+
cable_car: [13, 71, 161, 255],
|
|
95
|
+
gondola: [30, 96, 180, 255],
|
|
96
|
+
mixed_lift: [55, 71, 79, 255],
|
|
97
|
+
chair_lift: [38, 50, 56, 255],
|
|
98
|
+
drag_lift: [109, 76, 65, 255],
|
|
99
|
+
"t-bar": [109, 76, 65, 255],
|
|
100
|
+
"j-bar": [109, 76, 65, 255],
|
|
101
|
+
platter: [109, 76, 65, 255],
|
|
102
|
+
rope_tow: [141, 110, 99, 255],
|
|
103
|
+
magic_carpet: [96, 125, 139, 255],
|
|
104
|
+
zip_line: [0, 105, 92, 255],
|
|
105
|
+
goods: [97, 97, 97, 255],
|
|
106
|
+
default: [66, 66, 66, 255]
|
|
107
|
+
};
|
|
108
|
+
function liftCablesLayer(input, opts = {}) {
|
|
109
|
+
const exaggeration = opts.exaggeration ?? 1;
|
|
110
|
+
const colors = { ...LIFT_COLORS, ...opts.colors };
|
|
111
|
+
const props = {
|
|
112
|
+
id: opts.id ?? "unmap-lift-cables",
|
|
113
|
+
data: liftsOf(input),
|
|
114
|
+
positionFormat: "XYZ",
|
|
115
|
+
getPath: (lift) => lift.cable.map((p) => scaled(p, exaggeration)),
|
|
116
|
+
getColor: (lift) => colors[lift.aerialway] ?? colors.default ?? LIFT_COLORS.default,
|
|
117
|
+
getWidth: opts.width ?? 2,
|
|
118
|
+
widthUnits: opts.widthUnits ?? "pixels",
|
|
119
|
+
widthMinPixels: 1,
|
|
120
|
+
// Cables are seen from every pitch; billboarding keeps a thin line from vanishing edge-on.
|
|
121
|
+
billboard: true,
|
|
122
|
+
capRounded: true,
|
|
123
|
+
jointRounded: true,
|
|
124
|
+
pickable: opts.pickable ?? false,
|
|
125
|
+
updateTriggers: {
|
|
126
|
+
getPath: [exaggeration],
|
|
127
|
+
getColor: [JSON.stringify(opts.colors ?? {})]
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return new PathLayer(opts.beforeId ? { ...props, beforeId: opts.beforeId } : props);
|
|
131
|
+
}
|
|
132
|
+
function lineBearingAt(lift, p) {
|
|
133
|
+
const line = lift.cable.length >= 2 ? lift.cable : lift.stations;
|
|
134
|
+
let best = Infinity;
|
|
135
|
+
let at = bearing(lift.stations[0], lift.stations[1]);
|
|
136
|
+
for (let i = 1; i < line.length; i++) {
|
|
137
|
+
const a = line[i - 1];
|
|
138
|
+
const b = line[i];
|
|
139
|
+
const d = segmentDistance2(p, a, b);
|
|
140
|
+
if (d < best && (a[0] !== b[0] || a[1] !== b[1])) {
|
|
141
|
+
best = d;
|
|
142
|
+
at = bearing(a, b);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return at;
|
|
146
|
+
}
|
|
147
|
+
function liftModelInstances(input, exaggeration = 1) {
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const lift of liftsOf(input)) {
|
|
150
|
+
const add = (kind, points) => points.forEach(
|
|
151
|
+
(p, index) => out.push({
|
|
152
|
+
id: `${lift.id}:${kind}:${index}`,
|
|
153
|
+
liftId: lift.id,
|
|
154
|
+
aerialway: lift.aerialway,
|
|
155
|
+
kind,
|
|
156
|
+
index,
|
|
157
|
+
position: scaled(p, exaggeration),
|
|
158
|
+
bearing: lineBearingAt(lift, p)
|
|
159
|
+
})
|
|
160
|
+
);
|
|
161
|
+
add("station", lift.stations);
|
|
162
|
+
add("pylon", lift.pylons);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
var KINDS = ["pylon", "station"];
|
|
167
|
+
function liftModelsLayer(input, opts) {
|
|
168
|
+
const exaggeration = opts.exaggeration ?? 1;
|
|
169
|
+
const yawOffset = opts.yawOffset ?? 0;
|
|
170
|
+
const prefix = opts.id ?? "unmap-lift-models";
|
|
171
|
+
const all = liftModelInstances(input, exaggeration);
|
|
172
|
+
const layers = [];
|
|
173
|
+
for (const kind of KINDS) {
|
|
174
|
+
const url = opts.models[kind];
|
|
175
|
+
if (!url) continue;
|
|
176
|
+
const props = {
|
|
177
|
+
id: `${prefix}-${kind}`,
|
|
178
|
+
data: all.filter((i) => i.kind === kind),
|
|
179
|
+
scenegraph: url,
|
|
180
|
+
getPosition: (i) => i.position,
|
|
181
|
+
getOrientation: (i) => [0, -i.bearing + yawOffset, 90],
|
|
182
|
+
sizeScale: opts.sizeScale ?? 1,
|
|
183
|
+
_lighting: "pbr",
|
|
184
|
+
pickable: opts.pickable ?? false,
|
|
185
|
+
updateTriggers: { getOrientation: [yawOffset] }
|
|
186
|
+
};
|
|
187
|
+
layers.push(
|
|
188
|
+
new ScenegraphLayer(
|
|
189
|
+
opts.beforeId ? { ...props, beforeId: opts.beforeId } : props
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return layers;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/overlay.ts
|
|
197
|
+
import { MapLibreOverlay } from "@deck.gl/maplibre";
|
|
198
|
+
var warned = /* @__PURE__ */ new WeakSet();
|
|
199
|
+
function antialiased(map) {
|
|
200
|
+
const gl = map.getCanvas().getContext("webgl2");
|
|
201
|
+
return gl?.getContextAttributes()?.antialias;
|
|
202
|
+
}
|
|
203
|
+
function createDeckOverlay(map, opts = {}) {
|
|
204
|
+
if (antialiased(map) === false && !warned.has(map)) {
|
|
205
|
+
warned.add(map);
|
|
206
|
+
console.warn(
|
|
207
|
+
"@unmap/deck: this map was created without antialiasing, so deck.gl lines will draw with hard edges. Create it with `maplibre: deckMapOptions` (createMap) or spread `deckMapOptions` into the MapLibre Map options."
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const overlay = new MapLibreOverlay({ ...opts, interleaved: true });
|
|
211
|
+
map.addControl(overlay);
|
|
212
|
+
let removed = false;
|
|
213
|
+
let layers = opts.layers ?? [];
|
|
214
|
+
const reapplyWhenIdle = () => {
|
|
215
|
+
if (removed || map.isStyleLoaded()) return;
|
|
216
|
+
map.once("idle", () => {
|
|
217
|
+
if (!removed) overlay.setProps({ layers });
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
reapplyWhenIdle();
|
|
221
|
+
return {
|
|
222
|
+
overlay,
|
|
223
|
+
setLayers(next) {
|
|
224
|
+
layers = next;
|
|
225
|
+
overlay.setProps({ layers });
|
|
226
|
+
reapplyWhenIdle();
|
|
227
|
+
},
|
|
228
|
+
remove() {
|
|
229
|
+
if (removed) return;
|
|
230
|
+
removed = true;
|
|
231
|
+
map.removeControl(overlay);
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/index.ts
|
|
237
|
+
import { UnmapError as UnmapError2 } from "@unmap/core";
|
|
238
|
+
export {
|
|
239
|
+
LIFTS_3D_SCHEMA,
|
|
240
|
+
LIFT_COLORS,
|
|
241
|
+
Lifts3dError,
|
|
242
|
+
UnmapError2 as UnmapError,
|
|
243
|
+
bakedZ,
|
|
244
|
+
createDeckOverlay,
|
|
245
|
+
deckMapOptions,
|
|
246
|
+
fetchLifts3d,
|
|
247
|
+
liftCablesLayer,
|
|
248
|
+
liftModelInstances,
|
|
249
|
+
liftModelsLayer
|
|
250
|
+
};
|
|
251
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/geometry.ts","../src/options.ts","../src/fetch.ts","../src/layers.ts","../src/overlay.ts","../src/index.ts"],"sourcesContent":["import type { LngLatZ } from \"./types\";\n\n/**\n * The height to draw a baked point at, on a map whose terrain has this vertical exaggeration.\n *\n * MapLibre draws terrain at `DEM height × exaggeration`, measured from sea level (no offset), and\n * deck.gl layers are not draped on that terrain: they sit at whatever z they are given. A baked z\n * therefore has to be scaled by the same factor to stay on the terrain the map draws. At the\n * default exaggeration of 1 the baked z is returned as is.\n *\n * The whole height is scaled, including the part that is above the ground (a tower, the cable's\n * clearance), because the lifts document carries one z per point, not ground plus offset. At 1.5×\n * a 12 m tower draws 18 m tall: still on the ground at its foot, just taller. Read the map's own\n * value with `map.getTerrain()?.exaggeration ?? 1`.\n */\nexport function bakedZ(point: LngLatZ, exaggeration = 1): number {\n if (!(Number.isFinite(exaggeration) && exaggeration > 0)) {\n throw new RangeError(`exaggeration must be a positive number, got ${exaggeration}`);\n }\n return (point[2] ?? 0) * exaggeration;\n}\n\n/** `point` with its z scaled by `exaggeration`; lng and lat untouched. */\nexport function scaled(point: LngLatZ, exaggeration: number): LngLatZ {\n return [point[0], point[1], bakedZ(point, exaggeration)];\n}\n\nconst RAD = Math.PI / 180;\n\n/** Initial great-circle bearing from `a` to `b`, degrees clockwise from north, in [0, 360). */\nexport function bearing(a: readonly number[], b: readonly number[]): number {\n const [lng1 = 0, lat1 = 0] = a;\n const [lng2 = 0, lat2 = 0] = b;\n const φ1 = lat1 * RAD;\n const φ2 = lat2 * RAD;\n const Δλ = (lng2 - lng1) * RAD;\n const y = Math.sin(Δλ) * Math.cos(φ2);\n const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ);\n return ((Math.atan2(y, x) / RAD) + 360) % 360;\n}\n\n/** Squared planar distance from `p` to segment `a`–`b`, in degrees with longitude scaled by cos(lat). */\nexport function segmentDistance2(p: readonly number[], a: readonly number[], b: readonly number[]): number {\n const k = Math.cos((p[1] ?? 0) * RAD);\n const px = (p[0] ?? 0) * k, py = p[1] ?? 0;\n const ax = (a[0] ?? 0) * k, ay = a[1] ?? 0;\n const bx = (b[0] ?? 0) * k, by = b[1] ?? 0;\n const dx = bx - ax, dy = by - ay;\n const len2 = dx * dx + dy * dy;\n const t = len2 === 0 ? 0 : Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / len2));\n const cx = ax + t * dx - px, cy = ay + t * dy - py;\n return cx * cx + cy * cy;\n}\n","/**\n * MapLibre options `@unmap/deck` needs at construction time. Pass them as `createMap`'s\n * `maplibre` option (or spread them into `new maplibregl.Map({...})`).\n *\n * MapLibre creates its WebGL context with `antialias: false`, which gives deck.gl's lift cables\n * hard, stair-stepped edges. Antialiasing is a context attribute, so it can only be chosen when\n * the map is created; `createDeckOverlay` cannot turn it on later and warns instead.\n */\nexport const deckMapOptions: { canvasContextAttributes: { antialias: true } } = {\n canvasContextAttributes: { antialias: true },\n};\n","import { DEFAULT_GATEWAY, UnmapError, parseRetryAfter } from \"@unmap/core\";\nimport type { Lifts3dDocument } from \"./types\";\n\n/** The only document schema this package reads. */\nexport const LIFTS_3D_SCHEMA = \"unmap.ski-lifts-3d.v1\";\n\n/**\n * A lifts-3D failure: a non-2xx answer from the gateway (`status` and `code` from it, such as\n * `403 addon_required` for a key without the ski add-on), or a body that is not a\n * `unmap.ski-lifts-3d.v1` document (`code: \"schema_mismatch\"`).\n *\n * Extends `UnmapError`, so one `instanceof UnmapError` catches it with every other unmap error.\n */\nexport class Lifts3dError extends UnmapError {\n constructor(status: number, message: string, code?: string, retryAfterSeconds?: number) {\n super(status, message, code, retryAfterSeconds);\n this.name = \"Lifts3dError\";\n }\n}\n\nexport interface FetchLifts3dOptions {\n /** An unmap key with the `ski` add-on. It is sent to `gateway` and nowhere else. */\n key: string;\n /** A ski area id from `/ski/areas`, such as `osm-way-123`. */\n areaId: string;\n /** Gateway origin; defaults to `https://api.unmap.dev`. */\n gateway?: string;\n /** A `fetch` to use instead of the global one. */\n fetch?: typeof fetch;\n signal?: AbortSignal;\n}\n\n/**\n * Fetch an area's baked 3D lifts (`GET /ski/areas/{id}/lifts-3d`).\n *\n * The data sits behind the `ski` add-on; the renderers in this package do not. The document's z\n * values are metres above sea level from `terrain_release`, ready for `liftCablesLayer` and\n * `liftModelsLayer`.\n */\nexport async function fetchLifts3d(opts: FetchLifts3dOptions): Promise<Lifts3dDocument> {\n if (!opts.key) throw new Lifts3dError(400, \"fetchLifts3d needs a key\");\n if (!opts.areaId) throw new Lifts3dError(400, \"fetchLifts3d needs an areaId\");\n const base = (opts.gateway ?? DEFAULT_GATEWAY).replace(/\\/+$/, \"\");\n const url = `${base}/ski/areas/${encodeURIComponent(opts.areaId)}/lifts-3d?${new URLSearchParams({ key: opts.key })}`;\n // Bound to globalThis: a bare `fetch` called with another receiver is rejected by browsers.\n const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const res = await fetchImpl(url, { signal: opts.signal, headers: { accept: \"application/json\" } });\n\n if (!res.ok) {\n const fallback = `lifts-3d ${res.status}`;\n let body: { error?: unknown; code?: unknown } | undefined;\n try {\n body = (await res.json()) as { error?: unknown; code?: unknown };\n } catch {\n body = undefined;\n }\n const reason = typeof body?.error === \"string\" ? body.error : undefined;\n const code = typeof body?.code === \"string\" ? body.code : undefined;\n const retryAfter = parseRetryAfter(res.headers?.get(\"retry-after\") ?? null) ?? undefined;\n throw new Lifts3dError(res.status, reason ? `${reason} (${fallback})` : fallback, code, retryAfter);\n }\n\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Lifts3dError(res.status, \"lifts-3d answered with a body that is not JSON\", \"schema_mismatch\");\n }\n // The gateway answers with the ski routes' `{ data, meta }` envelope; the release is meta's\n // data_version. The document callers get back is flat.\n const envelope = body as { data?: Record<string, unknown>; meta?: { data_version?: unknown } } | null;\n const data = envelope && typeof envelope === \"object\" ? envelope.data : undefined;\n const flat = data ? { ...data, release: envelope?.meta?.data_version } : undefined;\n if (!isLifts3dDocument(flat)) {\n const got = data && \"schema\" in data ? String(data.schema) : \"no schema\";\n throw new Lifts3dError(res.status, `lifts-3d answered with ${got}; this client reads ${LIFTS_3D_SCHEMA}`, \"schema_mismatch\");\n }\n return { schema: flat.schema, release: flat.release, terrain_release: flat.terrain_release, area_id: flat.area_id, lifts: flat.lifts };\n}\n\nfunction isLifts3dDocument(body: unknown): body is Lifts3dDocument {\n if (typeof body !== \"object\" || body === null || Array.isArray(body)) return false;\n const doc = body as Record<string, unknown>;\n return doc.schema === LIFTS_3D_SCHEMA && Array.isArray(doc.lifts) && typeof doc.release === \"string\"\n && typeof doc.area_id === \"string\" && typeof doc.terrain_release === \"string\";\n}\n","import { PathLayer, type PathLayerProps } from \"@deck.gl/layers\";\nimport { ScenegraphLayer, type ScenegraphLayerProps } from \"@deck.gl/mesh-layers\";\nimport { bearing, scaled, segmentDistance2 } from \"./geometry\";\nimport type { Lift3d, Lifts3dDocument, LngLatZ, RGBA } from \"./types\";\n\n/** A lifts document, or just its `lifts`. */\nexport type LiftsInput = Lifts3dDocument | readonly Lift3d[];\n\nconst liftsOf = (input: LiftsInput): Lift3d[] =>\n (Array.isArray(input) ? input : (input as Lifts3dDocument).lifts) as Lift3d[];\n\n/** Known OSM `aerialway=*` values, plus `default` for anything else. */\nexport type LiftColorKey =\n | \"cable_car\"\n | \"gondola\"\n | \"mixed_lift\"\n | \"chair_lift\"\n | \"drag_lift\"\n | \"t-bar\"\n | \"j-bar\"\n | \"platter\"\n | \"rope_tow\"\n | \"magic_carpet\"\n | \"zip_line\"\n | \"goods\"\n | \"default\";\n\n/**\n * Cable colour by aerialway. Dark, cool and unsaturated so a cable reads as steel against the\n * winter style's snow; enclosed cabins (gondola, cable car) in blue, surface lifts in brown.\n * Magenta is the interface accent and stays out.\n */\nexport const LIFT_COLORS: Readonly<Record<LiftColorKey, RGBA>> = {\n cable_car: [13, 71, 161, 255],\n gondola: [30, 96, 180, 255],\n mixed_lift: [55, 71, 79, 255],\n chair_lift: [38, 50, 56, 255],\n drag_lift: [109, 76, 65, 255],\n \"t-bar\": [109, 76, 65, 255],\n \"j-bar\": [109, 76, 65, 255],\n platter: [109, 76, 65, 255],\n rope_tow: [141, 110, 99, 255],\n magic_carpet: [96, 125, 139, 255],\n zip_line: [0, 105, 92, 255],\n goods: [97, 97, 97, 255],\n default: [66, 66, 66, 255],\n};\n\nexport interface LiftCablesLayerOptions {\n /** Layer id. Defaults to `unmap-lift-cables`; keep it stable so deck.gl diffs instead of rebuilding. */\n id?: string;\n /**\n * The map's terrain exaggeration (`map.getTerrain()?.exaggeration ?? 1`). Every baked z is\n * multiplied by it, from sea level, as MapLibre multiplies the DEM; see `bakedZ`. Default 1.\n */\n exaggeration?: number;\n /** Cable width in `widthUnits`. Default 2. */\n width?: number;\n /** `pixels` (the default) keeps cables legible at every zoom; `meters` makes them real-sized. */\n widthUnits?: \"pixels\" | \"meters\" | \"common\";\n /** Colours per aerialway, merged over `LIFT_COLORS`. */\n colors?: Partial<Record<string, RGBA>>;\n /** Pick lifts on hover and click. Default false. */\n pickable?: boolean;\n /** MapLibre layer id to draw beneath (interleaved mode), e.g. the first label layer. */\n beforeId?: string;\n}\n\n/**\n * A deck.gl `PathLayer` drawing each lift's `cable` in 3D at its baked height.\n *\n * Heights are baked by the pipeline and never queried at render time: deck.gl layers do not drape\n * on MapLibre terrain, so the z in the file is the only thing that puts a cable on the mountain.\n */\nexport function liftCablesLayer(input: LiftsInput, opts: LiftCablesLayerOptions = {}): PathLayer<Lift3d> {\n const exaggeration = opts.exaggeration ?? 1;\n const colors = { ...LIFT_COLORS, ...opts.colors } as Record<string, RGBA | undefined>;\n const props: PathLayerProps<Lift3d> = {\n id: opts.id ?? \"unmap-lift-cables\",\n data: liftsOf(input),\n positionFormat: \"XYZ\",\n getPath: (lift) => lift.cable.map((p) => scaled(p, exaggeration)),\n getColor: (lift) => colors[lift.aerialway] ?? colors.default ?? LIFT_COLORS.default,\n getWidth: opts.width ?? 2,\n widthUnits: opts.widthUnits ?? \"pixels\",\n widthMinPixels: 1,\n // Cables are seen from every pitch; billboarding keeps a thin line from vanishing edge-on.\n billboard: true,\n capRounded: true,\n jointRounded: true,\n pickable: opts.pickable ?? false,\n updateTriggers: {\n getPath: [exaggeration],\n getColor: [JSON.stringify(opts.colors ?? {})],\n },\n };\n // `beforeId` is read by @deck.gl/maplibre in interleaved mode, not declared on PathLayerProps.\n return new PathLayer<Lift3d>(opts.beforeId ? { ...props, beforeId: opts.beforeId } as PathLayerProps<Lift3d> : props);\n}\n\n/** What a model stands for. `station` is both terminals; `pylon` is every tower. */\nexport type LiftModelKind = \"pylon\" | \"station\";\n\n/** One placed model: a station or a pylon of one lift. */\nexport interface LiftModelInstance {\n /** `<liftId>:<kind>:<index>`, stable across releases while OSM ids are. */\n id: string;\n liftId: string;\n aerialway: string;\n kind: LiftModelKind;\n index: number;\n /** Baked position with z already scaled by the exaggeration. */\n position: LngLatZ;\n /** Direction of the line at this point, degrees clockwise from north. */\n bearing: number;\n}\n\n/** The bearing of the cable segment nearest to `p`, or of the station line if there is no cable. */\nfunction lineBearingAt(lift: Lift3d, p: LngLatZ): number {\n const line = lift.cable.length >= 2 ? lift.cable : lift.stations;\n let best = Infinity;\n let at = bearing(lift.stations[0], lift.stations[1]);\n for (let i = 1; i < line.length; i++) {\n const a = line[i - 1]!;\n const b = line[i]!;\n const d = segmentDistance2(p, a, b);\n if (d < best && (a[0] !== b[0] || a[1] !== b[1])) {\n best = d;\n at = bearing(a, b);\n }\n }\n return at;\n}\n\n/**\n * Every station and pylon as a model instance, oriented along its lift. Exposed so a caller can\n * build their own layer (or a label layer) over the same points `liftModelsLayer` draws.\n */\nexport function liftModelInstances(input: LiftsInput, exaggeration = 1): LiftModelInstance[] {\n const out: LiftModelInstance[] = [];\n for (const lift of liftsOf(input)) {\n const add = (kind: LiftModelKind, points: readonly LngLatZ[]) =>\n points.forEach((p, index) =>\n out.push({\n id: `${lift.id}:${kind}:${index}`,\n liftId: lift.id,\n aerialway: lift.aerialway,\n kind,\n index,\n position: scaled(p, exaggeration),\n bearing: lineBearingAt(lift, p),\n }),\n );\n add(\"station\", lift.stations);\n add(\"pylon\", lift.pylons);\n }\n return out;\n}\n\nexport interface LiftModelsLayerOptions {\n /**\n * glTF/GLB URL per kind. Supplied by the caller; this package bundles no models. A kind with no\n * URL is not drawn.\n */\n models: Partial<Record<LiftModelKind, string>>;\n /** Id prefix; each layer is `<id>-<kind>`. Defaults to `unmap-lift-models`. */\n id?: string;\n /** The map's terrain exaggeration, as for `liftCablesLayer`. Default 1. */\n exaggeration?: number;\n /** Model units to metres. Default 1 (models authored in metres). */\n sizeScale?: number;\n /** Extra yaw in degrees, for a model authored facing another way. Default 0. */\n yawOffset?: number;\n pickable?: boolean;\n beforeId?: string;\n}\n\nconst KINDS: readonly LiftModelKind[] = [\"pylon\", \"station\"];\n\n/**\n * deck.gl `ScenegraphLayer`s placing a model at each pylon and station, one layer per kind that\n * has a URL in `models` (a ScenegraphLayer draws one scenegraph).\n *\n * The model origin goes at the baked point. Models are glTF (Y up), so they are rolled 90° onto\n * deck.gl's Z up, and yawed by `-bearing` so the model's forward axis runs up the line.\n */\nexport function liftModelsLayer(input: LiftsInput, opts: LiftModelsLayerOptions): ScenegraphLayer<LiftModelInstance>[] {\n const exaggeration = opts.exaggeration ?? 1;\n const yawOffset = opts.yawOffset ?? 0;\n const prefix = opts.id ?? \"unmap-lift-models\";\n const all = liftModelInstances(input, exaggeration);\n const layers: ScenegraphLayer<LiftModelInstance>[] = [];\n for (const kind of KINDS) {\n const url = opts.models[kind];\n if (!url) continue;\n const props: ScenegraphLayerProps<LiftModelInstance> = {\n id: `${prefix}-${kind}`,\n data: all.filter((i) => i.kind === kind),\n scenegraph: url,\n getPosition: (i) => i.position,\n getOrientation: (i) => [0, -i.bearing + yawOffset, 90],\n sizeScale: opts.sizeScale ?? 1,\n _lighting: \"pbr\",\n pickable: opts.pickable ?? false,\n updateTriggers: { getOrientation: [yawOffset] },\n };\n layers.push(\n new ScenegraphLayer<LiftModelInstance>(\n opts.beforeId ? ({ ...props, beforeId: opts.beforeId } as ScenegraphLayerProps<LiftModelInstance>) : props,\n ),\n );\n }\n return layers;\n}\n","import { MapLibreOverlay, type MapLibreOverlayProps } from \"@deck.gl/maplibre\";\nimport type { LayersList } from \"@deck.gl/core\";\nimport type { Map as MaplibreMap } from \"maplibre-gl\";\n\n/** The map `createDeckOverlay` takes: a MapLibre `Map`, such as `createMap` returns. */\nexport type DeckOverlayMap = MaplibreMap;\n\n/** Overlay props, less `interleaved`: this package always shares MapLibre's WebGL context. */\nexport type DeckOverlayOptions = Omit<MapLibreOverlayProps, \"interleaved\">;\n\nexport interface DeckOverlay {\n /** The underlying `MapLibreOverlay`, for picking (`pickObject`) or any other deck.gl prop. */\n readonly overlay: MapLibreOverlay;\n /** Replace the drawn layers. Keep layer ids stable so deck.gl diffs rather than rebuilds. */\n setLayers(layers: LayersList): void;\n /** Take the overlay off the map and release its GPU resources. Safe to call twice. */\n remove(): void;\n}\n\nconst warned = new WeakSet<object>();\n\n/**\n * Whether the map's WebGL context was created with antialiasing. `undefined` when it cannot be\n * told (no WebGL2 context yet). Reads the context the map already made: `getContext` with the\n * same type returns the existing one and never creates a second.\n */\nfunction antialiased(map: DeckOverlayMap): boolean | undefined {\n const gl = map.getCanvas().getContext(\"webgl2\");\n return gl?.getContextAttributes()?.antialias;\n}\n\n/**\n * Add a deck.gl overlay to a MapLibre map, interleaved: deck.gl draws into MapLibre's own WebGL\n * context and layer stack (one context, depth shared with the terrain), never a second canvas.\n *\n * Create the map with `deckMapOptions` for smooth cables; antialiasing cannot be switched on after\n * the map exists, so this warns once per map when it is off.\n *\n * const deck = createDeckOverlay(map, { layers: [liftCablesLayer(lifts)] });\n * deck.setLayers([...]);\n * deck.remove();\n */\nexport function createDeckOverlay(map: DeckOverlayMap, opts: DeckOverlayOptions = {}): DeckOverlay {\n if (antialiased(map) === false && !warned.has(map)) {\n warned.add(map);\n console.warn(\n \"@unmap/deck: this map was created without antialiasing, so deck.gl lines will draw with hard \" +\n \"edges. Create it with `maplibre: deckMapOptions` (createMap) or spread `deckMapOptions` into \" +\n \"the MapLibre Map options.\",\n );\n }\n const overlay = new MapLibreOverlay({ ...opts, interleaved: true });\n map.addControl(overlay);\n let removed = false;\n let layers: LayersList = opts.layers ?? [];\n // @deck.gl/maplibre adds interleaved layers to the map only while `map.isStyleLoaded()`, which is\n // false while any tile is still loading, and retries only on `styledata`. Layers set during a\n // camera flight would otherwise never be drawn, so they are applied again once the map is idle.\n const reapplyWhenIdle = () => {\n if (removed || map.isStyleLoaded()) return;\n map.once(\"idle\", () => {\n if (!removed) overlay.setProps({ layers });\n });\n };\n reapplyWhenIdle();\n return {\n overlay,\n setLayers(next) {\n layers = next;\n overlay.setProps({ layers });\n reapplyWhenIdle();\n },\n remove() {\n if (removed) return;\n removed = true;\n map.removeControl(overlay);\n },\n };\n}\n","/**\n * @unmap/deck: deck.gl layers for unmap maps, interleaved in MapLibre's WebGL context.\n *\n * Optional and separate from @unmap/maps, so a plain map never loads deck.gl. `fetchLifts3d`,\n * `bakedZ` and `deckMapOptions` import no renderer, and the package is `sideEffects: false`, so a\n * bundler drops deck.gl from a chunk that uses only them; load the layers with a dynamic\n * `import(\"@unmap/deck\")` when the user turns 3D on.\n */\nexport type { Lift3d, Lifts3dDocument, LngLatZ, RGBA } from \"./types\";\nexport { bakedZ } from \"./geometry\";\nexport { deckMapOptions } from \"./options\";\nexport { LIFTS_3D_SCHEMA, Lifts3dError, fetchLifts3d, type FetchLifts3dOptions } from \"./fetch\";\nexport {\n LIFT_COLORS,\n liftCablesLayer,\n liftModelInstances,\n liftModelsLayer,\n type LiftCablesLayerOptions,\n type LiftColorKey,\n type LiftModelInstance,\n type LiftModelKind,\n type LiftModelsLayerOptions,\n type LiftsInput,\n} from \"./layers\";\nexport { createDeckOverlay, type DeckOverlay, type DeckOverlayMap, type DeckOverlayOptions } from \"./overlay\";\n\n// Re-exported so `catch (e) { if (e instanceof UnmapError) }` works without adding @unmap/core.\nexport { UnmapError } from \"@unmap/core\";\n"],"mappings":"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","names":["body","UnmapError"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unmap/deck",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"description": "deck.gl layers for unmap maps: 3D ski lifts on MapLibre terrain, interleaved in one WebGL context",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"unmap",
|
|
8
|
+
"deck.gl",
|
|
9
|
+
"maplibre",
|
|
10
|
+
"3d",
|
|
11
|
+
"terrain",
|
|
12
|
+
"ski"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://unmap.dev/docs/apis/maps",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/unmap-dev/sdk-js.git",
|
|
18
|
+
"directory": "packages/deck"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/unmap-dev/sdk-js/issues"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@unmap/core": "0.25.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@deck.gl/core": "~9.4.0",
|
|
45
|
+
"@deck.gl/layers": "~9.4.0",
|
|
46
|
+
"@deck.gl/maplibre": "~9.4.0",
|
|
47
|
+
"@deck.gl/mesh-layers": "~9.4.0",
|
|
48
|
+
"@luma.gl/core": "~9.4.0",
|
|
49
|
+
"maplibre-gl": "^6.9.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@deck.gl/core": "9.4.0",
|
|
53
|
+
"@deck.gl/layers": "9.4.0",
|
|
54
|
+
"@deck.gl/maplibre": "9.4.0",
|
|
55
|
+
"@deck.gl/mesh-layers": "9.4.0",
|
|
56
|
+
"@luma.gl/core": "9.4.0",
|
|
57
|
+
"maplibre-gl": "^6.9.0",
|
|
58
|
+
"tsup": "^8.3.0",
|
|
59
|
+
"typescript": "^5.6.0",
|
|
60
|
+
"vitest": "^2.1.0"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsup",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"typecheck": "tsc --noEmit"
|
|
66
|
+
}
|
|
6
67
|
}
|