@fieldia/map 0.16.0 → 0.17.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.
Files changed (86) hide show
  1. package/README.md +43 -8
  2. package/package.json +4 -4
  3. package/src/index.d.mts +1 -1
  4. package/src/index.d.ts +1 -1
  5. package/src/index.js.map +1 -1
  6. package/src/index.mjs.map +1 -1
  7. package/src/lib/area.js +2 -1
  8. package/src/lib/area.js.map +1 -1
  9. package/src/lib/area.mjs +3 -2
  10. package/src/lib/area.mjs.map +1 -1
  11. package/src/lib/form-map.d.mts +3 -1
  12. package/src/lib/form-map.d.ts +3 -1
  13. package/src/lib/form-map.js +63 -2
  14. package/src/lib/form-map.js.map +1 -1
  15. package/src/lib/form-map.mjs +63 -2
  16. package/src/lib/form-map.mjs.map +1 -1
  17. package/src/lib/heat.d.mts +25 -0
  18. package/src/lib/heat.d.ts +25 -0
  19. package/src/lib/heat.js +111 -0
  20. package/src/lib/heat.js.map +1 -0
  21. package/src/lib/heat.mjs +108 -0
  22. package/src/lib/heat.mjs.map +1 -0
  23. package/src/lib/labels.d.mts +20 -0
  24. package/src/lib/labels.d.ts +20 -0
  25. package/src/lib/labels.js +48 -0
  26. package/src/lib/labels.js.map +1 -1
  27. package/src/lib/labels.mjs +48 -0
  28. package/src/lib/labels.mjs.map +1 -1
  29. package/src/lib/list-view.js +120 -5
  30. package/src/lib/list-view.js.map +1 -1
  31. package/src/lib/list-view.mjs +120 -5
  32. package/src/lib/list-view.mjs.map +1 -1
  33. package/src/lib/map-core.d.mts +8 -0
  34. package/src/lib/map-core.d.ts +8 -0
  35. package/src/lib/map-core.js +35 -2
  36. package/src/lib/map-core.js.map +1 -1
  37. package/src/lib/map-core.mjs +34 -2
  38. package/src/lib/map-core.mjs.map +1 -1
  39. package/src/lib/measure.d.mts +15 -0
  40. package/src/lib/measure.d.ts +15 -0
  41. package/src/lib/measure.js +157 -0
  42. package/src/lib/measure.js.map +1 -0
  43. package/src/lib/measure.mjs +153 -0
  44. package/src/lib/measure.mjs.map +1 -0
  45. package/src/lib/place.js +3 -1
  46. package/src/lib/place.js.map +1 -1
  47. package/src/lib/place.mjs +4 -2
  48. package/src/lib/place.mjs.map +1 -1
  49. package/src/lib/providers.d.mts +16 -0
  50. package/src/lib/providers.d.ts +16 -0
  51. package/src/lib/providers.js +7 -0
  52. package/src/lib/providers.js.map +1 -1
  53. package/src/lib/providers.mjs +7 -0
  54. package/src/lib/providers.mjs.map +1 -1
  55. package/src/lib/reach.d.mts +25 -0
  56. package/src/lib/reach.d.ts +25 -0
  57. package/src/lib/reach.js +158 -0
  58. package/src/lib/reach.js.map +1 -0
  59. package/src/lib/reach.mjs +153 -0
  60. package/src/lib/reach.mjs.map +1 -0
  61. package/src/lib/regions.d.mts +137 -0
  62. package/src/lib/regions.d.ts +137 -0
  63. package/src/lib/regions.js +456 -0
  64. package/src/lib/regions.js.map +1 -0
  65. package/src/lib/regions.mjs +445 -0
  66. package/src/lib/regions.mjs.map +1 -0
  67. package/src/lib/routes.d.mts +16 -4
  68. package/src/lib/routes.d.ts +16 -4
  69. package/src/lib/routes.js +40 -3
  70. package/src/lib/routes.js.map +1 -1
  71. package/src/lib/routes.mjs +38 -3
  72. package/src/lib/routes.mjs.map +1 -1
  73. package/src/lib/settings.d.mts +9 -1
  74. package/src/lib/settings.d.ts +9 -1
  75. package/src/lib/styles.d.mts +2 -2
  76. package/src/lib/styles.d.ts +2 -2
  77. package/src/lib/styles.js +10 -0
  78. package/src/lib/styles.js.map +1 -1
  79. package/src/lib/styles.mjs +10 -0
  80. package/src/lib/styles.mjs.map +1 -1
  81. package/src/lib/tools.d.mts +27 -0
  82. package/src/lib/tools.d.ts +27 -0
  83. package/src/lib/tools.js +84 -0
  84. package/src/lib/tools.js.map +1 -0
  85. package/src/lib/tools.mjs +81 -0
  86. package/src/lib/tools.mjs.map +1 -0
