@tellegen/svelte 0.4.6 → 0.4.7
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/dist/TellegenMap.svelte +21 -26
- package/dist/globe-horizon.d.ts +6 -0
- package/dist/globe-horizon.js +41 -0
- package/package.json +1 -1
package/dist/TellegenMap.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
3
3
|
import { tick, untrack } from 'svelte';
|
|
4
|
-
import type { Layer, PickingInfo } from '@deck.gl/core';
|
|
4
|
+
import type { Layer, LayerExtension, PickingInfo } from '@deck.gl/core';
|
|
5
5
|
import type { IconLayer, IconLayerProps, PathLayer, ScatterplotLayer } from '@deck.gl/layers';
|
|
6
6
|
import type { MapLibreOverlay } from '@deck.gl/maplibre';
|
|
7
7
|
import type { LngLatBoundsLike, Map as MapLibreMap } from 'maplibre-gl';
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
transformerIconAtlas,
|
|
55
55
|
transformerIconMapping
|
|
56
56
|
} from './transformer-icon.js';
|
|
57
|
+
import { createGlobeHorizon, withGlobeHorizon } from './globe-horizon.js';
|
|
57
58
|
import { foldMapBounds } from './map-bounds.js';
|
|
58
59
|
import { getAppState, getController } from './context.svelte.js';
|
|
59
60
|
import { displayFmt, fmt, rgbaCss } from './format.js';
|
|
@@ -239,6 +240,8 @@
|
|
|
239
240
|
isHovering: boolean;
|
|
240
241
|
};
|
|
241
242
|
let layerCtors = $state.raw<LayerCtors | null>(null);
|
|
243
|
+
// Hides the far side of the globe; built with the deck modules.
|
|
244
|
+
let globeHorizon: LayerExtension | null = null;
|
|
242
245
|
|
|
243
246
|
interface CaseDisplay {
|
|
244
247
|
prices: Map<number, number>;
|
|
@@ -818,9 +821,10 @@
|
|
|
818
821
|
}
|
|
819
822
|
|
|
820
823
|
async function loadMapModules() {
|
|
821
|
-
const [maplibre, deckMap, layers, workerUrl] = await Promise.all([
|
|
824
|
+
const [maplibre, deckMap, deckCore, layers, workerUrl] = await Promise.all([
|
|
822
825
|
import('maplibre-gl'),
|
|
823
826
|
import('@deck.gl/maplibre'),
|
|
827
|
+
import('@deck.gl/core'),
|
|
824
828
|
import('@deck.gl/layers'),
|
|
825
829
|
// A package export, not a relative import: its `development`
|
|
826
830
|
// condition keeps the worker's `?worker&url` import out of the dev
|
|
@@ -832,6 +836,7 @@
|
|
|
832
836
|
return {
|
|
833
837
|
maplibregl: maplibre,
|
|
834
838
|
MapLibreOverlay: deckMap.MapLibreOverlay,
|
|
839
|
+
horizon: createGlobeHorizon(deckCore.LayerExtension),
|
|
835
840
|
PathLayer: layers.PathLayer,
|
|
836
841
|
ScatterplotLayer: layers.ScatterplotLayer,
|
|
837
842
|
IconLayer: layers.IconLayer
|
|
@@ -844,26 +849,19 @@
|
|
|
844
849
|
// maplibre's own WebGL2 context instead of a second canvas, so the page
|
|
845
850
|
// holds one GL context rather than two. Safari evicts WebGL contexts under
|
|
846
851
|
// memory and tab pressure, and one context survives that far better; the
|
|
847
|
-
// deck layers also inherit maplibre's 4096 canvas clamp. The
|
|
848
|
-
// writes no depth
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
function
|
|
852
|
-
return {
|
|
853
|
-
depthWriteEnabled: false,
|
|
854
|
-
depthCompare: type === 'globe' ? ('less-equal' as const) : ('always' as const)
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
function buildOverlay(
|
|
859
|
-
MapLibreOverlay: MapModules['MapLibreOverlay'],
|
|
860
|
-
type: MapProjection
|
|
861
|
-
): MapLibreOverlay {
|
|
852
|
+
// deck layers also inherit maplibre's 4096 canvas clamp. The basemap
|
|
853
|
+
// writes no depth we can trust (on the globe, deck's sphere and maplibre's
|
|
854
|
+
// disagree near the surface), so the overlay always draws on top, and on
|
|
855
|
+
// the globe the horizon extension hides what lies on the far side.
|
|
856
|
+
function buildOverlay(MapLibreOverlay: MapModules['MapLibreOverlay']): MapLibreOverlay {
|
|
862
857
|
const touch = coarsePointer();
|
|
863
858
|
return new MapLibreOverlay({
|
|
864
859
|
interleaved: true,
|
|
865
860
|
layers: [],
|
|
866
|
-
parameters:
|
|
861
|
+
parameters: {
|
|
862
|
+
depthWriteEnabled: false,
|
|
863
|
+
depthCompare: 'always'
|
|
864
|
+
},
|
|
867
865
|
// a small bus draws ~9px across, well under a fingertip
|
|
868
866
|
pickingRadius: touch ? 12 : 0,
|
|
869
867
|
onClick: (info: PickingInfo) => {
|
|
@@ -894,9 +892,10 @@
|
|
|
894
892
|
let cleanup = () => {};
|
|
895
893
|
let cancelled = false;
|
|
896
894
|
void loadMapModules()
|
|
897
|
-
.then(({ maplibregl, MapLibreOverlay, PathLayer, ScatterplotLayer, IconLayer }) => {
|
|
895
|
+
.then(({ maplibregl, MapLibreOverlay, horizon, PathLayer, ScatterplotLayer, IconLayer }) => {
|
|
898
896
|
if (cancelled) return;
|
|
899
897
|
layerCtors = { PathLayer, ScatterplotLayer, IconLayer };
|
|
898
|
+
globeHorizon = horizon;
|
|
900
899
|
const m = new maplibregl.Map({
|
|
901
900
|
container,
|
|
902
901
|
style: spec.primary,
|
|
@@ -924,10 +923,7 @@
|
|
|
924
923
|
onFallback: (reason) => onBasemapFallback?.(reason),
|
|
925
924
|
onFailure: (reason) => onBasemapError?.(reason)
|
|
926
925
|
});
|
|
927
|
-
const o = buildOverlay(
|
|
928
|
-
MapLibreOverlay,
|
|
929
|
-
untrack(() => projection)
|
|
930
|
-
);
|
|
926
|
+
const o = buildOverlay(MapLibreOverlay);
|
|
931
927
|
m.addControl(o);
|
|
932
928
|
m.on('click', (e) => {
|
|
933
929
|
if (app.placingId) onplacecase(e.lngLat.lng, e.lngLat.lat);
|
|
@@ -1069,7 +1065,6 @@
|
|
|
1069
1065
|
// MapLibre throws, and the `style.load` handler above applies it instead.
|
|
1070
1066
|
$effect(() => {
|
|
1071
1067
|
const type = projection;
|
|
1072
|
-
overlay?.setProps({ parameters: overlayParameters(type) });
|
|
1073
1068
|
const m = map;
|
|
1074
1069
|
if (!m) return;
|
|
1075
1070
|
try {
|
|
@@ -1158,7 +1153,7 @@
|
|
|
1158
1153
|
updateTriggers: { getFillColor: [study, mode, theme], getRadius: [app.busSizing] }
|
|
1159
1154
|
})
|
|
1160
1155
|
);
|
|
1161
|
-
overlay.setProps({ layers: [...layers, ...extraLayers] });
|
|
1156
|
+
overlay.setProps({ layers: withGlobeHorizon([...layers, ...extraLayers], globeHorizon) });
|
|
1162
1157
|
return;
|
|
1163
1158
|
}
|
|
1164
1159
|
for (const c of app.cases) {
|
|
@@ -1431,7 +1426,7 @@
|
|
|
1431
1426
|
})
|
|
1432
1427
|
);
|
|
1433
1428
|
}
|
|
1434
|
-
overlay.setProps({ layers: [...layers, ...extraLayers] });
|
|
1429
|
+
overlay.setProps({ layers: withGlobeHorizon([...layers, ...extraLayers], globeHorizon) });
|
|
1435
1430
|
});
|
|
1436
1431
|
|
|
1437
1432
|
$effect(() => {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Layer, LayerExtension } from '@deck.gl/core';
|
|
2
|
+
/** A deck.gl extension that hides geometry on the far side of the globe. It
|
|
3
|
+
* takes deck's `LayerExtension` class, which the map loads lazily. */
|
|
4
|
+
export declare function createGlobeHorizon(Base: typeof LayerExtension): LayerExtension;
|
|
5
|
+
/** The layers with the extension added, once each. */
|
|
6
|
+
export declare function withGlobeHorizon(layers: Layer[], horizon: LayerExtension | null): Layer[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// On the globe, geometry past the horizon is on the far side of the earth and
|
|
2
|
+
// must not show through it. A depth test against maplibre's globe cannot do
|
|
3
|
+
// this: deck's globe and maplibre's write different depth near the surface,
|
|
4
|
+
// so the test also cut away parts of a network facing the camera. Instead
|
|
5
|
+
// each vertex measures itself against the horizon in deck's own common space,
|
|
6
|
+
// where the globe is a sphere at the origin and `project.cameraPosition` is
|
|
7
|
+
// the eye: a surface point p faces the eye when (eye − p)·p > 0. Fragments
|
|
8
|
+
// past it are discarded; on the flat map nothing changes.
|
|
9
|
+
const INJECT = {
|
|
10
|
+
'vs:#decl': 'out float tgHorizon;',
|
|
11
|
+
'vs:DECKGL_FILTER_GL_POSITION': `
|
|
12
|
+
vec3 tgPoint = geometry.position.xyz;
|
|
13
|
+
tgHorizon = project.projectionMode == PROJECTION_MODE_GLOBE
|
|
14
|
+
? dot(tgPoint, project.cameraPosition - tgPoint) / max(dot(tgPoint, tgPoint), 1.0) + 1e-4
|
|
15
|
+
: 1.0;
|
|
16
|
+
`,
|
|
17
|
+
'fs:#decl': 'in float tgHorizon;',
|
|
18
|
+
'fs:DECKGL_FILTER_COLOR': 'if (tgHorizon < 0.0) discard;'
|
|
19
|
+
};
|
|
20
|
+
/** A deck.gl extension that hides geometry on the far side of the globe. It
|
|
21
|
+
* takes deck's `LayerExtension` class, which the map loads lazily. */
|
|
22
|
+
export function createGlobeHorizon(Base) {
|
|
23
|
+
class GlobeHorizonExtension extends Base {
|
|
24
|
+
static extensionName = 'GlobeHorizonExtension';
|
|
25
|
+
getShaders() {
|
|
26
|
+
return { inject: INJECT };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return new GlobeHorizonExtension();
|
|
30
|
+
}
|
|
31
|
+
/** The layers with the extension added, once each. */
|
|
32
|
+
export function withGlobeHorizon(layers, horizon) {
|
|
33
|
+
if (!horizon)
|
|
34
|
+
return layers;
|
|
35
|
+
return layers.map((layer) => {
|
|
36
|
+
const extensions = layer.props.extensions ?? [];
|
|
37
|
+
return extensions.includes(horizon)
|
|
38
|
+
? layer
|
|
39
|
+
: layer.clone({ extensions: [...extensions, horizon] });
|
|
40
|
+
});
|
|
41
|
+
}
|