@motionscript/geo 0.0.0-stage → 0.1.0-alpha.3
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 +21 -0
- package/LICENSE +201 -0
- package/dist/border/format.d.ts +73 -0
- package/dist/border/format.d.ts.map +1 -0
- package/dist/border/format.js +68 -0
- package/dist/border/format.js.map +1 -0
- package/dist/border/geo-border.d.ts +44 -0
- package/dist/border/geo-border.d.ts.map +1 -0
- package/dist/border/geo-border.js +234 -0
- package/dist/border/geo-border.js.map +1 -0
- package/dist/border/geometry.d.ts +40 -0
- package/dist/border/geometry.d.ts.map +1 -0
- package/dist/border/geometry.js +382 -0
- package/dist/border/geometry.js.map +1 -0
- package/dist/border/index.d.ts +15 -0
- package/dist/border/index.d.ts.map +1 -0
- package/dist/border/index.js +15 -0
- package/dist/border/index.js.map +1 -0
- package/dist/border/loader.d.ts +31 -0
- package/dist/border/loader.d.ts.map +1 -0
- package/dist/border/loader.js +87 -0
- package/dist/border/loader.js.map +1 -0
- package/dist/border/registry.d.ts +64 -0
- package/dist/border/registry.d.ts.map +1 -0
- package/dist/border/registry.js +152 -0
- package/dist/border/registry.js.map +1 -0
- package/dist/border/selection.d.ts +21 -0
- package/dist/border/selection.d.ts.map +1 -0
- package/dist/border/selection.js +86 -0
- package/dist/border/selection.js.map +1 -0
- package/dist/border/simplify.d.ts +12 -0
- package/dist/border/simplify.d.ts.map +1 -0
- package/dist/border/simplify.js +116 -0
- package/dist/border/simplify.js.map +1 -0
- package/dist/browser/index.js +25 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/manifest.json +11 -0
- package/dist/globe/atmosphere.d.ts +38 -0
- package/dist/globe/atmosphere.d.ts.map +1 -0
- package/dist/globe/atmosphere.js +91 -0
- package/dist/globe/atmosphere.js.map +1 -0
- package/dist/globe/borders.d.ts +39 -0
- package/dist/globe/borders.d.ts.map +1 -0
- package/dist/globe/borders.js +116 -0
- package/dist/globe/borders.js.map +1 -0
- package/dist/globe/data/ne-110m.d.ts +25 -0
- package/dist/globe/data/ne-110m.d.ts.map +1 -0
- package/dist/globe/data/ne-110m.js +198 -0
- package/dist/globe/data/ne-110m.js.map +1 -0
- package/dist/globe/globe-places.d.ts +140 -0
- package/dist/globe/globe-places.d.ts.map +1 -0
- package/dist/globe/globe-places.js +262 -0
- package/dist/globe/globe-places.js.map +1 -0
- package/dist/globe/globe.d.ts +193 -0
- package/dist/globe/globe.d.ts.map +1 -0
- package/dist/globe/globe.js +435 -0
- package/dist/globe/globe.js.map +1 -0
- package/dist/globe/index.d.ts +17 -0
- package/dist/globe/index.d.ts.map +1 -0
- package/dist/globe/index.js +17 -0
- package/dist/globe/index.js.map +1 -0
- package/dist/globe/places.d.ts +69 -0
- package/dist/globe/places.d.ts.map +1 -0
- package/dist/globe/places.js +94 -0
- package/dist/globe/places.js.map +1 -0
- package/dist/globe/projection.d.ts +182 -0
- package/dist/globe/projection.d.ts.map +1 -0
- package/dist/globe/projection.js +215 -0
- package/dist/globe/projection.js.map +1 -0
- package/dist/globe/world-map.d.ts +83 -0
- package/dist/globe/world-map.d.ts.map +1 -0
- package/dist/globe/world-map.js +169 -0
- package/dist/globe/world-map.js.map +1 -0
- package/dist/globe/world.d.ts +46 -0
- package/dist/globe/world.d.ts.map +1 -0
- package/dist/globe/world.js +71 -0
- package/dist/globe/world.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes.d.ts +19 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +19 -0
- package/dist/nodes.js.map +1 -0
- package/package.json +69 -3
- package/registry.json +31 -0
- package/src/border/format.ts +147 -0
- package/src/border/geo-border.ts +260 -0
- package/src/border/geometry.ts +463 -0
- package/src/border/index.ts +14 -0
- package/src/border/loader.ts +97 -0
- package/src/border/registry.ts +191 -0
- package/src/border/selection.ts +95 -0
- package/src/border/simplify.ts +110 -0
- package/src/globe/atmosphere.ts +98 -0
- package/src/globe/borders.ts +166 -0
- package/src/globe/data/ne-110m.ts +214 -0
- package/src/globe/globe-places.ts +352 -0
- package/src/globe/globe.ts +552 -0
- package/src/globe/index.ts +16 -0
- package/src/globe/places.ts +132 -0
- package/src/globe/projection.ts +261 -0
- package/src/globe/world-map.ts +227 -0
- package/src/globe/world.ts +111 -0
- package/src/index.ts +5 -0
- package/src/nodes.ts +19 -0
- package/README.md +0 -4
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type GeoFeature, type GeoFileData, type GeoIndex } from "./format.js";
|
|
2
|
+
/** Prefix every data key carries; bumped with the file format. */
|
|
3
|
+
export declare const GEO_DATA_VERSION = "v1";
|
|
4
|
+
/**
|
|
5
|
+
* Answers a data key — `v1/countries/FR`, `v1/states/US`, `v1/index` — with the
|
|
6
|
+
* parsed file, or `null` when this source does not have it. A throw means "could
|
|
7
|
+
* not tell" (offline, a 500) and is retried on the next load.
|
|
8
|
+
*/
|
|
9
|
+
export type GeoSource = (key: string) => Promise<unknown>;
|
|
10
|
+
type Position = readonly [number, number] | readonly number[];
|
|
11
|
+
type GeoPolygonShape = {
|
|
12
|
+
type: "Polygon";
|
|
13
|
+
coordinates: readonly (readonly Position[])[];
|
|
14
|
+
} | {
|
|
15
|
+
type: "MultiPolygon";
|
|
16
|
+
coordinates: readonly (readonly (readonly Position[])[])[];
|
|
17
|
+
};
|
|
18
|
+
/** GeoJSON in lon/lat: a Polygon, a MultiPolygon, or a Feature holding one. */
|
|
19
|
+
export type GeoShape = GeoPolygonShape | {
|
|
20
|
+
type: "Feature";
|
|
21
|
+
geometry: GeoPolygonShape;
|
|
22
|
+
properties?: {
|
|
23
|
+
name?: string;
|
|
24
|
+
} | null;
|
|
25
|
+
};
|
|
26
|
+
export interface GeoDefinition {
|
|
27
|
+
shape: GeoShape;
|
|
28
|
+
name?: string;
|
|
29
|
+
/** The country a subdivision code expands under, when it has one. */
|
|
30
|
+
parent?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Fetches `<base>/<key>.json`; `base` may instead map a key to its URL. */
|
|
33
|
+
export declare function geoUrlSource(base: string | ((key: string) => string), init?: RequestInit): GeoSource;
|
|
34
|
+
/** Wraps a function returning the file as text, bytes or parsed JSON (e.g. `fs.readFile`). */
|
|
35
|
+
export declare function geoLoaderSource(load: (key: string) => Promise<string | ArrayBuffer | Uint8Array | object | null | undefined>): GeoSource;
|
|
36
|
+
declare class GeoRegistry {
|
|
37
|
+
private _sources;
|
|
38
|
+
private _defined;
|
|
39
|
+
private _generation;
|
|
40
|
+
private _serial;
|
|
41
|
+
/** Bumps whenever a source or a definition is added or removed. */
|
|
42
|
+
get generation(): number;
|
|
43
|
+
get hasSources(): boolean;
|
|
44
|
+
/** Adds a source after every source already registered. Returns its removal. */
|
|
45
|
+
use(source: GeoSource): () => void;
|
|
46
|
+
/**
|
|
47
|
+
* Shapes (GeoJSON, lon/lat) under codes of your choosing, checked before any
|
|
48
|
+
* source. A definition replaces generated data for the same code.
|
|
49
|
+
*/
|
|
50
|
+
define(shapes: Record<string, GeoShape | GeoDefinition>): () => void;
|
|
51
|
+
defined(code: string): GeoFeature | undefined;
|
|
52
|
+
/** Removes every source and definition — for tests. */
|
|
53
|
+
reset(): void;
|
|
54
|
+
/**
|
|
55
|
+
* Asks each source in registration order; the first non-null answer wins.
|
|
56
|
+
* Resolves `null` only when every source answered that it has no such file.
|
|
57
|
+
*/
|
|
58
|
+
resolve(key: string): Promise<GeoFileData | GeoIndex | null>;
|
|
59
|
+
/** Every listed region's code, names and parent, for a picker. */
|
|
60
|
+
index(): Promise<GeoIndex | null>;
|
|
61
|
+
}
|
|
62
|
+
export declare const geoRegistry: GeoRegistry;
|
|
63
|
+
export {};
|
|
64
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/border/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,QAAQ,EAGhB,MAAM,UAAU,CAAA;AAGjB,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,OAAwB,CAAA;AAErD;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAEzD,KAAK,QAAQ,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAA;AAC7D,KAAK,eAAe,GACd;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,WAAW,EAAE,SAAS,CAAC,SAAS,QAAQ,EAAE,CAAC,EAAE,CAAA;CAAE,GAClE;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,WAAW,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,QAAQ,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;CAAE,CAAA;AAC1F,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,EAAE,eAAe,CAAC;IAAC,UAAU,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,CAAA;AAE9H,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,QAAQ,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,SAAS,CASpG;AAED,8FAA8F;AAC9F,wBAAgB,eAAe,CAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GAC9F,SAAS,CAUX;AA2CD,cAAM,WAAW;IACb,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAA6D;IAC7E,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,OAAO,CAAI;IAEnB,mEAAmE;IACnE,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,gFAAgF;IAChF,GAAG,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,IAAI;IAWlC;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,aAAa,CAAC,GAAG,MAAM,IAAI;IAiBpE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAI7C,uDAAuD;IACvD,KAAK,IAAI,IAAI;IAMb;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,QAAQ,GAAG,IAAI,CAAC;IAiBlE,kEAAkE;IAC5D,KAAK,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;CAG1C;AAED,eAAO,MAAM,WAAW,aAAoB,CAAA"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { geoArea } from "d3-geo";
|
|
2
|
+
import { GEO_DATA_FORMAT, } from "./format.js";
|
|
3
|
+
import { ringBuckets } from "./simplify.js";
|
|
4
|
+
/** Prefix every data key carries; bumped with the file format. */
|
|
5
|
+
export const GEO_DATA_VERSION = `v${GEO_DATA_FORMAT}`;
|
|
6
|
+
/** Fetches `<base>/<key>.json`; `base` may instead map a key to its URL. */
|
|
7
|
+
export function geoUrlSource(base, init) {
|
|
8
|
+
return async (key) => {
|
|
9
|
+
const url = typeof base === "function" ? base(key) : `${base.replace(/\/+$/, "")}/${key}.json`;
|
|
10
|
+
const response = await fetch(url, init);
|
|
11
|
+
// Supabase Storage answers a missing object with 400, not 404.
|
|
12
|
+
if (response.status === 404 || response.status === 400)
|
|
13
|
+
return null;
|
|
14
|
+
if (!response.ok)
|
|
15
|
+
throw new Error(`${url} → HTTP ${response.status}`);
|
|
16
|
+
return response.json();
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Wraps a function returning the file as text, bytes or parsed JSON (e.g. `fs.readFile`). */
|
|
20
|
+
export function geoLoaderSource(load) {
|
|
21
|
+
return async (key) => {
|
|
22
|
+
const value = await load(key);
|
|
23
|
+
if (value == null)
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof value === "string")
|
|
26
|
+
return JSON.parse(value);
|
|
27
|
+
if (value instanceof ArrayBuffer || value instanceof Uint8Array) {
|
|
28
|
+
return JSON.parse(new TextDecoder().decode(value));
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function definedFeature(code, definition, serial) {
|
|
34
|
+
const shape = definition.shape.type === "Feature" ? definition.shape.geometry : definition.shape;
|
|
35
|
+
const feature = definition.shape.type === "Feature" ? definition.shape : null;
|
|
36
|
+
const polygons = shape.type === "Polygon" ? [shape.coordinates] : shape.coordinates;
|
|
37
|
+
let arcIndex = 0;
|
|
38
|
+
const toRing = (ring) => {
|
|
39
|
+
const closed = ring.length > 0 && ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1];
|
|
40
|
+
const coordinates = closed ? ring : [...ring, ring[0]];
|
|
41
|
+
const points = new Float64Array(coordinates.length * 2);
|
|
42
|
+
coordinates.forEach(([lon, lat], i) => {
|
|
43
|
+
points[i * 2] = lon;
|
|
44
|
+
points[i * 2 + 1] = lat;
|
|
45
|
+
});
|
|
46
|
+
return { arc: { key: `define:${serial}:${code}:${arcIndex++}`, points, buckets: ringBuckets(points) }, reversed: false };
|
|
47
|
+
};
|
|
48
|
+
const measured = polygons.map((rings) => {
|
|
49
|
+
const resolved = rings.map((ring) => [toRing(ring)]);
|
|
50
|
+
const ringAreas = rings.map((ring) => {
|
|
51
|
+
const area = geoArea({ type: "Polygon", coordinates: [ring] });
|
|
52
|
+
return area > 2 * Math.PI ? 4 * Math.PI - area : area;
|
|
53
|
+
});
|
|
54
|
+
const area = Math.max(0, ringAreas[0] - ringAreas.slice(1).reduce((a, b) => a + b, 0));
|
|
55
|
+
return { rings: resolved, ringAreas, area, mainland: true };
|
|
56
|
+
});
|
|
57
|
+
const name = definition.name ?? feature?.properties?.name ?? code;
|
|
58
|
+
return {
|
|
59
|
+
code,
|
|
60
|
+
name,
|
|
61
|
+
nameNl: name,
|
|
62
|
+
parent: definition.parent ?? (code.includes("-") ? code.slice(0, 2) : null),
|
|
63
|
+
first: true,
|
|
64
|
+
listed: false,
|
|
65
|
+
remote: false,
|
|
66
|
+
polygons: measured,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
class GeoRegistry {
|
|
70
|
+
_sources = [];
|
|
71
|
+
_defined = new Map();
|
|
72
|
+
_generation = 0;
|
|
73
|
+
_serial = 0;
|
|
74
|
+
/** Bumps whenever a source or a definition is added or removed. */
|
|
75
|
+
get generation() {
|
|
76
|
+
return this._generation;
|
|
77
|
+
}
|
|
78
|
+
get hasSources() {
|
|
79
|
+
return this._sources.length > 0;
|
|
80
|
+
}
|
|
81
|
+
/** Adds a source after every source already registered. Returns its removal. */
|
|
82
|
+
use(source) {
|
|
83
|
+
this._sources.push(source);
|
|
84
|
+
this._generation++;
|
|
85
|
+
return () => {
|
|
86
|
+
const i = this._sources.indexOf(source);
|
|
87
|
+
if (i < 0)
|
|
88
|
+
return;
|
|
89
|
+
this._sources.splice(i, 1);
|
|
90
|
+
this._generation++;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Shapes (GeoJSON, lon/lat) under codes of your choosing, checked before any
|
|
95
|
+
* source. A definition replaces generated data for the same code.
|
|
96
|
+
*/
|
|
97
|
+
define(shapes) {
|
|
98
|
+
const serial = ++this._serial;
|
|
99
|
+
const codes = Object.entries(shapes).map(([rawCode, value]) => {
|
|
100
|
+
const code = rawCode.trim().toUpperCase();
|
|
101
|
+
const definition = "shape" in value ? value : { shape: value };
|
|
102
|
+
this._defined.set(code, { serial, feature: definedFeature(code, definition, serial) });
|
|
103
|
+
return code;
|
|
104
|
+
});
|
|
105
|
+
this._generation++;
|
|
106
|
+
return () => {
|
|
107
|
+
for (const code of codes) {
|
|
108
|
+
if (this._defined.get(code)?.serial === serial)
|
|
109
|
+
this._defined.delete(code);
|
|
110
|
+
}
|
|
111
|
+
this._generation++;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
defined(code) {
|
|
115
|
+
return this._defined.get(code)?.feature;
|
|
116
|
+
}
|
|
117
|
+
/** Removes every source and definition — for tests. */
|
|
118
|
+
reset() {
|
|
119
|
+
this._sources = [];
|
|
120
|
+
this._defined.clear();
|
|
121
|
+
this._generation++;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Asks each source in registration order; the first non-null answer wins.
|
|
125
|
+
* Resolves `null` only when every source answered that it has no such file.
|
|
126
|
+
*/
|
|
127
|
+
async resolve(key) {
|
|
128
|
+
if (this._sources.length === 0) {
|
|
129
|
+
throw new Error("No geo source registered — call geoRegistry.use(geoUrlSource(…)) before rendering GeoBorder");
|
|
130
|
+
}
|
|
131
|
+
let failure = null;
|
|
132
|
+
for (const source of [...this._sources]) {
|
|
133
|
+
try {
|
|
134
|
+
const value = await source(key);
|
|
135
|
+
if (value != null)
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
failure ??= error;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (failure)
|
|
143
|
+
throw failure;
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/** Every listed region's code, names and parent, for a picker. */
|
|
147
|
+
async index() {
|
|
148
|
+
return (await this.resolve(`${GEO_DATA_VERSION}/index`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export const geoRegistry = new GeoRegistry();
|
|
152
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/border/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { geoArea } from \"d3-geo\"\n\nimport {\n GEO_DATA_FORMAT,\n type GeoFeature,\n type GeoFileData,\n type GeoIndex,\n type GeoPolygon,\n type GeoRingRef,\n} from \"./format\"\nimport { ringBuckets } from \"./simplify\"\n\n/** Prefix every data key carries; bumped with the file format. */\nexport const GEO_DATA_VERSION = `v${GEO_DATA_FORMAT}`\n\n/**\n * Answers a data key — `v1/countries/FR`, `v1/states/US`, `v1/index` — with the\n * parsed file, or `null` when this source does not have it. A throw means \"could\n * not tell\" (offline, a 500) and is retried on the next load.\n */\nexport type GeoSource = (key: string) => Promise<unknown>\n\ntype Position = readonly [number, number] | readonly number[]\ntype GeoPolygonShape =\n | { type: \"Polygon\"; coordinates: readonly (readonly Position[])[] }\n | { type: \"MultiPolygon\"; coordinates: readonly (readonly (readonly Position[])[])[] }\n/** GeoJSON in lon/lat: a Polygon, a MultiPolygon, or a Feature holding one. */\nexport type GeoShape = GeoPolygonShape | { type: \"Feature\"; geometry: GeoPolygonShape; properties?: { name?: string } | null }\n\nexport interface GeoDefinition {\n shape: GeoShape\n name?: string\n /** The country a subdivision code expands under, when it has one. */\n parent?: string\n}\n\n/** Fetches `<base>/<key>.json`; `base` may instead map a key to its URL. */\nexport function geoUrlSource(base: string | ((key: string) => string), init?: RequestInit): GeoSource {\n return async (key) => {\n const url = typeof base === \"function\" ? base(key) : `${base.replace(/\\/+$/, \"\")}/${key}.json`\n const response = await fetch(url, init)\n // Supabase Storage answers a missing object with 400, not 404.\n if (response.status === 404 || response.status === 400) return null\n if (!response.ok) throw new Error(`${url} → HTTP ${response.status}`)\n return response.json()\n }\n}\n\n/** Wraps a function returning the file as text, bytes or parsed JSON (e.g. `fs.readFile`). */\nexport function geoLoaderSource(\n load: (key: string) => Promise<string | ArrayBuffer | Uint8Array | object | null | undefined>\n): GeoSource {\n return async (key) => {\n const value = await load(key)\n if (value == null) return null\n if (typeof value === \"string\") return JSON.parse(value)\n if (value instanceof ArrayBuffer || value instanceof Uint8Array) {\n return JSON.parse(new TextDecoder().decode(value))\n }\n return value\n }\n}\n\nfunction definedFeature(code: string, definition: GeoDefinition, serial: number): GeoFeature {\n const shape = definition.shape.type === \"Feature\" ? definition.shape.geometry : definition.shape\n const feature = definition.shape.type === \"Feature\" ? definition.shape : null\n const polygons: readonly (readonly (readonly Position[])[])[] =\n shape.type === \"Polygon\" ? [shape.coordinates] : shape.coordinates\n let arcIndex = 0\n\n const toRing = (ring: readonly Position[]): GeoRingRef => {\n const closed = ring.length > 0 && ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1]\n const coordinates = closed ? ring : [...ring, ring[0]]\n const points = new Float64Array(coordinates.length * 2)\n coordinates.forEach(([lon, lat], i) => {\n points[i * 2] = lon\n points[i * 2 + 1] = lat\n })\n return { arc: { key: `define:${serial}:${code}:${arcIndex++}`, points, buckets: ringBuckets(points) }, reversed: false }\n }\n\n const measured: GeoPolygon[] = polygons.map((rings) => {\n const resolved = rings.map((ring) => [toRing(ring)])\n const ringAreas = rings.map((ring) => {\n const area = geoArea({ type: \"Polygon\", coordinates: [ring as [number, number][]] })\n return area > 2 * Math.PI ? 4 * Math.PI - area : area\n })\n const area = Math.max(0, ringAreas[0] - ringAreas.slice(1).reduce((a, b) => a + b, 0))\n return { rings: resolved, ringAreas, area, mainland: true }\n })\n\n const name = definition.name ?? feature?.properties?.name ?? code\n return {\n code,\n name,\n nameNl: name,\n parent: definition.parent ?? (code.includes(\"-\") ? code.slice(0, 2) : null),\n first: true,\n listed: false,\n remote: false,\n polygons: measured,\n }\n}\n\nclass GeoRegistry {\n private _sources: GeoSource[] = []\n private _defined = new Map<string, { serial: number; feature: GeoFeature }>()\n private _generation = 0\n private _serial = 0\n\n /** Bumps whenever a source or a definition is added or removed. */\n get generation(): number {\n return this._generation\n }\n\n get hasSources(): boolean {\n return this._sources.length > 0\n }\n\n /** Adds a source after every source already registered. Returns its removal. */\n use(source: GeoSource): () => void {\n this._sources.push(source)\n this._generation++\n return () => {\n const i = this._sources.indexOf(source)\n if (i < 0) return\n this._sources.splice(i, 1)\n this._generation++\n }\n }\n\n /**\n * Shapes (GeoJSON, lon/lat) under codes of your choosing, checked before any\n * source. A definition replaces generated data for the same code.\n */\n define(shapes: Record<string, GeoShape | GeoDefinition>): () => void {\n const serial = ++this._serial\n const codes = Object.entries(shapes).map(([rawCode, value]) => {\n const code = rawCode.trim().toUpperCase()\n const definition = \"shape\" in value ? value : { shape: value }\n this._defined.set(code, { serial, feature: definedFeature(code, definition, serial) })\n return code\n })\n this._generation++\n return () => {\n for (const code of codes) {\n if (this._defined.get(code)?.serial === serial) this._defined.delete(code)\n }\n this._generation++\n }\n }\n\n defined(code: string): GeoFeature | undefined {\n return this._defined.get(code)?.feature\n }\n\n /** Removes every source and definition — for tests. */\n reset(): void {\n this._sources = []\n this._defined.clear()\n this._generation++\n }\n\n /**\n * Asks each source in registration order; the first non-null answer wins.\n * Resolves `null` only when every source answered that it has no such file.\n */\n async resolve(key: string): Promise<GeoFileData | GeoIndex | null> {\n if (this._sources.length === 0) {\n throw new Error(\"No geo source registered — call geoRegistry.use(geoUrlSource(…)) before rendering GeoBorder\")\n }\n let failure: unknown = null\n for (const source of [...this._sources]) {\n try {\n const value = await source(key)\n if (value != null) return value as GeoFileData | GeoIndex\n } catch (error) {\n failure ??= error\n }\n }\n if (failure) throw failure\n return null\n }\n\n /** Every listed region's code, names and parent, for a picker. */\n async index(): Promise<GeoIndex | null> {\n return (await this.resolve(`${GEO_DATA_VERSION}/index`)) as GeoIndex | null\n }\n}\n\nexport const geoRegistry = new GeoRegistry()\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GeoFeature } from "./format.js";
|
|
2
|
+
export type GeoLevel = "country" | "state";
|
|
3
|
+
export type GeoFrame = "mainland" | "all";
|
|
4
|
+
/** `"fr, de"`, `["FR", "de"]` → `["FR", "DE"]`; unique, in order, empties dropped. */
|
|
5
|
+
export declare function normalizeIso(value: unknown): string[];
|
|
6
|
+
/** Every data file a selection reads, for `declareAssets`. Depends on nothing but its arguments. */
|
|
7
|
+
export declare function geoFilesFor(codes: readonly string[], level: GeoLevel): string[];
|
|
8
|
+
export interface GeoSelection {
|
|
9
|
+
features: GeoFeature[];
|
|
10
|
+
/** A file this selection needs has not landed yet. */
|
|
11
|
+
pending: boolean;
|
|
12
|
+
/** Codes whose file loaded without them. */
|
|
13
|
+
unknown: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The features a selection draws. A bare country code at state level is its
|
|
17
|
+
* first-level subdivisions — minus the remote ones when framing the mainland —
|
|
18
|
+
* or the country itself when it has none.
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectGeoFeatures(codes: readonly string[], level: GeoLevel, frame: GeoFrame): GeoSelection;
|
|
21
|
+
//# sourceMappingURL=selection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../../src/border/selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAI1C,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAA;AAC1C,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAA;AAIzC,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CASrD;AAMD,oGAAoG;AACpG,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,EAAE,CAQ/E;AAED,MAAM,WAAW,YAAY;IACzB,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,sDAAsD;IACtD,OAAO,EAAE,OAAO,CAAA;IAChB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAA;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,YAAY,CA6C1G"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { countryFileKey, geoFileEntry, statesFileKey } from "./loader.js";
|
|
2
|
+
import { geoRegistry } from "./registry.js";
|
|
3
|
+
const CODE = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/;
|
|
4
|
+
/** `"fr, de"`, `["FR", "de"]` → `["FR", "DE"]`; unique, in order, empties dropped. */
|
|
5
|
+
export function normalizeIso(value) {
|
|
6
|
+
const raw = Array.isArray(value) ? value : typeof value === "string" ? value.split(/[\s,;]+/) : [];
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const item of raw) {
|
|
9
|
+
if (typeof item !== "string")
|
|
10
|
+
continue;
|
|
11
|
+
const code = item.trim().toUpperCase();
|
|
12
|
+
if (code && !out.includes(code))
|
|
13
|
+
out.push(code);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function isSubdivision(code) {
|
|
18
|
+
return code.includes("-");
|
|
19
|
+
}
|
|
20
|
+
/** Every data file a selection reads, for `declareAssets`. Depends on nothing but its arguments. */
|
|
21
|
+
export function geoFilesFor(codes, level) {
|
|
22
|
+
const files = new Set();
|
|
23
|
+
for (const code of codes) {
|
|
24
|
+
if (!CODE.test(code) || geoRegistry.defined(code))
|
|
25
|
+
continue;
|
|
26
|
+
if (isSubdivision(code))
|
|
27
|
+
files.add(statesFileKey(code.slice(0, 2)));
|
|
28
|
+
else
|
|
29
|
+
files.add(level === "state" ? statesFileKey(code) : countryFileKey(code));
|
|
30
|
+
}
|
|
31
|
+
return [...files];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The features a selection draws. A bare country code at state level is its
|
|
35
|
+
* first-level subdivisions — minus the remote ones when framing the mainland —
|
|
36
|
+
* or the country itself when it has none.
|
|
37
|
+
*/
|
|
38
|
+
export function selectGeoFeatures(codes, level, frame) {
|
|
39
|
+
const features = [];
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const unknown = [];
|
|
42
|
+
let pending = false;
|
|
43
|
+
const add = (feature) => {
|
|
44
|
+
if (seen.has(feature.code))
|
|
45
|
+
return;
|
|
46
|
+
seen.add(feature.code);
|
|
47
|
+
features.push(feature);
|
|
48
|
+
};
|
|
49
|
+
for (const code of codes) {
|
|
50
|
+
const defined = geoRegistry.defined(code);
|
|
51
|
+
if (defined) {
|
|
52
|
+
add(defined);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!CODE.test(code)) {
|
|
56
|
+
unknown.push(code);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const subdivision = isSubdivision(code);
|
|
60
|
+
const key = subdivision || level === "state" ? statesFileKey(code.slice(0, 2)) : countryFileKey(code);
|
|
61
|
+
const entry = geoFileEntry(key);
|
|
62
|
+
if (!entry || entry.status !== "ready") {
|
|
63
|
+
if (!entry)
|
|
64
|
+
pending = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const file = entry.file.features;
|
|
68
|
+
if (subdivision || level === "country") {
|
|
69
|
+
const feature = file.get(code);
|
|
70
|
+
if (feature)
|
|
71
|
+
add(feature);
|
|
72
|
+
else
|
|
73
|
+
unknown.push(code);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const children = [...file.values()].filter((f) => f.parent === code && f.first && !(frame === "mainland" && f.remote));
|
|
77
|
+
if (children.length > 0)
|
|
78
|
+
children.forEach(add);
|
|
79
|
+
else if (file.get(code))
|
|
80
|
+
add(file.get(code));
|
|
81
|
+
else
|
|
82
|
+
unknown.push(code);
|
|
83
|
+
}
|
|
84
|
+
return { features, pending, unknown };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=selection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection.js","sourceRoot":"","sources":["../../src/border/selection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAKxC,MAAM,IAAI,GAAG,6BAA6B,CAAA;AAE1C,sFAAsF;AACtF,MAAM,UAAU,YAAY,CAAC,KAAc;IACvC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAClG,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;QACrB,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,SAAQ;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QACtC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACnD,CAAC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IAC/B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;AAC7B,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,WAAW,CAAC,KAAwB,EAAE,KAAe;IACjE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAA;IAC/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC3D,IAAI,aAAa,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;;YAC9D,KAAK,CAAC,GAAG,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAA;IAClF,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAA;AACrB,CAAC;AAUD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAwB,EAAE,KAAe,EAAE,KAAe;IACxF,MAAM,QAAQ,GAAiB,EAAE,CAAA;IACjC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,IAAI,OAAO,GAAG,KAAK,CAAA;IAEnB,MAAM,GAAG,GAAG,CAAC,OAAmB,EAAE,EAAE;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAM;QAClC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACtB,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC1B,CAAC,CAAA;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACvB,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACzC,IAAI,OAAO,EAAE,CAAC;YACV,GAAG,CAAC,OAAO,CAAC,CAAA;YACZ,SAAQ;QACZ,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAClB,SAAQ;QACZ,CAAC;QACD,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,CAAC,CAAA;QACvC,MAAM,GAAG,GAAG,WAAW,IAAI,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;QACrG,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK;gBAAE,OAAO,GAAG,IAAI,CAAA;YAC1B,SAAQ;QACZ,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAA;QAChC,IAAI,WAAW,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAC9B,IAAI,OAAO;gBAAE,GAAG,CAAC,OAAO,CAAC,CAAA;;gBACpB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YACvB,SAAQ;QACZ,CAAC;QACD,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CACtC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,CAC7E,CAAA;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;aACzC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAE,CAAC,CAAA;;YACxC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,CAAA;AACzC,CAAC","sourcesContent":["import type { GeoFeature } from \"./format\"\nimport { countryFileKey, geoFileEntry, statesFileKey } from \"./loader\"\nimport { geoRegistry } from \"./registry\"\n\nexport type GeoLevel = \"country\" | \"state\"\nexport type GeoFrame = \"mainland\" | \"all\"\n\nconst CODE = /^[A-Z]{2}(-[A-Z0-9]{1,3})?$/\n\n/** `\"fr, de\"`, `[\"FR\", \"de\"]` → `[\"FR\", \"DE\"]`; unique, in order, empties dropped. */\nexport function normalizeIso(value: unknown): string[] {\n const raw = Array.isArray(value) ? value : typeof value === \"string\" ? value.split(/[\\s,;]+/) : []\n const out: string[] = []\n for (const item of raw) {\n if (typeof item !== \"string\") continue\n const code = item.trim().toUpperCase()\n if (code && !out.includes(code)) out.push(code)\n }\n return out\n}\n\nfunction isSubdivision(code: string): boolean {\n return code.includes(\"-\")\n}\n\n/** Every data file a selection reads, for `declareAssets`. Depends on nothing but its arguments. */\nexport function geoFilesFor(codes: readonly string[], level: GeoLevel): string[] {\n const files = new Set<string>()\n for (const code of codes) {\n if (!CODE.test(code) || geoRegistry.defined(code)) continue\n if (isSubdivision(code)) files.add(statesFileKey(code.slice(0, 2)))\n else files.add(level === \"state\" ? statesFileKey(code) : countryFileKey(code))\n }\n return [...files]\n}\n\nexport interface GeoSelection {\n features: GeoFeature[]\n /** A file this selection needs has not landed yet. */\n pending: boolean\n /** Codes whose file loaded without them. */\n unknown: string[]\n}\n\n/**\n * The features a selection draws. A bare country code at state level is its\n * first-level subdivisions — minus the remote ones when framing the mainland —\n * or the country itself when it has none.\n */\nexport function selectGeoFeatures(codes: readonly string[], level: GeoLevel, frame: GeoFrame): GeoSelection {\n const features: GeoFeature[] = []\n const seen = new Set<string>()\n const unknown: string[] = []\n let pending = false\n\n const add = (feature: GeoFeature) => {\n if (seen.has(feature.code)) return\n seen.add(feature.code)\n features.push(feature)\n }\n\n for (const code of codes) {\n const defined = geoRegistry.defined(code)\n if (defined) {\n add(defined)\n continue\n }\n if (!CODE.test(code)) {\n unknown.push(code)\n continue\n }\n const subdivision = isSubdivision(code)\n const key = subdivision || level === \"state\" ? statesFileKey(code.slice(0, 2)) : countryFileKey(code)\n const entry = geoFileEntry(key)\n if (!entry || entry.status !== \"ready\") {\n if (!entry) pending = true\n continue\n }\n const file = entry.file.features\n if (subdivision || level === \"country\") {\n const feature = file.get(code)\n if (feature) add(feature)\n else unknown.push(code)\n continue\n }\n const children = [...file.values()].filter(\n (f) => f.parent === code && f.first && !(frame === \"mainland\" && f.remote)\n )\n if (children.length > 0) children.forEach(add)\n else if (file.get(code)) add(file.get(code)!)\n else unknown.push(code)\n }\n\n return { features, pending, unknown }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visvalingam–Whyatt weights for a hand-defined ring, in the same buckets the
|
|
3
|
+
* generated data carries (`-2·log2` of the effective area in steradians), so a
|
|
4
|
+
* shape passed to `geoRegistry.define` answers to `detail` like any other.
|
|
5
|
+
*
|
|
6
|
+
* Planar in degrees with longitude scaled by latitude: accurate enough for the
|
|
7
|
+
* handful of shapes an author defines, where the generated data uses the exact
|
|
8
|
+
* spherical area.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ringBuckets(points: Float64Array): Uint8Array;
|
|
11
|
+
export declare function weightBucket(weight: number): number;
|
|
12
|
+
//# sourceMappingURL=simplify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simplify.d.ts","sourceRoot":"","sources":["../../src/border/simplify.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,UAAU,CA8F5D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAInD"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visvalingam–Whyatt weights for a hand-defined ring, in the same buckets the
|
|
3
|
+
* generated data carries (`-2·log2` of the effective area in steradians), so a
|
|
4
|
+
* shape passed to `geoRegistry.define` answers to `detail` like any other.
|
|
5
|
+
*
|
|
6
|
+
* Planar in degrees with longitude scaled by latitude: accurate enough for the
|
|
7
|
+
* handful of shapes an author defines, where the generated data uses the exact
|
|
8
|
+
* spherical area.
|
|
9
|
+
*/
|
|
10
|
+
export function ringBuckets(points) {
|
|
11
|
+
const n = points.length / 2;
|
|
12
|
+
const buckets = new Uint8Array(n);
|
|
13
|
+
if (n < 3)
|
|
14
|
+
return buckets;
|
|
15
|
+
const radians = Math.PI / 180;
|
|
16
|
+
const weight = new Float64Array(n);
|
|
17
|
+
const prev = new Int32Array(n);
|
|
18
|
+
const next = new Int32Array(n);
|
|
19
|
+
for (let i = 0; i < n; i++) {
|
|
20
|
+
prev[i] = i - 1;
|
|
21
|
+
next[i] = i + 1;
|
|
22
|
+
}
|
|
23
|
+
const area = (a, b, c) => {
|
|
24
|
+
const cos = Math.cos(points[b * 2 + 1] * radians);
|
|
25
|
+
const ax = points[a * 2] * cos, ay = points[a * 2 + 1];
|
|
26
|
+
const bx = points[b * 2] * cos, by = points[b * 2 + 1];
|
|
27
|
+
const cx = points[c * 2] * cos, cy = points[c * 2 + 1];
|
|
28
|
+
return (Math.abs((bx - ax) * (cy - ay) - (cx - ax) * (by - ay)) / 2) * radians * radians;
|
|
29
|
+
};
|
|
30
|
+
const heap = [];
|
|
31
|
+
const position = new Int32Array(n).fill(-1);
|
|
32
|
+
const less = (i, j) => weight[heap[i]] < weight[heap[j]];
|
|
33
|
+
const swap = (i, j) => {
|
|
34
|
+
;
|
|
35
|
+
[heap[i], heap[j]] = [heap[j], heap[i]];
|
|
36
|
+
position[heap[i]] = i;
|
|
37
|
+
position[heap[j]] = j;
|
|
38
|
+
};
|
|
39
|
+
const up = (i) => {
|
|
40
|
+
while (i > 0) {
|
|
41
|
+
const parent = (i - 1) >> 1;
|
|
42
|
+
if (!less(i, parent))
|
|
43
|
+
break;
|
|
44
|
+
swap(i, parent);
|
|
45
|
+
i = parent;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const down = (i) => {
|
|
49
|
+
for (;;) {
|
|
50
|
+
const l = 2 * i + 1;
|
|
51
|
+
const r = l + 1;
|
|
52
|
+
let smallest = i;
|
|
53
|
+
if (l < heap.length && less(l, smallest))
|
|
54
|
+
smallest = l;
|
|
55
|
+
if (r < heap.length && less(r, smallest))
|
|
56
|
+
smallest = r;
|
|
57
|
+
if (smallest === i)
|
|
58
|
+
return;
|
|
59
|
+
swap(i, smallest);
|
|
60
|
+
i = smallest;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const push = (i) => {
|
|
64
|
+
position[i] = heap.length;
|
|
65
|
+
heap.push(i);
|
|
66
|
+
up(heap.length - 1);
|
|
67
|
+
};
|
|
68
|
+
const remove = (i) => {
|
|
69
|
+
const at = position[i];
|
|
70
|
+
const last = heap.pop();
|
|
71
|
+
position[i] = -1;
|
|
72
|
+
if (at < heap.length) {
|
|
73
|
+
heap[at] = last;
|
|
74
|
+
position[last] = at;
|
|
75
|
+
up(at);
|
|
76
|
+
down(position[last]);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
for (let i = 1; i < n - 1; i++) {
|
|
80
|
+
weight[i] = area(i - 1, i, i + 1);
|
|
81
|
+
push(i);
|
|
82
|
+
}
|
|
83
|
+
let floor = 0;
|
|
84
|
+
const final = new Float64Array(n);
|
|
85
|
+
final[0] = final[n - 1] = Infinity;
|
|
86
|
+
while (heap.length > 0) {
|
|
87
|
+
const i = heap[0];
|
|
88
|
+
remove(i);
|
|
89
|
+
floor = Math.max(floor, weight[i]);
|
|
90
|
+
final[i] = floor;
|
|
91
|
+
const p = prev[i];
|
|
92
|
+
const q = next[i];
|
|
93
|
+
if (p >= 0)
|
|
94
|
+
next[p] = q;
|
|
95
|
+
if (q < n)
|
|
96
|
+
prev[q] = p;
|
|
97
|
+
for (const j of [p, q]) {
|
|
98
|
+
if (j <= 0 || j >= n - 1 || position[j] < 0)
|
|
99
|
+
continue;
|
|
100
|
+
remove(j);
|
|
101
|
+
weight[j] = area(prev[j], j, next[j]);
|
|
102
|
+
push(j);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (let i = 0; i < n; i++)
|
|
106
|
+
buckets[i] = weightBucket(final[i]);
|
|
107
|
+
return buckets;
|
|
108
|
+
}
|
|
109
|
+
export function weightBucket(weight) {
|
|
110
|
+
if (weight === Infinity)
|
|
111
|
+
return 0;
|
|
112
|
+
if (!(weight > 0))
|
|
113
|
+
return 255;
|
|
114
|
+
return Math.min(254, Math.max(1, Math.round(-2 * Math.log2(weight))));
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=simplify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simplify.js","sourceRoot":"","sources":["../../src/border/simplify.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,MAAoB;IAC5C,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAA;IAC3B,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;IACjC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAA;IAEzB,MAAM,OAAO,GAAG,IAAI,CAAC,EAAE,GAAG,GAAG,CAAA;IAC7B,MAAM,MAAM,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAA;IAClC,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;IAC9B,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAA;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACf,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,CAAS,EAAU,EAAE;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,CAAA;QACjD,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACtD,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACtD,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAA;QACtD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,GAAG,OAAO,CAAA;IAC5F,CAAC,CAAA;IAED,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IACxE,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE;QAClC,CAAC;QAAA,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QACxC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACrB,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC,CAAA;IACD,MAAM,EAAE,GAAG,CAAC,CAAS,EAAE,EAAE;QACrB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;YAC3B,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC;gBAAE,MAAK;YAC3B,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;YACf,CAAC,GAAG,MAAM,CAAA;QACd,CAAC;IACL,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE;QACvB,SAAS,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACnB,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACf,IAAI,QAAQ,GAAG,CAAC,CAAA;YAChB,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;gBAAE,QAAQ,GAAG,CAAC,CAAA;YACtD,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;gBAAE,QAAQ,GAAG,CAAC,CAAA;YACtD,IAAI,QAAQ,KAAK,CAAC;gBAAE,OAAM;YAC1B,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;YACjB,CAAC,GAAG,QAAQ,CAAA;QAChB,CAAC;IACL,CAAC,CAAA;IACD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE;QACvB,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAA;QACzB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACZ,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACvB,CAAC,CAAA;IACD,MAAM,MAAM,GAAG,CAAC,CAAS,EAAE,EAAE;QACzB,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAA;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAG,CAAA;QACxB,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;QAChB,IAAI,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACnB,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAA;YACf,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAA;YACnB,EAAE,CAAC,EAAE,CAAC,CAAA;YACN,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;QACxB,CAAC;IACL,CAAC,CAAA;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACjC,IAAI,CAAC,CAAC,CAAC,CAAA;IACX,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,CAAC,CAAC,CAAA;IACjC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAA;IAClC,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,MAAM,CAAC,CAAC,CAAC,CAAA;QACT,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;QAClC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;QAChB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,IAAI,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACvB,IAAI,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACtB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;gBAAE,SAAQ;YACrD,MAAM,CAAC,CAAC,CAAC,CAAA;YACT,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;YACrC,IAAI,CAAC,CAAC,CAAC,CAAA;QACX,CAAC;IACL,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/D,OAAO,OAAO,CAAA;AAClB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc;IACvC,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAA;IACjC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;QAAE,OAAO,GAAG,CAAA;IAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AACzE,CAAC","sourcesContent":["/**\n * Visvalingam–Whyatt weights for a hand-defined ring, in the same buckets the\n * generated data carries (`-2·log2` of the effective area in steradians), so a\n * shape passed to `geoRegistry.define` answers to `detail` like any other.\n *\n * Planar in degrees with longitude scaled by latitude: accurate enough for the\n * handful of shapes an author defines, where the generated data uses the exact\n * spherical area.\n */\nexport function ringBuckets(points: Float64Array): Uint8Array {\n const n = points.length / 2\n const buckets = new Uint8Array(n)\n if (n < 3) return buckets\n\n const radians = Math.PI / 180\n const weight = new Float64Array(n)\n const prev = new Int32Array(n)\n const next = new Int32Array(n)\n for (let i = 0; i < n; i++) {\n prev[i] = i - 1\n next[i] = i + 1\n }\n\n const area = (a: number, b: number, c: number): number => {\n const cos = Math.cos(points[b * 2 + 1] * radians)\n const ax = points[a * 2] * cos, ay = points[a * 2 + 1]\n const bx = points[b * 2] * cos, by = points[b * 2 + 1]\n const cx = points[c * 2] * cos, cy = points[c * 2 + 1]\n return (Math.abs((bx - ax) * (cy - ay) - (cx - ax) * (by - ay)) / 2) * radians * radians\n }\n\n const heap: number[] = []\n const position = new Int32Array(n).fill(-1)\n const less = (i: number, j: number) => weight[heap[i]] < weight[heap[j]]\n const swap = (i: number, j: number) => {\n ;[heap[i], heap[j]] = [heap[j], heap[i]]\n position[heap[i]] = i\n position[heap[j]] = j\n }\n const up = (i: number) => {\n while (i > 0) {\n const parent = (i - 1) >> 1\n if (!less(i, parent)) break\n swap(i, parent)\n i = parent\n }\n }\n const down = (i: number) => {\n for (;;) {\n const l = 2 * i + 1\n const r = l + 1\n let smallest = i\n if (l < heap.length && less(l, smallest)) smallest = l\n if (r < heap.length && less(r, smallest)) smallest = r\n if (smallest === i) return\n swap(i, smallest)\n i = smallest\n }\n }\n const push = (i: number) => {\n position[i] = heap.length\n heap.push(i)\n up(heap.length - 1)\n }\n const remove = (i: number) => {\n const at = position[i]\n const last = heap.pop()!\n position[i] = -1\n if (at < heap.length) {\n heap[at] = last\n position[last] = at\n up(at)\n down(position[last])\n }\n }\n\n for (let i = 1; i < n - 1; i++) {\n weight[i] = area(i - 1, i, i + 1)\n push(i)\n }\n\n let floor = 0\n const final = new Float64Array(n)\n final[0] = final[n - 1] = Infinity\n while (heap.length > 0) {\n const i = heap[0]\n remove(i)\n floor = Math.max(floor, weight[i])\n final[i] = floor\n const p = prev[i]\n const q = next[i]\n if (p >= 0) next[p] = q\n if (q < n) prev[q] = p\n for (const j of [p, q]) {\n if (j <= 0 || j >= n - 1 || position[j] < 0) continue\n remove(j)\n weight[j] = area(prev[j], j, next[j])\n push(j)\n }\n }\n\n for (let i = 0; i < n; i++) buckets[i] = weightBucket(final[i])\n return buckets\n}\n\nexport function weightBucket(weight: number): number {\n if (weight === Infinity) return 0\n if (!(weight > 0)) return 255\n return Math.min(254, Math.max(1, Math.round(-2 * Math.log2(weight))))\n}\n"]}
|