@@ -0,0 +1,158 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.insideRing = insideRing;
4
+ exports.insideArea = insideArea;
5
+ exports.formAreas = formAreas;
6
+ const geo_1 = require("./geo");
7
+ const map_core_1 = require("./map-core");
8
+ /**
9
+ * A form's map's areas that follow its places: the area within some minutes
10
+ * of a place, asked of the map's provider (or the app) and asked again once
11
+ * the place has moved far enough; and its fences, the areas fields hold drawn
12
+ * faintly, each fence's steps run when the place it watches goes into its
13
+ * area or comes out of it — never at first sight, when it was there already.
14
+ * Loaded with the first map that has either.
15
+ */
16
+ /** A reachable area is asked for again once its place has moved this far, in metres, and at most this often. */
17
+ const REACH_MOVE_M = 250;
18
+ const REACH_EVERY_MS = 10_000;
19
+ /** Whether a place is inside a ring of places: a line from it crosses the ring's edges an odd number of times. */
20
+ function insideRing(at, ring) {
21
+ let odd = false;
22
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
23
+ const [a, b] = [ring[i], ring[j]];
24
+ if (a.lat > at.lat !== b.lat > at.lat && at.lng < ((b.lng - a.lng) * (at.lat - a.lat)) / (b.lat - a.lat) + a.lng)
25
+ odd = !odd;
26
+ }
27
+ return odd;
28
+ }
29
+ /** Whether a place is inside an area as an area field keeps it: within a circle's radius, or inside a polygon or a box. */
30
+ function insideArea(at, area) {
31
+ return area.shape === 'circle' ? (0, geo_1.metresBetween)(area.center, at) <= area.radius : insideRing(at, area.points);
32
+ }
33
+ function formAreas(options) {
34
+ const { node, map, L } = options;
35
+ const reaches = new Map();
36
+ /** Whether each fence's place was inside its area when last seen. */
37
+ const insideNow = new Map();
38
+ const fenceShapes = new Map();
39
+ let gone = false;
40
+ let last = null;
41
+ // Their own pane: over areas by colour, under routes and pins.
42
+ if (!map.getPane('fd-reach'))
43
+ map.createPane('fd-reach').style.zIndex = '390';
44
+ function drawReaches(positionOf, hidden) {
45
+ for (const layer of node.layers) {
46
+ const spec = layer.reachable;
47
+ if (!spec)
48
+ continue;
49
+ let state = reaches.get(layer.id);
50
+ if (!state)
51
+ reaches.set(layer.id, (state = { asking: 0, askedAt: 0 }));
52
+ const from = positionOf(spec.from);
53
+ if (!from || hidden(layer.id)) {
54
+ state.drawn?.remove();
55
+ state.drawn = undefined;
56
+ continue;
57
+ }
58
+ const moved = !state.at || ((0, geo_1.metresBetween)(state.at, from) > REACH_MOVE_M && Date.now() - state.askedAt > REACH_EVERY_MS);
59
+ if (moved)
60
+ ask(layer.id, state, from, spec.minutes, spec.mode ?? 'car');
61
+ if (state.found && !state.drawn) {
62
+ state.drawn = L.polygon(state.found.rings.map((ring) => ring.map((p) => [p.lat, p.lng])), { className: 'fd-map-reach', pane: 'fd-reach', interactive: false }).addTo(map);
63
+ }
64
+ }
65
+ }
66
+ function ask(id, state, at, minutes, mode) {
67
+ state.at = at;
68
+ state.askedAt = Date.now();
69
+ if (!options.finder) {
70
+ (0, map_core_1.warnOnce)(`The map's provider (${options.provider}) works out no reachable areas: give the maps a reach function of the app's own, or use Azure Maps or Mapbox.`);
71
+ return;
72
+ }
73
+ state.abort?.abort();
74
+ const abort = new AbortController();
75
+ state.abort = abort;
76
+ const asked = ++state.asking;
77
+ options.finder({ at, minutes, mode, signal: abort.signal }).then((found) => {
78
+ if (gone || asked !== state.asking)
79
+ return;
80
+ const first = !state.found;
81
+ state.found = found;
82
+ state.drawn?.remove();
83
+ state.drawn = undefined;
84
+ if (last)
85
+ areas.update(...last);
86
+ options.found(first, found.rings.flat());
87
+ }, (error) => {
88
+ if (gone || abort.signal.aborted)
89
+ return;
90
+ (0, map_core_1.warnOnce)(`The reachable area "${id}" was not found: ${error instanceof Error ? error.message : String(error)}`);
91
+ });
92
+ }
93
+ /** The areas fields hold that fences watch, drawn faintly so the person sees where steps will run. */
94
+ function drawFenceAreas(values) {
95
+ for (const name of new Set((node.fences ?? []).flatMap((fence) => (fence.area !== undefined ? [fence.area] : [])))) {
96
+ const area = (0, geo_1.readArea)(values[name]);
97
+ const drawn = JSON.stringify(area);
98
+ const had = fenceShapes.get(name);
99
+ if (had?.drawn === drawn)
100
+ continue;
101
+ had?.path.remove();
102
+ fenceShapes.delete(name);
103
+ if (!area)
104
+ continue;
105
+ const style = { className: 'fd-map-fence', pane: 'fd-reach', interactive: false };
106
+ const path = area.shape === 'circle' ? L.circle([area.center.lat, area.center.lng], { ...style, radius: area.radius }) : L.polygon(area.points.map((p) => [p.lat, p.lng]), style);
107
+ fenceShapes.set(name, { drawn, path: path.addTo(map) });
108
+ }
109
+ }
110
+ function checkFences(values, positionOf) {
111
+ node.fences?.forEach((fence, i) => {
112
+ // Where the place is going, not where its pin is gliding.
113
+ const at = positionOf(fence.layer);
114
+ if (!at)
115
+ return;
116
+ let inside;
117
+ if (fence.area !== undefined) {
118
+ const area = (0, geo_1.readArea)(values[fence.area]);
119
+ if (!area)
120
+ return;
121
+ inside = insideArea(at, area);
122
+ }
123
+ else {
124
+ const found = reaches.get(fence.within)?.found;
125
+ if (!found)
126
+ return;
127
+ inside = found.rings.some((ring) => insideRing(at, ring));
128
+ }
129
+ const was = insideNow.get(i);
130
+ insideNow.set(i, inside);
131
+ if (was === undefined || was === inside)
132
+ return;
133
+ if (inside === (fence.when === 'enter'))
134
+ options.run(fence.steps);
135
+ });
136
+ }
137
+ const areas = {
138
+ update(values, positionOf, hidden) {
139
+ if (gone)
140
+ return;
141
+ last = [values, positionOf, hidden];
142
+ drawReaches(positionOf, hidden);
143
+ drawFenceAreas(values);
144
+ checkFences(values, positionOf);
145
+ },
146
+ destroy() {
147
+ gone = true;
148
+ for (const state of reaches.values()) {
149
+ state.abort?.abort();
150
+ state.drawn?.remove();
151
+ }
152
+ for (const shape of fenceShapes.values())
153
+ shape.path.remove();
154
+ },
155
+ };
156
+ return areas;
157
+ }
158
+ //# sourceMappingURL=reach.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reach.js","sourceRoot":"","sources":["../../../../../libs/map/src/lib/reach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,153 @@
1
+ import { metresBetween, readArea } from './geo.mjs';
2
+ import { warnOnce } from './map-core.mjs';
3
+ /**
4
+ * A form's map's areas that follow its places: the area within some minutes
5
+ * of a place, asked of the map's provider (or the app) and asked again once
6
+ * the place has moved far enough; and its fences, the areas fields hold drawn
7
+ * faintly, each fence's steps run when the place it watches goes into its
8
+ * area or comes out of it — never at first sight, when it was there already.
9
+ * Loaded with the first map that has either.
10
+ */
11
+ /** A reachable area is asked for again once its place has moved this far, in metres, and at most this often. */
12
+ const REACH_MOVE_M = 250;
13
+ const REACH_EVERY_MS = 10_000;
14
+ /** Whether a place is inside a ring of places: a line from it crosses the ring's edges an odd number of times. */
15
+ export function insideRing(at, ring) {
16
+ let odd = false;
17
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
18
+ const [a, b] = [ring[i], ring[j]];
19
+ if (a.lat > at.lat !== b.lat > at.lat && at.lng < ((b.lng - a.lng) * (at.lat - a.lat)) / (b.lat - a.lat) + a.lng)
20
+ odd = !odd;
21
+ }
22
+ return odd;
23
+ }
24
+ /** Whether a place is inside an area as an area field keeps it: within a circle's radius, or inside a polygon or a box. */
25
+ export function insideArea(at, area) {
26
+ return area.shape === 'circle' ? metresBetween(area.center, at) <= area.radius : insideRing(at, area.points);
27
+ }
28
+ export function formAreas(options) {
29
+ const { node, map, L } = options;
30
+ const reaches = new Map();
31
+ /** Whether each fence's place was inside its area when last seen. */
32
+ const insideNow = new Map();
33
+ const fenceShapes = new Map();
34
+ let gone = false;
35
+ let last = null;
36
+ // Their own pane: over areas by colour, under routes and pins.
37
+ if (!map.getPane('fd-reach'))
38
+ map.createPane('fd-reach').style.zIndex = '390';
39
+ function drawReaches(positionOf, hidden) {
40
+ for (const layer of node.layers) {
41
+ const spec = layer.reachable;
42
+ if (!spec)
43
+ continue;
44
+ let state = reaches.get(layer.id);
45
+ if (!state)
46
+ reaches.set(layer.id, (state = { asking: 0, askedAt: 0 }));
47
+ const from = positionOf(spec.from);
48
+ if (!from || hidden(layer.id)) {
49
+ state.drawn?.remove();
50
+ state.drawn = undefined;
51
+ continue;
52
+ }
53
+ const moved = !state.at || (metresBetween(state.at, from) > REACH_MOVE_M && Date.now() - state.askedAt > REACH_EVERY_MS);
54
+ if (moved)
55
+ ask(layer.id, state, from, spec.minutes, spec.mode ?? 'car');
56
+ if (state.found && !state.drawn) {
57
+ state.drawn = L.polygon(state.found.rings.map((ring) => ring.map((p) => [p.lat, p.lng])), { className: 'fd-map-reach', pane: 'fd-reach', interactive: false }).addTo(map);
58
+ }
59
+ }
60
+ }
61
+ function ask(id, state, at, minutes, mode) {
62
+ state.at = at;
63
+ state.askedAt = Date.now();
64
+ if (!options.finder) {
65
+ warnOnce(`The map's provider (${options.provider}) works out no reachable areas: give the maps a reach function of the app's own, or use Azure Maps or Mapbox.`);
66
+ return;
67
+ }
68
+ state.abort?.abort();
69
+ const abort = new AbortController();
70
+ state.abort = abort;
71
+ const asked = ++state.asking;
72
+ options.finder({ at, minutes, mode, signal: abort.signal }).then((found) => {
73
+ if (gone || asked !== state.asking)
74
+ return;
75
+ const first = !state.found;
76
+ state.found = found;
77
+ state.drawn?.remove();
78
+ state.drawn = undefined;
79
+ if (last)
80
+ areas.update(...last);
81
+ options.found(first, found.rings.flat());
82
+ }, (error) => {
83
+ if (gone || abort.signal.aborted)
84
+ return;
85
+ warnOnce(`The reachable area "${id}" was not found: ${error instanceof Error ? error.message : String(error)}`);
86
+ });
87
+ }
88
+ /** The areas fields hold that fences watch, drawn faintly so the person sees where steps will run. */
89
+ function drawFenceAreas(values) {
90
+ for (const name of new Set((node.fences ?? []).flatMap((fence) => (fence.area !== undefined ? [fence.area] : [])))) {
91
+ const area = readArea(values[name]);
92
+ const drawn = JSON.stringify(area);
93
+ const had = fenceShapes.get(name);
94
+ if (had?.drawn === drawn)
95
+ continue;
96
+ had?.path.remove();
97
+ fenceShapes.delete(name);
98
+ if (!area)
99
+ continue;
100
+ const style = { className: 'fd-map-fence', pane: 'fd-reach', interactive: false };
101
+ const path = area.shape === 'circle' ? L.circle([area.center.lat, area.center.lng], { ...style, radius: area.radius }) : L.polygon(area.points.map((p) => [p.lat, p.lng]), style);
102
+ fenceShapes.set(name, { drawn, path: path.addTo(map) });
103
+ }
104
+ }
105
+ function checkFences(values, positionOf) {
106
+ node.fences?.forEach((fence, i) => {
107
+ // Where the place is going, not where its pin is gliding.
108
+ const at = positionOf(fence.layer);
109
+ if (!at)
110
+ return;
111
+ let inside;
112
+ if (fence.area !== undefined) {
113
+ const area = readArea(values[fence.area]);
114
+ if (!area)
115
+ return;
116
+ inside = insideArea(at, area);
117
+ }
118
+ else {
119
+ const found = reaches.get(fence.within)?.found;
120
+ if (!found)
121
+ return;
122
+ inside = found.rings.some((ring) => insideRing(at, ring));
123
+ }
124
+ const was = insideNow.get(i);
125
+ insideNow.set(i, inside);
126
+ if (was === undefined || was === inside)
127
+ return;
128
+ if (inside === (fence.when === 'enter'))
129
+ options.run(fence.steps);
130
+ });
131
+ }
132
+ const areas = {
133
+ update(values, positionOf, hidden) {
134
+ if (gone)
135
+ return;
136
+ last = [values, positionOf, hidden];
137
+ drawReaches(positionOf, hidden);
138
+ drawFenceAreas(values);
139
+ checkFences(values, positionOf);
140
+ },
141
+ destroy() {
142
+ gone = true;
143
+ for (const state of reaches.values()) {
144
+ state.abort?.abort();
145
+ state.drawn?.remove();
146
+ }
147
+ for (const shape of fenceShapes.values())
148
+ shape.path.remove();
149
+ },
150
+ };
151
+ return areas;
152
+ }
153
+ //# sourceMappingURL=reach.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reach.mjs","sourceRoot":"","sources":["../../../../../libs/map/src/lib/reach.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,137 @@
1
+ import type * as Leaflet from 'leaflet';
2
+ import type { Locale, MapLayer, MapRegions, ResolvedFilter } from '@fieldia/core';
3
+ import { type MapLabels } from './labels.mjs';
4
+ import type { LeafletModule } from './leaflet.mjs';
5
+ import { type MapBox } from './map-core.mjs';
6
+ /**
7
+ * Areas shaded by colour on a map: districts, zones, municipalities. Their
8
+ * borders are the app's (a GeoJSON FeatureCollection of polygons), each known
9
+ * by one of its properties and named by another. An area's number shades it
10
+ * in up to five steps of the page's accent, from light to strong, with a
11
+ * legend; an area with none is outlined only. The pointer over one lights it
12
+ * and says its name and number; a press does what the map says (search for its
13
+ * records, or choose it). On the keyboard the areas are one stop: Tab reaches
14
+ * the first (or the one chosen), the arrow keys go from area to area in
15
+ * reading order, Enter or Space presses one. Loaded with the first map that
16
+ * has them.
17
+ */
18
+ export interface Border {
19
+ /** Its key as text, and as the border holds it: what a record's field holds. */
20
+ key: string;
21
+ value: string | number;
22
+ name: string;
23
+ properties: Record<string, unknown>;
24
+ /** Its polygons, each as rings of [longitude, latitude]: the outer ring, then its holes. */
25
+ polygons: [number, number][][][];
26
+ }
27
+ /** The borders a set holds: Polygon and MultiPolygon features with a key; anything else is passed over. */
28
+ export declare function readBorders(collection: unknown, spec: {
29
+ key: string;
30
+ name?: string;
31
+ }): Border[];
32
+ /** A set of borders the app's maps give, by name: asked for once for all the maps sharing the settings; undefined when there is no such set. */
33
+ export declare function loadBorders(sets: Record<string, unknown> | undefined, name: string): Promise<unknown>;
34
+ /**
35
+ * The shades' steps: up to five, each ending on a round number, so the legend
36
+ * reads "11–20, 21–30…" rather than "9.6–19.2". They start from nothing, or
37
+ * from a round number under the least when every number is well above
38
+ * nothing (fees of 15 to 40 start at 10). Whole numbers (counts) step by whole
39
+ * numbers.
40
+ */
41
+ export declare function shadeSteps(values: readonly number[]): {
42
+ from: number;
43
+ ends: number[];
44
+ };
45
+ /** The shade a number falls in: 0 for the lightest, null for none. */
46
+ export declare function shadeOf(value: number | null | undefined, steps: readonly number[]): number | null;
47
+ export declare const REGIONS_CSS = "\npath.fd-map-region { stroke: var(--fd-surface); stroke-width: 1.4; stroke-opacity: 0.95; fill: var(--fd-accent); fill-opacity: 0.06; cursor: pointer; transition: fill-opacity 0.12s; }\npath.fd-map-region[data-shade] { fill: var(--fd-region); fill-opacity: 0.5; }\npath.fd-map-region:hover, path.fd-map-region[data-lit] { fill-opacity: 0.8; stroke: var(--fd-text); stroke-width: 2; }\npath.fd-map-region[data-chosen] { stroke: var(--fd-accent); stroke-width: 3.5; stroke-opacity: 1; }\npath.fd-map-region:focus { outline: none; }\n/* While a way is measured or an area drawn, every press is the map's. */\n.fd-map-box[data-measuring] path.fd-map-region, .fd-map-box[data-drawing] path.fd-map-region { pointer-events: none; }\npath.fd-map-region:focus-visible { stroke: var(--fd-focus); stroke-width: 4; stroke-opacity: 1; }\n.fd-map-box[data-plain] path.fd-map-region { cursor: default; }\n.fd-map-legend {\n position: absolute; right: 8px; bottom: 26px; z-index: 1000; display: grid; gap: 3px; max-width: min(240px, 60%); padding: 6px 9px; border-radius: 6px;\n font-size: 12px; line-height: 1.35; background: color-mix(in srgb, var(--fd-surface) 94%, transparent); color: var(--fd-text); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);\n}\n.fd-map-legend[hidden] { display: none; }\n.fd-map-legend-title { font-weight: 700; overflow-wrap: anywhere; }\n.fd-map-legend ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }\n.fd-map-legend li { display: flex; align-items: center; gap: 7px; font-variant-numeric: tabular-nums; }\n.fd-map-legend i { width: 18px; height: 11px; flex: none; border-radius: 2px; background: var(--fd-region, transparent); border: 1px solid color-mix(in srgb, var(--fd-text) 35%, transparent); }\n.fd-map-legend i[data-shade] { background: color-mix(in srgb, var(--fd-region) 50%, var(--fd-surface)); }\n.leaflet-tooltip.fd-map-tip {\n padding: 3px 8px; border: 0; border-radius: 6px; font: inherit; font-size: 12.5px; font-weight: 600; white-space: nowrap;\n background: var(--fd-surface); color: var(--fd-text); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);\n}\n.leaflet-tooltip.fd-map-tip::before { display: none; }\n";
48
+ export interface RegionsLayer {
49
+ /** Each area's number, by its key (null: none shaded), and the area chosen. */
50
+ shade(values: ReadonlyMap<string, number> | null, chosen: string | null): void;
51
+ /** The area under a place, if any: for a fence, or a record's own area. */
52
+ at(lat: number, lng: number): Border | null;
53
+ /** On the map or off it, as its layer's `invisible` says. */
54
+ show(on: boolean): void;
55
+ destroy(): void;
56
+ }
57
+ export declare function drawRegions(options: {
58
+ box: MapBox;
59
+ map: Leaflet.Map;
60
+ L: LeafletModule;
61
+ document: Document;
62
+ locale: Locale;
63
+ labels: MapLabels;
64
+ borders: Border[];
65
+ /** The legend's title, and the areas' name as a group: "Orders", "Districts". */
66
+ title: string;
67
+ legend: boolean;
68
+ /** What a press on an area does; without it, the areas are only shown. */
69
+ press?: (border: Border) => void;
70
+ }): RegionsLayer;
71
+ /** Inside a polygon: inside its outer ring and none of its holes. */
72
+ export declare function insideRings(x: number, y: number, rings: [number, number][][]): boolean;
73
+ /**
74
+ * A list's areas shaded by its records: drawn once their borders load, their
75
+ * numbers asked for again with each search — each area's records under the
76
+ * search but for an area searched itself, so the others keep their shades —
77
+ * or a number of their own. A press searches for an area's records.
78
+ */
79
+ export declare function listRegions(options: {
80
+ sets: Record<string, unknown> | undefined;
81
+ spec: MapRegions & {
82
+ field: string;
83
+ };
84
+ box: MapBox;
85
+ map: Leaflet.Map;
86
+ L: LeafletModule;
87
+ document: Document;
88
+ locale: Locale;
89
+ labels: MapLabels & {
90
+ inRegion: string;
91
+ };
92
+ title: string;
93
+ model: string;
94
+ dataSource: {
95
+ groups?(request: {
96
+ model: string;
97
+ field: string;
98
+ filter: ResolvedFilter[];
99
+ }): Promise<{
100
+ value: unknown;
101
+ count: number;
102
+ }[]>;
103
+ } | undefined;
104
+ addSearch(search: {
105
+ label: string;
106
+ filter: ResolvedFilter[];
107
+ }): void;
108
+ /** The records the map has, to count when the data source cannot. */
109
+ records(): readonly {
110
+ values: Record<string, unknown>;
111
+ }[];
112
+ }): {
113
+ shade(filter: readonly ResolvedFilter[], current: () => boolean): Promise<void>;
114
+ destroy(): void;
115
+ };
116
+ /**
117
+ * A form's areas by colour, a layer each: shaded by a number of their own,
118
+ * the one the record's field holds drawn strong, a press choosing one into
119
+ * that field where the person may change it; on the map or off it as each
120
+ * layer's `invisible` says.
121
+ */
122
+ export declare function formRegions(options: {
123
+ sets: Record<string, unknown> | undefined;
124
+ layers: readonly MapLayer[];
125
+ box: MapBox;
126
+ map: Leaflet.Map;
127
+ L: LeafletModule;
128
+ document: Document;
129
+ locale: Locale;
130
+ labels: MapLabels;
131
+ /** Whether a person may write a field now: a press chooses only where they may. */
132
+ writable(field: string): boolean;
133
+ choose(field: string, value: string | number): void;
134
+ }): {
135
+ update(values: Record<string, unknown>, hidden: (layerId: string) => boolean): void;
136
+ destroy(): void;
137
+ };
@@ -0,0 +1,137 @@
1
+ import type * as Leaflet from 'leaflet';
2
+ import type { Locale, MapLayer, MapRegions, ResolvedFilter } from '@fieldia/core';
3
+ import { type MapLabels } from './labels';
4
+ import type { LeafletModule } from './leaflet';
5
+ import { type MapBox } from './map-core';
6
+ /**
7
+ * Areas shaded by colour on a map: districts, zones, municipalities. Their
8
+ * borders are the app's (a GeoJSON FeatureCollection of polygons), each known
9
+ * by one of its properties and named by another. An area's number shades it
10
+ * in up to five steps of the page's accent, from light to strong, with a
11
+ * legend; an area with none is outlined only. The pointer over one lights it
12
+ * and says its name and number; a press does what the map says (search for its
13
+ * records, or choose it). On the keyboard the areas are one stop: Tab reaches
14
+ * the first (or the one chosen), the arrow keys go from area to area in
15
+ * reading order, Enter or Space presses one. Loaded with the first map that
16
+ * has them.
17
+ */
18
+ export interface Border {
19
+ /** Its key as text, and as the border holds it: what a record's field holds. */
20
+ key: string;
21
+ value: string | number;
22
+ name: string;
23
+ properties: Record<string, unknown>;
24
+ /** Its polygons, each as rings of [longitude, latitude]: the outer ring, then its holes. */
25
+ polygons: [number, number][][][];
26
+ }
27
+ /** The borders a set holds: Polygon and MultiPolygon features with a key; anything else is passed over. */
28
+ export declare function readBorders(collection: unknown, spec: {
29
+ key: string;
30
+ name?: string;
31
+ }): Border[];
32
+ /** A set of borders the app's maps give, by name: asked for once for all the maps sharing the settings; undefined when there is no such set. */
33
+ export declare function loadBorders(sets: Record<string, unknown> | undefined, name: string): Promise<unknown>;
34
+ /**
35
+ * The shades' steps: up to five, each ending on a round number, so the legend
36
+ * reads "11–20, 21–30…" rather than "9.6–19.2". They start from nothing, or
37
+ * from a round number under the least when every number is well above
38
+ * nothing (fees of 15 to 40 start at 10). Whole numbers (counts) step by whole
39
+ * numbers.
40
+ */
41
+ export declare function shadeSteps(values: readonly number[]): {
42
+ from: number;
43
+ ends: number[];
44
+ };
45
+ /** The shade a number falls in: 0 for the lightest, null for none. */
46
+ export declare function shadeOf(value: number | null | undefined, steps: readonly number[]): number | null;
47
+ export declare const REGIONS_CSS = "\npath.fd-map-region { stroke: var(--fd-surface); stroke-width: 1.4; stroke-opacity: 0.95; fill: var(--fd-accent); fill-opacity: 0.06; cursor: pointer; transition: fill-opacity 0.12s; }\npath.fd-map-region[data-shade] { fill: var(--fd-region); fill-opacity: 0.5; }\npath.fd-map-region:hover, path.fd-map-region[data-lit] { fill-opacity: 0.8; stroke: var(--fd-text); stroke-width: 2; }\npath.fd-map-region[data-chosen] { stroke: var(--fd-accent); stroke-width: 3.5; stroke-opacity: 1; }\npath.fd-map-region:focus { outline: none; }\n/* While a way is measured or an area drawn, every press is the map's. */\n.fd-map-box[data-measuring] path.fd-map-region, .fd-map-box[data-drawing] path.fd-map-region { pointer-events: none; }\npath.fd-map-region:focus-visible { stroke: var(--fd-focus); stroke-width: 4; stroke-opacity: 1; }\n.fd-map-box[data-plain] path.fd-map-region { cursor: default; }\n.fd-map-legend {\n position: absolute; right: 8px; bottom: 26px; z-index: 1000; display: grid; gap: 3px; max-width: min(240px, 60%); padding: 6px 9px; border-radius: 6px;\n font-size: 12px; line-height: 1.35; background: color-mix(in srgb, var(--fd-surface) 94%, transparent); color: var(--fd-text); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18);\n}\n.fd-map-legend[hidden] { display: none; }\n.fd-map-legend-title { font-weight: 700; overflow-wrap: anywhere; }\n.fd-map-legend ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }\n.fd-map-legend li { display: flex; align-items: center; gap: 7px; font-variant-numeric: tabular-nums; }\n.fd-map-legend i { width: 18px; height: 11px; flex: none; border-radius: 2px; background: var(--fd-region, transparent); border: 1px solid color-mix(in srgb, var(--fd-text) 35%, transparent); }\n.fd-map-legend i[data-shade] { background: color-mix(in srgb, var(--fd-region) 50%, var(--fd-surface)); }\n.leaflet-tooltip.fd-map-tip {\n padding: 3px 8px; border: 0; border-radius: 6px; font: inherit; font-size: 12.5px; font-weight: 600; white-space: nowrap;\n background: var(--fd-surface); color: var(--fd-text); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);\n}\n.leaflet-tooltip.fd-map-tip::before { display: none; }\n";
48
+ export interface RegionsLayer {
49
+ /** Each area's number, by its key (null: none shaded), and the area chosen. */
50
+ shade(values: ReadonlyMap<string, number> | null, chosen: string | null): void;
51
+ /** The area under a place, if any: for a fence, or a record's own area. */
52
+ at(lat: number, lng: number): Border | null;
53
+ /** On the map or off it, as its layer's `invisible` says. */
54
+ show(on: boolean): void;
55
+ destroy(): void;
56
+ }
57
+ export declare function drawRegions(options: {
58
+ box: MapBox;
59
+ map: Leaflet.Map;
60
+ L: LeafletModule;
61
+ document: Document;
62
+ locale: Locale;
63
+ labels: MapLabels;
64
+ borders: Border[];
65
+ /** The legend's title, and the areas' name as a group: "Orders", "Districts". */
66
+ title: string;
67
+ legend: boolean;
68
+ /** What a press on an area does; without it, the areas are only shown. */
69
+ press?: (border: Border) => void;
70
+ }): RegionsLayer;
71
+ /** Inside a polygon: inside its outer ring and none of its holes. */
72
+ export declare function insideRings(x: number, y: number, rings: [number, number][][]): boolean;
73
+ /**
74
+ * A list's areas shaded by its records: drawn once their borders load, their
75
+ * numbers asked for again with each search — each area's records under the
76
+ * search but for an area searched itself, so the others keep their shades —
77
+ * or a number of their own. A press searches for an area's records.
78
+ */
79
+ export declare function listRegions(options: {
80
+ sets: Record<string, unknown> | undefined;
81
+ spec: MapRegions & {
82
+ field: string;
83
+ };
84
+ box: MapBox;
85
+ map: Leaflet.Map;
86
+ L: LeafletModule;
87
+ document: Document;
88
+ locale: Locale;
89
+ labels: MapLabels & {
90
+ inRegion: string;
91
+ };
92
+ title: string;
93
+ model: string;
94
+ dataSource: {
95
+ groups?(request: {
96
+ model: string;
97
+ field: string;
98
+ filter: ResolvedFilter[];
99
+ }): Promise<{
100
+ value: unknown;
101
+ count: number;
102
+ }[]>;
103
+ } | undefined;
104
+ addSearch(search: {
105
+ label: string;
106
+ filter: ResolvedFilter[];
107
+ }): void;
108
+ /** The records the map has, to count when the data source cannot. */
109
+ records(): readonly {
110
+ values: Record<string, unknown>;
111
+ }[];
112
+ }): {
113
+ shade(filter: readonly ResolvedFilter[], current: () => boolean): Promise<void>;
114
+ destroy(): void;
115
+ };
116
+ /**
117
+ * A form's areas by colour, a layer each: shaded by a number of their own,
118
+ * the one the record's field holds drawn strong, a press choosing one into
119
+ * that field where the person may change it; on the map or off it as each
120
+ * layer's `invisible` says.
121
+ */
122
+ export declare function formRegions(options: {
123
+ sets: Record<string, unknown> | undefined;
124
+ layers: readonly MapLayer[];
125
+ box: MapBox;
126
+ map: Leaflet.Map;
127
+ L: LeafletModule;
128
+ document: Document;
129
+ locale: Locale;
130
+ labels: MapLabels;
131
+ /** Whether a person may write a field now: a press chooses only where they may. */
132
+ writable(field: string): boolean;
133
+ choose(field: string, value: string | number): void;
134
+ }): {
135
+ update(values: Record<string, unknown>, hidden: (layerId: string) => boolean): void;
136
+ destroy(): void;
137
+ };