@nika-js/onlymap 0.7.6 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/README.md +3 -1
- package/dist/{LercDecode.es-sRuTJq5Y.js → LercDecode.es--DH6_OFE.js} +1 -1
- package/dist/{basemap-BCGMmc2F.js → basemap-2ZZ_9gFn.js} +528 -502
- package/dist/basemap.d.ts +15 -0
- package/dist/cartograph/api.d.ts +45 -0
- package/dist/cartograph/atlas.d.ts +50 -0
- package/dist/cartograph/core-loader.d.ts +35 -0
- package/dist/cartograph/cvd.d.ts +106 -0
- package/dist/cartograph/element-base.d.ts +26 -0
- package/dist/cartograph/elements/om-atlas.d.ts +47 -0
- package/dist/cartograph/elements/om-cartograph.d.ts +76 -0
- package/dist/cartograph/elements/om-frame.d.ts +111 -0
- package/dist/cartograph/elements/om-graticule.d.ts +24 -0
- package/dist/cartograph/elements/om-image.d.ts +12 -0
- package/dist/cartograph/elements/om-legend.d.ts +16 -0
- package/dist/cartograph/elements/om-north.d.ts +11 -0
- package/dist/cartograph/elements/om-scalebar.d.ts +13 -0
- package/dist/cartograph/elements/om-shape.d.ts +16 -0
- package/dist/cartograph/elements/om-text.d.ts +9 -0
- package/dist/cartograph/georef.d.ts +126 -0
- package/dist/cartograph/graticule.d.ts +70 -0
- package/dist/cartograph/html-data.d.ts +27 -0
- package/dist/cartograph/index.d.ts +1 -0
- package/dist/cartograph/layout.d.ts +23 -0
- package/dist/cartograph/legend.d.ts +75 -0
- package/dist/cartograph/live-frame.d.ts +69 -0
- package/dist/cartograph/paint.d.ts +54 -0
- package/dist/cartograph/refs.d.ts +33 -0
- package/dist/cartograph/render.d.ts +64 -0
- package/dist/cartograph/scalebar.d.ts +91 -0
- package/dist/cartograph/schema.d.ts +38 -0
- package/dist/cartograph/standalone.d.ts +1 -0
- package/dist/cartograph/textlayout.d.ts +45 -0
- package/dist/cartograph/tokens.d.ts +45 -0
- package/dist/cartograph/url-actions.d.ts +26 -0
- package/dist/cartograph/validate.d.ts +38 -0
- package/dist/cartograph/zip.d.ts +22 -0
- package/dist/cartograph.css +1 -0
- package/dist/cartograph.js +2768 -0
- package/dist/cartograph.standalone.js +7640 -0
- package/dist/crs-DsDQ4Q4i.js +71 -0
- package/dist/download.d.ts +6 -0
- package/dist/feature-access.d.ts +14 -0
- package/dist/field-access.d.ts +1 -10
- package/dist/{geoparquet-CK9IjEjH.js → geoparquet-DDZzee5Z.js} +1 -1
- package/dist/{index-CNRYpb0x.js → index-BGOblzWN.js} +5134 -5070
- package/dist/index-CcLC9jE5.js +4798 -0
- package/dist/{index-CIEJyv5x.js → index-CeoVzd6t.js} +1 -1
- package/dist/{index-CW3bdfHB.js → index-Cz1VsWzO.js} +2 -2
- package/dist/{index-CN_tn36D.js → index-DkxFFflv.js} +1 -1
- package/dist/{index-C1ZQGJxa.js → index-DphcuoPv.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/{lerc-Bb2JZ92v.js → lerc-D7TfTy6l.js} +2 -2
- package/dist/onlymap.standalone.js +8820 -8730
- package/dist/onlymapjs.js +12 -11
- package/dist/{raster-DpYssEe7.js → raster-D_3YKFZz.js} +3 -3
- package/dist/{raster-pipeline-BVCB-0yq.js → raster-pipeline-D1yeoJo2.js} +1 -1
- package/dist/runtime-core.d.ts +30 -0
- package/dist/snapshot.d.ts +17 -0
- package/dist/units.d.ts +9 -0
- package/dist/version.d.ts +1 -1
- package/dist/{zarr-DNb4iqbL.js → zarr-DH5Ntt5U.js} +2 -2
- package/docs/cartograph.md +396 -0
- package/llms.txt +1 -0
- package/onlymapjs.html-data.json +974 -0
- package/package.json +13 -2
- package/skills/onlymapjs/SKILL.md +2 -2
- package/skills/onlymapjs/references/syntax.md +26 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame georeferencing — the ONE interface every piece of furniture consumes
|
|
3
|
+
* (scale bar, north indicator, graticule, locator inset, tokens), so nothing
|
|
4
|
+
* else has to know whether a frame is a live map or a static raster.
|
|
5
|
+
*
|
|
6
|
+
* THE LOAD-BEARING DECISION: the projective homography is fitted in the
|
|
7
|
+
* frame's CRS PLANE, never in raw lon/lat.
|
|
8
|
+
*
|
|
9
|
+
* A map image is a projective (perspective) transform of its own projection
|
|
10
|
+
* plane — that is exactly what a pitched camera does to flat map coordinates,
|
|
11
|
+
* and what a north-up crop does trivially. It is NOT a projective transform of
|
|
12
|
+
* longitude/latitude: Web Mercator's y = R·ln(tan(π/4+φ/2)) is nonlinear in
|
|
13
|
+
* latitude, so a homography fitted to four lon/lat corners reproduces those
|
|
14
|
+
* four corners exactly and misplaces every interior point, with error growing
|
|
15
|
+
* with the frame's latitude span (a 40°-span frame is off by millimetres at
|
|
16
|
+
* the centre — a visibly wrong graticule). Fitting in the CRS plane makes the
|
|
17
|
+
* corner fit exact AND the interior correct:
|
|
18
|
+
*
|
|
19
|
+
* project(lngLat) = H · crsForward(lngLat)
|
|
20
|
+
*
|
|
21
|
+
* `crsForward` is closed-form for the two CRSs that need no dependency —
|
|
22
|
+
* EPSG:3857 (spherical Mercator) and EPSG:4326 (plate carrée, where the
|
|
23
|
+
* forward IS identity, which is why a naive lon/lat fit happens to be right
|
|
24
|
+
* for equirectangular rasters and wrong for everything else). Any other
|
|
25
|
+
* projected CRS resolves through the core's lazy proj4 (src/crs.ts) — the
|
|
26
|
+
* only reason this module ever awaits anything.
|
|
27
|
+
*/
|
|
28
|
+
import type { LngLat } from "../geodesy";
|
|
29
|
+
/** A planar coordinate in a CRS (metres for projected CRSs, degrees for geographic). */
|
|
30
|
+
export type PlaneXY = [number, number];
|
|
31
|
+
/** Forward/inverse pair for a frame's CRS — the plane the homography is fitted in. */
|
|
32
|
+
export interface CrsForward {
|
|
33
|
+
code: string;
|
|
34
|
+
forward(lngLat: LngLat): PlaneXY;
|
|
35
|
+
inverse(xy: PlaneXY): LngLat;
|
|
36
|
+
}
|
|
37
|
+
export declare function mercatorForward([lng, lat]: LngLat): PlaneXY;
|
|
38
|
+
export declare function mercatorInverse([x, y]: PlaneXY): LngLat;
|
|
39
|
+
/** Normalize "3857" / "epsg:3857" / "EPSG::3857" to "EPSG:3857"; null when unparseable. */
|
|
40
|
+
export declare function normalizeCrs(raw: string | null | undefined): string | null;
|
|
41
|
+
/** The CRSs this module handles with no proj4 load. */
|
|
42
|
+
export declare function builtinCrsForward(crs: string | null | undefined): CrsForward | null;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve any CRS to a forward/inverse pair, loading proj4 (lazily, through
|
|
45
|
+
* the core's own module) only for codes without a closed form. `crsDef` is an
|
|
46
|
+
* authored "+proj=…" string for CRSs the library does not bundle.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveCrsForward(crs: string | null | undefined, crsDef?: string | null): Promise<CrsForward | null>;
|
|
49
|
+
/** Formulaic UTM definition — the zones no regional table can enumerate. */
|
|
50
|
+
export declare function utmDef(zone: number, hemisphere: "N" | "S"): string | null;
|
|
51
|
+
/** Row-major 3×3: [a,b,c, d,e,f, g,h,i] mapping [x,y,1] → [u,v,w]. */
|
|
52
|
+
export type Mat3 = readonly [number, number, number, number, number, number, number, number, number];
|
|
53
|
+
/**
|
|
54
|
+
* The homography taking four source points to four destination points.
|
|
55
|
+
* Sources are normalized (centroid-shifted, isotropically scaled) before the
|
|
56
|
+
* fit — CRS-plane coordinates run to ±20 000 000 m, and an unnormalized 8×8
|
|
57
|
+
* DLT on those loses most of its precision.
|
|
58
|
+
*/
|
|
59
|
+
export declare function homographyFromQuad(src: readonly PlaneXY[], dst: readonly PlaneXY[]): {
|
|
60
|
+
matrix: Mat3;
|
|
61
|
+
normalize: (p: PlaneXY) => PlaneXY;
|
|
62
|
+
denormalize: (p: PlaneXY) => PlaneXY;
|
|
63
|
+
} | null;
|
|
64
|
+
export declare function applyMat3(m: Mat3, [x, y]: PlaneXY): PlaneXY | null;
|
|
65
|
+
export declare function invertMat3(m: Mat3): Mat3 | null;
|
|
66
|
+
export interface FrameGeoref {
|
|
67
|
+
/** The frame's CRS, normalized (e.g. "EPSG:3857"). */
|
|
68
|
+
readonly crs: string;
|
|
69
|
+
/** The frame's four corners in lon/lat, order tl, tr, br, bl. */
|
|
70
|
+
readonly corners: readonly [LngLat, LngLat, LngLat, LngLat];
|
|
71
|
+
readonly widthMm: number;
|
|
72
|
+
readonly heightMm: number;
|
|
73
|
+
/**
|
|
74
|
+
* True when the corner quad could not produce a usable mapping (collinear
|
|
75
|
+
* corners, a near-horizon capture). Furniture must refuse to draw rather
|
|
76
|
+
* than draw something wrong; the validator surfaces it.
|
|
77
|
+
*/
|
|
78
|
+
readonly degenerate: boolean;
|
|
79
|
+
/** Lon/lat → millimetres from the FRAME's top-left. Null when off the projective horizon. */
|
|
80
|
+
project(lngLat: LngLat): {
|
|
81
|
+
xMm: number;
|
|
82
|
+
yMm: number;
|
|
83
|
+
} | null;
|
|
84
|
+
/** Millimetres from the frame's top-left → lon/lat. Null when unmappable. */
|
|
85
|
+
unproject(xMm: number, yMm: number): LngLat | null;
|
|
86
|
+
/** Ground metres per page millimetre along the frame's horizontal centre line. */
|
|
87
|
+
groundMetersPerMm(): number;
|
|
88
|
+
}
|
|
89
|
+
export interface CreateGeorefOptions {
|
|
90
|
+
corners: readonly [LngLat, LngLat, LngLat, LngLat];
|
|
91
|
+
widthMm: number;
|
|
92
|
+
heightMm: number;
|
|
93
|
+
/** Resolved CRS plane; defaults to Web Mercator (what live frames always are). */
|
|
94
|
+
crsForward?: CrsForward | null;
|
|
95
|
+
}
|
|
96
|
+
export declare function createFrameGeoref({ corners, widthMm, heightMm, crsForward }: CreateGeorefOptions): FrameGeoref;
|
|
97
|
+
/**
|
|
98
|
+
* Parse a `corners=` attribute: JSON `[[lng,lat],[lng,lat],[lng,lat],[lng,lat]]`
|
|
99
|
+
* (tl, tr, br, bl) or a flat list of eight numbers in the same order. Returns
|
|
100
|
+
* null for anything else — the validator turns that into an error, because a
|
|
101
|
+
* frame placed from bad corners looks exactly like one placed from good ones.
|
|
102
|
+
*/
|
|
103
|
+
export declare function parseCorners(raw: string | null | undefined): [LngLat, LngLat, LngLat, LngLat] | null;
|
|
104
|
+
/**
|
|
105
|
+
* Which way is north, in degrees clockwise from the frame's "up".
|
|
106
|
+
*
|
|
107
|
+
* Derived from the georeference rather than from a `bearing` attribute, so it
|
|
108
|
+
* is correct for every frame kind — a rotated capture, a projected static
|
|
109
|
+
* raster whose grid north differs from true north, a pitched view. Null when
|
|
110
|
+
* the frame has no usable mapping.
|
|
111
|
+
*/
|
|
112
|
+
export declare function northBearingDegrees(georef: FrameGeoref): number | null;
|
|
113
|
+
/**
|
|
114
|
+
* Relative scale variation across a frame's width, as a fraction (0.02 = 2%).
|
|
115
|
+
*
|
|
116
|
+
* The quantity a scale bar's honesty depends on: in Mercator the ground
|
|
117
|
+
* distance per page millimetre changes with sec(latitude), so a bar is only
|
|
118
|
+
* true near its anchor. Measuring the actual variation is the right test —
|
|
119
|
+
* a 5° span at the equator is harmless (~0.4%) while 3° at 60°N is not (~9%).
|
|
120
|
+
*/
|
|
121
|
+
export declare function scaleVariation(georef: FrameGeoref): number | null;
|
|
122
|
+
/**
|
|
123
|
+
* Corners for a north-up frame from a lon/lat bounding box — the shape a
|
|
124
|
+
* producer has when it exported a plain map view rather than a pitched one.
|
|
125
|
+
*/
|
|
126
|
+
export declare function cornersFromBounds(west: number, south: number, east: number, north: number): [LngLat, LngLat, LngLat, LngLat];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graticule generation — coordinate grid lines over a frame.
|
|
3
|
+
*
|
|
4
|
+
* Two grids, one algorithm. A GEOGRAPHIC graticule draws meridians and
|
|
5
|
+
* parallels at whole degrees; a PROJECTED grid (a UTM sheet's 1 km squares)
|
|
6
|
+
* draws lines at whole eastings and northings in the frame's projected CRS.
|
|
7
|
+
* Both are "walk a regular lattice in some coordinate system, project each
|
|
8
|
+
* point through the frame's georeference, clip to the frame" — so both come
|
|
9
|
+
* out of `buildGraticule` with the grid CRS supplying the lattice.
|
|
10
|
+
*
|
|
11
|
+
* Lines are DENSIFIED rather than drawn corner to corner: a straight line in
|
|
12
|
+
* one coordinate system is a curve in another, and a two-point meridian would
|
|
13
|
+
* visibly bow away from the truth on a wide or rotated frame.
|
|
14
|
+
*/
|
|
15
|
+
import type { LngLat } from "../geodesy";
|
|
16
|
+
import type { FrameGeoref } from "./georef";
|
|
17
|
+
export interface GraticuleLine {
|
|
18
|
+
/** Points in frame millimetres. */
|
|
19
|
+
points: {
|
|
20
|
+
xMm: number;
|
|
21
|
+
yMm: number;
|
|
22
|
+
}[];
|
|
23
|
+
/** The coordinate value this line marks (degrees, or projected metres). */
|
|
24
|
+
value: number;
|
|
25
|
+
axis: "x" | "y";
|
|
26
|
+
}
|
|
27
|
+
export interface GraticuleLabel {
|
|
28
|
+
text: string;
|
|
29
|
+
xMm: number;
|
|
30
|
+
yMm: number;
|
|
31
|
+
/** Which frame edge the label hangs off. */
|
|
32
|
+
edge: "top" | "bottom" | "left" | "right";
|
|
33
|
+
axis: "x" | "y";
|
|
34
|
+
}
|
|
35
|
+
export interface Graticule {
|
|
36
|
+
lines: GraticuleLine[];
|
|
37
|
+
labels: GraticuleLabel[];
|
|
38
|
+
/** The interval actually used, after any automatic choice. */
|
|
39
|
+
intervalX: number;
|
|
40
|
+
intervalY: number;
|
|
41
|
+
}
|
|
42
|
+
/** A plane the lattice is walked in: identity for geographic, proj4 for projected. */
|
|
43
|
+
export interface GridPlane {
|
|
44
|
+
/** Grid coordinates → lon/lat, so the georeference can place them. */
|
|
45
|
+
toLngLat(x: number, y: number): LngLat;
|
|
46
|
+
/** Lon/lat → grid coordinates, for working out which lines are in view. */
|
|
47
|
+
fromLngLat(lngLat: LngLat): [number, number];
|
|
48
|
+
}
|
|
49
|
+
/** The geographic plane: grid coordinates ARE lon/lat. */
|
|
50
|
+
export declare const GEOGRAPHIC_PLANE: GridPlane;
|
|
51
|
+
/** A "nice" interval (1-2-5 × 10ⁿ) giving roughly `target` divisions across a span. */
|
|
52
|
+
export declare function niceInterval(span: number, target?: number): number;
|
|
53
|
+
export interface BuildGraticuleOptions {
|
|
54
|
+
georef: FrameGeoref;
|
|
55
|
+
plane?: GridPlane;
|
|
56
|
+
/** Grid spacing in the plane's units; omitted, a nice interval is chosen. */
|
|
57
|
+
interval?: number | null;
|
|
58
|
+
intervalX?: number | null;
|
|
59
|
+
intervalY?: number | null;
|
|
60
|
+
labels?: "none" | "inside" | "outside";
|
|
61
|
+
/** Formats a coordinate value for its label. */
|
|
62
|
+
format?: (value: number, axis: "x" | "y") => string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Build the grid for a frame. Returns null when the frame has no usable
|
|
66
|
+
* georeference — furniture must then refuse to draw rather than guess.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildGraticule({ georef, plane, interval, intervalX, intervalY, labels, format }: BuildGraticuleOptions): Graticule | null;
|
|
69
|
+
/** Degrees-minutes-seconds, for a geographic graticule that wants it. */
|
|
70
|
+
export declare function formatDms(value: number, axis: "x" | "y"): string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CartographAttrData {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
values?: {
|
|
5
|
+
name: string;
|
|
6
|
+
}[];
|
|
7
|
+
}
|
|
8
|
+
export interface CartographTagData {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
attributes: CartographAttrData[];
|
|
12
|
+
references?: {
|
|
13
|
+
name: string;
|
|
14
|
+
url: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Descriptions for the placement attributes every placed element carries
|
|
19
|
+
* (schema.ts PLACED_ATTR_NAMES). Millimetres from the page top-left.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PLACED_COMMON: CartographAttrData[];
|
|
22
|
+
/**
|
|
23
|
+
* The cartograph tags for `onlymapjs.html-data.json` — merged into
|
|
24
|
+
* buildHtmlCustomData()'s output by src/html-data.ts so ONE custom-data
|
|
25
|
+
* file ships for the whole package.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCartographHtmlData(): CartographTagData[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./api";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page geometry — the one place millimetres meet pixels.
|
|
3
|
+
*
|
|
4
|
+
* CSS defines 1mm ≡ 96/25.4 px, so elements positioned with mm CSS units
|
|
5
|
+
* agree between screen preview and print BY CONSTRUCTION; this constant
|
|
6
|
+
* exists only for canvas compositing (renderCartograph) and tests. Nothing
|
|
7
|
+
* else in src/cartograph/ multiplies by 3.78.
|
|
8
|
+
*/
|
|
9
|
+
export declare const PX_PER_MM: number;
|
|
10
|
+
export interface PageSizeMm {
|
|
11
|
+
widthMm: number;
|
|
12
|
+
heightMm: number;
|
|
13
|
+
}
|
|
14
|
+
/** Page presets in PORTRAIT mm — orientation="landscape" swaps them. */
|
|
15
|
+
export declare const PAGE_PRESETS: Readonly<Record<string, PageSizeMm>>;
|
|
16
|
+
export declare const DEFAULT_PAGE_SIZE = "A4";
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the page box from the authored attributes. Explicit width+height
|
|
19
|
+
* (custom size) win over the preset; an unknown preset falls back to A4 so
|
|
20
|
+
* the page still renders (the validator flags it — Phase 1). Orientation
|
|
21
|
+
* applies to presets only: custom dimensions are already oriented.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolvePageSize(size: string | null, width: string | null, height: string | null, orientation: string | null): PageSizeMm;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Legend derivation.
|
|
3
|
+
*
|
|
4
|
+
* THE INVARIANT: a cartograph legend is never stored. Its rows are computed
|
|
5
|
+
* from the frame's map at render time, so the legend cannot drift out of
|
|
6
|
+
* agreement with the map it describes — the failure class that makes printed
|
|
7
|
+
* cartography untrustworthy, and the one QGIS still has.
|
|
8
|
+
*
|
|
9
|
+
* The source of truth is the mounted map's own IR (`getLayers()` →
|
|
10
|
+
* `meta.legend`, `meta.label`, `meta.color`), NOT a re-parse of the authored
|
|
11
|
+
* colour attributes. That matters: `meta.legend` is also populated by the
|
|
12
|
+
* `classify=` family (class breaks computed from the DATA, which no static
|
|
13
|
+
* read of the attributes could reproduce) and by rasters resolving their
|
|
14
|
+
* domain at load. Re-deriving from source would silently disagree with the
|
|
15
|
+
* map for exactly the layers whose symbology was computed rather than
|
|
16
|
+
* authored.
|
|
17
|
+
*/
|
|
18
|
+
import type { LegendSpec } from "../legend-spec";
|
|
19
|
+
/** The swatch a row draws — the geometry a reader expects for that layer kind. */
|
|
20
|
+
export type SwatchShape = "polygon" | "line" | "point" | "ramp";
|
|
21
|
+
export interface LegendRow {
|
|
22
|
+
layerId: string;
|
|
23
|
+
label: string;
|
|
24
|
+
/** Primary colour for the row's swatch. */
|
|
25
|
+
color: string;
|
|
26
|
+
shape: SwatchShape;
|
|
27
|
+
/** Full symbology detail (ramp stops, class or category entries), when the layer has one. */
|
|
28
|
+
legend?: LegendSpec;
|
|
29
|
+
/** Sort key from an `order=` override; rows without one keep map order. */
|
|
30
|
+
order?: number;
|
|
31
|
+
}
|
|
32
|
+
/** The subset of a layer's IR the legend needs — structurally what `getLayers()` returns. */
|
|
33
|
+
export interface LegendLayerInput {
|
|
34
|
+
id: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
meta?: {
|
|
37
|
+
label?: string;
|
|
38
|
+
color?: string;
|
|
39
|
+
legend?: LegendSpec;
|
|
40
|
+
};
|
|
41
|
+
props?: {
|
|
42
|
+
visible?: boolean;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function swatchShapeFor(layer: LegendLayerInput): SwatchShape;
|
|
47
|
+
/** The row swatch, matching the core legend widget's own rule exactly. */
|
|
48
|
+
export declare function rowColorFor(layer: LegendLayerInput): string;
|
|
49
|
+
/** Categories beyond this many collapse into a "+N more" row — a printed legend has finite space. */
|
|
50
|
+
export declare const MAX_CATEGORY_ENTRIES = 12;
|
|
51
|
+
export interface LegendRowOverride {
|
|
52
|
+
layer: string;
|
|
53
|
+
/** Which derived row to target: a category value or class label. Absent targets the layer's row. */
|
|
54
|
+
match?: string;
|
|
55
|
+
label?: string;
|
|
56
|
+
hide?: boolean;
|
|
57
|
+
order?: number;
|
|
58
|
+
color?: string;
|
|
59
|
+
shape?: SwatchShape;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Derive the rows for a legend from a frame's layers, applying overrides.
|
|
63
|
+
*
|
|
64
|
+
* Hidden layers are omitted (the core legend widget shows the layer row but
|
|
65
|
+
* not its symbology; on paper an invisible layer has no business appearing at
|
|
66
|
+
* all).
|
|
67
|
+
*/
|
|
68
|
+
export declare function deriveLegendRows(layers: readonly LegendLayerInput[], overrides?: readonly LegendRowOverride[]): LegendRow[];
|
|
69
|
+
/** Read `<om-legend-row>` children into override records. */
|
|
70
|
+
export declare function readOverrides(legendEl: Element): LegendRowOverride[];
|
|
71
|
+
/**
|
|
72
|
+
* Literal rows for a legend that cannot derive — a static frame has no layers,
|
|
73
|
+
* so the producer writes the rows and marks the legend `derived="false"`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readLiteralRows(legendEl: Element): LegendRow[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mounting a live map inside a frame, and turning its camera into a
|
|
3
|
+
* georeference.
|
|
4
|
+
*
|
|
5
|
+
* Two jobs the frame element delegates here so its own file stays about
|
|
6
|
+
* placement and rendering:
|
|
7
|
+
*
|
|
8
|
+
* 1. SANITIZED ADOPTION of a map manifest fetched from `src=`. A cartograph
|
|
9
|
+
* may reference a map document from anywhere, and the fetched HTML is
|
|
10
|
+
* rebuilt element by element rather than assigned through innerHTML:
|
|
11
|
+
* scripts are dropped except the inert `application/json` blocks that
|
|
12
|
+
* carry inline layer data, and `on*` handler attributes never survive.
|
|
13
|
+
* An inline `<om-map>` CHILD is not sanitized — the author already owns
|
|
14
|
+
* that document.
|
|
15
|
+
* 2. CORNERS FROM THE CAMERA. A live frame's georeference comes from its own
|
|
16
|
+
* `center/zoom/bearing/pitch` and its pixel box, through the core's
|
|
17
|
+
* WebMercatorViewport — the same viewport class the renderer uses, so the
|
|
18
|
+
* furniture agrees with the pixels.
|
|
19
|
+
*/
|
|
20
|
+
import type { LngLat } from "../geodesy";
|
|
21
|
+
import { type CoreModule } from "./core-loader";
|
|
22
|
+
export type Corners = [LngLat, LngLat, LngLat, LngLat];
|
|
23
|
+
/**
|
|
24
|
+
* Rebuild a fetched subtree as fresh elements, keeping only inert content.
|
|
25
|
+
* Returns null if the source has nothing to adopt.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sanitizeAdoptedMap(source: Element, doc: Document): Element | null;
|
|
28
|
+
/** Fetch a map document and return its `<om-map>`, sanitized and ready to mount. */
|
|
29
|
+
export declare function fetchMapElement(src: string, doc?: Document): Promise<Element>;
|
|
30
|
+
export interface FrameCamera {
|
|
31
|
+
center: LngLat;
|
|
32
|
+
zoom: number;
|
|
33
|
+
bearing: number;
|
|
34
|
+
pitch: number;
|
|
35
|
+
}
|
|
36
|
+
/** Read the frame's own camera; null when it has not authored one. */
|
|
37
|
+
export declare function readCamera(el: Element): FrameCamera | null;
|
|
38
|
+
/**
|
|
39
|
+
* The four ground corners a camera sees in a `widthPx × heightPx` box, in
|
|
40
|
+
* tl, tr, br, bl order.
|
|
41
|
+
*
|
|
42
|
+
* Null when the view cannot be georeferenced as a quad: past roughly 60° of
|
|
43
|
+
* pitch the top edge unprojects at or beyond the horizon, where "the ground
|
|
44
|
+
* under this pixel" has no finite answer. Returning null there — rather than
|
|
45
|
+
* a huge number that looks like a coordinate — is what lets the frame mark
|
|
46
|
+
* itself degenerate and the furniture refuse to draw.
|
|
47
|
+
*/
|
|
48
|
+
export declare function cornersFromCamera(core: CoreModule, camera: FrameCamera, widthPx: number, heightPx: number): Corners | null;
|
|
49
|
+
/** Load the core and build an `<om-map>` for a frame, from `src=` or an inline child. */
|
|
50
|
+
export declare function buildLiveMap(frame: Element): Promise<{
|
|
51
|
+
core: CoreModule;
|
|
52
|
+
map: Element;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Stamp the FRAME's camera onto the map. The frame's camera is authoritative
|
|
56
|
+
* by design: the same map document can appear twice on one page at different
|
|
57
|
+
* extents (the locator-inset pattern), so the document's own center/zoom are
|
|
58
|
+
* deliberately ignored.
|
|
59
|
+
*
|
|
60
|
+
* TWO steps, and the second is not optional. `<om-map>` reads center/zoom ONCE,
|
|
61
|
+
* as its initial view — its attribute observer deliberately does not watch the
|
|
62
|
+
* camera, so that a manifest edit never fights a user's pan. An inline
|
|
63
|
+
* `<om-map>` child has therefore already booted at ITS OWN camera by the time a
|
|
64
|
+
* frame mounts it, and writing the attributes alone leaves the map sitting
|
|
65
|
+
* wherever the document said (for a fresh document, [0,0] at zoom 1 — an empty
|
|
66
|
+
* ocean). So the attributes are written for the DOM's sake (serialization,
|
|
67
|
+
* agents, re-mounts) and the live camera is driven through the runtime.
|
|
68
|
+
*/
|
|
69
|
+
export declare function applyCamera(map: Element, camera: FrameCamera): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canvas paint contract used by `renderCartograph`.
|
|
3
|
+
*
|
|
4
|
+
* Every placed element knows how to draw itself onto a 2D context at an
|
|
5
|
+
* arbitrary scale, so export is a walk over the page's own children rather
|
|
6
|
+
* than a second, drifting description of the page. Painters read their
|
|
7
|
+
* COMPUTED style — the browser has already resolved theme custom properties,
|
|
8
|
+
* attribute overrides and inherited values, so the exported pixels use exactly
|
|
9
|
+
* the values the screen and the printed page use.
|
|
10
|
+
*/
|
|
11
|
+
export interface PaintEnv {
|
|
12
|
+
/** Canvas pixels per CSS pixel — dpi / 96. */
|
|
13
|
+
scale: number;
|
|
14
|
+
/** Canvas pixels per millimetre — dpi / 25.4. */
|
|
15
|
+
pxPerMm: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function paintEnvForDpi(dpi: number): PaintEnv;
|
|
18
|
+
export interface CartographPainter {
|
|
19
|
+
/**
|
|
20
|
+
* Draw this element. The context is pre-translated to the element's own
|
|
21
|
+
* top-left corner and rotated by its `rotation`, so painters draw in their
|
|
22
|
+
* own local box starting at (0, 0), in CANVAS pixels.
|
|
23
|
+
*/
|
|
24
|
+
paintCartograph(ctx: CanvasRenderingContext2D, env: PaintEnv): void;
|
|
25
|
+
/**
|
|
26
|
+
* Optional async pre-pass, awaited before ANY element paints. A live frame
|
|
27
|
+
* uses it to re-render its map at the export resolution; painting itself
|
|
28
|
+
* stays synchronous so the composite order is trivially deterministic.
|
|
29
|
+
*/
|
|
30
|
+
prepareCartograph?(env: PaintEnv): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Drop anything `prepareCartograph` produced, so a failed pre-pass paints
|
|
33
|
+
* nothing rather than the previous page's stale capture.
|
|
34
|
+
*/
|
|
35
|
+
discardCartograph?(): void;
|
|
36
|
+
}
|
|
37
|
+
export declare function isPainter(node: unknown): node is Element & CartographPainter;
|
|
38
|
+
export interface BoxMm {
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
w: number;
|
|
42
|
+
h: number;
|
|
43
|
+
rotation: number;
|
|
44
|
+
z: number | null;
|
|
45
|
+
}
|
|
46
|
+
/** An element's authored box in millimetres — the placement source of truth. */
|
|
47
|
+
export declare function boxMm(el: Element): BoxMm;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a computed-style length (e.g. "12.5px") to a number, with a
|
|
50
|
+
* fallback for the values happy-dom and other partial DOMs leave empty.
|
|
51
|
+
*/
|
|
52
|
+
export declare function styleLength(value: string | null | undefined, fallback?: number): number;
|
|
53
|
+
/** A CSS colour that actually paints — treats empty/transparent as "skip". */
|
|
54
|
+
export declare function paintableColor(value: string | null | undefined): string | null;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-element references (`for=`, `overview-of=`, atlas `for=`) and the
|
|
3
|
+
* shared "this reference goes nowhere" rendering.
|
|
4
|
+
*
|
|
5
|
+
* A dangling reference is a validation ERROR and renders a visible inline
|
|
6
|
+
* placeholder, never nothing: a scale bar that silently vanishes because its
|
|
7
|
+
* frame id has a typo is indistinguishable, on a printed page, from a scale
|
|
8
|
+
* bar the author chose not to include.
|
|
9
|
+
*/
|
|
10
|
+
import type { OmFrameElement } from "./elements/om-frame";
|
|
11
|
+
/** The `<om-cartograph>` an element belongs to. */
|
|
12
|
+
export declare function ownerCartograph(el: Element): Element | null;
|
|
13
|
+
/** Resolve a frame id within the same cartograph. */
|
|
14
|
+
export declare function findFrame(el: Element, id: string | null | undefined): OmFrameElement | null;
|
|
15
|
+
/** Every frame on the page, in document order. */
|
|
16
|
+
export declare function allFrames(root: Element): OmFrameElement[];
|
|
17
|
+
/** The frame a `for=`-less furniture element implicitly binds to (only when there is exactly one). */
|
|
18
|
+
export declare function soleFrame(el: Element): OmFrameElement | null;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the frame this element draws for: an explicit `for=`, or the page's
|
|
21
|
+
* only frame when `for=` is absent.
|
|
22
|
+
*/
|
|
23
|
+
export declare function referencedFrame(el: Element): OmFrameElement | null;
|
|
24
|
+
/** Show the inline "unlinked" note, replacing whatever the element rendered. */
|
|
25
|
+
export declare function showUnlinked(el: HTMLElement, message: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Drop the "unlinked" note once the element can render for real. Elements that
|
|
28
|
+
* rebuild their whole subtree get this for free; one that APPENDS its output
|
|
29
|
+
* (om-legend, which must preserve its authored config children) has to say so,
|
|
30
|
+
* or the page keeps a stale "waiting for its frame" line above a legend that
|
|
31
|
+
* is already showing rows.
|
|
32
|
+
*/
|
|
33
|
+
export declare function clearUnlinked(el: Element): void;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { OmCartographElement } from "./elements/om-cartograph";
|
|
2
|
+
/**
|
|
3
|
+
* Conservative canvas ceilings. Browsers differ (Chrome allows ~2^28 px of
|
|
4
|
+
* area, iOS Safari far less), and exceeding them fails SILENTLY — a blank or
|
|
5
|
+
* null-blob export. Clamping to a documented budget and REPORTING the dpi
|
|
6
|
+
* actually delivered beats handing back an empty page.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MAX_CANVAS_DIMENSION = 16384;
|
|
9
|
+
export declare const MAX_CANVAS_AREA = 134217728;
|
|
10
|
+
export interface RenderCartographOptions {
|
|
11
|
+
/** Target resolution; defaults to the page's dpi= attribute, else 300. */
|
|
12
|
+
dpi?: number;
|
|
13
|
+
format?: "png" | "jpeg";
|
|
14
|
+
/** JPEG quality 0–1 (ignored for PNG). */
|
|
15
|
+
quality?: number;
|
|
16
|
+
/** Override the canvas ceilings (tests, hosts that know their environment). */
|
|
17
|
+
maxDimension?: number;
|
|
18
|
+
maxArea?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Simulate a colour-vision deficiency in the exported raster
|
|
21
|
+
* (NikaGeospatial/onlymap-js#42). Default
|
|
22
|
+
* NONE — even when the page is being previewed under `cvd=`, because an
|
|
23
|
+
* export is a deliverable and a simulated deliverable helps nobody. Pass
|
|
24
|
+
* `"page"` to honour the page's own `cvd=`, or a type to force one.
|
|
25
|
+
*/
|
|
26
|
+
cvd?: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface RenderCartographResult {
|
|
29
|
+
blob: Blob;
|
|
30
|
+
widthPx: number;
|
|
31
|
+
heightPx: number;
|
|
32
|
+
/** The resolution actually delivered — lower than requested when clamped. */
|
|
33
|
+
dpi: number;
|
|
34
|
+
/** Non-fatal problems worth surfacing to the user (clamping, skipped frames). */
|
|
35
|
+
warnings: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare function renderCartograph(page?: OmCartographElement | null, options?: RenderCartographOptions): Promise<RenderCartographResult>;
|
|
38
|
+
export interface RenderAtlasOptions extends RenderCartographOptions {
|
|
39
|
+
/** Render only this page (0-based) — the host contract for `?export=&page=i`. */
|
|
40
|
+
page?: number;
|
|
41
|
+
/** Called after each page, for progress reporting on a long run. */
|
|
42
|
+
onPage?: (index: number, count: number) => void;
|
|
43
|
+
}
|
|
44
|
+
export interface RenderAtlasResult {
|
|
45
|
+
/** One ZIP of every page, or the single page's own image when `page` was given. */
|
|
46
|
+
blob: Blob;
|
|
47
|
+
filenames: string[];
|
|
48
|
+
pages: number;
|
|
49
|
+
dpi: number;
|
|
50
|
+
warnings: string[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Render every page of an atlas.
|
|
54
|
+
*
|
|
55
|
+
* SEQUENTIALLY, and reusing the one live map: seeking moves the existing
|
|
56
|
+
* frames rather than cloning the page. Cloning would mount a fresh WebGL
|
|
57
|
+
* context per page, and browsers cap live contexts at roughly 8–16 — an atlas
|
|
58
|
+
* of any useful size would silently start printing blank pages. This way the
|
|
59
|
+
* ceiling is one map, whatever N is.
|
|
60
|
+
*
|
|
61
|
+
* The result is a single ZIP because browsers block or prompt on a loop of N
|
|
62
|
+
* downloads; a host wanting one page at a time passes `page`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function renderCartographAtlas(page?: OmCartographElement | null, options?: RenderAtlasOptions): Promise<RenderAtlasResult>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scale-bar maths — ground distance per page millimetre, and the "nice"
|
|
3
|
+
* segment lengths a printed bar is allowed to use.
|
|
4
|
+
*
|
|
5
|
+
* A scale bar whose segments read "0 · 347 · 694 m" is useless to the person
|
|
6
|
+
* holding a ruler over the page, so segment lengths snap to a 1-2-5 series
|
|
7
|
+
* (1, 2, 5, 10, 20, 50, …) in the bar's own unit system, and the bar's drawn
|
|
8
|
+
* width follows from that choice rather than the other way round.
|
|
9
|
+
*/
|
|
10
|
+
import { type UnitSystem } from "../units";
|
|
11
|
+
export type ScalebarUnits = UnitSystem;
|
|
12
|
+
export { niceNumberBelow } from "../units";
|
|
13
|
+
export interface ScalebarSpec {
|
|
14
|
+
/** Ground metres spanned by ONE segment. */
|
|
15
|
+
segmentMeters: number;
|
|
16
|
+
/** Page millimetres spanned by one segment. */
|
|
17
|
+
segmentMm: number;
|
|
18
|
+
segments: number;
|
|
19
|
+
/** Total drawn width, mm (segments × segmentMm). */
|
|
20
|
+
totalMm: number;
|
|
21
|
+
/** Tick labels, one per boundary (segments + 1 of them), in `unitLabel` units. */
|
|
22
|
+
labels: string[];
|
|
23
|
+
unitLabel: string;
|
|
24
|
+
/** The representative fraction denominator — the "1:25 000" number. */
|
|
25
|
+
scaleDenominator: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ScalebarOptions {
|
|
28
|
+
groundMetersPerMm: number;
|
|
29
|
+
/** The bar's authored box width in mm — the bar is drawn at or below this. */
|
|
30
|
+
maxWidthMm: number;
|
|
31
|
+
segments?: number;
|
|
32
|
+
units?: ScalebarUnits;
|
|
33
|
+
/** Ground metres per segment, overriding the automatic 1-2-5 choice. */
|
|
34
|
+
segmentLengthMeters?: number | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the bar. Null when there is no usable scale (a degenerate georef, a
|
|
38
|
+
* zero-width box) — furniture must render an inline "no scale" placeholder
|
|
39
|
+
* rather than a bar that lies.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildScalebar({ groundMetersPerMm, maxWidthMm, segments, units, segmentLengthMeters }: ScalebarOptions): ScalebarSpec | null;
|
|
42
|
+
export type ScalebarStyle = "single" | "double" | "line" | "ticks";
|
|
43
|
+
export interface ScalebarDrawing {
|
|
44
|
+
/** Filled/hollow segment rectangles, in mm relative to the element's box. */
|
|
45
|
+
bars: {
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
w: number;
|
|
49
|
+
h: number;
|
|
50
|
+
filled: boolean;
|
|
51
|
+
}[];
|
|
52
|
+
/** Rules and ticks, in mm. */
|
|
53
|
+
strokes: {
|
|
54
|
+
x1: number;
|
|
55
|
+
y1: number;
|
|
56
|
+
x2: number;
|
|
57
|
+
y2: number;
|
|
58
|
+
}[];
|
|
59
|
+
/** Tick labels — `y` is the text BASELINE, in mm. */
|
|
60
|
+
labels: {
|
|
61
|
+
text: string;
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
anchor: "start" | "middle" | "end";
|
|
65
|
+
}[];
|
|
66
|
+
fontMm: number;
|
|
67
|
+
strokeMm: number;
|
|
68
|
+
/** Ground metres one segment spans — carried here so a host drawing the bar needs nothing else. */
|
|
69
|
+
segmentMeters: number;
|
|
70
|
+
/** Unit the labels are in ("m", "km", "mi", "nmi"). */
|
|
71
|
+
unitLabel: string;
|
|
72
|
+
/** Representative-fraction denominator at this scale. */
|
|
73
|
+
scaleDenominator: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Turn a computed bar into geometry, ONCE. The SVG renderer and the canvas
|
|
77
|
+
* exporter both consume this, so an exported PNG and the printed page cannot
|
|
78
|
+
* disagree about where the ticks are.
|
|
79
|
+
*/
|
|
80
|
+
export declare function buildScalebarDrawing(spec: ScalebarSpec, style: ScalebarStyle, boxHeightMm: number): ScalebarDrawing;
|
|
81
|
+
/**
|
|
82
|
+
* Digit-group separator for printed scale text: U+202F NARROW NO-BREAK SPACE.
|
|
83
|
+
* ISO 31-0 / SI group digits with a thin space rather than a comma or point
|
|
84
|
+
* (which mean different things either side of the Atlantic), and the NO-BREAK
|
|
85
|
+
* variant is the one a printed page needs — "1:25 000" must never wrap across
|
|
86
|
+
* a line. Pinned by codepoint in the tests so it cannot drift to an ASCII
|
|
87
|
+
* space during a copy-edit.
|
|
88
|
+
*/
|
|
89
|
+
export declare const SCALE_GROUP_SEPARATOR = "\u202F";
|
|
90
|
+
/** "1:25 000" — the representative fraction, grouped for print. */
|
|
91
|
+
export declare function formatScaleDenominator(denominator: number): string;
|