@macrostrat/column-views 3.11.0 → 3.12.1

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 (138) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/dist/age-axis.cjs +1 -2
  3. package/dist/age-axis.cjs.map +1 -1
  4. package/dist/age-axis.js +1 -2
  5. package/dist/age-axis.js.map +1 -1
  6. package/dist/age-model/data.cjs +57 -0
  7. package/dist/age-model/data.cjs.map +1 -0
  8. package/dist/age-model/data.d.ts +29 -0
  9. package/dist/age-model/data.js +57 -0
  10. package/dist/age-model/data.js.map +1 -0
  11. package/dist/age-model/details.cjs +260 -0
  12. package/dist/age-model/details.cjs.map +1 -0
  13. package/dist/age-model/details.d.ts +39 -0
  14. package/dist/age-model/details.js +257 -0
  15. package/dist/age-model/details.js.map +1 -0
  16. package/dist/age-model/details.module.sass.cjs +42 -0
  17. package/dist/age-model/details.module.sass.cjs.map +1 -0
  18. package/dist/age-model/details.module.sass.js +40 -0
  19. package/dist/age-model/details.module.sass.js.map +1 -0
  20. package/dist/age-model/index.d.ts +5 -0
  21. package/dist/age-model/legacy.cjs +28 -0
  22. package/dist/age-model/legacy.cjs.map +1 -0
  23. package/dist/age-model/legacy.d.ts +6 -0
  24. package/dist/age-model/legacy.js +26 -0
  25. package/dist/age-model/legacy.js.map +1 -0
  26. package/dist/age-model/surfaces.cjs +354 -0
  27. package/dist/age-model/surfaces.cjs.map +1 -0
  28. package/dist/age-model/surfaces.d.ts +68 -0
  29. package/dist/age-model/surfaces.js +351 -0
  30. package/dist/age-model/surfaces.js.map +1 -0
  31. package/dist/age-model/surfaces.module.sass.cjs +45 -0
  32. package/dist/age-model/surfaces.module.sass.cjs.map +1 -0
  33. package/dist/age-model/surfaces.module.sass.js +43 -0
  34. package/dist/age-model/surfaces.module.sass.js.map +1 -0
  35. package/dist/age-model/types.cjs +229 -0
  36. package/dist/age-model/types.cjs.map +1 -0
  37. package/dist/age-model/types.d.ts +112 -0
  38. package/dist/age-model/types.js +229 -0
  39. package/dist/age-model/types.js.map +1 -0
  40. package/dist/column-views.css +406 -74
  41. package/dist/column.cjs +20 -13
  42. package/dist/column.cjs.map +1 -1
  43. package/dist/column.d.ts +21 -6
  44. package/dist/column.js +21 -14
  45. package/dist/column.js.map +1 -1
  46. package/dist/column.module.sass.cjs +19 -15
  47. package/dist/column.module.sass.cjs.map +1 -1
  48. package/dist/column.module.sass.js +19 -15
  49. package/dist/column.module.sass.js.map +1 -1
  50. package/dist/data-provider/store.cjs +34 -1
  51. package/dist/data-provider/store.cjs.map +1 -1
  52. package/dist/data-provider/store.d.ts +5 -0
  53. package/dist/data-provider/store.js +35 -2
  54. package/dist/data-provider/store.js.map +1 -1
  55. package/dist/index.cjs +78 -18
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.ts +3 -1
  58. package/dist/index.js +55 -4
  59. package/dist/index.js.map +1 -1
  60. package/dist/notes.cjs +11 -2
  61. package/dist/notes.cjs.map +1 -1
  62. package/dist/notes.d.ts +11 -1
  63. package/dist/notes.js +11 -2
  64. package/dist/notes.js.map +1 -1
  65. package/dist/prepare-units/composite-scale.cjs +18 -16
  66. package/dist/prepare-units/composite-scale.cjs.map +1 -1
  67. package/dist/prepare-units/composite-scale.js +18 -16
  68. package/dist/prepare-units/composite-scale.js.map +1 -1
  69. package/dist/prepare-units/density.cjs +83 -0
  70. package/dist/prepare-units/density.cjs.map +1 -0
  71. package/dist/prepare-units/density.d.ts +92 -0
  72. package/dist/prepare-units/density.js +83 -0
  73. package/dist/prepare-units/density.js.map +1 -0
  74. package/dist/prepare-units/index.cjs +15 -7
  75. package/dist/prepare-units/index.cjs.map +1 -1
  76. package/dist/prepare-units/index.d.ts +1 -0
  77. package/dist/prepare-units/index.js +15 -7
  78. package/dist/prepare-units/index.js.map +1 -1
  79. package/dist/prepare-units/types.cjs.map +1 -1
  80. package/dist/prepare-units/types.d.ts +34 -13
  81. package/dist/prepare-units/types.js.map +1 -1
  82. package/dist/section.cjs +108 -24
  83. package/dist/section.cjs.map +1 -1
  84. package/dist/section.d.ts +66 -6
  85. package/dist/section.js +109 -25
  86. package/dist/section.js.map +1 -1
  87. package/dist/timescale-zoom.cjs +191 -0
  88. package/dist/timescale-zoom.cjs.map +1 -0
  89. package/dist/timescale-zoom.d.ts +74 -0
  90. package/dist/timescale-zoom.js +191 -0
  91. package/dist/timescale-zoom.js.map +1 -0
  92. package/dist/unit-details/age-range.cjs +14 -43
  93. package/dist/unit-details/age-range.cjs.map +1 -1
  94. package/dist/unit-details/age-range.d.ts +1 -13
  95. package/dist/unit-details/age-range.js +6 -43
  96. package/dist/unit-details/age-range.js.map +1 -1
  97. package/dist/unit-details/utils.cjs +0 -5
  98. package/dist/unit-details/utils.cjs.map +1 -1
  99. package/dist/unit-details/utils.js +0 -5
  100. package/dist/unit-details/utils.js.map +1 -1
  101. package/dist/units/composite.cjs +4 -1
  102. package/dist/units/composite.cjs.map +1 -1
  103. package/dist/units/composite.d.ts +2 -0
  104. package/dist/units/composite.js +4 -1
  105. package/dist/units/composite.js.map +1 -1
  106. package/package.json +8 -8
  107. package/src/age-model/data.ts +98 -0
  108. package/src/age-model/details.module.sass +108 -0
  109. package/src/age-model/details.ts +333 -0
  110. package/src/age-model/index.ts +5 -0
  111. package/src/age-model/legacy.ts +30 -0
  112. package/src/age-model/surfaces.module.sass +168 -0
  113. package/src/age-model/surfaces.ts +568 -0
  114. package/src/age-model/types.ts +392 -0
  115. package/src/column.module.sass +59 -8
  116. package/src/column.ts +49 -23
  117. package/src/data-provider/store.ts +50 -1
  118. package/src/index.ts +5 -1
  119. package/src/notes.ts +20 -2
  120. package/src/prepare-units/composite-scale.ts +34 -31
  121. package/src/prepare-units/density.ts +197 -0
  122. package/src/prepare-units/index.ts +25 -6
  123. package/src/prepare-units/types.ts +34 -13
  124. package/src/section.ts +219 -33
  125. package/src/timescale-zoom.ts +341 -0
  126. package/src/unit-details/age-range.ts +12 -58
  127. package/src/units/composite.ts +11 -0
  128. package/dist/age-model-overlay.cjs +0 -50
  129. package/dist/age-model-overlay.cjs.map +0 -1
  130. package/dist/age-model-overlay.d.ts +0 -24
  131. package/dist/age-model-overlay.js +0 -48
  132. package/dist/age-model-overlay.js.map +0 -1
  133. package/dist/age-model-overlay.module.sass.cjs +0 -12
  134. package/dist/age-model-overlay.module.sass.cjs.map +0 -1
  135. package/dist/age-model-overlay.module.sass.js +0 -11
  136. package/dist/age-model-overlay.module.sass.js.map +0 -1
  137. package/src/age-model-overlay.module.sass +0 -11
  138. package/src/age-model-overlay.ts +0 -96
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const columnComponents = require("@macrostrat/column-components");
4
+ const surfaceStatuses = [
5
+ "absolute",
6
+ "relative",
7
+ "spike",
8
+ "imposed",
9
+ "modeled",
10
+ "",
11
+ "derived"
12
+ ];
13
+ const TIE_POINT_STATUSES = [
14
+ "absolute",
15
+ "relative",
16
+ "spike",
17
+ "imposed"
18
+ ];
19
+ const surfaceStatusLabels = {
20
+ absolute: "Absolute",
21
+ relative: "Relative",
22
+ spike: "Spike",
23
+ imposed: "Imposed",
24
+ modeled: "Modeled",
25
+ "": "Unspecified",
26
+ derived: "Derived"
27
+ };
28
+ const surfaceStatusDescriptions = {
29
+ absolute: "Independently dated; the age is authoritative and the interval position is derived from it",
30
+ relative: "Calibrated to a position within a geologic interval; the age follows the timescale",
31
+ spike: "Tied to a formally defined boundary point",
32
+ imposed: "Interval-relative representation imposed on a boundary with no chronostratigraphic tie of its own (for instance, thickness interpolation)",
33
+ modeled: "Interpolated by the age model between calibrated surfaces",
34
+ "": "No status recorded",
35
+ derived: "Computed from unit tops and bottoms; not stored in the column's age model"
36
+ };
37
+ function surfaceToken(value) {
38
+ if (value == null || value === "") return "unspecified";
39
+ return value.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
40
+ }
41
+ function nullifyUnitID(id) {
42
+ if (id == null || id === 0) return null;
43
+ return id;
44
+ }
45
+ const INTERVAL_BOUND_TOLERANCE = 1e-3;
46
+ function inferTiePointStatuses(surfaces, tolerance = INTERVAL_BOUND_TOLERANCE) {
47
+ const extents = sectionAgeExtents(surfaces);
48
+ return surfaces.map((surface) => {
49
+ if (surface.status !== "modeled") return surface;
50
+ if (surface.calibration == null) return surface;
51
+ const { proportion } = surface;
52
+ const onIntervalBound = proportion != null && (Math.abs(proportion) <= tolerance || Math.abs(proportion - 1) <= tolerance);
53
+ if (!onIntervalBound && !boundsPackage(surface, extents)) {
54
+ return surface;
55
+ }
56
+ return { ...surface, status: "relative", statusInferred: true };
57
+ });
58
+ }
59
+ function boundsPackage(surface, extents) {
60
+ const extent = extents.get(surface.section_id ?? null);
61
+ if (extent == null) return false;
62
+ const [youngest, oldest] = extent;
63
+ if (surface.unitsAbove.length === 0 && surface.age === youngest) return true;
64
+ return surface.unitsBelow.length === 0 && surface.age === oldest;
65
+ }
66
+ function sectionAgeExtents(surfaces) {
67
+ const extents = /* @__PURE__ */ new Map();
68
+ for (const surface of surfaces) {
69
+ const key = surface.section_id ?? null;
70
+ const extent = extents.get(key);
71
+ if (extent == null) {
72
+ extents.set(key, [surface.age, surface.age]);
73
+ continue;
74
+ }
75
+ extent[0] = Math.min(extent[0], surface.age);
76
+ extent[1] = Math.max(extent[1], surface.age);
77
+ }
78
+ return extents;
79
+ }
80
+ function boundaryToSurface(boundary) {
81
+ const above = nullifyUnitID(boundary.unit_above);
82
+ const below = nullifyUnitID(boundary.unit_below);
83
+ let calibration = null;
84
+ if (boundary.interval_id != null) {
85
+ calibration = {
86
+ id: boundary.interval_id,
87
+ name: boundary.interval_name,
88
+ b_age: boundary.age_bottom,
89
+ t_age: boundary.age_top
90
+ };
91
+ }
92
+ return {
93
+ id: boundary.boundary_id,
94
+ age: boundary.model_age,
95
+ position: boundary.boundary_position,
96
+ status: boundary.boundary_status ?? "",
97
+ type: boundary.boundary_type ?? "",
98
+ calibration,
99
+ proportion: boundary.rel_position,
100
+ unitsAbove: above == null ? [] : [above],
101
+ unitsBelow: below == null ? [] : [below],
102
+ section_id: boundary.section_id,
103
+ ref_id: boundary.ref_id,
104
+ boundary
105
+ };
106
+ }
107
+ function surfacesFromBoundaries(boundaries, options = {}) {
108
+ const { inferTiePoints = true } = options;
109
+ if (boundaries == null) return [];
110
+ let surfaces = boundaries.map(boundaryToSurface);
111
+ if (inferTiePoints) {
112
+ surfaces = inferTiePointStatuses(surfaces);
113
+ }
114
+ return surfaces.sort((a, b) => a.age - b.age);
115
+ }
116
+ function surfacesFromUnits(units, options = {}) {
117
+ const { tolerance = 1e-3, axisType = columnComponents.ColumnAxisType.AGE } = options;
118
+ if (units == null) return [];
119
+ const byPosition = isPositionAxis(axisType);
120
+ const surfaces = [];
121
+ function keyOf(surface) {
122
+ if (byPosition) return surface.position ?? null;
123
+ return surface.age;
124
+ }
125
+ function addBound(unit, top) {
126
+ const age = top ? unit.t_age : unit.b_age;
127
+ const position = asNumber(top ? unit.t_pos : unit.b_pos);
128
+ const key = byPosition ? position : age;
129
+ if (key == null) return;
130
+ let surface = surfaces.find((d) => {
131
+ const k = keyOf(d);
132
+ return k != null && Math.abs(k - key) < tolerance;
133
+ });
134
+ if (surface == null) {
135
+ surface = {
136
+ id: "",
137
+ age,
138
+ position,
139
+ status: "derived",
140
+ type: "",
141
+ calibration: null,
142
+ proportion: null,
143
+ unitsAbove: [],
144
+ unitsBelow: [],
145
+ section_id: unit.section_id
146
+ };
147
+ surfaces.push(surface);
148
+ }
149
+ if (top) {
150
+ surface.unitsBelow.push(unit.unit_id);
151
+ } else {
152
+ surface.unitsAbove.push(unit.unit_id);
153
+ }
154
+ }
155
+ for (const unit of units) {
156
+ addBound(unit, true);
157
+ addBound(unit, false);
158
+ }
159
+ surfaces.sort((a, b) => compareAlongAxis(a, b, axisType));
160
+ return surfaces.map((s, i) => ({ ...s, id: `derived-${i}` }));
161
+ }
162
+ function surfacePosition(surface, axisType) {
163
+ if (isPositionAxis(axisType)) {
164
+ return surface.position ?? null;
165
+ }
166
+ return surface.age ?? null;
167
+ }
168
+ function isPositionAxis(axisType) {
169
+ return axisType === columnComponents.ColumnAxisType.HEIGHT || axisType === columnComponents.ColumnAxisType.DEPTH;
170
+ }
171
+ function compareAlongAxis(a, b, axisType) {
172
+ const pa = surfacePosition(a, axisType);
173
+ const pb = surfacePosition(b, axisType);
174
+ if (pa == null || pb == null) return 0;
175
+ if (axisType === columnComponents.ColumnAxisType.HEIGHT) {
176
+ return pb - pa;
177
+ }
178
+ return pa - pb;
179
+ }
180
+ function formatProportion(proportion) {
181
+ if (proportion == null) return null;
182
+ return `${Math.round(proportion * 100)}%`;
183
+ }
184
+ function surfaceLabel(surface) {
185
+ const { calibration } = surface;
186
+ if (calibration != null) {
187
+ const prop = formatProportion(surface.proportion);
188
+ if (prop != null) {
189
+ return `${calibration.name} · ${prop}`;
190
+ }
191
+ return calibration.name;
192
+ }
193
+ return formatAge(surface.age);
194
+ }
195
+ function formatAge(age) {
196
+ if (age == null) return "";
197
+ let value = age;
198
+ let unit = "Ma";
199
+ if (Math.abs(age) < 1) {
200
+ value = age * 1e3;
201
+ unit = "ka";
202
+ }
203
+ const _value = value.toLocaleString("en-US", { maximumFractionDigits: 2 });
204
+ return `${_value} ${unit}`;
205
+ }
206
+ function asNumber(value) {
207
+ if (value == null || value === "") return null;
208
+ const n = Number(value);
209
+ if (Number.isNaN(n)) return null;
210
+ return n;
211
+ }
212
+ exports.INTERVAL_BOUND_TOLERANCE = INTERVAL_BOUND_TOLERANCE;
213
+ exports.TIE_POINT_STATUSES = TIE_POINT_STATUSES;
214
+ exports.boundaryToSurface = boundaryToSurface;
215
+ exports.compareAlongAxis = compareAlongAxis;
216
+ exports.formatAge = formatAge;
217
+ exports.formatProportion = formatProportion;
218
+ exports.inferTiePointStatuses = inferTiePointStatuses;
219
+ exports.isPositionAxis = isPositionAxis;
220
+ exports.nullifyUnitID = nullifyUnitID;
221
+ exports.surfaceLabel = surfaceLabel;
222
+ exports.surfacePosition = surfacePosition;
223
+ exports.surfaceStatusDescriptions = surfaceStatusDescriptions;
224
+ exports.surfaceStatusLabels = surfaceStatusLabels;
225
+ exports.surfaceStatuses = surfaceStatuses;
226
+ exports.surfaceToken = surfaceToken;
227
+ exports.surfacesFromBoundaries = surfacesFromBoundaries;
228
+ exports.surfacesFromUnits = surfacesFromUnits;
229
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","sources":["../../src/age-model/types.ts"],"sourcesContent":["/** Surfaces: the view model for a column's age model.\n *\n * A `ColumnSurface` is one horizontal boundary in a column. It is what the\n * surfaces view renders, and it is deliberately data-agnostic: a surface can\n * come from an `/age_model` boundary (`boundaryToSurface`), be derived from\n * unit tops and bottoms when a column has no age-model rows\n * (`surfacesFromUnits`), or be built by an editor from its own state.\n */\nimport type {\n AgeModelBoundary,\n BoundaryStatus,\n BoundaryType,\n UnitLong,\n} from \"@macrostrat/api-types\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\n\nexport type { AgeModelBoundary, BoundaryStatus, BoundaryType };\n\n/** A boundary status, plus `derived` for surfaces computed from units rather\n * than stored in the age model. */\nexport type SurfaceStatus = BoundaryStatus | \"derived\";\n\n/** The interval a surface is calibrated against */\nexport interface SurfaceCalibration {\n id: number;\n name: string;\n b_age: number;\n t_age: number;\n}\n\nexport interface ColumnSurface {\n id: number | string;\n /** Modeled age of the surface (Ma) */\n age: number;\n /** Measured position (height or depth) along the column, when known */\n position?: number | null;\n status: SurfaceStatus;\n type: BoundaryType;\n /** The calibration interval, and the surface's position within it\n * (0 at the interval's base, 1 at its top) */\n calibration?: SurfaceCalibration | null;\n proportion?: number | null;\n /** True when `status` was inferred rather than read from the data — see\n * `inferTiePointStatuses`. */\n statusInferred?: boolean;\n unitsAbove: number[];\n unitsBelow: number[];\n section_id?: number | null;\n ref_id?: number | null;\n /** The API record, when the surface came from `/age_model` */\n boundary?: AgeModelBoundary;\n}\n\nexport const surfaceStatuses: SurfaceStatus[] = [\n \"absolute\",\n \"relative\",\n \"spike\",\n \"imposed\",\n \"modeled\",\n \"\",\n \"derived\",\n];\n\n/** The statuses at which the age model was actually constrained — the tie\n * points — as opposed to surfaces whose age falls out of the model\n * (`modeled`), carries no status, or is merely derived from units. These are\n * the surfaces worth labeling by default. */\nexport const TIE_POINT_STATUSES: SurfaceStatus[] = [\n \"absolute\",\n \"relative\",\n \"spike\",\n \"imposed\",\n];\n\nexport const surfaceStatusLabels: Record<SurfaceStatus, string> = {\n absolute: \"Absolute\",\n relative: \"Relative\",\n spike: \"Spike\",\n imposed: \"Imposed\",\n modeled: \"Modeled\",\n \"\": \"Unspecified\",\n derived: \"Derived\",\n};\n\nexport const surfaceStatusDescriptions: Record<SurfaceStatus, string> = {\n absolute:\n \"Independently dated; the age is authoritative and the interval position is derived from it\",\n relative:\n \"Calibrated to a position within a geologic interval; the age follows the timescale\",\n spike: \"Tied to a formally defined boundary point\",\n imposed:\n \"Interval-relative representation imposed on a boundary with no chronostratigraphic tie of its own (for instance, thickness interpolation)\",\n modeled: \"Interpolated by the age model between calibrated surfaces\",\n \"\": \"No status recorded\",\n derived:\n \"Computed from unit tops and bottoms; not stored in the column's age model\",\n};\n\n/** A CSS-safe token for a status or type (`angular unconformity` →\n * `angular-unconformity`, `\"\"` → `unspecified`). */\nexport function surfaceToken(value: string | null | undefined): string {\n if (value == null || value === \"\") return \"unspecified\";\n return value.replace(/[^a-z0-9]+/gi, \"-\").toLowerCase();\n}\n\n/** Legacy `unit_boundaries` rows use `0` as the \"no unit\" sentinel. */\nexport function nullifyUnitID(id: number | null | undefined): number | null {\n if (id == null || id === 0) return null;\n return id;\n}\n\n/** How close to an interval's base or top a surface must sit to count as\n * bounding it (as a fraction of the interval). */\nexport const INTERVAL_BOUND_TOLERANCE = 0.001;\n\n/** Promote the `modeled` surfaces that cannot in fact have been interpolated\n * to `relative`.\n *\n * This is a client-side affordance over a known gap in the data. An age model\n * interpolates *between* tie points, so a surface with no constraint to\n * interpolate from has to be one itself, whatever `boundary_status` says. Two\n * cases are recognizable from the boundaries alone:\n *\n * - **It sits on the base or top of its calibration interval** (proportion 0\n * or 1). A height referred directly to an interval bounds the model rather\n * than falling out of it.\n * - **It is the edge of a gap-bound package** — the youngest surface in its\n * section with no unit above, or the oldest with none below. There is\n * nothing beyond it to interpolate against.\n *\n * Both are common in columns whose age model was built by assigning positions\n * within intervals, where the importer recorded everything as `modeled`;\n * filtering to the tie-point statuses would otherwise hide real ones. Promoted\n * surfaces are marked `statusInferred`, so views can say where the status came\n * from; nothing about the underlying record changes.\n *\n * Two cases are deliberately left alone. A *mid-interval* position is\n * ambiguous: an assigned position and an interpolated one are the same record\n * there, and only the data can settle it. And an open side in the *interior*\n * of a section is a lateral relationship, not a package edge — a unit that\n * pinches out while others span past it — which the model can interpolate\n * normally.\n */\nexport function inferTiePointStatuses(\n surfaces: ColumnSurface[],\n tolerance: number = INTERVAL_BOUND_TOLERANCE,\n): ColumnSurface[] {\n const extents = sectionAgeExtents(surfaces);\n\n return surfaces.map((surface) => {\n if (surface.status !== \"modeled\") return surface;\n if (surface.calibration == null) return surface;\n\n const { proportion } = surface;\n const onIntervalBound =\n proportion != null &&\n (Math.abs(proportion) <= tolerance ||\n Math.abs(proportion - 1) <= tolerance);\n\n if (!onIntervalBound && !boundsPackage(surface, extents)) {\n return surface;\n }\n return { ...surface, status: \"relative\", statusInferred: true };\n });\n}\n\n/** An open side only means a package edge at the ends of the section. In its\n * interior it means a unit pinching out while others span past it, which the\n * age model interpolates like any other surface. */\nfunction boundsPackage(\n surface: ColumnSurface,\n extents: Map<ColumnSurface[\"section_id\"], [number, number]>,\n): boolean {\n const extent = extents.get(surface.section_id ?? null);\n if (extent == null) return false;\n const [youngest, oldest] = extent;\n if (surface.unitsAbove.length === 0 && surface.age === youngest) return true;\n return surface.unitsBelow.length === 0 && surface.age === oldest;\n}\n\n/** The youngest and oldest surface age in each section. */\nfunction sectionAgeExtents(\n surfaces: ColumnSurface[],\n): Map<ColumnSurface[\"section_id\"], [number, number]> {\n const extents = new Map<ColumnSurface[\"section_id\"], [number, number]>();\n for (const surface of surfaces) {\n const key = surface.section_id ?? null;\n const extent = extents.get(key);\n if (extent == null) {\n extents.set(key, [surface.age, surface.age]);\n continue;\n }\n extent[0] = Math.min(extent[0], surface.age);\n extent[1] = Math.max(extent[1], surface.age);\n }\n return extents;\n}\n\nexport function boundaryToSurface(boundary: AgeModelBoundary): ColumnSurface {\n const above = nullifyUnitID(boundary.unit_above);\n const below = nullifyUnitID(boundary.unit_below);\n let calibration: SurfaceCalibration | null = null;\n if (boundary.interval_id != null) {\n calibration = {\n id: boundary.interval_id,\n name: boundary.interval_name,\n b_age: boundary.age_bottom,\n t_age: boundary.age_top,\n };\n }\n return {\n id: boundary.boundary_id,\n age: boundary.model_age,\n position: boundary.boundary_position,\n status: boundary.boundary_status ?? \"\",\n type: boundary.boundary_type ?? \"\",\n calibration,\n proportion: boundary.rel_position,\n unitsAbove: above == null ? [] : [above],\n unitsBelow: below == null ? [] : [below],\n section_id: boundary.section_id,\n ref_id: boundary.ref_id,\n boundary,\n };\n}\n\nexport interface SurfacesFromBoundariesOptions {\n /** Promote `modeled` surfaces that must in fact constrain the age model to\n * `relative` (default `true`) — see `inferTiePointStatuses`. */\n inferTiePoints?: boolean;\n}\n\n/** Surfaces for a column's age-model boundaries, youngest first. */\nexport function surfacesFromBoundaries(\n boundaries: AgeModelBoundary[] | null | undefined,\n options: SurfacesFromBoundariesOptions = {},\n): ColumnSurface[] {\n const { inferTiePoints = true } = options;\n if (boundaries == null) return [];\n let surfaces = boundaries.map(boundaryToSurface);\n if (inferTiePoints) {\n surfaces = inferTiePointStatuses(surfaces);\n }\n return surfaces.sort((a, b) => a.age - b.age);\n}\n\nexport interface SurfacesFromUnitsOptions {\n /** Surfaces closer than this (in the axis's units) are merged */\n tolerance?: number;\n /** Which unit field the surfaces are keyed on: ages for age columns,\n * measured positions for height and depth columns */\n axisType?: ColumnAxisType;\n}\n\n/** Derive surfaces from unit tops and bottoms. Every distinct top or bottom\n * becomes one surface with status `derived`; units sharing a boundary share\n * the surface. This is the fallback when a column has no `unit_boundaries`. */\nexport function surfacesFromUnits<T extends UnitLong>(\n units: T[] | null | undefined,\n options: SurfacesFromUnitsOptions = {},\n): ColumnSurface[] {\n const { tolerance = 0.001, axisType = ColumnAxisType.AGE } = options;\n if (units == null) return [];\n const byPosition = isPositionAxis(axisType);\n\n const surfaces: ColumnSurface[] = [];\n\n function keyOf(surface: ColumnSurface): number | null {\n if (byPosition) return surface.position ?? null;\n return surface.age;\n }\n\n function addBound(unit: T, top: boolean) {\n const age = top ? unit.t_age : unit.b_age;\n const position = asNumber(top ? unit.t_pos : unit.b_pos);\n const key = byPosition ? position : age;\n if (key == null) return;\n\n let surface = surfaces.find((d) => {\n const k = keyOf(d);\n return k != null && Math.abs(k - key) < tolerance;\n });\n if (surface == null) {\n surface = {\n id: \"\",\n age,\n position,\n status: \"derived\",\n type: \"\",\n calibration: null,\n proportion: null,\n unitsAbove: [],\n unitsBelow: [],\n section_id: unit.section_id,\n };\n surfaces.push(surface);\n }\n // The unit whose top this is lies *below* the surface, and vice versa\n if (top) {\n surface.unitsBelow.push(unit.unit_id);\n } else {\n surface.unitsAbove.push(unit.unit_id);\n }\n }\n\n for (const unit of units) {\n addBound(unit, true);\n addBound(unit, false);\n }\n\n surfaces.sort((a, b) => compareAlongAxis(a, b, axisType));\n return surfaces.map((s, i) => ({ ...s, id: `derived-${i}` }));\n}\n\n/** The coordinate a surface is drawn at for a given axis type: its age for\n * age columns, its measured position for height and depth columns. `null`\n * when the surface has no coordinate on that axis. */\nexport function surfacePosition(\n surface: ColumnSurface,\n axisType: ColumnAxisType | null | undefined,\n): number | null {\n if (isPositionAxis(axisType)) {\n return surface.position ?? null;\n }\n return surface.age ?? null;\n}\n\nexport function isPositionAxis(\n axisType: ColumnAxisType | null | undefined,\n): boolean {\n return (\n axisType === ColumnAxisType.HEIGHT || axisType === ColumnAxisType.DEPTH\n );\n}\n\n/** Order surfaces from the top of the column downwards. */\nexport function compareAlongAxis(\n a: ColumnSurface,\n b: ColumnSurface,\n axisType: ColumnAxisType | null | undefined,\n): number {\n const pa = surfacePosition(a, axisType);\n const pb = surfacePosition(b, axisType);\n if (pa == null || pb == null) return 0;\n if (axisType === ColumnAxisType.HEIGHT) {\n // Heights increase upwards\n return pb - pa;\n }\n // Ages and depths increase downwards\n return pa - pb;\n}\n\n/** Percent of the way up its calibration interval, e.g. `85%`. */\nexport function formatProportion(\n proportion: number | null | undefined,\n): string | null {\n if (proportion == null) return null;\n return `${Math.round(proportion * 100)}%`;\n}\n\n/** A short text label for a surface: its calibration (\"Calymmian · 85%\")\n * or, failing that, its age. */\nexport function surfaceLabel(surface: ColumnSurface): string {\n const { calibration } = surface;\n if (calibration != null) {\n const prop = formatProportion(surface.proportion);\n if (prop != null) {\n return `${calibration.name} · ${prop}`;\n }\n return calibration.name;\n }\n return formatAge(surface.age);\n}\n\nexport function formatAge(age: number | null | undefined): string {\n if (age == null) return \"\";\n let value = age;\n let unit = \"Ma\";\n if (Math.abs(age) < 1) {\n value = age * 1000;\n unit = \"ka\";\n }\n const _value = value.toLocaleString(\"en-US\", { maximumFractionDigits: 2 });\n return `${_value} ${unit}`;\n}\n\nfunction asNumber(value: unknown): number | null {\n if (value == null || value === \"\") return null;\n const n = Number(value);\n if (Number.isNaN(n)) return null;\n return n;\n}\n"],"names":["ColumnAxisType"],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import { AgeModelBoundary, BoundaryStatus, BoundaryType, UnitLong } from '@macrostrat/api-types';
2
+ import { ColumnAxisType } from '@macrostrat/column-components';
3
+ export type { AgeModelBoundary, BoundaryStatus, BoundaryType };
4
+ /** A boundary status, plus `derived` for surfaces computed from units rather
5
+ * than stored in the age model. */
6
+ export type SurfaceStatus = BoundaryStatus | "derived";
7
+ /** The interval a surface is calibrated against */
8
+ export interface SurfaceCalibration {
9
+ id: number;
10
+ name: string;
11
+ b_age: number;
12
+ t_age: number;
13
+ }
14
+ export interface ColumnSurface {
15
+ id: number | string;
16
+ /** Modeled age of the surface (Ma) */
17
+ age: number;
18
+ /** Measured position (height or depth) along the column, when known */
19
+ position?: number | null;
20
+ status: SurfaceStatus;
21
+ type: BoundaryType;
22
+ /** The calibration interval, and the surface's position within it
23
+ * (0 at the interval's base, 1 at its top) */
24
+ calibration?: SurfaceCalibration | null;
25
+ proportion?: number | null;
26
+ /** True when `status` was inferred rather than read from the data — see
27
+ * `inferTiePointStatuses`. */
28
+ statusInferred?: boolean;
29
+ unitsAbove: number[];
30
+ unitsBelow: number[];
31
+ section_id?: number | null;
32
+ ref_id?: number | null;
33
+ /** The API record, when the surface came from `/age_model` */
34
+ boundary?: AgeModelBoundary;
35
+ }
36
+ export declare const surfaceStatuses: SurfaceStatus[];
37
+ /** The statuses at which the age model was actually constrained — the tie
38
+ * points — as opposed to surfaces whose age falls out of the model
39
+ * (`modeled`), carries no status, or is merely derived from units. These are
40
+ * the surfaces worth labeling by default. */
41
+ export declare const TIE_POINT_STATUSES: SurfaceStatus[];
42
+ export declare const surfaceStatusLabels: Record<SurfaceStatus, string>;
43
+ export declare const surfaceStatusDescriptions: Record<SurfaceStatus, string>;
44
+ /** A CSS-safe token for a status or type (`angular unconformity` →
45
+ * `angular-unconformity`, `""` → `unspecified`). */
46
+ export declare function surfaceToken(value: string | null | undefined): string;
47
+ /** Legacy `unit_boundaries` rows use `0` as the "no unit" sentinel. */
48
+ export declare function nullifyUnitID(id: number | null | undefined): number | null;
49
+ /** How close to an interval's base or top a surface must sit to count as
50
+ * bounding it (as a fraction of the interval). */
51
+ export declare const INTERVAL_BOUND_TOLERANCE = 0.001;
52
+ /** Promote the `modeled` surfaces that cannot in fact have been interpolated
53
+ * to `relative`.
54
+ *
55
+ * This is a client-side affordance over a known gap in the data. An age model
56
+ * interpolates *between* tie points, so a surface with no constraint to
57
+ * interpolate from has to be one itself, whatever `boundary_status` says. Two
58
+ * cases are recognizable from the boundaries alone:
59
+ *
60
+ * - **It sits on the base or top of its calibration interval** (proportion 0
61
+ * or 1). A height referred directly to an interval bounds the model rather
62
+ * than falling out of it.
63
+ * - **It is the edge of a gap-bound package** — the youngest surface in its
64
+ * section with no unit above, or the oldest with none below. There is
65
+ * nothing beyond it to interpolate against.
66
+ *
67
+ * Both are common in columns whose age model was built by assigning positions
68
+ * within intervals, where the importer recorded everything as `modeled`;
69
+ * filtering to the tie-point statuses would otherwise hide real ones. Promoted
70
+ * surfaces are marked `statusInferred`, so views can say where the status came
71
+ * from; nothing about the underlying record changes.
72
+ *
73
+ * Two cases are deliberately left alone. A *mid-interval* position is
74
+ * ambiguous: an assigned position and an interpolated one are the same record
75
+ * there, and only the data can settle it. And an open side in the *interior*
76
+ * of a section is a lateral relationship, not a package edge — a unit that
77
+ * pinches out while others span past it — which the model can interpolate
78
+ * normally.
79
+ */
80
+ export declare function inferTiePointStatuses(surfaces: ColumnSurface[], tolerance?: number): ColumnSurface[];
81
+ export declare function boundaryToSurface(boundary: AgeModelBoundary): ColumnSurface;
82
+ export interface SurfacesFromBoundariesOptions {
83
+ /** Promote `modeled` surfaces that must in fact constrain the age model to
84
+ * `relative` (default `true`) — see `inferTiePointStatuses`. */
85
+ inferTiePoints?: boolean;
86
+ }
87
+ /** Surfaces for a column's age-model boundaries, youngest first. */
88
+ export declare function surfacesFromBoundaries(boundaries: AgeModelBoundary[] | null | undefined, options?: SurfacesFromBoundariesOptions): ColumnSurface[];
89
+ export interface SurfacesFromUnitsOptions {
90
+ /** Surfaces closer than this (in the axis's units) are merged */
91
+ tolerance?: number;
92
+ /** Which unit field the surfaces are keyed on: ages for age columns,
93
+ * measured positions for height and depth columns */
94
+ axisType?: ColumnAxisType;
95
+ }
96
+ /** Derive surfaces from unit tops and bottoms. Every distinct top or bottom
97
+ * becomes one surface with status `derived`; units sharing a boundary share
98
+ * the surface. This is the fallback when a column has no `unit_boundaries`. */
99
+ export declare function surfacesFromUnits<T extends UnitLong>(units: T[] | null | undefined, options?: SurfacesFromUnitsOptions): ColumnSurface[];
100
+ /** The coordinate a surface is drawn at for a given axis type: its age for
101
+ * age columns, its measured position for height and depth columns. `null`
102
+ * when the surface has no coordinate on that axis. */
103
+ export declare function surfacePosition(surface: ColumnSurface, axisType: ColumnAxisType | null | undefined): number | null;
104
+ export declare function isPositionAxis(axisType: ColumnAxisType | null | undefined): boolean;
105
+ /** Order surfaces from the top of the column downwards. */
106
+ export declare function compareAlongAxis(a: ColumnSurface, b: ColumnSurface, axisType: ColumnAxisType | null | undefined): number;
107
+ /** Percent of the way up its calibration interval, e.g. `85%`. */
108
+ export declare function formatProportion(proportion: number | null | undefined): string | null;
109
+ /** A short text label for a surface: its calibration ("Calymmian · 85%")
110
+ * or, failing that, its age. */
111
+ export declare function surfaceLabel(surface: ColumnSurface): string;
112
+ export declare function formatAge(age: number | null | undefined): string;
@@ -0,0 +1,229 @@
1
+ import { ColumnAxisType } from "@macrostrat/column-components";
2
+ const surfaceStatuses = [
3
+ "absolute",
4
+ "relative",
5
+ "spike",
6
+ "imposed",
7
+ "modeled",
8
+ "",
9
+ "derived"
10
+ ];
11
+ const TIE_POINT_STATUSES = [
12
+ "absolute",
13
+ "relative",
14
+ "spike",
15
+ "imposed"
16
+ ];
17
+ const surfaceStatusLabels = {
18
+ absolute: "Absolute",
19
+ relative: "Relative",
20
+ spike: "Spike",
21
+ imposed: "Imposed",
22
+ modeled: "Modeled",
23
+ "": "Unspecified",
24
+ derived: "Derived"
25
+ };
26
+ const surfaceStatusDescriptions = {
27
+ absolute: "Independently dated; the age is authoritative and the interval position is derived from it",
28
+ relative: "Calibrated to a position within a geologic interval; the age follows the timescale",
29
+ spike: "Tied to a formally defined boundary point",
30
+ imposed: "Interval-relative representation imposed on a boundary with no chronostratigraphic tie of its own (for instance, thickness interpolation)",
31
+ modeled: "Interpolated by the age model between calibrated surfaces",
32
+ "": "No status recorded",
33
+ derived: "Computed from unit tops and bottoms; not stored in the column's age model"
34
+ };
35
+ function surfaceToken(value) {
36
+ if (value == null || value === "") return "unspecified";
37
+ return value.replace(/[^a-z0-9]+/gi, "-").toLowerCase();
38
+ }
39
+ function nullifyUnitID(id) {
40
+ if (id == null || id === 0) return null;
41
+ return id;
42
+ }
43
+ const INTERVAL_BOUND_TOLERANCE = 1e-3;
44
+ function inferTiePointStatuses(surfaces, tolerance = INTERVAL_BOUND_TOLERANCE) {
45
+ const extents = sectionAgeExtents(surfaces);
46
+ return surfaces.map((surface) => {
47
+ if (surface.status !== "modeled") return surface;
48
+ if (surface.calibration == null) return surface;
49
+ const { proportion } = surface;
50
+ const onIntervalBound = proportion != null && (Math.abs(proportion) <= tolerance || Math.abs(proportion - 1) <= tolerance);
51
+ if (!onIntervalBound && !boundsPackage(surface, extents)) {
52
+ return surface;
53
+ }
54
+ return { ...surface, status: "relative", statusInferred: true };
55
+ });
56
+ }
57
+ function boundsPackage(surface, extents) {
58
+ const extent = extents.get(surface.section_id ?? null);
59
+ if (extent == null) return false;
60
+ const [youngest, oldest] = extent;
61
+ if (surface.unitsAbove.length === 0 && surface.age === youngest) return true;
62
+ return surface.unitsBelow.length === 0 && surface.age === oldest;
63
+ }
64
+ function sectionAgeExtents(surfaces) {
65
+ const extents = /* @__PURE__ */ new Map();
66
+ for (const surface of surfaces) {
67
+ const key = surface.section_id ?? null;
68
+ const extent = extents.get(key);
69
+ if (extent == null) {
70
+ extents.set(key, [surface.age, surface.age]);
71
+ continue;
72
+ }
73
+ extent[0] = Math.min(extent[0], surface.age);
74
+ extent[1] = Math.max(extent[1], surface.age);
75
+ }
76
+ return extents;
77
+ }
78
+ function boundaryToSurface(boundary) {
79
+ const above = nullifyUnitID(boundary.unit_above);
80
+ const below = nullifyUnitID(boundary.unit_below);
81
+ let calibration = null;
82
+ if (boundary.interval_id != null) {
83
+ calibration = {
84
+ id: boundary.interval_id,
85
+ name: boundary.interval_name,
86
+ b_age: boundary.age_bottom,
87
+ t_age: boundary.age_top
88
+ };
89
+ }
90
+ return {
91
+ id: boundary.boundary_id,
92
+ age: boundary.model_age,
93
+ position: boundary.boundary_position,
94
+ status: boundary.boundary_status ?? "",
95
+ type: boundary.boundary_type ?? "",
96
+ calibration,
97
+ proportion: boundary.rel_position,
98
+ unitsAbove: above == null ? [] : [above],
99
+ unitsBelow: below == null ? [] : [below],
100
+ section_id: boundary.section_id,
101
+ ref_id: boundary.ref_id,
102
+ boundary
103
+ };
104
+ }
105
+ function surfacesFromBoundaries(boundaries, options = {}) {
106
+ const { inferTiePoints = true } = options;
107
+ if (boundaries == null) return [];
108
+ let surfaces = boundaries.map(boundaryToSurface);
109
+ if (inferTiePoints) {
110
+ surfaces = inferTiePointStatuses(surfaces);
111
+ }
112
+ return surfaces.sort((a, b) => a.age - b.age);
113
+ }
114
+ function surfacesFromUnits(units, options = {}) {
115
+ const { tolerance = 1e-3, axisType = ColumnAxisType.AGE } = options;
116
+ if (units == null) return [];
117
+ const byPosition = isPositionAxis(axisType);
118
+ const surfaces = [];
119
+ function keyOf(surface) {
120
+ if (byPosition) return surface.position ?? null;
121
+ return surface.age;
122
+ }
123
+ function addBound(unit, top) {
124
+ const age = top ? unit.t_age : unit.b_age;
125
+ const position = asNumber(top ? unit.t_pos : unit.b_pos);
126
+ const key = byPosition ? position : age;
127
+ if (key == null) return;
128
+ let surface = surfaces.find((d) => {
129
+ const k = keyOf(d);
130
+ return k != null && Math.abs(k - key) < tolerance;
131
+ });
132
+ if (surface == null) {
133
+ surface = {
134
+ id: "",
135
+ age,
136
+ position,
137
+ status: "derived",
138
+ type: "",
139
+ calibration: null,
140
+ proportion: null,
141
+ unitsAbove: [],
142
+ unitsBelow: [],
143
+ section_id: unit.section_id
144
+ };
145
+ surfaces.push(surface);
146
+ }
147
+ if (top) {
148
+ surface.unitsBelow.push(unit.unit_id);
149
+ } else {
150
+ surface.unitsAbove.push(unit.unit_id);
151
+ }
152
+ }
153
+ for (const unit of units) {
154
+ addBound(unit, true);
155
+ addBound(unit, false);
156
+ }
157
+ surfaces.sort((a, b) => compareAlongAxis(a, b, axisType));
158
+ return surfaces.map((s, i) => ({ ...s, id: `derived-${i}` }));
159
+ }
160
+ function surfacePosition(surface, axisType) {
161
+ if (isPositionAxis(axisType)) {
162
+ return surface.position ?? null;
163
+ }
164
+ return surface.age ?? null;
165
+ }
166
+ function isPositionAxis(axisType) {
167
+ return axisType === ColumnAxisType.HEIGHT || axisType === ColumnAxisType.DEPTH;
168
+ }
169
+ function compareAlongAxis(a, b, axisType) {
170
+ const pa = surfacePosition(a, axisType);
171
+ const pb = surfacePosition(b, axisType);
172
+ if (pa == null || pb == null) return 0;
173
+ if (axisType === ColumnAxisType.HEIGHT) {
174
+ return pb - pa;
175
+ }
176
+ return pa - pb;
177
+ }
178
+ function formatProportion(proportion) {
179
+ if (proportion == null) return null;
180
+ return `${Math.round(proportion * 100)}%`;
181
+ }
182
+ function surfaceLabel(surface) {
183
+ const { calibration } = surface;
184
+ if (calibration != null) {
185
+ const prop = formatProportion(surface.proportion);
186
+ if (prop != null) {
187
+ return `${calibration.name} · ${prop}`;
188
+ }
189
+ return calibration.name;
190
+ }
191
+ return formatAge(surface.age);
192
+ }
193
+ function formatAge(age) {
194
+ if (age == null) return "";
195
+ let value = age;
196
+ let unit = "Ma";
197
+ if (Math.abs(age) < 1) {
198
+ value = age * 1e3;
199
+ unit = "ka";
200
+ }
201
+ const _value = value.toLocaleString("en-US", { maximumFractionDigits: 2 });
202
+ return `${_value} ${unit}`;
203
+ }
204
+ function asNumber(value) {
205
+ if (value == null || value === "") return null;
206
+ const n = Number(value);
207
+ if (Number.isNaN(n)) return null;
208
+ return n;
209
+ }
210
+ export {
211
+ INTERVAL_BOUND_TOLERANCE,
212
+ TIE_POINT_STATUSES,
213
+ boundaryToSurface,
214
+ compareAlongAxis,
215
+ formatAge,
216
+ formatProportion,
217
+ inferTiePointStatuses,
218
+ isPositionAxis,
219
+ nullifyUnitID,
220
+ surfaceLabel,
221
+ surfacePosition,
222
+ surfaceStatusDescriptions,
223
+ surfaceStatusLabels,
224
+ surfaceStatuses,
225
+ surfaceToken,
226
+ surfacesFromBoundaries,
227
+ surfacesFromUnits
228
+ };
229
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sources":["../../src/age-model/types.ts"],"sourcesContent":["/** Surfaces: the view model for a column's age model.\n *\n * A `ColumnSurface` is one horizontal boundary in a column. It is what the\n * surfaces view renders, and it is deliberately data-agnostic: a surface can\n * come from an `/age_model` boundary (`boundaryToSurface`), be derived from\n * unit tops and bottoms when a column has no age-model rows\n * (`surfacesFromUnits`), or be built by an editor from its own state.\n */\nimport type {\n AgeModelBoundary,\n BoundaryStatus,\n BoundaryType,\n UnitLong,\n} from \"@macrostrat/api-types\";\nimport { ColumnAxisType } from \"@macrostrat/column-components\";\n\nexport type { AgeModelBoundary, BoundaryStatus, BoundaryType };\n\n/** A boundary status, plus `derived` for surfaces computed from units rather\n * than stored in the age model. */\nexport type SurfaceStatus = BoundaryStatus | \"derived\";\n\n/** The interval a surface is calibrated against */\nexport interface SurfaceCalibration {\n id: number;\n name: string;\n b_age: number;\n t_age: number;\n}\n\nexport interface ColumnSurface {\n id: number | string;\n /** Modeled age of the surface (Ma) */\n age: number;\n /** Measured position (height or depth) along the column, when known */\n position?: number | null;\n status: SurfaceStatus;\n type: BoundaryType;\n /** The calibration interval, and the surface's position within it\n * (0 at the interval's base, 1 at its top) */\n calibration?: SurfaceCalibration | null;\n proportion?: number | null;\n /** True when `status` was inferred rather than read from the data — see\n * `inferTiePointStatuses`. */\n statusInferred?: boolean;\n unitsAbove: number[];\n unitsBelow: number[];\n section_id?: number | null;\n ref_id?: number | null;\n /** The API record, when the surface came from `/age_model` */\n boundary?: AgeModelBoundary;\n}\n\nexport const surfaceStatuses: SurfaceStatus[] = [\n \"absolute\",\n \"relative\",\n \"spike\",\n \"imposed\",\n \"modeled\",\n \"\",\n \"derived\",\n];\n\n/** The statuses at which the age model was actually constrained — the tie\n * points — as opposed to surfaces whose age falls out of the model\n * (`modeled`), carries no status, or is merely derived from units. These are\n * the surfaces worth labeling by default. */\nexport const TIE_POINT_STATUSES: SurfaceStatus[] = [\n \"absolute\",\n \"relative\",\n \"spike\",\n \"imposed\",\n];\n\nexport const surfaceStatusLabels: Record<SurfaceStatus, string> = {\n absolute: \"Absolute\",\n relative: \"Relative\",\n spike: \"Spike\",\n imposed: \"Imposed\",\n modeled: \"Modeled\",\n \"\": \"Unspecified\",\n derived: \"Derived\",\n};\n\nexport const surfaceStatusDescriptions: Record<SurfaceStatus, string> = {\n absolute:\n \"Independently dated; the age is authoritative and the interval position is derived from it\",\n relative:\n \"Calibrated to a position within a geologic interval; the age follows the timescale\",\n spike: \"Tied to a formally defined boundary point\",\n imposed:\n \"Interval-relative representation imposed on a boundary with no chronostratigraphic tie of its own (for instance, thickness interpolation)\",\n modeled: \"Interpolated by the age model between calibrated surfaces\",\n \"\": \"No status recorded\",\n derived:\n \"Computed from unit tops and bottoms; not stored in the column's age model\",\n};\n\n/** A CSS-safe token for a status or type (`angular unconformity` →\n * `angular-unconformity`, `\"\"` → `unspecified`). */\nexport function surfaceToken(value: string | null | undefined): string {\n if (value == null || value === \"\") return \"unspecified\";\n return value.replace(/[^a-z0-9]+/gi, \"-\").toLowerCase();\n}\n\n/** Legacy `unit_boundaries` rows use `0` as the \"no unit\" sentinel. */\nexport function nullifyUnitID(id: number | null | undefined): number | null {\n if (id == null || id === 0) return null;\n return id;\n}\n\n/** How close to an interval's base or top a surface must sit to count as\n * bounding it (as a fraction of the interval). */\nexport const INTERVAL_BOUND_TOLERANCE = 0.001;\n\n/** Promote the `modeled` surfaces that cannot in fact have been interpolated\n * to `relative`.\n *\n * This is a client-side affordance over a known gap in the data. An age model\n * interpolates *between* tie points, so a surface with no constraint to\n * interpolate from has to be one itself, whatever `boundary_status` says. Two\n * cases are recognizable from the boundaries alone:\n *\n * - **It sits on the base or top of its calibration interval** (proportion 0\n * or 1). A height referred directly to an interval bounds the model rather\n * than falling out of it.\n * - **It is the edge of a gap-bound package** — the youngest surface in its\n * section with no unit above, or the oldest with none below. There is\n * nothing beyond it to interpolate against.\n *\n * Both are common in columns whose age model was built by assigning positions\n * within intervals, where the importer recorded everything as `modeled`;\n * filtering to the tie-point statuses would otherwise hide real ones. Promoted\n * surfaces are marked `statusInferred`, so views can say where the status came\n * from; nothing about the underlying record changes.\n *\n * Two cases are deliberately left alone. A *mid-interval* position is\n * ambiguous: an assigned position and an interpolated one are the same record\n * there, and only the data can settle it. And an open side in the *interior*\n * of a section is a lateral relationship, not a package edge — a unit that\n * pinches out while others span past it — which the model can interpolate\n * normally.\n */\nexport function inferTiePointStatuses(\n surfaces: ColumnSurface[],\n tolerance: number = INTERVAL_BOUND_TOLERANCE,\n): ColumnSurface[] {\n const extents = sectionAgeExtents(surfaces);\n\n return surfaces.map((surface) => {\n if (surface.status !== \"modeled\") return surface;\n if (surface.calibration == null) return surface;\n\n const { proportion } = surface;\n const onIntervalBound =\n proportion != null &&\n (Math.abs(proportion) <= tolerance ||\n Math.abs(proportion - 1) <= tolerance);\n\n if (!onIntervalBound && !boundsPackage(surface, extents)) {\n return surface;\n }\n return { ...surface, status: \"relative\", statusInferred: true };\n });\n}\n\n/** An open side only means a package edge at the ends of the section. In its\n * interior it means a unit pinching out while others span past it, which the\n * age model interpolates like any other surface. */\nfunction boundsPackage(\n surface: ColumnSurface,\n extents: Map<ColumnSurface[\"section_id\"], [number, number]>,\n): boolean {\n const extent = extents.get(surface.section_id ?? null);\n if (extent == null) return false;\n const [youngest, oldest] = extent;\n if (surface.unitsAbove.length === 0 && surface.age === youngest) return true;\n return surface.unitsBelow.length === 0 && surface.age === oldest;\n}\n\n/** The youngest and oldest surface age in each section. */\nfunction sectionAgeExtents(\n surfaces: ColumnSurface[],\n): Map<ColumnSurface[\"section_id\"], [number, number]> {\n const extents = new Map<ColumnSurface[\"section_id\"], [number, number]>();\n for (const surface of surfaces) {\n const key = surface.section_id ?? null;\n const extent = extents.get(key);\n if (extent == null) {\n extents.set(key, [surface.age, surface.age]);\n continue;\n }\n extent[0] = Math.min(extent[0], surface.age);\n extent[1] = Math.max(extent[1], surface.age);\n }\n return extents;\n}\n\nexport function boundaryToSurface(boundary: AgeModelBoundary): ColumnSurface {\n const above = nullifyUnitID(boundary.unit_above);\n const below = nullifyUnitID(boundary.unit_below);\n let calibration: SurfaceCalibration | null = null;\n if (boundary.interval_id != null) {\n calibration = {\n id: boundary.interval_id,\n name: boundary.interval_name,\n b_age: boundary.age_bottom,\n t_age: boundary.age_top,\n };\n }\n return {\n id: boundary.boundary_id,\n age: boundary.model_age,\n position: boundary.boundary_position,\n status: boundary.boundary_status ?? \"\",\n type: boundary.boundary_type ?? \"\",\n calibration,\n proportion: boundary.rel_position,\n unitsAbove: above == null ? [] : [above],\n unitsBelow: below == null ? [] : [below],\n section_id: boundary.section_id,\n ref_id: boundary.ref_id,\n boundary,\n };\n}\n\nexport interface SurfacesFromBoundariesOptions {\n /** Promote `modeled` surfaces that must in fact constrain the age model to\n * `relative` (default `true`) — see `inferTiePointStatuses`. */\n inferTiePoints?: boolean;\n}\n\n/** Surfaces for a column's age-model boundaries, youngest first. */\nexport function surfacesFromBoundaries(\n boundaries: AgeModelBoundary[] | null | undefined,\n options: SurfacesFromBoundariesOptions = {},\n): ColumnSurface[] {\n const { inferTiePoints = true } = options;\n if (boundaries == null) return [];\n let surfaces = boundaries.map(boundaryToSurface);\n if (inferTiePoints) {\n surfaces = inferTiePointStatuses(surfaces);\n }\n return surfaces.sort((a, b) => a.age - b.age);\n}\n\nexport interface SurfacesFromUnitsOptions {\n /** Surfaces closer than this (in the axis's units) are merged */\n tolerance?: number;\n /** Which unit field the surfaces are keyed on: ages for age columns,\n * measured positions for height and depth columns */\n axisType?: ColumnAxisType;\n}\n\n/** Derive surfaces from unit tops and bottoms. Every distinct top or bottom\n * becomes one surface with status `derived`; units sharing a boundary share\n * the surface. This is the fallback when a column has no `unit_boundaries`. */\nexport function surfacesFromUnits<T extends UnitLong>(\n units: T[] | null | undefined,\n options: SurfacesFromUnitsOptions = {},\n): ColumnSurface[] {\n const { tolerance = 0.001, axisType = ColumnAxisType.AGE } = options;\n if (units == null) return [];\n const byPosition = isPositionAxis(axisType);\n\n const surfaces: ColumnSurface[] = [];\n\n function keyOf(surface: ColumnSurface): number | null {\n if (byPosition) return surface.position ?? null;\n return surface.age;\n }\n\n function addBound(unit: T, top: boolean) {\n const age = top ? unit.t_age : unit.b_age;\n const position = asNumber(top ? unit.t_pos : unit.b_pos);\n const key = byPosition ? position : age;\n if (key == null) return;\n\n let surface = surfaces.find((d) => {\n const k = keyOf(d);\n return k != null && Math.abs(k - key) < tolerance;\n });\n if (surface == null) {\n surface = {\n id: \"\",\n age,\n position,\n status: \"derived\",\n type: \"\",\n calibration: null,\n proportion: null,\n unitsAbove: [],\n unitsBelow: [],\n section_id: unit.section_id,\n };\n surfaces.push(surface);\n }\n // The unit whose top this is lies *below* the surface, and vice versa\n if (top) {\n surface.unitsBelow.push(unit.unit_id);\n } else {\n surface.unitsAbove.push(unit.unit_id);\n }\n }\n\n for (const unit of units) {\n addBound(unit, true);\n addBound(unit, false);\n }\n\n surfaces.sort((a, b) => compareAlongAxis(a, b, axisType));\n return surfaces.map((s, i) => ({ ...s, id: `derived-${i}` }));\n}\n\n/** The coordinate a surface is drawn at for a given axis type: its age for\n * age columns, its measured position for height and depth columns. `null`\n * when the surface has no coordinate on that axis. */\nexport function surfacePosition(\n surface: ColumnSurface,\n axisType: ColumnAxisType | null | undefined,\n): number | null {\n if (isPositionAxis(axisType)) {\n return surface.position ?? null;\n }\n return surface.age ?? null;\n}\n\nexport function isPositionAxis(\n axisType: ColumnAxisType | null | undefined,\n): boolean {\n return (\n axisType === ColumnAxisType.HEIGHT || axisType === ColumnAxisType.DEPTH\n );\n}\n\n/** Order surfaces from the top of the column downwards. */\nexport function compareAlongAxis(\n a: ColumnSurface,\n b: ColumnSurface,\n axisType: ColumnAxisType | null | undefined,\n): number {\n const pa = surfacePosition(a, axisType);\n const pb = surfacePosition(b, axisType);\n if (pa == null || pb == null) return 0;\n if (axisType === ColumnAxisType.HEIGHT) {\n // Heights increase upwards\n return pb - pa;\n }\n // Ages and depths increase downwards\n return pa - pb;\n}\n\n/** Percent of the way up its calibration interval, e.g. `85%`. */\nexport function formatProportion(\n proportion: number | null | undefined,\n): string | null {\n if (proportion == null) return null;\n return `${Math.round(proportion * 100)}%`;\n}\n\n/** A short text label for a surface: its calibration (\"Calymmian · 85%\")\n * or, failing that, its age. */\nexport function surfaceLabel(surface: ColumnSurface): string {\n const { calibration } = surface;\n if (calibration != null) {\n const prop = formatProportion(surface.proportion);\n if (prop != null) {\n return `${calibration.name} · ${prop}`;\n }\n return calibration.name;\n }\n return formatAge(surface.age);\n}\n\nexport function formatAge(age: number | null | undefined): string {\n if (age == null) return \"\";\n let value = age;\n let unit = \"Ma\";\n if (Math.abs(age) < 1) {\n value = age * 1000;\n unit = \"ka\";\n }\n const _value = value.toLocaleString(\"en-US\", { maximumFractionDigits: 2 });\n return `${_value} ${unit}`;\n}\n\nfunction asNumber(value: unknown): number | null {\n if (value == null || value === \"\") return null;\n const n = Number(value);\n if (Number.isNaN(n)) return null;\n return n;\n}\n"],"names":[],"mappings":"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"}