@mailwoman/soil 9.4.0 → 10.1.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 +118 -116
- package/lib/index.ts +66 -98
- package/lib/paths.ts +24 -0
- package/lib/schema.ts +188 -120
- package/lib/vocabulary.ts +40 -107
- package/out/index.d.ts +52 -70
- package/out/index.d.ts.map +1 -1
- package/out/index.js +24 -72
- package/out/index.js.map +1 -1
- package/out/paths.d.ts +19 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +21 -0
- package/out/paths.js.map +1 -0
- package/out/schema.d.ts +187 -119
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +31 -31
- package/out/schema.js.map +1 -1
- package/out/sdk/acquire.d.ts +15 -24
- package/out/sdk/acquire.d.ts.map +1 -1
- package/out/sdk/acquire.js +5 -20
- package/out/sdk/acquire.js.map +1 -1
- package/out/sdk/build-soil.d.ts +67 -70
- package/out/sdk/build-soil.d.ts.map +1 -1
- package/out/sdk/build-soil.js +65 -95
- package/out/sdk/build-soil.js.map +1 -1
- package/out/sdk/cell-tiers.d.ts +7 -19
- package/out/sdk/cell-tiers.d.ts.map +1 -1
- package/out/sdk/cell-tiers.js +19 -38
- package/out/sdk/cell-tiers.js.map +1 -1
- package/out/sdk/cells.d.ts +15 -41
- package/out/sdk/cells.d.ts.map +1 -1
- package/out/sdk/cells.js +11 -38
- package/out/sdk/cells.js.map +1 -1
- package/out/sdk/client.d.ts +23 -48
- package/out/sdk/client.d.ts.map +1 -1
- package/out/sdk/client.js +19 -63
- package/out/sdk/client.js.map +1 -1
- package/out/sdk/download.d.ts +24 -47
- package/out/sdk/download.d.ts.map +1 -1
- package/out/sdk/download.js +14 -54
- package/out/sdk/download.js.map +1 -1
- package/out/sdk/ingest/chunk.d.ts +77 -0
- package/out/sdk/ingest/chunk.d.ts.map +1 -0
- package/out/sdk/{ingest-chunk.js → ingest/chunk.js} +19 -17
- package/out/sdk/ingest/chunk.js.map +1 -0
- package/out/sdk/ingest/worker.d.ts +9 -0
- package/out/sdk/ingest/worker.d.ts.map +1 -0
- package/out/{scripts/ingest-chunk.js → sdk/ingest/worker.js} +11 -10
- package/out/sdk/ingest/worker.js.map +1 -0
- package/out/sdk/ingest.d.ts +40 -52
- package/out/sdk/ingest.d.ts.map +1 -1
- package/out/sdk/ingest.js +18 -61
- package/out/sdk/ingest.js.map +1 -1
- package/out/sdk/measure-resolutions.d.ts +5 -16
- package/out/sdk/measure-resolutions.d.ts.map +1 -1
- package/out/sdk/measure-resolutions.js +3 -15
- package/out/sdk/measure-resolutions.js.map +1 -1
- package/out/sdk/reduce.d.ts +33 -59
- package/out/sdk/reduce.d.ts.map +1 -1
- package/out/sdk/reduce.js +47 -78
- package/out/sdk/reduce.js.map +1 -1
- package/out/sdk/survey-area.d.ts +16 -48
- package/out/sdk/survey-area.d.ts.map +1 -1
- package/out/sdk/survey-area.js +33 -77
- package/out/sdk/survey-area.js.map +1 -1
- package/out/sdk/tabular.d.ts +32 -31
- package/out/sdk/tabular.d.ts.map +1 -1
- package/out/sdk/tabular.js +58 -56
- package/out/sdk/tabular.js.map +1 -1
- package/out/sdk/test-kit.d.ts +57 -0
- package/out/sdk/test-kit.d.ts.map +1 -0
- package/out/{test-kit.js → sdk/test-kit.js} +18 -39
- package/out/sdk/test-kit.js.map +1 -0
- package/out/sdk/verify.d.ts +15 -51
- package/out/sdk/verify.d.ts.map +1 -1
- package/out/sdk/verify.js +19 -77
- package/out/sdk/verify.js.map +1 -1
- package/out/vocabulary.d.ts +37 -103
- package/out/vocabulary.d.ts.map +1 -1
- package/out/vocabulary.js +34 -107
- package/out/vocabulary.js.map +1 -1
- package/package.json +36 -190
- package/{lib/sdk → sdk}/acquire.ts +17 -27
- package/{lib/sdk → sdk}/build-soil.ts +114 -128
- package/{lib/sdk → sdk}/cell-tiers.ts +20 -39
- package/{lib/sdk → sdk}/cells.ts +17 -43
- package/sdk/client.ts +147 -0
- package/sdk/download.ts +131 -0
- package/{lib/sdk/ingest-chunk.ts → sdk/ingest/chunk.ts} +34 -26
- package/{lib/scripts/ingest-chunk.ts → sdk/ingest/worker.ts} +10 -9
- package/sdk/ingest.ts +253 -0
- package/{lib/sdk → sdk}/measure-resolutions.ts +5 -16
- package/sdk/reduce.ts +344 -0
- package/{lib/sdk → sdk}/survey-area.ts +39 -83
- package/{lib/sdk → sdk}/tabular.ts +62 -59
- package/{lib → sdk}/test-kit.ts +18 -40
- package/{lib/sdk → sdk}/verify.ts +29 -85
- package/lib/sdk/client.ts +0 -184
- package/lib/sdk/download.ts +0 -161
- package/lib/sdk/index.ts +0 -20
- package/lib/sdk/ingest.ts +0 -278
- package/lib/sdk/reduce.ts +0 -375
- package/out/scripts/ingest-chunk.d.ts +0 -11
- package/out/scripts/ingest-chunk.d.ts.map +0 -1
- package/out/scripts/ingest-chunk.js.map +0 -1
- package/out/sdk/index.d.ts +0 -20
- package/out/sdk/index.d.ts.map +0 -1
- package/out/sdk/index.js +0 -20
- package/out/sdk/index.js.map +0 -1
- package/out/sdk/ingest-chunk.d.ts +0 -73
- package/out/sdk/ingest-chunk.d.ts.map +0 -1
- package/out/sdk/ingest-chunk.js.map +0 -1
- package/out/test-kit.d.ts +0 -79
- package/out/test-kit.d.ts.map +0 -1
- package/out/test-kit.js.map +0 -1
package/out/sdk/reduce.js
CHANGED
|
@@ -3,69 +3,42 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
6
|
+
* Reduces the delineations that reach a cell into that cell's capability-class distribution.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
8
|
+
* The result is a distribution because most map units mix several soil components.
|
|
9
|
+
* The survey cannot separate those components at its mapping scale. Shares are normalized over the mapped part of the cell.
|
|
10
|
+
* `mapped_share` records how large that part is. Capability class 8 is a rated class, so it is stored as a
|
|
11
|
+
* class share. The unrated share, the no-data share and the not-rateable share each have their own column.
|
|
37
12
|
*/
|
|
38
|
-
import { parseJSONStrict } from "@mailwoman/core/json";
|
|
13
|
+
import { parseJSONStrict, stringifyJSON } from "@mailwoman/core/json";
|
|
39
14
|
import { pointInEncodedRings } from "@mailwoman/spatial";
|
|
40
15
|
import { cellToChildren, cellToLatLng } from "h3-js";
|
|
41
16
|
import { SOIL_SHARE_WEIGHTING } from "#vocabulary";
|
|
42
17
|
/**
|
|
43
|
-
*
|
|
18
|
+
* The number of H3 levels below the index resolution at which the weighting lattice samples.
|
|
44
19
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
20
|
+
* Children at a finer resolution have equal area, so counting which delineation
|
|
21
|
+
* covers each child center estimates the covered area.
|
|
22
|
+
* A depth of 2 gives 49 children, or about 2% per child.
|
|
23
|
+
*
|
|
24
|
+
* NRCS publishes component shares no finer than 2%, and each extra level costs seven times as much.
|
|
48
25
|
*/
|
|
49
26
|
export const WEIGHT_LATTICE_DEPTH = 2;
|
|
50
27
|
/**
|
|
51
|
-
*
|
|
28
|
+
* The class share below which a class is added to `other_share` instead of stored.
|
|
52
29
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* why the remainder is stored explicitly and the shares still sum to 1.
|
|
30
|
+
* The floor is below the lattice's 2% step, so it only removes the small shares
|
|
31
|
+
* that minor components contribute.
|
|
32
|
+
* The reducer adds them to `other_share` so all shares sum to 1.
|
|
57
33
|
*/
|
|
58
34
|
export const CLASS_SHARE_FLOOR = 0.01;
|
|
59
35
|
/**
|
|
60
|
-
*
|
|
36
|
+
* Builds the profile of one map unit from its components.
|
|
61
37
|
*
|
|
62
|
-
* A `no_mapping` map unit contributes
|
|
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.
|
|
38
|
+
* A `no_mapping` map unit contributes only to `noData`, because it has no soil mapping.
|
|
65
39
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* 100, and a national build must not depend on that holding everywhere.
|
|
40
|
+
* Components are weighted by `comppct_r`, the component's representative percentage of its map unit.
|
|
41
|
+
* The weights are divided by their actual total, because the percentages may not sum to 100.
|
|
69
42
|
*/
|
|
70
43
|
export function mapUnitProfile(mapUnit, components) {
|
|
71
44
|
if (mapUnit.no_mapping) {
|
|
@@ -75,10 +48,8 @@ export function mapUnitProfile(mapUnit, components) {
|
|
|
75
48
|
for (const component of components) {
|
|
76
49
|
total += component.comppct_r;
|
|
77
50
|
}
|
|
78
|
-
//
|
|
79
|
-
//
|
|
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.
|
|
51
|
+
// Components with zero total weight give no proportions, so the map unit counts as no data.
|
|
52
|
+
// An empty distribution would drop the delineation's area from every share.
|
|
82
53
|
if (total <= 0) {
|
|
83
54
|
return { classShares: new Map(), unrated: 0, notRateable: 0, noData: 1 };
|
|
84
55
|
}
|
|
@@ -93,10 +64,8 @@ export function mapUnitProfile(mapUnit, components) {
|
|
|
93
64
|
classShares.set(component.nirrcapcl, (classShares.get(component.nirrcapcl) ?? 0) + weight);
|
|
94
65
|
continue;
|
|
95
66
|
}
|
|
96
|
-
// A
|
|
97
|
-
//
|
|
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.
|
|
67
|
+
// A miscellaneous area, such as rock outcrop or water, cannot take a capability rating.
|
|
68
|
+
// Any other component with a NULL rating is a soil that the survey did not rate.
|
|
100
69
|
if (component.compkind === "Miscellaneous area") {
|
|
101
70
|
notRateable += weight;
|
|
102
71
|
}
|
|
@@ -107,11 +76,11 @@ export function mapUnitProfile(mapUnit, components) {
|
|
|
107
76
|
return { classShares, unrated, notRateable, noData: 0 };
|
|
108
77
|
}
|
|
109
78
|
/**
|
|
110
|
-
*
|
|
79
|
+
* Reduces one cell.
|
|
111
80
|
*
|
|
112
|
-
* @throws {Error} When a candidate
|
|
113
|
-
*
|
|
114
|
-
*
|
|
81
|
+
* @throws {Error} When a candidate's map unit has no profile.
|
|
82
|
+
* A missing profile means the attribute join is incomplete.
|
|
83
|
+
* The remaining candidates would describe only part of the cell.
|
|
115
84
|
*/
|
|
116
85
|
export function reduceCell(cell, resolution, candidates, profiles, h3Cell) {
|
|
117
86
|
const weights = new Map();
|
|
@@ -119,8 +88,7 @@ export function reduceCell(cell, resolution, candidates, profiles, h3Cell) {
|
|
|
119
88
|
let mappedShare = 1;
|
|
120
89
|
const whole = candidates.length === 1 ? candidates.find((candidate) => candidate.containment === "whole") : undefined;
|
|
121
90
|
if (whole) {
|
|
122
|
-
//
|
|
123
|
-
// the same answer at 49 times the cost.
|
|
91
|
+
// A single delineation covers the whole cell, so the lattice would give the same answer.
|
|
124
92
|
weights.set(whole.mukey, 1);
|
|
125
93
|
}
|
|
126
94
|
else {
|
|
@@ -136,10 +104,10 @@ export function reduceCell(cell, resolution, candidates, profiles, h3Cell) {
|
|
|
136
104
|
weights.set(owner.mukey, (weights.get(owner.mukey) ?? 0) + 1);
|
|
137
105
|
}
|
|
138
106
|
if (!covered) {
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
// caller
|
|
107
|
+
// No child center fell inside a delineation.
|
|
108
|
+
// A sliver can clip only a corner.
|
|
109
|
+
// This row has a mapped share of zero.
|
|
110
|
+
// The caller drops it.
|
|
143
111
|
return {
|
|
144
112
|
row: emptyRow(h3Cell, candidates.length),
|
|
145
113
|
topClassUnderHalf: false,
|
|
@@ -154,10 +122,10 @@ export function reduceCell(cell, resolution, candidates, profiles, h3Cell) {
|
|
|
154
122
|
return assembleRow(h3Cell, weights, profiles, mappedShare, candidates.length, sampled);
|
|
155
123
|
}
|
|
156
124
|
/**
|
|
157
|
-
*
|
|
125
|
+
* Returns the delineation that covers a point, or `undefined` when none does.
|
|
158
126
|
*
|
|
159
|
-
*
|
|
160
|
-
* that could contain the point
|
|
127
|
+
* A bounding-box test runs first so that the ray cast runs only on delineations
|
|
128
|
+
* that could contain the point.
|
|
161
129
|
*/
|
|
162
130
|
function candidateAt(candidates, latitude, longitude) {
|
|
163
131
|
for (const candidate of candidates) {
|
|
@@ -173,7 +141,7 @@ function candidateAt(candidates, latitude, longitude) {
|
|
|
173
141
|
return undefined;
|
|
174
142
|
}
|
|
175
143
|
/**
|
|
176
|
-
*
|
|
144
|
+
* Combines the per-map-unit weights with their profiles into the stored row.
|
|
177
145
|
*/
|
|
178
146
|
function assembleRow(h3Cell, weights, profiles, mappedShare, delineations, sampled) {
|
|
179
147
|
const classShares = new Map();
|
|
@@ -192,8 +160,7 @@ function assembleRow(h3Cell, weights, profiles, mappedShare, delineations, sampl
|
|
|
192
160
|
notRateable += profile.notRateable * weight;
|
|
193
161
|
noData += profile.noData * weight;
|
|
194
162
|
}
|
|
195
|
-
//
|
|
196
|
-
// rather than dropped, so the five shares sum to 1 and a reader can see how much was folded away.
|
|
163
|
+
// Classes below the floor go into `other_share` so that the shares still sum to 1.
|
|
197
164
|
let other = 0;
|
|
198
165
|
const kept = [];
|
|
199
166
|
for (const [code, share] of classShares) {
|
|
@@ -209,7 +176,7 @@ function assembleRow(h3Cell, weights, profiles, mappedShare, delineations, sampl
|
|
|
209
176
|
return {
|
|
210
177
|
row: {
|
|
211
178
|
h3_cell: h3Cell,
|
|
212
|
-
class_shares:
|
|
179
|
+
class_shares: stringifyJSON(Object.fromEntries(kept.map(([code, share]) => [code, round(share)]))),
|
|
213
180
|
unrated_share: round(unrated),
|
|
214
181
|
notrateable_share: round(notRateable),
|
|
215
182
|
nodata_share: round(noData),
|
|
@@ -225,8 +192,10 @@ function assembleRow(h3Cell, weights, profiles, mappedShare, delineations, sampl
|
|
|
225
192
|
};
|
|
226
193
|
}
|
|
227
194
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
195
|
+
* Returns the row for a cell that no lattice point landed in.
|
|
196
|
+
*
|
|
197
|
+
* The caller drops a row with a zero `mapped_share`, because storing it would
|
|
198
|
+
* look like a surveyed cell with no soil.
|
|
230
199
|
*/
|
|
231
200
|
function emptyRow(h3Cell, delineations) {
|
|
232
201
|
return {
|
|
@@ -244,16 +213,16 @@ function emptyRow(h3Cell, delineations) {
|
|
|
244
213
|
};
|
|
245
214
|
}
|
|
246
215
|
/**
|
|
247
|
-
*
|
|
248
|
-
* still sum to 1 within a rounding error a reader can see is rounding.
|
|
216
|
+
* The number of decimals kept in a stored share, far finer than the lattice's 2% step.
|
|
249
217
|
*/
|
|
250
218
|
const SHARE_DECIMALS = 6;
|
|
251
219
|
function round(value) {
|
|
252
220
|
return Number(value.toFixed(SHARE_DECIMALS));
|
|
253
221
|
}
|
|
254
222
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
223
|
+
* Returns the sum of a stored row's class shares and four other shares.
|
|
224
|
+
*
|
|
225
|
+
* Tests use it to check that the shares sum to 1.
|
|
257
226
|
*/
|
|
258
227
|
export function shareTotal(row) {
|
|
259
228
|
const classes = parseJSONStrict(row.class_shares);
|
package/out/sdk/reduce.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reduce.js","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"reduce.js","sourceRoot":"","sources":["../../sdk/reduce.ts"],"names":[],"mappings":"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"}
|
package/out/sdk/survey-area.d.ts
CHANGED
|
@@ -2,40 +2,14 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
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
5
|
*/
|
|
30
6
|
import type { ParsedGeometry } from "@mailwoman/spatial";
|
|
31
7
|
import type { PathBuilderLike } from "path-ts";
|
|
32
8
|
import type { SoilComponentTable, SoilMapUnitTable } from "#schema";
|
|
33
9
|
import { type DomainMember } from "#sdk/tabular";
|
|
34
10
|
/**
|
|
35
|
-
* The declared domains this layer validates against
|
|
36
|
-
*
|
|
37
|
-
* `capability_class` is shared by `nirrcapcl`, `irrcapcl` and `muaggatt.niccdcd`, which is why one domain covers three
|
|
38
|
-
* columns.
|
|
11
|
+
* The declared domains this layer validates against and stores; `capability_class`
|
|
12
|
+
* covers `nirrcapcl`, `irrcapcl` and `muaggatt.niccdcd`.
|
|
39
13
|
*/
|
|
40
14
|
export declare const STORED_DOMAINS: readonly ["capability_class", "capability_subclass", "farmland_classification", "component_kind", "mapunit_kind", "mapunit_status"];
|
|
41
15
|
/**
|
|
@@ -51,8 +25,8 @@ export interface SurveyAreaAttributes {
|
|
|
51
25
|
sourceScale: number | null;
|
|
52
26
|
mappingScale: number | null;
|
|
53
27
|
/**
|
|
54
|
-
* The area the authority publishes for this survey area, in acres —
|
|
55
|
-
* compares against.
|
|
28
|
+
* The area the authority publishes for this survey area, in acres —
|
|
29
|
+
* the independent witness the ring-area check compares against.
|
|
56
30
|
*/
|
|
57
31
|
areaAcres: number | null;
|
|
58
32
|
mapUnits: SoilMapUnitTable[];
|
|
@@ -62,44 +36,38 @@ export interface SurveyAreaAttributes {
|
|
|
62
36
|
/**
|
|
63
37
|
* Read one survey area's tabular export.
|
|
64
38
|
*
|
|
65
|
-
* @throws {Error} When the metadata's use constraints no longer
|
|
66
|
-
*
|
|
39
|
+
* @throws {Error} When the metadata's use constraints no longer include the
|
|
40
|
+
* public-information sentence, when a `Choice` column holds a value outside the
|
|
41
|
+
* authority's own declared domain, or when the export declares no legend row.
|
|
67
42
|
*/
|
|
68
43
|
export declare function readSurveyAreaAttributes(tabularDirectory: PathBuilderLike, areaSymbol: string): Promise<SurveyAreaAttributes>;
|
|
69
44
|
/**
|
|
70
|
-
* What the shipped FGDC metadata
|
|
45
|
+
* What the shipped FGDC metadata records about this survey area's dates and its license.
|
|
71
46
|
*/
|
|
72
47
|
export interface FGDCMetadata {
|
|
73
48
|
/**
|
|
74
|
-
* The citation's own `pubdate
|
|
49
|
+
* The citation's own `pubdate` as an ISO date — the refresh rather than the survey date.
|
|
75
50
|
*/
|
|
76
51
|
publicationDate: string;
|
|
77
52
|
/**
|
|
78
|
-
* The
|
|
53
|
+
* The oldest source citation date in the lineage, as an ISO date or a bare year.
|
|
79
54
|
*/
|
|
80
55
|
oldestSourceDate: string | null;
|
|
81
56
|
oldestSourceTitle: string | null;
|
|
82
57
|
oldestSourceScale: number | null;
|
|
83
58
|
}
|
|
84
59
|
/**
|
|
85
|
-
* Read the metadata
|
|
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.
|
|
60
|
+
* Read the metadata nrcs ships inside the archive.
|
|
93
61
|
*
|
|
94
|
-
* @throws {Error} When the metadata
|
|
95
|
-
*
|
|
62
|
+
* @throws {Error} When the metadata has no publication date, or its use constraints
|
|
63
|
+
* no longer include the public-information sentence.
|
|
96
64
|
*/
|
|
97
65
|
export declare function readFGDCMetadata(xml: string, areaSymbol: string): FGDCMetadata;
|
|
98
66
|
/**
|
|
99
67
|
* Read the survey area's own outline shapefile as a GeoJSON geometry.
|
|
100
68
|
*
|
|
101
|
-
* @throws {Error} When the shapefile holds anything other than exactly one feature.
|
|
102
|
-
*
|
|
69
|
+
* @throws {Error} When the shapefile holds anything other than exactly one feature.
|
|
70
|
+
* The function would silently choose which ground the coverage claim describes if it took the first feature.
|
|
103
71
|
*/
|
|
104
|
-
export declare function readSurveyAreaOutline(shapefilePath:
|
|
72
|
+
export declare function readSurveyAreaOutline(shapefilePath: PathBuilderLike): Promise<ParsedGeometry>;
|
|
105
73
|
//# sourceMappingURL=survey-area.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"survey-area.d.ts","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"survey-area.d.ts","sourceRoot":"","sources":["../../sdk/survey-area.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,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;;;GAGG;AACH,eAAO,MAAM,cAAc,YAC1B,kBAAkB,EAClB,qBAAqB,EACrB,yBAAyB,EACzB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,CACP,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;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC7C,gBAAgB,EAAE,eAAe,EACjC,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,oBAAoB,CAAC,CAkI/B;AAmED;;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;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CA0B9E;AAkFD;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAqBnG"}
|
package/out/sdk/survey-area.js
CHANGED
|
@@ -2,40 +2,15 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
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
5
|
*/
|
|
30
|
-
import { parseJSONStrict } from "@mailwoman/core/json";
|
|
6
|
+
import { parseJSONStrict, stringifyJSON } from "@mailwoman/core/json";
|
|
31
7
|
import { runFile } from "@mailwoman/core/process";
|
|
8
|
+
import { normalizeWhitespace } from "@mailwoman/core/strings/format";
|
|
32
9
|
import { domainCodes, readDeclaredDomains, readTable, readTabularDictionary } from "#sdk/tabular";
|
|
33
10
|
import { COINTERP_OVERALL_RULE_DEPTH, farmlandScope, NCCPI_V3_RULE_NAME, SSURGO_NO_MAPPING_NAMES, SSURGO_NO_MAPPING_SYMBOLS, SSURGO_PUBLIC_INFORMATION_SENTENCE, } from "#vocabulary";
|
|
34
11
|
/**
|
|
35
|
-
* The declared domains this layer validates against
|
|
36
|
-
*
|
|
37
|
-
* `capability_class` is shared by `nirrcapcl`, `irrcapcl` and `muaggatt.niccdcd`, which is why one domain covers three
|
|
38
|
-
* columns.
|
|
12
|
+
* The declared domains this layer validates against and stores; `capability_class`
|
|
13
|
+
* covers `nirrcapcl`, `irrcapcl` and `muaggatt.niccdcd`.
|
|
39
14
|
*/
|
|
40
15
|
export const STORED_DOMAINS = [
|
|
41
16
|
"capability_class",
|
|
@@ -48,8 +23,9 @@ export const STORED_DOMAINS = [
|
|
|
48
23
|
/**
|
|
49
24
|
* Read one survey area's tabular export.
|
|
50
25
|
*
|
|
51
|
-
* @throws {Error} When the metadata's use constraints no longer
|
|
52
|
-
*
|
|
26
|
+
* @throws {Error} When the metadata's use constraints no longer include the
|
|
27
|
+
* public-information sentence, when a `Choice` column holds a value outside the
|
|
28
|
+
* authority's own declared domain, or when the export declares no legend row.
|
|
53
29
|
*/
|
|
54
30
|
export async function readSurveyAreaAttributes(tabularDirectory, areaSymbol) {
|
|
55
31
|
const dictionary = await readTabularDictionary(tabularDirectory);
|
|
@@ -114,8 +90,8 @@ export async function readSurveyAreaAttributes(tabularDirectory, areaSymbol) {
|
|
|
114
90
|
return {
|
|
115
91
|
cokey: row.cokey,
|
|
116
92
|
mukey: row.mukey,
|
|
117
|
-
// A blank `comppct_r` is a component with no declared weight
|
|
118
|
-
//
|
|
93
|
+
// A blank `comppct_r` is a component with no declared weight, recorded as zero
|
|
94
|
+
// rather than dropped so the component still appears.
|
|
119
95
|
comppct_r: row.comppct_r ? Number(row.comppct_r) : 0,
|
|
120
96
|
compname: nullable(row.compname),
|
|
121
97
|
compkind: nullable(row.compkind),
|
|
@@ -163,11 +139,8 @@ export async function readSurveyAreaAttributes(tabularDirectory, areaSymbol) {
|
|
|
163
139
|
/**
|
|
164
140
|
* A polygon the authority drew with no soil mapping behind it.
|
|
165
141
|
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* carrying NO components at all. A map unit with no components has nothing to rate whatever it is called, and reading
|
|
169
|
-
* it as "rated nothing" rather than "no mapping" would put it in `unrated_share` — a claim that the survey looked and
|
|
170
|
-
* declined, when it did not look.
|
|
142
|
+
* A map unit with no components has no component to rate, so it reads as no mapping
|
|
143
|
+
* rather than assigned no rating.
|
|
171
144
|
*/
|
|
172
145
|
function isNoMapping(musym, muname, componentCount) {
|
|
173
146
|
if (SSURGO_NO_MAPPING_SYMBOLS.has(musym.toUpperCase()))
|
|
@@ -179,28 +152,23 @@ function isNoMapping(musym, muname, componentCount) {
|
|
|
179
152
|
/**
|
|
180
153
|
* Refuse a value outside the authority's own declared domain.
|
|
181
154
|
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* NULL is a real state in every one of these columns and means something specific — for `nirrcapcl` it means the survey
|
|
185
|
-
* did not rate the component, which is not class 8.
|
|
155
|
+
* A blank is a real NULL state rather than a violation, recording that the
|
|
156
|
+
* survey did not rate the component.
|
|
186
157
|
*/
|
|
187
158
|
function assertDeclared(declared, value, domain, where) {
|
|
188
159
|
if (!value)
|
|
189
160
|
return;
|
|
190
161
|
if (declared.has(value))
|
|
191
162
|
return;
|
|
192
|
-
throw new Error(`soil survey area: ${where} holds ${
|
|
163
|
+
throw new Error(`soil survey area: ${where} holds ${stringifyJSON(value)}, which is not in the authority's declared ${domain} domain (${declared.size} members, read from the archive's own msdomdet.txt) — an unknown code is a source-schema change, and coercing it would turn "the source changed" into "there is nothing here"`);
|
|
193
164
|
}
|
|
194
165
|
function nullable(value) {
|
|
195
166
|
return value || null;
|
|
196
167
|
}
|
|
197
168
|
/**
|
|
198
|
-
* The
|
|
169
|
+
* The nccpi v3.0 overall index per component.
|
|
199
170
|
*
|
|
200
|
-
*
|
|
201
|
-
* one row per component at {@link COINTERP_OVERALL_RULE_DEPTH}: 369 of 369 components on `IA153`, of which 327 carry a
|
|
202
|
-
* value. Sub-rules at greater depths are the submodels (corn, soybeans, small grains, cotton), which this layer does
|
|
203
|
-
* not carry.
|
|
171
|
+
* Sub-rules at greater depths are submodels this layer does not include.
|
|
204
172
|
*/
|
|
205
173
|
async function readNCCPI(tabularDirectory, dictionary) {
|
|
206
174
|
const rows = await readTable(tabularDirectory, dictionary, "cointerp", [
|
|
@@ -222,22 +190,15 @@ async function readNCCPI(tabularDirectory, dictionary) {
|
|
|
222
190
|
return byCokey;
|
|
223
191
|
}
|
|
224
192
|
/**
|
|
225
|
-
* Read the metadata
|
|
226
|
-
*
|
|
227
|
-
* Targeted extraction rather than a general XML parse, and NOT for want of a parser — `@mailwoman/core` ships
|
|
228
|
-
* `htmlparser2`. A parser RECOVERS an unclosed element by giving it the rest of the document as its content, and the
|
|
229
|
-
* two values below that throw would then stamp the artifact with that content instead. {@link elementText} answers
|
|
230
|
-
* `undefined` for an element it cannot read, which is what makes the throw reachable. Every value this reader cannot
|
|
231
|
-
* find is reported as `null` EXCEPT the publication date and the licence sentence, which throw — those two decide the
|
|
232
|
-
* artifact's vintage and whether it may be shipped at all, and neither has a safe default.
|
|
193
|
+
* Read the metadata nrcs ships inside the archive.
|
|
233
194
|
*
|
|
234
|
-
* @throws {Error} When the metadata
|
|
235
|
-
*
|
|
195
|
+
* @throws {Error} When the metadata has no publication date, or its use constraints
|
|
196
|
+
* no longer include the public-information sentence.
|
|
236
197
|
*/
|
|
237
198
|
export function readFGDCMetadata(xml, areaSymbol) {
|
|
238
199
|
const useConstraints = elementText(xml, "useconst");
|
|
239
200
|
if (!useConstraints?.includes(SSURGO_PUBLIC_INFORMATION_SENTENCE)) {
|
|
240
|
-
throw new Error(`soil survey area: ${areaSymbol}'s FGDC use constraints do not carry ${
|
|
201
|
+
throw new Error(`soil survey area: ${areaSymbol}'s FGDC use constraints do not carry ${stringifyJSON(SSURGO_PUBLIC_INFORMATION_SENTENCE)} — that sentence is the grant this layer ships on, so a survey area without it must not be built into a distributable artifact`);
|
|
241
202
|
}
|
|
242
203
|
const publicationDate = elementText(xml, "pubdate");
|
|
243
204
|
if (!publicationDate) {
|
|
@@ -253,33 +214,30 @@ export function readFGDCMetadata(xml, areaSymbol) {
|
|
|
253
214
|
};
|
|
254
215
|
}
|
|
255
216
|
/**
|
|
256
|
-
* The lineage's source citations
|
|
217
|
+
* The lineage's source citations — what the polygons rest on and when each was made.
|
|
257
218
|
*/
|
|
258
219
|
function readSourceCitations(xml) {
|
|
259
220
|
const citations = [];
|
|
260
221
|
for (const body of elementBlocks(xml, "srcinfo")) {
|
|
261
|
-
// `caldate`
|
|
262
|
-
//
|
|
222
|
+
// `caldate` stores a single date.
|
|
223
|
+
// `begdate` stores a range's start date in this layer.
|
|
263
224
|
const date = elementText(body, "caldate") ?? elementText(body, "begdate");
|
|
264
225
|
if (!date)
|
|
265
226
|
continue;
|
|
266
227
|
const scale = elementText(body, "srcscale");
|
|
267
228
|
citations.push({
|
|
268
229
|
date: date.trim(),
|
|
269
|
-
title: (elementText(body, "title") ?? "")
|
|
230
|
+
title: normalizeWhitespace(elementText(body, "title") ?? ""),
|
|
270
231
|
scale: scale ? Number(scale) : null,
|
|
271
232
|
});
|
|
272
233
|
}
|
|
273
234
|
return citations;
|
|
274
235
|
}
|
|
275
236
|
/**
|
|
276
|
-
* The text of the first `<name>` element, whitespace
|
|
237
|
+
* The text of the first `<name>` element, with whitespace preserved.
|
|
277
238
|
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* partner: the lazy run re-scans to the end from every candidate start. The input here is a 43,251-character document
|
|
281
|
-
* that arrived over the network inside a downloaded archive, so "a malformed one cannot happen" is not a claim this
|
|
282
|
-
* reader gets to make. Two `indexOf` calls answer the same question in one pass.
|
|
239
|
+
* Two `indexOf` calls avoid the polynomial backtracking a regex takes on a document
|
|
240
|
+
* whose opening tag has no closing partner.
|
|
283
241
|
*/
|
|
284
242
|
function elementText(xml, name) {
|
|
285
243
|
const open = `<${name}>`;
|
|
@@ -288,13 +246,11 @@ function elementText(xml, name) {
|
|
|
288
246
|
return undefined;
|
|
289
247
|
const from = start + open.length;
|
|
290
248
|
const end = xml.indexOf(`</${name}>`, from);
|
|
291
|
-
// An element with no closing tag is unreadable
|
|
292
|
-
// mean the value could not be read rather than that it is blank.
|
|
249
|
+
// An element with no closing tag is unreadable rather than empty, the same answer an absent element gets.
|
|
293
250
|
return end === -1 ? undefined : xml.slice(from, end);
|
|
294
251
|
}
|
|
295
252
|
/**
|
|
296
|
-
* Every `<name>` element's inner text
|
|
297
|
-
* for the same reason.
|
|
253
|
+
* Every `<name>` element's inner text in document order, the repeating counterpart of {@link elementText}.
|
|
298
254
|
*/
|
|
299
255
|
function elementBlocks(xml, name) {
|
|
300
256
|
const open = `<${name}>`;
|
|
@@ -314,8 +270,8 @@ function elementBlocks(xml, name) {
|
|
|
314
270
|
}
|
|
315
271
|
}
|
|
316
272
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
273
|
+
* A bare year stays bare, since padding it to January 1 would invent a precision
|
|
274
|
+
* the citation does not claim.
|
|
319
275
|
*/
|
|
320
276
|
function normalizeFGDCDate(value) {
|
|
321
277
|
const trimmed = value.trim();
|
|
@@ -325,8 +281,8 @@ function normalizeFGDCDate(value) {
|
|
|
325
281
|
/**
|
|
326
282
|
* Read the survey area's own outline shapefile as a GeoJSON geometry.
|
|
327
283
|
*
|
|
328
|
-
* @throws {Error} When the shapefile holds anything other than exactly one feature.
|
|
329
|
-
*
|
|
284
|
+
* @throws {Error} When the shapefile holds anything other than exactly one feature.
|
|
285
|
+
* The function would silently choose which ground the coverage claim describes if it took the first feature.
|
|
330
286
|
*/
|
|
331
287
|
export async function readSurveyAreaOutline(shapefilePath) {
|
|
332
288
|
const { stdout } = await runFile("ogr2ogr", ["-f", "GeoJSON", "/vsistdout/", "-t_srs", "EPSG:4326", shapefilePath], {
|