@bitbaum/geo-kit 0.1.1 → 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/README.md +57 -9
- package/dist/map/camera.d.ts +86 -0
- package/dist/map/camera.js +118 -0
- package/dist/map/controller.d.ts +72 -0
- package/dist/map/controller.js +344 -0
- package/dist/map/cull.d.ts +28 -0
- package/dist/map/cull.js +46 -0
- package/dist/map/index.d.ts +17 -0
- package/dist/map/index.js +17 -0
- package/dist/map/motion.d.ts +29 -0
- package/dist/map/motion.js +58 -0
- package/dist/map/paint.d.ts +66 -0
- package/dist/map/paint.js +174 -0
- package/dist/map/pick.d.ts +17 -0
- package/dist/map/pick.js +34 -0
- package/dist/map/shapes.d.ts +59 -0
- package/dist/map/shapes.js +105 -0
- package/dist/map/trace.d.ts +37 -0
- package/dist/map/trace.js +73 -0
- package/package.json +39 -6
package/README.md
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
1
|
# `@bitbaum/geo-kit`
|
|
2
2
|
|
|
3
3
|
Country-neutral contracts for sharing versioned geography data across
|
|
4
|
-
applications
|
|
5
|
-
|
|
4
|
+
applications, and one renderer for it (`@bitbaum/geo-kit/map`). The package
|
|
5
|
+
owns no country packs, political claims, source choices, personal data, HTTP
|
|
6
|
+
server, or UI framework.
|
|
6
7
|
|
|
7
8
|
## Install
|
|
8
9
|
|
|
9
|
-
The Git repository is the install source until npm publishing is bootstrapped.
|
|
10
|
-
Pin the reviewed v0.1.1 source commit for a reproducible install:
|
|
11
|
-
|
|
12
10
|
```sh
|
|
13
|
-
pnpm add
|
|
11
|
+
pnpm add @bitbaum/geo-kit
|
|
14
12
|
```
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
Install releases from npm, not a commit: a semver range is what lets a fix
|
|
15
|
+
reach every product. `@bitbaum/geo-kit@0.1.1` is on npm; later releases are
|
|
16
|
+
published by this repository's `publish.yml`.
|
|
19
17
|
|
|
20
18
|
## Ownership
|
|
21
19
|
|
|
@@ -86,6 +84,56 @@ The library never reads the system clock. Pass the date displayed by the app to
|
|
|
86
84
|
support historical maps deterministically. An explicit viewpoint is opt-in;
|
|
87
85
|
the library does not treat any boundary representation as the default truth.
|
|
88
86
|
|
|
87
|
+
## Drawing it: `@bitbaum/geo-kit/map`
|
|
88
|
+
|
|
89
|
+
One map for every product: an orthographic globe on Canvas 2D (`d3-geo`).
|
|
90
|
+
Zoomed far in, the globe is a flat map of the place in front of the reader, so
|
|
91
|
+
the world, a country and a commune are one projection and one renderer.
|
|
92
|
+
Canvas 2D rather than WebGL: a few thousand path segments a frame fit a
|
|
93
|
+
phone's frame budget, and it runs wherever the page runs.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
pnpm add @bitbaum/geo-kit d3-geo topojson-client
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`d3-geo` (and `topojson-client`, for TopoJSON) are optional peer
|
|
100
|
+
dependencies: a product that only loads data does not install them.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { cameraForBounds, createMap, shapesFromTopology } from "@bitbaum/geo-kit/map";
|
|
104
|
+
|
|
105
|
+
const shapes = shapesFromTopology(topology, { object: "communes", idOf: (f) => String(f.id) });
|
|
106
|
+
const limits = { minZoom: 1, maxZoom: 2000, maxTilt: 80 };
|
|
107
|
+
const map = createMap(canvas, {
|
|
108
|
+
shapes,
|
|
109
|
+
palette: { sphere: "#fff", land: "#eee", border: "#999", hover: "#555", selected: "#000" },
|
|
110
|
+
fills: new Map([["261", "#08519c"]]),
|
|
111
|
+
limits,
|
|
112
|
+
onSelect: (id) => console.log(id),
|
|
113
|
+
});
|
|
114
|
+
map.resize(390, 600);
|
|
115
|
+
map.flyTo(cameraForBounds([5.96, 45.82, 10.49, 47.81], limits));
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
- **Framework-free.** `createMap` takes a `<canvas>` and returns methods
|
|
119
|
+
(`resize`, `setFills`, `setSelected`, `flyTo`, `zoomBy`, `destroy`, …); a
|
|
120
|
+
React or other page wraps it in a few lines and keeps no map state of its own.
|
|
121
|
+
- **Input.** One finger or the mouse turns it, two pinch, the wheel zooms, a
|
|
122
|
+
tap selects, a flick coasts. The keyboard treats the canvas as one control:
|
|
123
|
+
arrows turn, + and − zoom, Home resets, Enter selects what faces the reader.
|
|
124
|
+
- **Cost.** It draws only when something changes; in motion it paints the
|
|
125
|
+
coarse shapes (every third vertex of each shared arc, so neighbours still
|
|
126
|
+
meet), and full detail the frame after. Features on the far side, or off the
|
|
127
|
+
canvas when zoomed, are not drawn.
|
|
128
|
+
- **Reduced motion.** Under `prefers-reduced-motion` a flight is a cut, a flick
|
|
129
|
+
does not coast and the idle turn never starts.
|
|
130
|
+
- **Layers.** The palette's sphere, graticule, glow and limb shade are each
|
|
131
|
+
optional, so a lit globe of the world and a plain map where the data is the
|
|
132
|
+
picture are the same painter. `over` draws the caller's own layer (sites,
|
|
133
|
+
routes, figures) on top each frame, with the frame's projection.
|
|
134
|
+
- **Pure parts.** The camera, motion, culling, picking and the fast
|
|
135
|
+
projection are exported and tested without a browser.
|
|
136
|
+
|
|
89
137
|
## Assertions and proposed boundaries
|
|
90
138
|
|
|
91
139
|
`AreaAssertion` follows Solon's sourced `claims`, `administers`, and `proposes`
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the map looks, kept pure so it can be tested without a browser: where
|
|
3
|
+
* the disc sits in the visible part of the canvas, the projection for a view,
|
|
4
|
+
* and where to look to face a feature and how close.
|
|
5
|
+
*
|
|
6
|
+
* Angles are degrees. A view is the point of the Earth facing the reader
|
|
7
|
+
* (`center`, [lon, lat]) and a zoom factor `k` on top of the resting radius,
|
|
8
|
+
* so a sheet rising or a panel opening only moves and resizes the disc; it
|
|
9
|
+
* never changes what the reader is looking at. Zoomed far in, the
|
|
10
|
+
* orthographic globe is a flat map of the place in front of the reader, so
|
|
11
|
+
* one projection serves the world and a commune.
|
|
12
|
+
*/
|
|
13
|
+
import { type GeoProjection } from "d3-geo";
|
|
14
|
+
import type { Geometry, Position } from "geojson";
|
|
15
|
+
/** The part of the canvas nothing floats over, in CSS pixels. */
|
|
16
|
+
export interface View {
|
|
17
|
+
x0: number;
|
|
18
|
+
y0: number;
|
|
19
|
+
x1: number;
|
|
20
|
+
y1: number;
|
|
21
|
+
}
|
|
22
|
+
export interface Camera {
|
|
23
|
+
/** The point of the Earth at the centre of the disc, [lon, lat]. */
|
|
24
|
+
center: [number, number];
|
|
25
|
+
/** Zoom on top of the resting radius; 1 is the whole globe in view. */
|
|
26
|
+
k: number;
|
|
27
|
+
}
|
|
28
|
+
/** How far the reader may zoom and tilt. */
|
|
29
|
+
export interface Limits {
|
|
30
|
+
minZoom: number;
|
|
31
|
+
maxZoom: number;
|
|
32
|
+
/** How far north or south the reader can tilt: past this the poles spin. */
|
|
33
|
+
maxTilt: number;
|
|
34
|
+
}
|
|
35
|
+
/** A world: the whole globe at rest, a country at most. */
|
|
36
|
+
export declare const WORLD_LIMITS: Limits;
|
|
37
|
+
/** Centre and resting radius of the globe in the visible part of the canvas. */
|
|
38
|
+
export declare function disc(view: View): {
|
|
39
|
+
cx: number;
|
|
40
|
+
cy: number;
|
|
41
|
+
radius: number;
|
|
42
|
+
short: number;
|
|
43
|
+
};
|
|
44
|
+
export declare const clampZoom: (k: number, limits?: Limits) => number;
|
|
45
|
+
export declare const clampTilt: (lat: number, limits?: Limits) => number;
|
|
46
|
+
/** Longitude folded into (-180, 180]. */
|
|
47
|
+
export declare function wrapLon(lon: number): number;
|
|
48
|
+
/** The d3 rotation that turns `center` to face the reader. */
|
|
49
|
+
export declare function rotationOf(center: [number, number]): [number, number, number];
|
|
50
|
+
export declare function projectionFor(view: View, camera: Camera): GeoProjection;
|
|
51
|
+
/**
|
|
52
|
+
* The part of a feature to frame: its largest pieces, up to three quarters of
|
|
53
|
+
* its area. The whole outline is wrong for the features that matter most: the
|
|
54
|
+
* United States would be framed with Alaska and Hawaii (a view of the Pacific),
|
|
55
|
+
* France with Guiana, Norway with Svalbard.
|
|
56
|
+
*/
|
|
57
|
+
export declare function mainland(geometry: Geometry): Position[][][];
|
|
58
|
+
export interface Focus {
|
|
59
|
+
/** [lon, lat] to turn to the reader. */
|
|
60
|
+
center: [number, number];
|
|
61
|
+
/** Degrees from the centre to the farthest point of the part framed. */
|
|
62
|
+
reach: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Where to look to face a feature, and how much of the sphere it spans.
|
|
66
|
+
* Spherical throughout (geoCentroid, geoDistance), so Russia, Fiji and the
|
|
67
|
+
* United States, which cross the antimeridian, need no special case.
|
|
68
|
+
*/
|
|
69
|
+
export declare function focusOf(geometry: Geometry): Focus | null;
|
|
70
|
+
/**
|
|
71
|
+
* The zoom that fits a feature of angular `reach` in the disc. A point `r°`
|
|
72
|
+
* from the centre of an orthographic view lies `R·sin r` from its middle, so
|
|
73
|
+
* the feature fills `sin(reach)` of the radius at k = 1. Small features stop
|
|
74
|
+
* at `most` rather than filling the screen with one island.
|
|
75
|
+
*/
|
|
76
|
+
export declare function zoomFor(reach: number, { fill, most }?: {
|
|
77
|
+
fill?: number | undefined;
|
|
78
|
+
most?: number | undefined;
|
|
79
|
+
}): number;
|
|
80
|
+
/** The camera that faces a feature, or null when it has no area. */
|
|
81
|
+
export declare function cameraFor(geometry: Geometry, limits?: Limits, zoom?: {
|
|
82
|
+
fill?: number;
|
|
83
|
+
most?: number;
|
|
84
|
+
}): Camera | null;
|
|
85
|
+
/** The camera that faces a lon/lat box [west, south, east, north]. */
|
|
86
|
+
export declare function cameraForBounds([w, s, e, n]: [number, number, number, number], limits?: Limits, fill?: number): Camera;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the map looks, kept pure so it can be tested without a browser: where
|
|
3
|
+
* the disc sits in the visible part of the canvas, the projection for a view,
|
|
4
|
+
* and where to look to face a feature and how close.
|
|
5
|
+
*
|
|
6
|
+
* Angles are degrees. A view is the point of the Earth facing the reader
|
|
7
|
+
* (`center`, [lon, lat]) and a zoom factor `k` on top of the resting radius,
|
|
8
|
+
* so a sheet rising or a panel opening only moves and resizes the disc; it
|
|
9
|
+
* never changes what the reader is looking at. Zoomed far in, the
|
|
10
|
+
* orthographic globe is a flat map of the place in front of the reader, so
|
|
11
|
+
* one projection serves the world and a commune.
|
|
12
|
+
*/
|
|
13
|
+
import { geoArea, geoCentroid, geoDistance, geoOrthographic } from "d3-geo";
|
|
14
|
+
import { polygonsOf } from "./shapes.js";
|
|
15
|
+
/** A world: the whole globe at rest, a country at most. */
|
|
16
|
+
export const WORLD_LIMITS = { minZoom: 0.7, maxZoom: 14, maxTilt: 75 };
|
|
17
|
+
/** Centre and resting radius of the globe in the visible part of the canvas. */
|
|
18
|
+
export function disc(view) {
|
|
19
|
+
const w = Math.max(view.x1 - view.x0, 1);
|
|
20
|
+
const h = Math.max(view.y1 - view.y0, 1);
|
|
21
|
+
// Room around the limb for the atmosphere; a phone's strip above a sheet is
|
|
22
|
+
// short and wide, so the height decides there.
|
|
23
|
+
const radius = Math.max(Math.min(w, h) / 2 - Math.min(28, Math.min(w, h) * 0.08), 8);
|
|
24
|
+
return { cx: view.x0 + w / 2, cy: view.y0 + h / 2, radius, short: Math.min(w, h) };
|
|
25
|
+
}
|
|
26
|
+
export const clampZoom = (k, limits = WORLD_LIMITS) => Math.min(limits.maxZoom, Math.max(limits.minZoom, k));
|
|
27
|
+
export const clampTilt = (lat, limits = WORLD_LIMITS) => Math.min(limits.maxTilt, Math.max(-limits.maxTilt, lat));
|
|
28
|
+
/** Longitude folded into (-180, 180]. */
|
|
29
|
+
export function wrapLon(lon) {
|
|
30
|
+
const x = (((lon + 180) % 360) + 360) % 360;
|
|
31
|
+
return x === 0 ? 180 : x - 180;
|
|
32
|
+
}
|
|
33
|
+
/** The d3 rotation that turns `center` to face the reader. */
|
|
34
|
+
export function rotationOf(center) {
|
|
35
|
+
return [-center[0], -center[1], 0];
|
|
36
|
+
}
|
|
37
|
+
export function projectionFor(view, camera) {
|
|
38
|
+
const d = disc(view);
|
|
39
|
+
return (geoOrthographic()
|
|
40
|
+
.clipAngle(90)
|
|
41
|
+
// No adaptive resampling: boundary vertices are already closer than it would add.
|
|
42
|
+
.precision(0)
|
|
43
|
+
.translate([d.cx, d.cy])
|
|
44
|
+
.scale(d.radius * camera.k)
|
|
45
|
+
.rotate(rotationOf(camera.center)));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The part of a feature to frame: its largest pieces, up to three quarters of
|
|
49
|
+
* its area. The whole outline is wrong for the features that matter most: the
|
|
50
|
+
* United States would be framed with Alaska and Hawaii (a view of the Pacific),
|
|
51
|
+
* France with Guiana, Norway with Svalbard.
|
|
52
|
+
*/
|
|
53
|
+
export function mainland(geometry) {
|
|
54
|
+
const parts = polygonsOf(geometry)
|
|
55
|
+
.map((coordinates) => ({ coordinates, area: geoArea({ type: "Polygon", coordinates }) }))
|
|
56
|
+
.sort((a, b) => b.area - a.area);
|
|
57
|
+
const total = parts.reduce((s, p) => s + p.area, 0);
|
|
58
|
+
const kept = [];
|
|
59
|
+
let sum = 0;
|
|
60
|
+
for (const p of parts) {
|
|
61
|
+
kept.push(p.coordinates);
|
|
62
|
+
sum += p.area;
|
|
63
|
+
if (sum >= total * 0.75)
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return kept;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Where to look to face a feature, and how much of the sphere it spans.
|
|
70
|
+
* Spherical throughout (geoCentroid, geoDistance), so Russia, Fiji and the
|
|
71
|
+
* United States, which cross the antimeridian, need no special case.
|
|
72
|
+
*/
|
|
73
|
+
export function focusOf(geometry) {
|
|
74
|
+
const parts = mainland(geometry);
|
|
75
|
+
if (parts.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const [lon, lat] = geoCentroid({ type: "MultiPolygon", coordinates: parts });
|
|
78
|
+
const center = [lon, lat];
|
|
79
|
+
let reach = 0;
|
|
80
|
+
for (const polygon of parts)
|
|
81
|
+
for (const point of polygon[0] ?? [])
|
|
82
|
+
reach = Math.max(reach, geoDistance(center, [point[0] ?? 0, point[1] ?? 0]));
|
|
83
|
+
return { center, reach: (reach * 180) / Math.PI };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The zoom that fits a feature of angular `reach` in the disc. A point `r°`
|
|
87
|
+
* from the centre of an orthographic view lies `R·sin r` from its middle, so
|
|
88
|
+
* the feature fills `sin(reach)` of the radius at k = 1. Small features stop
|
|
89
|
+
* at `most` rather than filling the screen with one island.
|
|
90
|
+
*/
|
|
91
|
+
export function zoomFor(reach, { fill = 0.6, most = 7 } = {}) {
|
|
92
|
+
const r = Math.min(Math.max(reach, 0.0001), 90);
|
|
93
|
+
const k = fill / Math.sin((r * Math.PI) / 180);
|
|
94
|
+
return Math.min(Math.max(k, 1), most);
|
|
95
|
+
}
|
|
96
|
+
/** The camera that faces a feature, or null when it has no area. */
|
|
97
|
+
export function cameraFor(geometry, limits = WORLD_LIMITS, zoom = {}) {
|
|
98
|
+
const focus = focusOf(geometry);
|
|
99
|
+
if (!focus)
|
|
100
|
+
return null;
|
|
101
|
+
const k = zoomFor(focus.reach, { most: Math.min(7, limits.maxZoom), ...zoom });
|
|
102
|
+
return { center: [focus.center[0], clampTilt(focus.center[1], limits)], k: clampZoom(k, limits) };
|
|
103
|
+
}
|
|
104
|
+
/** The camera that faces a lon/lat box [west, south, east, north]. */
|
|
105
|
+
export function cameraForBounds([w, s, e, n], limits = WORLD_LIMITS, fill = 0.8) {
|
|
106
|
+
const east = e < w ? e + 360 : e;
|
|
107
|
+
const center = [wrapLon((w + east) / 2), (s + n) / 2];
|
|
108
|
+
const reach = Math.max(...[
|
|
109
|
+
[w, s],
|
|
110
|
+
[w, n],
|
|
111
|
+
[east, s],
|
|
112
|
+
[east, n],
|
|
113
|
+
].map((p) => (geoDistance(center, [p[0] ?? 0, p[1] ?? 0]) * 180) / Math.PI));
|
|
114
|
+
return {
|
|
115
|
+
center: [center[0], clampTilt(center[1], limits)],
|
|
116
|
+
k: clampZoom(zoomFor(reach, { fill, most: limits.maxZoom }), limits),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A map on one <canvas>, without a framework: hand it the canvas, the shapes
|
|
3
|
+
* and a palette, and it draws, turns, zooms and picks. One finger (or the
|
|
4
|
+
* mouse) turns it, two pinch, the wheel zooms, a tap selects the feature under
|
|
5
|
+
* it, a flick keeps it turning. The keyboard treats the canvas as one control:
|
|
6
|
+
* arrows turn it (Shift for larger steps), + and − zoom, Home resets, Enter
|
|
7
|
+
* selects the feature facing the reader.
|
|
8
|
+
*
|
|
9
|
+
* It draws only when something changes, so idle it costs nothing; while it
|
|
10
|
+
* moves it paints the coarse shapes, and full detail the frame after. Under
|
|
11
|
+
* prefers-reduced-motion a flight is a cut, a flick does not coast and the
|
|
12
|
+
* idle turn never starts. A page wraps it in whatever its framework is: it
|
|
13
|
+
* keeps no framework state of its own.
|
|
14
|
+
*/
|
|
15
|
+
import type { GeoProjection } from "d3-geo";
|
|
16
|
+
import { type Camera, type Limits, type View } from "./camera.ts";
|
|
17
|
+
import { type Fill, type Palette } from "./paint.ts";
|
|
18
|
+
import type { MapShapes } from "./shapes.ts";
|
|
19
|
+
import type { Basis } from "./trace.ts";
|
|
20
|
+
/** What a frame tells the caller's overlay. */
|
|
21
|
+
export interface FrameInfo {
|
|
22
|
+
now: number;
|
|
23
|
+
/** How far the current flight has come (0–1), or null when none. */
|
|
24
|
+
flight: number | null;
|
|
25
|
+
/** True while the map moves and paints its coarse shapes. */
|
|
26
|
+
moving: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface MapOptions<P = Record<string, unknown>> {
|
|
29
|
+
shapes: MapShapes<P>;
|
|
30
|
+
palette: Palette;
|
|
31
|
+
/** A colour per feature id; a feature without one is painted as land. */
|
|
32
|
+
fills?: ReadonlyMap<string, Fill>;
|
|
33
|
+
limits?: Limits;
|
|
34
|
+
/** Where the map rests and where Home returns to. */
|
|
35
|
+
home?: Camera;
|
|
36
|
+
/** How close a selected feature is framed: the share of the disc it fills, and the closest zoom (default 7). */
|
|
37
|
+
focusZoom?: {
|
|
38
|
+
fill?: number;
|
|
39
|
+
most?: number;
|
|
40
|
+
};
|
|
41
|
+
/** Turn slowly after a few seconds of rest, with nothing selected and zoomed out. */
|
|
42
|
+
idleTurn?: boolean;
|
|
43
|
+
/** A feature was tapped or chosen by keyboard; null for the sea. */
|
|
44
|
+
onSelect?: (id: string | null) => void;
|
|
45
|
+
/** The mouse is over a feature (null when over none), at a canvas point. */
|
|
46
|
+
onHover?: (id: string | null, x: number, y: number) => void;
|
|
47
|
+
/** Draw on top of the map each frame. */
|
|
48
|
+
over?: (ctx: CanvasRenderingContext2D, basis: Basis, projection: GeoProjection, frame: FrameInfo) => void;
|
|
49
|
+
/** True while the overlay animates and needs another frame. */
|
|
50
|
+
animating?: (now: number) => boolean;
|
|
51
|
+
/** True while the overlay wants the map held still (no idle turn). */
|
|
52
|
+
holdStill?: () => boolean;
|
|
53
|
+
}
|
|
54
|
+
export interface MapController {
|
|
55
|
+
/** Size the canvas, in CSS pixels, and the part of it nothing floats over. */
|
|
56
|
+
resize(width: number, height: number, view?: View): void;
|
|
57
|
+
setShapes<P>(shapes: MapShapes<P>): void;
|
|
58
|
+
setFills(fills: ReadonlyMap<string, Fill>): void;
|
|
59
|
+
setPalette(palette: Palette): void;
|
|
60
|
+
/** Mark a feature selected; `fly` turns the map to face it. */
|
|
61
|
+
setSelected(id: string | null, options?: {
|
|
62
|
+
fly?: boolean;
|
|
63
|
+
}): void;
|
|
64
|
+
flyTo(camera: Camera, duration?: number): void;
|
|
65
|
+
zoomBy(factor: number | "home"): void;
|
|
66
|
+
camera(): Camera;
|
|
67
|
+
projection(): GeoProjection | null;
|
|
68
|
+
/** Ask for a frame (the caller's overlay changed). */
|
|
69
|
+
invalidate(): void;
|
|
70
|
+
destroy(): void;
|
|
71
|
+
}
|
|
72
|
+
export declare function createMap<P>(canvas: HTMLCanvasElement, options: MapOptions<P>): MapController;
|