isard 0.0.0-stage → 0.2.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/CHANGELOG.md +114 -0
- package/LICENSE +21 -0
- package/README.md +150 -2
- package/dist/basemap.worker.d.ts +66 -0
- package/dist/basemap.worker.js +4 -0
- package/dist/basemap.worker.js.map +13 -0
- package/dist/basemaps.d.ts +46 -0
- package/dist/basemaps.js +95 -0
- package/dist/basemaps.js.map +1 -0
- package/dist/color.d.ts +8 -0
- package/dist/color.js +98 -0
- package/dist/color.js.map +1 -0
- package/dist/controls.d.ts +43 -0
- package/dist/controls.js +115 -0
- package/dist/controls.js.map +1 -0
- package/dist/decoder.worker.js +6 -0
- package/dist/decoder.worker.js.map +12 -0
- package/dist/decoders.d.ts +27 -0
- package/dist/decoders.js +109 -0
- package/dist/decoders.js.map +1 -0
- package/dist/equirectangular.d.ts +35 -0
- package/dist/equirectangular.js +113 -0
- package/dist/equirectangular.js.map +1 -0
- package/dist/extrusions.d.ts +79 -0
- package/dist/extrusions.js +215 -0
- package/dist/extrusions.js.map +1 -0
- package/dist/features.d.ts +159 -0
- package/dist/features.js +387 -0
- package/dist/features.js.map +1 -0
- package/dist/geoplateforme.d.ts +35 -0
- package/dist/geoplateforme.js +56 -0
- package/dist/geoplateforme.js.map +1 -0
- package/dist/glyphs.d.ts +104 -0
- package/dist/glyphs.js +383 -0
- package/dist/glyphs.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/interaction.d.ts +50 -0
- package/dist/interaction.js +243 -0
- package/dist/interaction.js.map +1 -0
- package/dist/isard_bg.wasm +0 -0
- package/dist/isard_core_bg.wasm +0 -0
- package/dist/layers.d.ts +100 -0
- package/dist/layers.js +281 -0
- package/dist/layers.js.map +1 -0
- package/dist/loader.d.ts +30 -0
- package/dist/loader.js +102 -0
- package/dist/loader.js.map +1 -0
- package/dist/map.d.ts +203 -0
- package/dist/map.js +1083 -0
- package/dist/map.js.map +1 -0
- package/dist/mvt.d.ts +19 -0
- package/dist/mvt.js +231 -0
- package/dist/mvt.js.map +1 -0
- package/dist/overlay.d.ts +118 -0
- package/dist/overlay.js +334 -0
- package/dist/overlay.js.map +1 -0
- package/dist/protocol.d.ts +30 -0
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -0
- package/dist/style.d.ts +7 -0
- package/dist/style.js +130 -0
- package/dist/style.js.map +1 -0
- package/dist/symbols.d.ts +61 -0
- package/dist/symbols.js +238 -0
- package/dist/symbols.js.map +1 -0
- package/dist/terrain.d.ts +14 -0
- package/dist/terrain.js +44 -0
- package/dist/terrain.js.map +1 -0
- package/dist/types.d.ts +304 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/vectorstyle.d.ts +83 -0
- package/dist/vectorstyle.js +518 -0
- package/dist/vectorstyle.js.map +1 -0
- package/dist/vectortiles.d.ts +121 -0
- package/dist/vectortiles.js +507 -0
- package/dist/vectortiles.js.map +1 -0
- package/dist/wasm/isard.d.ts +611 -0
- package/dist/wasm/isard.js +2606 -0
- package/dist/wasm/isard_core.d.ts +468 -0
- package/dist/wasm/isard_core.js +2360 -0
- package/package.json +49 -4
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* isard: a WebGL2 map engine for large, interactive data layers, labels and 3D Tiles.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { GeoMap } from 'isard';
|
|
6
|
+
* const map = await GeoMap.create(container, { center: [2.4246, 48.8457], zoom: 13 });
|
|
7
|
+
* const network = map.addNetwork({ nodes, edges, max: 1800, contour: 300 });
|
|
8
|
+
* map.on('hover', ({ lonLat }) => lonLat && network.setValues(travelTimes(lonLat)));
|
|
9
|
+
* map.addTiles3d({ url: 'https://…/tileset.json' });
|
|
10
|
+
* map.addControl(new NavigationControl());
|
|
11
|
+
* new Marker().setLonLat([2.4246, 48.8457]).setPopup(new Popup({ offset: [0, -34] }).setText('Here')).addTo(map);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { OPENFREEMAP_ATTRIBUTION, TERRAIN_TILES_ATTRIBUTION } from './basemaps.ts';
|
|
15
|
+
export { AttributionControl, type AttributionOptions, type Control, type ControlPosition, DEFAULT_ATTRIBUTION, NavigationControl, type NavigationOptions, } from './controls.ts';
|
|
16
|
+
export type { DecoderUrls } from './decoders.ts';
|
|
17
|
+
export type { ExtrusionOptions, ExtrusionStyle, PolygonArrays, TileKey } from './extrusions.ts';
|
|
18
|
+
export { ExtrusionLayer } from './extrusions.ts';
|
|
19
|
+
export type { CircleOptions, CircleStyle, Feature, FeatureCollection, FeatureData, FeatureLayerStyle, FillOptions, FillStyle, Geometry, LineOptions, LineStyle, Placement, Position, Properties, Ramp, Size, Stops, Styled, } from './features.ts';
|
|
20
|
+
export { CircleLayer, FeatureLayer, FillLayer, interpolate, LineLayer, parseColor } from './features.ts';
|
|
21
|
+
export { geoplateformeWfs, geoplateformeWms, geoplateformeWmts, IGN_ATTRIBUTION } from './geoplateforme.ts';
|
|
22
|
+
export type { TextPart } from './glyphs.ts';
|
|
23
|
+
export { DEFAULT_FONT, GLYPHS_URL } from './glyphs.ts';
|
|
24
|
+
export type { GroundOptions, GroundTexture } from './layers.ts';
|
|
25
|
+
export { GroundLayer, Layer, NetworkLayer, RasterLayer, Tiles3dLayer } from './layers.ts';
|
|
26
|
+
export { GeoMap } from './map.ts';
|
|
27
|
+
export { type Anchor, Marker, type MarkerEvent, type MarkerEvents, type MarkerOptions, Popup, type PopupEvents, type PopupOptions, } from './overlay.ts';
|
|
28
|
+
export type { SymbolOptions, SymbolStyle } from './symbols.ts';
|
|
29
|
+
export { SymbolLayer } from './symbols.ts';
|
|
30
|
+
export type { AnimationOptions, AtmosphereOptions, Basemap, BasemapName, BodyOptions, CelestialBody, FeatureHit, FlyToOptions, GeoMapOptions, LegacyBasemap, LoadingMode, LonLat, MapEvents, MapHoverFeatureEvent, MapPointerEvent, NetworkOptions, NetworkStyle, Point, Projection, RasterGrid, RasterOptions, TerrainEncoding, TerrainModel, TerrainOptions, Tiles3dOptions, Tiles3dStyle, } from './types.ts';
|
|
31
|
+
export type { Json, LabelCandidate, RasterSource, Style, StyleLayer } from './vectorstyle.ts';
|
|
32
|
+
export type { VectorTilesOptions } from './vectortiles.ts';
|
|
33
|
+
export { OPENFREEMAP_STYLES, PLAN_STYLES, VectorTiles } from './vectortiles.ts';
|
|
34
|
+
export type { Camera, NetworkStats, PickHit, PickResult, RasterLayerStats, Tiles3dStats } from './wasm/isard.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* isard: a WebGL2 map engine for large, interactive data layers, labels and 3D Tiles.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { GeoMap } from 'isard';
|
|
6
|
+
* const map = await GeoMap.create(container, { center: [2.4246, 48.8457], zoom: 13 });
|
|
7
|
+
* const network = map.addNetwork({ nodes, edges, max: 1800, contour: 300 });
|
|
8
|
+
* map.on('hover', ({ lonLat }) => lonLat && network.setValues(travelTimes(lonLat)));
|
|
9
|
+
* map.addTiles3d({ url: 'https://…/tileset.json' });
|
|
10
|
+
* map.addControl(new NavigationControl());
|
|
11
|
+
* new Marker().setLonLat([2.4246, 48.8457]).setPopup(new Popup({ offset: [0, -34] }).setText('Here')).addTo(map);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export { OPENFREEMAP_ATTRIBUTION, TERRAIN_TILES_ATTRIBUTION } from "./basemaps.js";
|
|
15
|
+
export { AttributionControl, DEFAULT_ATTRIBUTION, NavigationControl, } from "./controls.js";
|
|
16
|
+
export { ExtrusionLayer } from "./extrusions.js";
|
|
17
|
+
export { CircleLayer, FeatureLayer, FillLayer, interpolate, LineLayer, parseColor } from "./features.js";
|
|
18
|
+
export { geoplateformeWfs, geoplateformeWms, geoplateformeWmts, IGN_ATTRIBUTION } from "./geoplateforme.js";
|
|
19
|
+
export { DEFAULT_FONT, GLYPHS_URL } from "./glyphs.js";
|
|
20
|
+
export { GroundLayer, Layer, NetworkLayer, RasterLayer, Tiles3dLayer } from "./layers.js";
|
|
21
|
+
export { GeoMap } from "./map.js";
|
|
22
|
+
export { Marker, Popup, } from "./overlay.js";
|
|
23
|
+
export { SymbolLayer } from "./symbols.js";
|
|
24
|
+
export { OPENFREEMAP_STYLES, PLAN_STYLES, VectorTiles } from "./vectortiles.js";
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACnF,OAAO,EACL,kBAAkB,EAIlB,mBAAmB,EACnB,iBAAiB,GAElB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAqBjD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACzG,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE5G,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC1F,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAEL,MAAM,EAIN,KAAK,GAGN,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AA+B3C,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["/**\n * isard: a WebGL2 map engine for large, interactive data layers, labels and 3D Tiles.\n *\n * ```ts\n * import { GeoMap } from 'isard';\n * const map = await GeoMap.create(container, { center: [2.4246, 48.8457], zoom: 13 });\n * const network = map.addNetwork({ nodes, edges, max: 1800, contour: 300 });\n * map.on('hover', ({ lonLat }) => lonLat && network.setValues(travelTimes(lonLat)));\n * map.addTiles3d({ url: 'https://…/tileset.json' });\n * map.addControl(new NavigationControl());\n * new Marker().setLonLat([2.4246, 48.8457]).setPopup(new Popup({ offset: [0, -34] }).setText('Here')).addTo(map);\n * ```\n */\nexport { OPENFREEMAP_ATTRIBUTION, TERRAIN_TILES_ATTRIBUTION } from './basemaps.ts';\nexport {\n AttributionControl,\n type AttributionOptions,\n type Control,\n type ControlPosition,\n DEFAULT_ATTRIBUTION,\n NavigationControl,\n type NavigationOptions,\n} from './controls.ts';\nexport type { DecoderUrls } from './decoders.ts';\nexport type { ExtrusionOptions, ExtrusionStyle, PolygonArrays, TileKey } from './extrusions.ts';\nexport { ExtrusionLayer } from './extrusions.ts';\nexport type {\n CircleOptions,\n CircleStyle,\n Feature,\n FeatureCollection,\n FeatureData,\n FeatureLayerStyle,\n FillOptions,\n FillStyle,\n Geometry,\n LineOptions,\n LineStyle,\n Placement,\n Position,\n Properties,\n Ramp,\n Size,\n Stops,\n Styled,\n} from './features.ts';\nexport { CircleLayer, FeatureLayer, FillLayer, interpolate, LineLayer, parseColor } from './features.ts';\nexport { geoplateformeWfs, geoplateformeWms, geoplateformeWmts, IGN_ATTRIBUTION } from './geoplateforme.ts';\nexport type { TextPart } from './glyphs.ts';\nexport { DEFAULT_FONT, GLYPHS_URL } from './glyphs.ts';\nexport type { GroundOptions, GroundTexture } from './layers.ts';\nexport { GroundLayer, Layer, NetworkLayer, RasterLayer, Tiles3dLayer } from './layers.ts';\nexport { GeoMap } from './map.ts';\nexport {\n type Anchor,\n Marker,\n type MarkerEvent,\n type MarkerEvents,\n type MarkerOptions,\n Popup,\n type PopupEvents,\n type PopupOptions,\n} from './overlay.ts';\nexport type { SymbolOptions, SymbolStyle } from './symbols.ts';\nexport { SymbolLayer } from './symbols.ts';\nexport type {\n AnimationOptions,\n AtmosphereOptions,\n Basemap,\n BasemapName,\n BodyOptions,\n CelestialBody,\n FeatureHit,\n FlyToOptions,\n GeoMapOptions,\n LegacyBasemap,\n LoadingMode,\n LonLat,\n MapEvents,\n MapHoverFeatureEvent,\n MapPointerEvent,\n NetworkOptions,\n NetworkStyle,\n Point,\n Projection,\n RasterGrid,\n RasterOptions,\n TerrainEncoding,\n TerrainModel,\n TerrainOptions,\n Tiles3dOptions,\n Tiles3dStyle,\n} from './types.ts';\nexport type { Json, LabelCandidate, RasterSource, Style, StyleLayer } from './vectorstyle.ts';\nexport type { VectorTilesOptions } from './vectortiles.ts';\nexport { OPENFREEMAP_STYLES, PLAN_STYLES, VectorTiles } from './vectortiles.ts';\nexport type { Camera, NetworkStats, PickHit, PickResult, RasterLayerStats, Tiles3dStats } from './wasm/isard.js';\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mouse, touch and keyboard controls. Every gesture only updates the camera and asks for a frame: the engine draws at most
|
|
3
|
+
* once per display refresh, whatever the event rate of the device.
|
|
4
|
+
*/
|
|
5
|
+
import type { GeoMap } from './map.ts';
|
|
6
|
+
import type { Point } from './types.ts';
|
|
7
|
+
import type { Camera } from './wasm/isard.js';
|
|
8
|
+
/** What a key does to the camera: a pan in CSS pixels, a zoom, rotation or tilt step, or facing north */
|
|
9
|
+
export type KeyAction = {
|
|
10
|
+
pan: Point;
|
|
11
|
+
} | {
|
|
12
|
+
zoom: number;
|
|
13
|
+
} | {
|
|
14
|
+
bearing: number;
|
|
15
|
+
} | {
|
|
16
|
+
pitch: number;
|
|
17
|
+
} | {
|
|
18
|
+
reset: true;
|
|
19
|
+
};
|
|
20
|
+
/** The action of a key (`KeyboardEvent.key`), as MapLibre's: arrows pan (with Shift: rotate and tilt), + and - zoom
|
|
21
|
+
* (with Shift: two levels), 0 faces north and levels the view */
|
|
22
|
+
export declare function keyAction(key: string, shift: boolean): KeyAction | undefined;
|
|
23
|
+
/** The camera after a key's action: a pan moves the center by that many CSS pixels on screen (the ground in view,
|
|
24
|
+
* turned with the bearing, stretched by the tilt) */
|
|
25
|
+
export declare function keyCamera(action: KeyAction, camera: Camera): Partial<Camera>;
|
|
26
|
+
export declare class Interaction {
|
|
27
|
+
private readonly map;
|
|
28
|
+
private readonly pointers;
|
|
29
|
+
private readonly cleanup;
|
|
30
|
+
private wheel;
|
|
31
|
+
/** Recent pan moves, for the release speed: [time, dx, dy] */
|
|
32
|
+
private trail;
|
|
33
|
+
private moved;
|
|
34
|
+
constructor(map: GeoMap, options?: {
|
|
35
|
+
keyboard?: boolean;
|
|
36
|
+
});
|
|
37
|
+
destroy(): void;
|
|
38
|
+
/** A gesture holds the map (pointers down): the camera has not stopped moving */
|
|
39
|
+
get active(): boolean;
|
|
40
|
+
private local;
|
|
41
|
+
private down;
|
|
42
|
+
private move;
|
|
43
|
+
private up;
|
|
44
|
+
/** Keeps panning at the release speed (last 50 ms), decaying exponentially */
|
|
45
|
+
private inertia;
|
|
46
|
+
private onWheel;
|
|
47
|
+
private onKey;
|
|
48
|
+
/** Smooth zoom around a screen point; touchpads (`direct`) follow the fingers without easing */
|
|
49
|
+
private zoomBy;
|
|
50
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/** Zoom levels per wheel pixel (a mouse notch is about 100 px: 1/3 of a level) */
|
|
2
|
+
const WHEEL_RATE = 1 / 300;
|
|
3
|
+
/** Share of the remaining wheel zoom applied per frame (smooth, about 150 ms to settle) */
|
|
4
|
+
const WHEEL_EASE = 0.3;
|
|
5
|
+
/** Degrees per pixel when rotating or tilting with the mouse */
|
|
6
|
+
const ROTATE_RATE = 0.4;
|
|
7
|
+
/** A press that moves less than this (CSS pixels) is a click */
|
|
8
|
+
const CLICK_TOLERANCE = 4;
|
|
9
|
+
/** Pan speed kept on release, decaying with this time constant (ms) */
|
|
10
|
+
const INERTIA_DECAY = 280;
|
|
11
|
+
/** CSS pixels per arrow key press */
|
|
12
|
+
const KEY_PAN = 100;
|
|
13
|
+
/** Degrees per Shift + left or right arrow */
|
|
14
|
+
const KEY_BEARING = 15;
|
|
15
|
+
/** Degrees per Shift + up or down arrow */
|
|
16
|
+
const KEY_PITCH = 10;
|
|
17
|
+
/** Duration of the moves made by keys, milliseconds */
|
|
18
|
+
const KEY_DURATION = 300;
|
|
19
|
+
/** The action of a key (`KeyboardEvent.key`), as MapLibre's: arrows pan (with Shift: rotate and tilt), + and - zoom
|
|
20
|
+
* (with Shift: two levels), 0 faces north and levels the view */
|
|
21
|
+
export function keyAction(key, shift) {
|
|
22
|
+
switch (key) {
|
|
23
|
+
case 'ArrowLeft':
|
|
24
|
+
return shift ? { bearing: -KEY_BEARING } : { pan: [-KEY_PAN, 0] };
|
|
25
|
+
case 'ArrowRight':
|
|
26
|
+
return shift ? { bearing: KEY_BEARING } : { pan: [KEY_PAN, 0] };
|
|
27
|
+
case 'ArrowUp':
|
|
28
|
+
return shift ? { pitch: KEY_PITCH } : { pan: [0, -KEY_PAN] };
|
|
29
|
+
case 'ArrowDown':
|
|
30
|
+
return shift ? { pitch: -KEY_PITCH } : { pan: [0, KEY_PAN] };
|
|
31
|
+
case '+':
|
|
32
|
+
case '=':
|
|
33
|
+
return { zoom: shift ? 2 : 1 };
|
|
34
|
+
case '-':
|
|
35
|
+
case '_':
|
|
36
|
+
return { zoom: shift ? -2 : -1 };
|
|
37
|
+
case '0':
|
|
38
|
+
return { reset: true };
|
|
39
|
+
default:
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** The camera after a key's action: a pan moves the center by that many CSS pixels on screen (the ground in view,
|
|
44
|
+
* turned with the bearing, stretched by the tilt) */
|
|
45
|
+
export function keyCamera(action, camera) {
|
|
46
|
+
if ('zoom' in action)
|
|
47
|
+
return { zoom: camera.zoom + action.zoom };
|
|
48
|
+
if ('bearing' in action)
|
|
49
|
+
return { bearing: camera.bearing + action.bearing };
|
|
50
|
+
if ('pitch' in action)
|
|
51
|
+
return { pitch: Math.max(0, camera.pitch + action.pitch) };
|
|
52
|
+
if ('reset' in action)
|
|
53
|
+
return { bearing: 0, pitch: 0 };
|
|
54
|
+
const b = (camera.bearing * Math.PI) / 180;
|
|
55
|
+
const [sx, sy] = [action.pan[0], action.pan[1] / Math.max(0.25, Math.cos((camera.pitch * Math.PI) / 180))];
|
|
56
|
+
// Web Mercator units (0 to 1 across the world, y down); the world is 512 CSS pixels at zoom 0
|
|
57
|
+
const world = 512 * 2 ** camera.zoom;
|
|
58
|
+
const dx = (sx * Math.cos(b) - sy * Math.sin(b)) / world;
|
|
59
|
+
const dy = (sx * Math.sin(b) + sy * Math.cos(b)) / world;
|
|
60
|
+
const [lon, lat] = camera.center;
|
|
61
|
+
const phi = (Math.max(-85.0511, Math.min(85.0511, lat)) * Math.PI) / 180;
|
|
62
|
+
const x = (lon + 180) / 360 + dx;
|
|
63
|
+
const y = Math.max(0, Math.min(1, (1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 + dy));
|
|
64
|
+
const wrapped = ((((x * 360) % 360) + 360) % 360) - 180;
|
|
65
|
+
return { center: [wrapped, (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI] };
|
|
66
|
+
}
|
|
67
|
+
/** An element that takes the keys typed in it */
|
|
68
|
+
function editable(target) {
|
|
69
|
+
if (!(target instanceof HTMLElement))
|
|
70
|
+
return false;
|
|
71
|
+
return target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName);
|
|
72
|
+
}
|
|
73
|
+
export class Interaction {
|
|
74
|
+
map;
|
|
75
|
+
pointers = new Map();
|
|
76
|
+
cleanup = [];
|
|
77
|
+
wheel = { pending: 0, at: [0, 0] };
|
|
78
|
+
/** Recent pan moves, for the release speed: [time, dx, dy] */
|
|
79
|
+
trail = [];
|
|
80
|
+
moved = false;
|
|
81
|
+
constructor(map, options = {}) {
|
|
82
|
+
this.map = map;
|
|
83
|
+
const canvas = map.canvas;
|
|
84
|
+
const listen = (type, handler, options) => {
|
|
85
|
+
canvas.addEventListener(type, handler, options);
|
|
86
|
+
this.cleanup.push(() => canvas.removeEventListener(type, handler));
|
|
87
|
+
};
|
|
88
|
+
listen('pointerdown', (e) => this.down(e));
|
|
89
|
+
listen('pointermove', (e) => this.move(e));
|
|
90
|
+
listen('pointerup', (e) => this.up(e));
|
|
91
|
+
listen('pointercancel', (e) => this.up(e));
|
|
92
|
+
listen('pointerleave', (e) => {
|
|
93
|
+
if (e.pointerType !== 'mouse')
|
|
94
|
+
return;
|
|
95
|
+
map.hover(undefined);
|
|
96
|
+
map.emit('leave', {});
|
|
97
|
+
});
|
|
98
|
+
// On the container, the wheel zooms over markers and popups too
|
|
99
|
+
const container = map.getContainer();
|
|
100
|
+
const wheel = (e) => this.onWheel(e);
|
|
101
|
+
container.addEventListener('wheel', wheel, { passive: false });
|
|
102
|
+
this.cleanup.push(() => container.removeEventListener('wheel', wheel));
|
|
103
|
+
listen('dblclick', (e) => this.zoomBy(e.shiftKey ? -1 : 1, this.local(e)));
|
|
104
|
+
listen('contextmenu', (e) => e.preventDefault());
|
|
105
|
+
if (options.keyboard ?? true) {
|
|
106
|
+
// On the container: the keys work with the focus on a marker or a control too
|
|
107
|
+
const key = (e) => this.onKey(e);
|
|
108
|
+
container.addEventListener('keydown', key);
|
|
109
|
+
this.cleanup.push(() => container.removeEventListener('keydown', key));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
destroy() {
|
|
113
|
+
for (const off of this.cleanup)
|
|
114
|
+
off();
|
|
115
|
+
}
|
|
116
|
+
/** A gesture holds the map (pointers down): the camera has not stopped moving */
|
|
117
|
+
get active() {
|
|
118
|
+
return this.pointers.size > 0;
|
|
119
|
+
}
|
|
120
|
+
local(e) {
|
|
121
|
+
const r = this.map.canvas.getBoundingClientRect();
|
|
122
|
+
return [e.clientX - r.left, e.clientY - r.top];
|
|
123
|
+
}
|
|
124
|
+
down(e) {
|
|
125
|
+
this.map.canvas.setPointerCapture(e.pointerId);
|
|
126
|
+
const point = this.local(e);
|
|
127
|
+
const rotate = e.button === 2 || e.ctrlKey || e.shiftKey;
|
|
128
|
+
this.pointers.set(e.pointerId, { point, start: point, button: e.button, rotate });
|
|
129
|
+
this.trail = [];
|
|
130
|
+
this.moved = false;
|
|
131
|
+
// Grabbing the map stops any running animation (inertia, wheel zoom, easeTo)
|
|
132
|
+
this.wheel.pending = 0;
|
|
133
|
+
this.map.animate(undefined);
|
|
134
|
+
}
|
|
135
|
+
move(e) {
|
|
136
|
+
const point = this.local(e);
|
|
137
|
+
const p = this.pointers.get(e.pointerId);
|
|
138
|
+
if (!p) {
|
|
139
|
+
if (e.pointerType === 'mouse')
|
|
140
|
+
this.map.hover(point);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const [dx, dy] = [point[0] - p.point[0], point[1] - p.point[1]];
|
|
144
|
+
if (Math.hypot(point[0] - p.start[0], point[1] - p.start[1]) > CLICK_TOLERANCE)
|
|
145
|
+
this.moved = true;
|
|
146
|
+
const engine = this.map.engine;
|
|
147
|
+
const other = [...this.pointers.values()].find((q) => q !== p);
|
|
148
|
+
if (this.pointers.size === 2 && other) {
|
|
149
|
+
// Pinch: zoom by the change of spread, turn by the change of angle, pan by the midpoint
|
|
150
|
+
const before = Math.hypot(p.point[0] - other.point[0], p.point[1] - other.point[1]);
|
|
151
|
+
const after = Math.hypot(point[0] - other.point[0], point[1] - other.point[1]);
|
|
152
|
+
const angle = (q) => Math.atan2(q[1] - other.point[1], q[0] - other.point[0]);
|
|
153
|
+
const mid = (q) => [(q[0] + other.point[0]) / 2, (q[1] + other.point[1]) / 2];
|
|
154
|
+
engine.pan(...mid(p.point), ...mid(point));
|
|
155
|
+
if (before > 0 && after > 0)
|
|
156
|
+
engine.zoomAt(Math.log2(after / before), ...mid(point));
|
|
157
|
+
engine.rotate((-(angle(point) - angle(p.point)) * 180) / Math.PI, 0);
|
|
158
|
+
}
|
|
159
|
+
else if (p.rotate) {
|
|
160
|
+
engine.rotate(-dx * ROTATE_RATE, -dy * ROTATE_RATE);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
engine.pan(p.point[0], p.point[1], point[0], point[1]);
|
|
164
|
+
this.trail.push([e.timeStamp, dx, dy]);
|
|
165
|
+
}
|
|
166
|
+
p.point = point;
|
|
167
|
+
this.map.requestRender();
|
|
168
|
+
}
|
|
169
|
+
up(e) {
|
|
170
|
+
const p = this.pointers.get(e.pointerId);
|
|
171
|
+
if (!p)
|
|
172
|
+
return;
|
|
173
|
+
this.pointers.delete(e.pointerId);
|
|
174
|
+
// The next frame sends 'moveend' if the camera stays
|
|
175
|
+
this.map.requestRender();
|
|
176
|
+
if (!this.moved && e.type === 'pointerup' && p.button === 0 && this.pointers.size === 0) {
|
|
177
|
+
this.map.emit('click', { point: p.point, lonLat: this.map.unproject(p.point) });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!p.rotate && this.pointers.size === 0)
|
|
181
|
+
this.inertia(e.timeStamp, p.point);
|
|
182
|
+
}
|
|
183
|
+
/** Keeps panning at the release speed (last 50 ms), decaying exponentially */
|
|
184
|
+
inertia(now, from) {
|
|
185
|
+
const recent = this.trail.filter(([t]) => now - t < 50);
|
|
186
|
+
const first = recent[0];
|
|
187
|
+
if (!first || recent.length < 2)
|
|
188
|
+
return;
|
|
189
|
+
const span = Math.max(16, now - first[0]);
|
|
190
|
+
let vx = recent.reduce((s, [, dx]) => s + dx, 0) / span;
|
|
191
|
+
let vy = recent.reduce((s, [, , dy]) => s + dy, 0) / span;
|
|
192
|
+
if (Math.hypot(vx, vy) < 0.1)
|
|
193
|
+
return;
|
|
194
|
+
let last = performance.now();
|
|
195
|
+
let point = from;
|
|
196
|
+
this.map.animate((t) => {
|
|
197
|
+
const dt = Math.min(64, t - last);
|
|
198
|
+
last = t;
|
|
199
|
+
const decay = Math.exp(-dt / INERTIA_DECAY);
|
|
200
|
+
const next = [point[0] + vx * dt, point[1] + vy * dt];
|
|
201
|
+
this.map.engine.pan(point[0], point[1], next[0], next[1]);
|
|
202
|
+
point = next;
|
|
203
|
+
vx *= decay;
|
|
204
|
+
vy *= decay;
|
|
205
|
+
return Math.hypot(vx, vy) > 0.02;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
onWheel(e) {
|
|
209
|
+
e.preventDefault();
|
|
210
|
+
// Lines (Firefox mouse wheels) are about 33 px
|
|
211
|
+
const pixels = e.deltaMode === 1 ? e.deltaY * 33 : e.deltaY;
|
|
212
|
+
this.zoomBy(Math.max(-1, Math.min(1, -pixels * WHEEL_RATE)), this.local(e), e.deltaMode === 0 && Math.abs(pixels) < 40);
|
|
213
|
+
}
|
|
214
|
+
onKey(e) {
|
|
215
|
+
if (e.defaultPrevented || e.ctrlKey || e.metaKey || e.altKey || editable(e.target))
|
|
216
|
+
return;
|
|
217
|
+
const action = keyAction(e.key, e.shiftKey);
|
|
218
|
+
if (!action)
|
|
219
|
+
return;
|
|
220
|
+
e.preventDefault();
|
|
221
|
+
this.wheel.pending = 0;
|
|
222
|
+
const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
223
|
+
this.map.easeTo(keyCamera(action, this.map.getCamera()), reduced ? 0 : KEY_DURATION);
|
|
224
|
+
}
|
|
225
|
+
/** Smooth zoom around a screen point; touchpads (`direct`) follow the fingers without easing */
|
|
226
|
+
zoomBy(delta, at, direct = false) {
|
|
227
|
+
if (direct) {
|
|
228
|
+
this.map.engine.zoomAt(delta, at[0], at[1]);
|
|
229
|
+
this.map.requestRender();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Accumulate: a second notch before the end of the first one adds to it
|
|
233
|
+
this.wheel.pending += delta;
|
|
234
|
+
this.wheel.at = at;
|
|
235
|
+
this.map.animate(() => {
|
|
236
|
+
const step = Math.abs(this.wheel.pending) < 0.002 ? this.wheel.pending : this.wheel.pending * WHEEL_EASE;
|
|
237
|
+
this.wheel.pending -= step;
|
|
238
|
+
this.map.engine.zoomAt(step, this.wheel.at[0], this.wheel.at[1]);
|
|
239
|
+
return this.wheel.pending !== 0;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=interaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interaction.js","sourceRoot":"","sources":["../src/interaction.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Mouse, touch and keyboard controls. Every gesture only updates the camera and asks for a frame: the engine draws at most\n * once per display refresh, whatever the event rate of the device.\n */\nimport type { GeoMap } from './map.ts';\nimport type { Point } from './types.ts';\nimport type { Camera } from './wasm/isard.js';\n\n/** Zoom levels per wheel pixel (a mouse notch is about 100 px: 1/3 of a level) */\nconst WHEEL_RATE = 1 / 300;\n/** Share of the remaining wheel zoom applied per frame (smooth, about 150 ms to settle) */\nconst WHEEL_EASE = 0.3;\n/** Degrees per pixel when rotating or tilting with the mouse */\nconst ROTATE_RATE = 0.4;\n/** A press that moves less than this (CSS pixels) is a click */\nconst CLICK_TOLERANCE = 4;\n/** Pan speed kept on release, decaying with this time constant (ms) */\nconst INERTIA_DECAY = 280;\n\n/** CSS pixels per arrow key press */\nconst KEY_PAN = 100;\n/** Degrees per Shift + left or right arrow */\nconst KEY_BEARING = 15;\n/** Degrees per Shift + up or down arrow */\nconst KEY_PITCH = 10;\n/** Duration of the moves made by keys, milliseconds */\nconst KEY_DURATION = 300;\n\n/** What a key does to the camera: a pan in CSS pixels, a zoom, rotation or tilt step, or facing north */\nexport type KeyAction = { pan: Point } | { zoom: number } | { bearing: number } | { pitch: number } | { reset: true };\n\n/** The action of a key (`KeyboardEvent.key`), as MapLibre's: arrows pan (with Shift: rotate and tilt), + and - zoom\n * (with Shift: two levels), 0 faces north and levels the view */\nexport function keyAction(key: string, shift: boolean): KeyAction | undefined {\n switch (key) {\n case 'ArrowLeft':\n return shift ? { bearing: -KEY_BEARING } : { pan: [-KEY_PAN, 0] };\n case 'ArrowRight':\n return shift ? { bearing: KEY_BEARING } : { pan: [KEY_PAN, 0] };\n case 'ArrowUp':\n return shift ? { pitch: KEY_PITCH } : { pan: [0, -KEY_PAN] };\n case 'ArrowDown':\n return shift ? { pitch: -KEY_PITCH } : { pan: [0, KEY_PAN] };\n case '+':\n case '=':\n return { zoom: shift ? 2 : 1 };\n case '-':\n case '_':\n return { zoom: shift ? -2 : -1 };\n case '0':\n return { reset: true };\n default:\n return undefined;\n }\n}\n\n/** The camera after a key's action: a pan moves the center by that many CSS pixels on screen (the ground in view,\n * turned with the bearing, stretched by the tilt) */\nexport function keyCamera(action: KeyAction, camera: Camera): Partial<Camera> {\n if ('zoom' in action) return { zoom: camera.zoom + action.zoom };\n if ('bearing' in action) return { bearing: camera.bearing + action.bearing };\n if ('pitch' in action) return { pitch: Math.max(0, camera.pitch + action.pitch) };\n if ('reset' in action) return { bearing: 0, pitch: 0 };\n const b = (camera.bearing * Math.PI) / 180;\n const [sx, sy] = [action.pan[0], action.pan[1] / Math.max(0.25, Math.cos((camera.pitch * Math.PI) / 180))];\n // Web Mercator units (0 to 1 across the world, y down); the world is 512 CSS pixels at zoom 0\n const world = 512 * 2 ** camera.zoom;\n const dx = (sx * Math.cos(b) - sy * Math.sin(b)) / world;\n const dy = (sx * Math.sin(b) + sy * Math.cos(b)) / world;\n const [lon, lat] = camera.center;\n const phi = (Math.max(-85.0511, Math.min(85.0511, lat)) * Math.PI) / 180;\n const x = (lon + 180) / 360 + dx;\n const y = Math.max(0, Math.min(1, (1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 + dy));\n const wrapped = ((((x * 360) % 360) + 360) % 360) - 180;\n return { center: [wrapped, (Math.atan(Math.sinh(Math.PI * (1 - 2 * y))) * 180) / Math.PI] };\n}\n\n/** An element that takes the keys typed in it */\nfunction editable(target: EventTarget | null): boolean {\n if (!(target instanceof HTMLElement)) return false;\n return target.isContentEditable || ['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName);\n}\n\ninterface Pointer {\n point: Point;\n start: Point;\n button: number;\n rotate: boolean;\n}\n\nexport class Interaction {\n private readonly pointers = new Map<number, Pointer>();\n private readonly cleanup: (() => void)[] = [];\n private wheel = { pending: 0, at: [0, 0] as Point };\n /** Recent pan moves, for the release speed: [time, dx, dy] */\n private trail: [number, number, number][] = [];\n private moved = false;\n\n constructor(\n private readonly map: GeoMap,\n options: { keyboard?: boolean } = {},\n ) {\n const canvas = map.canvas;\n const listen = <K extends keyof HTMLElementEventMap>(\n type: K,\n handler: (e: HTMLElementEventMap[K]) => void,\n options?: AddEventListenerOptions,\n ) => {\n canvas.addEventListener(type, handler, options);\n this.cleanup.push(() => canvas.removeEventListener(type, handler));\n };\n listen('pointerdown', (e) => this.down(e));\n listen('pointermove', (e) => this.move(e));\n listen('pointerup', (e) => this.up(e));\n listen('pointercancel', (e) => this.up(e));\n listen('pointerleave', (e) => {\n if (e.pointerType !== 'mouse') return;\n map.hover(undefined);\n map.emit('leave', {});\n });\n // On the container, the wheel zooms over markers and popups too\n const container = map.getContainer();\n const wheel = (e: WheelEvent) => this.onWheel(e);\n container.addEventListener('wheel', wheel, { passive: false });\n this.cleanup.push(() => container.removeEventListener('wheel', wheel));\n listen('dblclick', (e) => this.zoomBy(e.shiftKey ? -1 : 1, this.local(e)));\n listen('contextmenu', (e) => e.preventDefault());\n if (options.keyboard ?? true) {\n // On the container: the keys work with the focus on a marker or a control too\n const key = (e: KeyboardEvent) => this.onKey(e);\n container.addEventListener('keydown', key);\n this.cleanup.push(() => container.removeEventListener('keydown', key));\n }\n }\n\n destroy(): void {\n for (const off of this.cleanup) off();\n }\n\n /** A gesture holds the map (pointers down): the camera has not stopped moving */\n get active(): boolean {\n return this.pointers.size > 0;\n }\n\n private local(e: MouseEvent): Point {\n const r = this.map.canvas.getBoundingClientRect();\n return [e.clientX - r.left, e.clientY - r.top];\n }\n\n private down(e: PointerEvent): void {\n this.map.canvas.setPointerCapture(e.pointerId);\n const point = this.local(e);\n const rotate = e.button === 2 || e.ctrlKey || e.shiftKey;\n this.pointers.set(e.pointerId, { point, start: point, button: e.button, rotate });\n this.trail = [];\n this.moved = false;\n // Grabbing the map stops any running animation (inertia, wheel zoom, easeTo)\n this.wheel.pending = 0;\n this.map.animate(undefined);\n }\n\n private move(e: PointerEvent): void {\n const point = this.local(e);\n const p = this.pointers.get(e.pointerId);\n if (!p) {\n if (e.pointerType === 'mouse') this.map.hover(point);\n return;\n }\n const [dx, dy] = [point[0] - p.point[0], point[1] - p.point[1]];\n if (Math.hypot(point[0] - p.start[0], point[1] - p.start[1]) > CLICK_TOLERANCE) this.moved = true;\n const engine = this.map.engine;\n const other = [...this.pointers.values()].find((q) => q !== p);\n if (this.pointers.size === 2 && other) {\n // Pinch: zoom by the change of spread, turn by the change of angle, pan by the midpoint\n const before = Math.hypot(p.point[0] - other.point[0], p.point[1] - other.point[1]);\n const after = Math.hypot(point[0] - other.point[0], point[1] - other.point[1]);\n const angle = (q: Point) => Math.atan2(q[1] - other.point[1], q[0] - other.point[0]);\n const mid = (q: Point): Point => [(q[0] + other.point[0]) / 2, (q[1] + other.point[1]) / 2];\n engine.pan(...mid(p.point), ...mid(point));\n if (before > 0 && after > 0) engine.zoomAt(Math.log2(after / before), ...mid(point));\n engine.rotate((-(angle(point) - angle(p.point)) * 180) / Math.PI, 0);\n } else if (p.rotate) {\n engine.rotate(-dx * ROTATE_RATE, -dy * ROTATE_RATE);\n } else {\n engine.pan(p.point[0], p.point[1], point[0], point[1]);\n this.trail.push([e.timeStamp, dx, dy]);\n }\n p.point = point;\n this.map.requestRender();\n }\n\n private up(e: PointerEvent): void {\n const p = this.pointers.get(e.pointerId);\n if (!p) return;\n this.pointers.delete(e.pointerId);\n // The next frame sends 'moveend' if the camera stays\n this.map.requestRender();\n if (!this.moved && e.type === 'pointerup' && p.button === 0 && this.pointers.size === 0) {\n this.map.emit('click', { point: p.point, lonLat: this.map.unproject(p.point) });\n return;\n }\n if (!p.rotate && this.pointers.size === 0) this.inertia(e.timeStamp, p.point);\n }\n\n /** Keeps panning at the release speed (last 50 ms), decaying exponentially */\n private inertia(now: number, from: Point): void {\n const recent = this.trail.filter(([t]) => now - t < 50);\n const first = recent[0];\n if (!first || recent.length < 2) return;\n const span = Math.max(16, now - first[0]);\n let vx = recent.reduce((s, [, dx]) => s + dx, 0) / span;\n let vy = recent.reduce((s, [, , dy]) => s + dy, 0) / span;\n if (Math.hypot(vx, vy) < 0.1) return;\n let last = performance.now();\n let point = from;\n this.map.animate((t) => {\n const dt = Math.min(64, t - last);\n last = t;\n const decay = Math.exp(-dt / INERTIA_DECAY);\n const next: Point = [point[0] + vx * dt, point[1] + vy * dt];\n this.map.engine.pan(point[0], point[1], next[0], next[1]);\n point = next;\n vx *= decay;\n vy *= decay;\n return Math.hypot(vx, vy) > 0.02;\n });\n }\n\n private onWheel(e: WheelEvent): void {\n e.preventDefault();\n // Lines (Firefox mouse wheels) are about 33 px\n const pixels = e.deltaMode === 1 ? e.deltaY * 33 : e.deltaY;\n this.zoomBy(\n Math.max(-1, Math.min(1, -pixels * WHEEL_RATE)),\n this.local(e),\n e.deltaMode === 0 && Math.abs(pixels) < 40,\n );\n }\n\n private onKey(e: KeyboardEvent): void {\n if (e.defaultPrevented || e.ctrlKey || e.metaKey || e.altKey || editable(e.target)) return;\n const action = keyAction(e.key, e.shiftKey);\n if (!action) return;\n e.preventDefault();\n this.wheel.pending = 0;\n const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\n this.map.easeTo(keyCamera(action, this.map.getCamera()), reduced ? 0 : KEY_DURATION);\n }\n\n /** Smooth zoom around a screen point; touchpads (`direct`) follow the fingers without easing */\n private zoomBy(delta: number, at: Point, direct = false): void {\n if (direct) {\n this.map.engine.zoomAt(delta, at[0], at[1]);\n this.map.requestRender();\n return;\n }\n // Accumulate: a second notch before the end of the first one adds to it\n this.wheel.pending += delta;\n this.wheel.at = at;\n this.map.animate(() => {\n const step = Math.abs(this.wheel.pending) < 0.002 ? this.wheel.pending : this.wheel.pending * WHEEL_EASE;\n this.wheel.pending -= step;\n this.map.engine.zoomAt(step, this.wheel.at[0], this.wheel.at[1]);\n return this.wheel.pending !== 0;\n });\n }\n}\n"]}
|
|
Binary file
|
|
Binary file
|
package/dist/layers.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { LonLat, NetworkStyle, Tiles3dStyle } from './types.ts';
|
|
2
|
+
import type { NetworkStats, Tiles3dStats } from './wasm/isard.js';
|
|
3
|
+
export declare abstract class Layer {
|
|
4
|
+
#private;
|
|
5
|
+
/** Id of the layer in the engine */
|
|
6
|
+
get id(): number;
|
|
7
|
+
remove(): void;
|
|
8
|
+
/** Moves the layer under `other`, or on top of every layer (above the labels of a vector basemap too) */
|
|
9
|
+
moveBefore(other: Layer | undefined): void;
|
|
10
|
+
}
|
|
11
|
+
/** Raster tiles (basemaps, overlays) */
|
|
12
|
+
export declare class RasterLayer extends Layer {
|
|
13
|
+
private readonly options;
|
|
14
|
+
setStyle(opacity: number, saturation?: number): void;
|
|
15
|
+
}
|
|
16
|
+
/** Line network colored by a value per node (travel time, water depth…) */
|
|
17
|
+
export declare class NetworkLayer extends Layer {
|
|
18
|
+
private geometry;
|
|
19
|
+
private costs;
|
|
20
|
+
private style;
|
|
21
|
+
private edgeWidths;
|
|
22
|
+
private values;
|
|
23
|
+
/** Node and line positions in Mercator, kept for a context loss: f32 offsets from an origin (exact to the centimetre
|
|
24
|
+
* across a region, half the memory of f64) */
|
|
25
|
+
private kept;
|
|
26
|
+
/** One value per node, in node order; negative (or 1e30 and above) = unreached. Costs one texture upload, drawn
|
|
27
|
+
* next frame. */
|
|
28
|
+
setValues(values: Float32Array): void;
|
|
29
|
+
/** Costs of the edges, A → B then B → A (see `NetworkOptions.costs`), when the way the network is travelled
|
|
30
|
+
* changes; undefined: values interpolated along the edges. The geometry stays on the graphics card. */
|
|
31
|
+
setCosts(costs: Float32Array | undefined): void;
|
|
32
|
+
setStyle(style: Partial<NetworkStyle>): void;
|
|
33
|
+
/** Width factor of each edge (undefined: 1 for all) */
|
|
34
|
+
setEdgeWidths(widths: Float32Array | undefined): void;
|
|
35
|
+
/** Pieces in all and drawn in the last frame */
|
|
36
|
+
stats(): NetworkStats | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Textured meshes in 3D Tiles */
|
|
39
|
+
export declare class Tiles3dLayer extends Layer {
|
|
40
|
+
private readonly url;
|
|
41
|
+
private style;
|
|
42
|
+
setStyle(style: Partial<Tiles3dStyle>): void;
|
|
43
|
+
/** Loading counters; undefined once the layer is removed, and while the engine with 3D Tiles loads (see
|
|
44
|
+
* `GeoMapOptions.tiles3d`) */
|
|
45
|
+
stats(): Tiles3dStats | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** A texture of a ground layer (see `GroundLayer`) */
|
|
48
|
+
export interface GroundTexture {
|
|
49
|
+
/** `r8`, `r8ui`, `r16ui`, `r16i`, `r32ui`, `r32f`, `rg16ui`, `rg32f`, `rgba8`, `rgba8ui`, `rgba16ui`, `rgba32f`:
|
|
50
|
+
* integer formats are read with `usampler2D` / `isampler2D` and `texelFetch` */
|
|
51
|
+
format: string;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
/** Layers of a 2D array (`sampler2DArray`, `usampler2DArray`…); 0 or undefined: a 2D texture */
|
|
55
|
+
depth?: number;
|
|
56
|
+
/** Linear filtering (non-integer formats); default nearest */
|
|
57
|
+
linear?: boolean;
|
|
58
|
+
/** Texels row after row (layer after layer), any typed array; undefined: left empty */
|
|
59
|
+
data?: ArrayBufferView;
|
|
60
|
+
}
|
|
61
|
+
export interface GroundOptions {
|
|
62
|
+
/**
|
|
63
|
+
* GLSL ES 3.0 defining `vec4 ground(vec2 offset)`: the color of the ground (premultiplied alpha, 0 for nothing) at
|
|
64
|
+
* `offset`, the Mercator offset of the pixel from `anchor` (times `ISARD_EARTH`: metres of EPSG:3857), exact to
|
|
65
|
+
* the millimetre at any zoom. The code declares its own uniforms and samplers; also there: `u_zoom`,
|
|
66
|
+
* `u_pixel_ratio`
|
|
67
|
+
*/
|
|
68
|
+
code: string;
|
|
69
|
+
/** Where the offsets are taken from */
|
|
70
|
+
anchor: LonLat;
|
|
71
|
+
/** Box it shows in, `[west, south, east, north]` (default the world): only the ground there is drawn again */
|
|
72
|
+
bounds?: [west: number, south: number, east: number, north: number];
|
|
73
|
+
textures?: Record<string, GroundTexture>;
|
|
74
|
+
uniforms?: Record<string, number | readonly number[]>;
|
|
75
|
+
}
|
|
76
|
+
/** The application's fragment shader run over the ground, draped on the relief (water depth, heat maps…) */
|
|
77
|
+
export declare class GroundLayer extends Layer {
|
|
78
|
+
private readonly options;
|
|
79
|
+
private readonly textures;
|
|
80
|
+
private readonly uniforms;
|
|
81
|
+
private visible;
|
|
82
|
+
/** Creates or replaces a texture of the code's sampler `name` */
|
|
83
|
+
setTexture(name: string, texture: GroundTexture): void;
|
|
84
|
+
/**
|
|
85
|
+
* Replaces a region of a texture: `data` holds `width` × `height` texels for (`x`, `y`) of `layer` (2D arrays).
|
|
86
|
+
* Keep the texture's own `data` up to date too if it must survive a WebGL context loss.
|
|
87
|
+
*/
|
|
88
|
+
updateTexture(name: string, data: ArrayBufferView, region: {
|
|
89
|
+
x?: number;
|
|
90
|
+
y?: number;
|
|
91
|
+
layer?: number;
|
|
92
|
+
width: number;
|
|
93
|
+
height: number;
|
|
94
|
+
}): void;
|
|
95
|
+
/** A uniform of the code: a number, or numbers for vectors, arrays and matrices (column major) */
|
|
96
|
+
setUniform(name: string, value: number | readonly number[]): void;
|
|
97
|
+
setBounds(bounds: [west: number, south: number, east: number, north: number]): void;
|
|
98
|
+
setVisible(visible: boolean): void;
|
|
99
|
+
private sendTexture;
|
|
100
|
+
}
|