@mailwoman/soil 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/lib/index.ts +474 -0
- package/lib/schema.ts +493 -0
- package/lib/scripts/ingest-chunk.ts +47 -0
- package/lib/sdk/acquire.ts +124 -0
- package/lib/sdk/build-soil.ts +778 -0
- package/lib/sdk/cell-tiers.ts +310 -0
- package/lib/sdk/cells.ts +224 -0
- package/lib/sdk/client.ts +273 -0
- package/lib/sdk/download.ts +161 -0
- package/lib/sdk/index.ts +20 -0
- package/lib/sdk/ingest-chunk.ts +182 -0
- package/lib/sdk/ingest.ts +271 -0
- package/lib/sdk/measure-resolutions.ts +81 -0
- package/lib/sdk/reduce.ts +375 -0
- package/lib/sdk/survey-area.ts +467 -0
- package/lib/sdk/tabular.ts +296 -0
- package/lib/sdk/verify.ts +369 -0
- package/lib/test-kit.ts +255 -0
- package/lib/vocabulary.ts +241 -0
- package/out/index.d.ts +199 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +255 -0
- package/out/index.js.map +1 -0
- package/out/schema.d.ts +369 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +186 -0
- package/out/schema.js.map +1 -0
- package/out/scripts/ingest-chunk.d.ts +11 -0
- package/out/scripts/ingest-chunk.d.ts.map +1 -0
- package/out/scripts/ingest-chunk.js +42 -0
- package/out/scripts/ingest-chunk.js.map +1 -0
- package/out/sdk/acquire.d.ts +60 -0
- package/out/sdk/acquire.d.ts.map +1 -0
- package/out/sdk/acquire.js +74 -0
- package/out/sdk/acquire.js.map +1 -0
- package/out/sdk/build-soil.d.ts +197 -0
- package/out/sdk/build-soil.d.ts.map +1 -0
- package/out/sdk/build-soil.js +434 -0
- package/out/sdk/build-soil.js.map +1 -0
- package/out/sdk/cell-tiers.d.ts +47 -0
- package/out/sdk/cell-tiers.d.ts.map +1 -0
- package/out/sdk/cell-tiers.js +194 -0
- package/out/sdk/cell-tiers.js.map +1 -0
- package/out/sdk/cells.d.ts +118 -0
- package/out/sdk/cells.d.ts.map +1 -0
- package/out/sdk/cells.js +140 -0
- package/out/sdk/cells.js.map +1 -0
- package/out/sdk/client.d.ts +120 -0
- package/out/sdk/client.d.ts.map +1 -0
- package/out/sdk/client.js +214 -0
- package/out/sdk/client.js.map +1 -0
- package/out/sdk/download.d.ts +84 -0
- package/out/sdk/download.d.ts.map +1 -0
- package/out/sdk/download.js +117 -0
- package/out/sdk/download.js.map +1 -0
- package/out/sdk/index.d.ts +20 -0
- package/out/sdk/index.d.ts.map +1 -0
- package/out/sdk/index.js +20 -0
- package/out/sdk/index.js.map +1 -0
- package/out/sdk/ingest-chunk.d.ts +73 -0
- package/out/sdk/ingest-chunk.d.ts.map +1 -0
- package/out/sdk/ingest-chunk.js +98 -0
- package/out/sdk/ingest-chunk.js.map +1 -0
- package/out/sdk/ingest.d.ts +132 -0
- package/out/sdk/ingest.d.ts.map +1 -0
- package/out/sdk/ingest.js +163 -0
- package/out/sdk/ingest.js.map +1 -0
- package/out/sdk/measure-resolutions.d.ts +39 -0
- package/out/sdk/measure-resolutions.d.ts.map +1 -0
- package/out/sdk/measure-resolutions.js +52 -0
- package/out/sdk/measure-resolutions.js.map +1 -0
- package/out/sdk/reduce.d.ts +122 -0
- package/out/sdk/reduce.d.ts.map +1 -0
- package/out/sdk/reduce.js +266 -0
- package/out/sdk/reduce.js.map +1 -0
- package/out/sdk/survey-area.d.ts +105 -0
- package/out/sdk/survey-area.d.ts.map +1 -0
- package/out/sdk/survey-area.js +346 -0
- package/out/sdk/survey-area.js.map +1 -0
- package/out/sdk/tabular.d.ts +106 -0
- package/out/sdk/tabular.d.ts.map +1 -0
- package/out/sdk/tabular.js +194 -0
- package/out/sdk/tabular.js.map +1 -0
- package/out/sdk/verify.d.ts +135 -0
- package/out/sdk/verify.d.ts.map +1 -0
- package/out/sdk/verify.js +221 -0
- package/out/sdk/verify.js.map +1 -0
- package/out/test-kit.d.ts +80 -0
- package/out/test-kit.d.ts.map +1 -0
- package/out/test-kit.js +228 -0
- package/out/test-kit.js.map +1 -0
- package/out/vocabulary.d.ts +179 -0
- package/out/vocabulary.d.ts.map +1 -0
- package/out/vocabulary.js +215 -0
- package/out/vocabulary.js.map +1 -0
- package/package.json +276 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The reduction: the containment index, read once, into one per-cell distribution both consumers share.
|
|
7
|
+
*
|
|
8
|
+
* A DISTRIBUTION, NEVER A WINNER, AND THAT IS FORCED BY MEASUREMENT. 84.0% of the 339,191 national map
|
|
9
|
+
* units hold two or more components; in 16.8% the largest component covers under half the map unit; and
|
|
10
|
+
* 85.4% of `IA153`'s delineations are smaller than one resolution-9 cell. No affordable cell size removes
|
|
11
|
+
* the mixture — it is a property of the survey, whose own `mukind` says so: 128,499 map units (38.0%) are
|
|
12
|
+
* complexes, associations or undifferentiated groups, which is NRCS stating that the soils are
|
|
13
|
+
* intermingled and cannot be separated at the mapping scale. A winner class would satisfy the result-level
|
|
14
|
+
* consumer and starve the signal consumer, which needs a magnitude to vary over.
|
|
15
|
+
*
|
|
16
|
+
* THE WEIGHT IS A UNIFORM-AREA LATTICE OVER THE CELL, AND THE GRAIN IS CHOSEN AGAINST THE AUTHORITY'S OWN.
|
|
17
|
+
* A cell's children at {@link WEIGHT_LATTICE_DEPTH} levels finer have equal area by construction, so
|
|
18
|
+
* counting which delineation covers each child centre estimates covered area without a polygon clip. At
|
|
19
|
+
* depth 2 that is 49 children — 2.04% per child, which is the finest share NRCS's own
|
|
20
|
+
* `muaggatt.niccdcdpct` ever reports (observed minimum: 2%). Resolving finer than the authority publishes
|
|
21
|
+
* would be precision this layer cannot source.
|
|
22
|
+
*
|
|
23
|
+
* A WHOLE CELL SKIPS THE LATTICE ENTIRELY, and that is exact rather than an optimization: a cell lying
|
|
24
|
+
* wholly inside one delineation is covered by that delineation and by nothing else, so its distribution is
|
|
25
|
+
* that map unit's component split and its `mapped_share` is 1.
|
|
26
|
+
*
|
|
27
|
+
* `mapped_share` EXISTS BECAUSE A SURVEY-AREA EDGE CELL IS PARTLY OUTSIDE EVERY DELINEATION. Without it,
|
|
28
|
+
* the unmapped remainder would silently deflate every class share — an absence represented as a small
|
|
29
|
+
* number, which is the one thing this schema exists to prevent. The five shares are normalized over the
|
|
30
|
+
* mapped part, so they sum to 1 exactly, and `mapped_share` says how much of the cell that was.
|
|
31
|
+
*
|
|
32
|
+
* CLASS 8 IS A CLASS SHARE, NOT AN ABSENCE. It is a determination — the survey looked and rated the land
|
|
33
|
+
* as precluding commercial plant production, and 67,547 national components carry it. Folding it in with
|
|
34
|
+
* `NOTCOM`, a water body and an unrated series would produce a well-formed wrong answer, and separating
|
|
35
|
+
* the four absences from the one positive negative is the whole reason this table has five columns rather
|
|
36
|
+
* than one.
|
|
37
|
+
*/
|
|
38
|
+
import { parseJSONStrict } from "@mailwoman/core/objects";
|
|
39
|
+
import { pointInEncodedRings } from "@mailwoman/spatial";
|
|
40
|
+
import { cellToChildren, cellToLatLng } from "h3-js";
|
|
41
|
+
import { SOIL_SHARE_WEIGHTING } from "#vocabulary";
|
|
42
|
+
/**
|
|
43
|
+
* How many resolution levels finer than the index the weighting lattice runs.
|
|
44
|
+
*
|
|
45
|
+
* Two, giving 49 children per cell and a 2.04% share granularity. That is deliberately matched to the finest share the
|
|
46
|
+
* authority itself publishes — `muaggatt.niccdcdpct`'s observed minimum is 2% — because a lattice finer than the
|
|
47
|
+
* source's own reporting grain buys precision this layer cannot source, at 7× the cost per level.
|
|
48
|
+
*/
|
|
49
|
+
export const WEIGHT_LATTICE_DEPTH = 2;
|
|
50
|
+
/**
|
|
51
|
+
* Class shares below this are folded into `other_share` rather than stored.
|
|
52
|
+
*
|
|
53
|
+
* One percent, which sits BELOW the lattice's own 2.04% granularity, so nothing a single child cell produces is
|
|
54
|
+
* truncated — what lands here is the long tail that component percentages create inside a child (a 1%-weight component
|
|
55
|
+
* inside one child cell contributes 0.02%). Truncating a long tail is legitimate; doing it silently is not, which is
|
|
56
|
+
* why the remainder is stored explicitly and the shares still sum to 1.
|
|
57
|
+
*/
|
|
58
|
+
export const CLASS_SHARE_FLOOR = 0.01;
|
|
59
|
+
/**
|
|
60
|
+
* Turn one map unit and its components into the per-unit-area profile the reduction folds in.
|
|
61
|
+
*
|
|
62
|
+
* A `no_mapping` map unit contributes wholly to `nodata` and NEVER to a class: it is a polygon the authority drew with
|
|
63
|
+
* no soil mapping behind it, and reading it as a low class would be the reassuring wrong number §3.2 of the survey is
|
|
64
|
+
* about.
|
|
65
|
+
*
|
|
66
|
+
* The split across components is by `comppct_r`, the component's representative percentage of its map unit, normalized
|
|
67
|
+
* by the total actually present rather than assumed to be 100 — measured on `IA153` all 152 map units sum to exactly
|
|
68
|
+
* 100, and a national build must not depend on that holding everywhere.
|
|
69
|
+
*/
|
|
70
|
+
export function mapUnitProfile(mapUnit, components) {
|
|
71
|
+
if (mapUnit.no_mapping) {
|
|
72
|
+
return { classShares: new Map(), unrated: 0, notRateable: 0, noData: 1 };
|
|
73
|
+
}
|
|
74
|
+
let total = 0;
|
|
75
|
+
for (const component of components) {
|
|
76
|
+
total += component.comppct_r;
|
|
77
|
+
}
|
|
78
|
+
// A map unit whose components carry no weight at all publishes no readable proportion, so nothing can be apportioned
|
|
79
|
+
// from it. It is marked `no_mapping` upstream for exactly this reason; reaching here with a zero total means the
|
|
80
|
+
// upstream check and this one disagree, and answering with an empty distribution would silently drop the delineation's
|
|
81
|
+
// area out of every share.
|
|
82
|
+
if (total <= 0) {
|
|
83
|
+
return { classShares: new Map(), unrated: 0, notRateable: 0, noData: 1 };
|
|
84
|
+
}
|
|
85
|
+
const classShares = new Map();
|
|
86
|
+
let unrated = 0;
|
|
87
|
+
let notRateable = 0;
|
|
88
|
+
for (const component of components) {
|
|
89
|
+
const weight = component.comppct_r / total;
|
|
90
|
+
if (weight <= 0)
|
|
91
|
+
continue;
|
|
92
|
+
if (component.nirrcapcl) {
|
|
93
|
+
classShares.set(component.nirrcapcl, (classShares.get(component.nirrcapcl) ?? 0) + weight);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
// A NULL rating means the survey did not rate this component, and WHY it did not is what separates the two buckets.
|
|
97
|
+
// A miscellaneous area is a non-soil area — rock outcrop, water — that the capability rating does not apply to; a
|
|
98
|
+
// named soil with no rating is one the survey chose not to rate. Read as one number they would both say "not
|
|
99
|
+
// arable", which neither of them says.
|
|
100
|
+
if (component.compkind === "Miscellaneous area") {
|
|
101
|
+
notRateable += weight;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
unrated += weight;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { classShares, unrated, notRateable, noData: 0 };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Reduce one cell.
|
|
111
|
+
*
|
|
112
|
+
* @throws {Error} When a candidate names a map unit the profile map does not hold. A missing profile means the
|
|
113
|
+
* attribute join is short, and answering with the remaining candidates would report a well-formed distribution over
|
|
114
|
+
* part of the cell.
|
|
115
|
+
*/
|
|
116
|
+
export function reduceCell(cell, resolution, candidates, profiles, h3Cell) {
|
|
117
|
+
const weights = new Map();
|
|
118
|
+
let sampled = false;
|
|
119
|
+
let mappedShare = 1;
|
|
120
|
+
const whole = candidates.length === 1 ? candidates.find((candidate) => candidate.containment === "whole") : undefined;
|
|
121
|
+
if (whole) {
|
|
122
|
+
// Exactly one delineation, and it covers the cell entirely. Nothing else can reach it, so the lattice would return
|
|
123
|
+
// the same answer at 49 times the cost.
|
|
124
|
+
weights.set(whole.mukey, 1);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
sampled = true;
|
|
128
|
+
const children = cellToChildren(cell, resolution + WEIGHT_LATTICE_DEPTH);
|
|
129
|
+
let covered = 0;
|
|
130
|
+
for (const child of children) {
|
|
131
|
+
const [latitude, longitude] = cellToLatLng(child);
|
|
132
|
+
const owner = candidateAt(candidates, latitude, longitude);
|
|
133
|
+
if (!owner)
|
|
134
|
+
continue;
|
|
135
|
+
covered++;
|
|
136
|
+
weights.set(owner.mukey, (weights.get(owner.mukey) ?? 0) + 1);
|
|
137
|
+
}
|
|
138
|
+
if (!covered) {
|
|
139
|
+
// Every child centre fell outside every delineation reaching the cell. The cell IS touched — the index says so —
|
|
140
|
+
// but no lattice point landed inside, which happens when a sliver clips a corner. Reporting shares over nothing
|
|
141
|
+
// would divide by zero; reporting a mapped share of zero is the truthful answer, and the row is dropped by the
|
|
142
|
+
// caller rather than stored as an all-zero distribution.
|
|
143
|
+
return {
|
|
144
|
+
row: emptyRow(h3Cell, candidates.length),
|
|
145
|
+
topClassUnderHalf: false,
|
|
146
|
+
sampled,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
mappedShare = covered / children.length;
|
|
150
|
+
for (const [mukey, count] of weights) {
|
|
151
|
+
weights.set(mukey, count / covered);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return assembleRow(h3Cell, weights, profiles, mappedShare, candidates.length, sampled);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* The delineation covering a point, or `undefined` where none does.
|
|
158
|
+
*
|
|
159
|
+
* The bounding box is the prefilter the geometry table stores precisely so the ray cast runs on the few delineations
|
|
160
|
+
* that could contain the point rather than on every delineation reaching the cell.
|
|
161
|
+
*/
|
|
162
|
+
function candidateAt(candidates, latitude, longitude) {
|
|
163
|
+
for (const candidate of candidates) {
|
|
164
|
+
if (longitude < candidate.minLon ||
|
|
165
|
+
longitude > candidate.maxLon ||
|
|
166
|
+
latitude < candidate.minLat ||
|
|
167
|
+
latitude > candidate.maxLat) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (pointInEncodedRings(candidate.rings, longitude, latitude))
|
|
171
|
+
return candidate;
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Fold the per-map-unit weights through their profiles into the stored row.
|
|
177
|
+
*/
|
|
178
|
+
function assembleRow(h3Cell, weights, profiles, mappedShare, delineations, sampled) {
|
|
179
|
+
const classShares = new Map();
|
|
180
|
+
let unrated = 0;
|
|
181
|
+
let notRateable = 0;
|
|
182
|
+
let noData = 0;
|
|
183
|
+
for (const [mukey, weight] of weights) {
|
|
184
|
+
const profile = profiles.get(mukey);
|
|
185
|
+
if (!profile) {
|
|
186
|
+
throw new Error(`soil reduce: cell ${h3Cell} names map unit ${mukey}, which the attribute join does not hold — a missing profile means the join is short, and reducing the remaining candidates would report a well-formed distribution over part of the cell`);
|
|
187
|
+
}
|
|
188
|
+
for (const [code, share] of profile.classShares) {
|
|
189
|
+
classShares.set(code, (classShares.get(code) ?? 0) + share * weight);
|
|
190
|
+
}
|
|
191
|
+
unrated += profile.unrated * weight;
|
|
192
|
+
notRateable += profile.notRateable * weight;
|
|
193
|
+
noData += profile.noData * weight;
|
|
194
|
+
}
|
|
195
|
+
// The floor truncates the long tail component percentages create inside a lattice child. The remainder is stored
|
|
196
|
+
// rather than dropped, so the five shares sum to 1 and a reader can see how much was folded away.
|
|
197
|
+
let other = 0;
|
|
198
|
+
const kept = [];
|
|
199
|
+
for (const [code, share] of classShares) {
|
|
200
|
+
if (share < CLASS_SHARE_FLOOR) {
|
|
201
|
+
other += share;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
kept.push([code, share]);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
kept.sort((left, right) => right[1] - left[1] || (left[0] < right[0] ? -1 : 1));
|
|
208
|
+
const top = kept[0];
|
|
209
|
+
return {
|
|
210
|
+
row: {
|
|
211
|
+
h3_cell: h3Cell,
|
|
212
|
+
class_shares: JSON.stringify(Object.fromEntries(kept.map(([code, share]) => [code, round(share)]))),
|
|
213
|
+
unrated_share: round(unrated),
|
|
214
|
+
notrateable_share: round(notRateable),
|
|
215
|
+
nodata_share: round(noData),
|
|
216
|
+
other_share: round(other),
|
|
217
|
+
mapped_share: round(mappedShare),
|
|
218
|
+
top_class: top ? top[0] : null,
|
|
219
|
+
top_class_share: top ? round(top[1]) : null,
|
|
220
|
+
weighting: SOIL_SHARE_WEIGHTING,
|
|
221
|
+
delineations,
|
|
222
|
+
},
|
|
223
|
+
topClassUnderHalf: !top || top[1] < 0.5,
|
|
224
|
+
sampled,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* A cell no lattice point landed inside. `mapped_share` zero says exactly that, and the caller drops it rather than
|
|
229
|
+
* storing an all-zero distribution that would read as a surveyed cell holding nothing.
|
|
230
|
+
*/
|
|
231
|
+
function emptyRow(h3Cell, delineations) {
|
|
232
|
+
return {
|
|
233
|
+
h3_cell: h3Cell,
|
|
234
|
+
class_shares: "{}",
|
|
235
|
+
unrated_share: 0,
|
|
236
|
+
notrateable_share: 0,
|
|
237
|
+
nodata_share: 0,
|
|
238
|
+
other_share: 0,
|
|
239
|
+
mapped_share: 0,
|
|
240
|
+
top_class: null,
|
|
241
|
+
top_class_share: null,
|
|
242
|
+
weighting: SOIL_SHARE_WEIGHTING,
|
|
243
|
+
delineations,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Six decimals — a millionth of a cell, far below the lattice's own 2% granularity, and enough that the stored shares
|
|
248
|
+
* still sum to 1 within a rounding error a reader can see is rounding.
|
|
249
|
+
*/
|
|
250
|
+
const SHARE_DECIMALS = 6;
|
|
251
|
+
function round(value) {
|
|
252
|
+
return Number(value.toFixed(SHARE_DECIMALS));
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The sum of a stored row's five shares. Exported because the invariant it checks — that they sum to 1 — is what makes
|
|
256
|
+
* `other_share` required rather than decorative, and a test that could not state the sum could not pin it.
|
|
257
|
+
*/
|
|
258
|
+
export function shareTotal(row) {
|
|
259
|
+
const classes = parseJSONStrict(row.class_shares);
|
|
260
|
+
let total = row.unrated_share + row.notrateable_share + row.nodata_share + row.other_share;
|
|
261
|
+
for (const share of Object.values(classes)) {
|
|
262
|
+
total += share;
|
|
263
|
+
}
|
|
264
|
+
return total;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=reduce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reduce.js","sourceRoot":"","sources":["../../lib/sdk/reduce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* One survey area's attributes, its own metadata, and its mapped footprint.
|
|
7
|
+
*
|
|
8
|
+
* THE FOOTPRINT IS THE SURVEY-AREA OUTLINE, NEVER THE UNION OF THE RATED POLYGONS. `NOTCOM`,
|
|
9
|
+
* access-denied and `NOTPUB` map units are INSIDE the footprint and carry no rating, so a footprint taken
|
|
10
|
+
* from the rated set would report them as unmapped when the authority has declared exactly what they are.
|
|
11
|
+
* The archive ships the outline as its own shapefile — `soilsa_a_<areasymbol>.shp`, one feature — which is
|
|
12
|
+
* why this layer never has to reconstruct it.
|
|
13
|
+
*
|
|
14
|
+
* THE REFRESH DATE IS NOT THE SURVEY DATE, AND CONFLATING THEM IS THE CURRENCY LIE THIS FILE EXISTS TO
|
|
15
|
+
* PREVENT. `IA153` carries `saverest` 2025-09-09 and version 28, and the FGDC lineage inside the same
|
|
16
|
+
* archive cites `Soil Survey of Polk County, Iowa`, 1:15,840, **1960**. The dataset's own
|
|
17
|
+
* time-period-of-content runs 1998-09-22 to 2025-09-09, so a consumer reading that as survey currency
|
|
18
|
+
* reads it wrong by sixty-five years. Both dates are stored, apart, with the title the older one came
|
|
19
|
+
* from so it is checkable rather than assertible.
|
|
20
|
+
*
|
|
21
|
+
* TWO SCALES, ALSO DIFFERENT FACTS. `legend.projectscale` is 12,000 for `IA153` — the scale the map units
|
|
22
|
+
* were digitized at. The 1960 source citation's own `srcscale` is 15,840 — the scale the ground was
|
|
23
|
+
* walked at. Storing one as the other would answer the enlargement caveat's question wrongly.
|
|
24
|
+
*
|
|
25
|
+
* THE LICENCE IS CHECKED PER SURVEY AREA, against the `useconst` element of the metadata that area ships.
|
|
26
|
+
* An area whose use constraints no longer say "This is public information" is a licence change, and a
|
|
27
|
+
* build that absorbed one would ship an artifact under terms nobody checked. The text is boilerplate
|
|
28
|
+
* repeated across SSURGO, which is why asserting it is cheap and why a change in it is loud.
|
|
29
|
+
*/
|
|
30
|
+
import type { ParsedGeometry } from "@mailwoman/spatial";
|
|
31
|
+
import type { PathBuilderLike } from "path-ts";
|
|
32
|
+
import type { SoilComponentTable, SoilMapUnitTable } from "#schema";
|
|
33
|
+
import { type DomainMember } from "#sdk/tabular";
|
|
34
|
+
/**
|
|
35
|
+
* The declared domains this layer validates against, and stores.
|
|
36
|
+
*
|
|
37
|
+
* `capability_class` is shared by `nirrcapcl`, `irrcapcl` and `muaggatt.niccdcd`, which is why one domain covers three
|
|
38
|
+
* columns.
|
|
39
|
+
*/
|
|
40
|
+
export declare const STORED_DOMAINS: readonly ["capability_class", "capability_subclass", "farmland_classification", "component_kind", "mapunit_kind", "mapunit_status"];
|
|
41
|
+
/**
|
|
42
|
+
* One survey area's tabular attributes, already joined and validated.
|
|
43
|
+
*/
|
|
44
|
+
export interface SurveyAreaAttributes {
|
|
45
|
+
areasymbol: string;
|
|
46
|
+
areaname: string;
|
|
47
|
+
saverest: string;
|
|
48
|
+
saversion: number | null;
|
|
49
|
+
surveySourceDate: string | null;
|
|
50
|
+
surveySourceTitle: string | null;
|
|
51
|
+
sourceScale: number | null;
|
|
52
|
+
mappingScale: number | null;
|
|
53
|
+
/**
|
|
54
|
+
* The area the authority publishes for this survey area, in acres — the independent witness the ring-area check
|
|
55
|
+
* compares against.
|
|
56
|
+
*/
|
|
57
|
+
areaAcres: number | null;
|
|
58
|
+
mapUnits: SoilMapUnitTable[];
|
|
59
|
+
components: SoilComponentTable[];
|
|
60
|
+
domains: DomainMember[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Read one survey area's tabular export.
|
|
64
|
+
*
|
|
65
|
+
* @throws {Error} When the metadata's use constraints no longer carry the public-information sentence, when a `Choice`
|
|
66
|
+
* column holds a value outside the authority's own declared domain, or when the export declares no legend row.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readSurveyAreaAttributes(tabularDirectory: PathBuilderLike, areaSymbol: string): Promise<SurveyAreaAttributes>;
|
|
69
|
+
/**
|
|
70
|
+
* What the shipped FGDC metadata says about this survey area's dates and its licence.
|
|
71
|
+
*/
|
|
72
|
+
export interface FGDCMetadata {
|
|
73
|
+
/**
|
|
74
|
+
* The citation's own `pubdate`, as an ISO date — the refresh.
|
|
75
|
+
*/
|
|
76
|
+
publicationDate: string;
|
|
77
|
+
/**
|
|
78
|
+
* The OLDEST source citation date in the lineage, as an ISO date or a bare year.
|
|
79
|
+
*/
|
|
80
|
+
oldestSourceDate: string | null;
|
|
81
|
+
oldestSourceTitle: string | null;
|
|
82
|
+
oldestSourceScale: number | null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Read the metadata NRCS ships inside the archive.
|
|
86
|
+
*
|
|
87
|
+
* Targeted extraction rather than a general XML parse, and NOT for want of a parser — `@mailwoman/core` ships
|
|
88
|
+
* `htmlparser2`. A parser RECOVERS an unclosed element by giving it the rest of the document as its content, and the
|
|
89
|
+
* two values below that throw would then stamp the artifact with that content instead. {@link elementText} answers
|
|
90
|
+
* `undefined` for an element it cannot read, which is what makes the throw reachable. Every value this reader cannot
|
|
91
|
+
* find is reported as `null` EXCEPT the publication date and the licence sentence, which throw — those two decide the
|
|
92
|
+
* artifact's vintage and whether it may be shipped at all, and neither has a safe default.
|
|
93
|
+
*
|
|
94
|
+
* @throws {Error} When the metadata carries no publication date, or its use constraints no longer carry the
|
|
95
|
+
* public-information sentence.
|
|
96
|
+
*/
|
|
97
|
+
export declare function readFGDCMetadata(xml: string, areaSymbol: string): FGDCMetadata;
|
|
98
|
+
/**
|
|
99
|
+
* Read the survey area's own outline shapefile as a GeoJSON geometry.
|
|
100
|
+
*
|
|
101
|
+
* @throws {Error} When the shapefile holds anything other than exactly one feature. Taking the first of several would
|
|
102
|
+
* silently choose which ground the coverage claim is about.
|
|
103
|
+
*/
|
|
104
|
+
export declare function readSurveyAreaOutline(shapefilePath: string): Promise<ParsedGeometry>;
|
|
105
|
+
//# sourceMappingURL=survey-area.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"survey-area.d.ts","sourceRoot":"","sources":["../../lib/sdk/survey-area.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AACnE,OAAO,EAAsE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAUpH;;;;;GAKG;AACH,eAAO,MAAM,cAAc,qIAOjB,CAAA;AAEV;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,gBAAgB,EAAE,CAAA;IAC5B,UAAU,EAAE,kBAAkB,EAAE,CAAA;IAChC,OAAO,EAAE,YAAY,EAAE,CAAA;CACvB;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC7C,gBAAgB,EAAE,eAAe,EACjC,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAkI/B;AA2ED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B;;OAEG;IACH,eAAe,EAAE,MAAM,CAAA;IACvB;;OAEG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CA0B9E;AAuFD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAqB1F"}
|