@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
package/lib/test-kit.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Hand-built survey areas for the fixture rung: geometry, attributes and an outline, with no network and
|
|
7
|
+
* no GDAL in the loop.
|
|
8
|
+
*
|
|
9
|
+
* A FIXTURE RUNG THAT COULD ONLY RUN THROUGH ogr2ogr WOULD TEST THE CONVERSION ON THE MACHINES THAT HAVE
|
|
10
|
+
* IT AND NOTHING AT ALL ON THE ONES THAT DO NOT. What these fixtures exercise is the whole database half —
|
|
11
|
+
* the declared-domain check, the cell classification, the area-weighted reduction, the four absence
|
|
12
|
+
* shares, the coverage rows, the manifest and the seal.
|
|
13
|
+
*
|
|
14
|
+
* THE ABSENCE CASES ARE THE POINT, AND IOWA HAS NONE OF THEM. Every Iowa survey area is fully digitized,
|
|
15
|
+
* so `NOTCOM`, `NOTPUB` and access-denied map units never appear in the live build — which means the only
|
|
16
|
+
* place `nodata_share` can be exercised is here. The same is true of a component whose rating is NULL for
|
|
17
|
+
* the not-rateable reason and of a class-8 rating: both exist in Iowa but sparsely, and a fixture pins the
|
|
18
|
+
* behaviour rather than hoping a county contains one.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// The exterior and hole ring builders live in `@mailwoman/spatial` — a winding convention rather than this
|
|
22
|
+
// product's geometry, and a second copy of it is a second place for a hole to stop being one.
|
|
23
|
+
import { rectangleRing } from "@mailwoman/spatial"
|
|
24
|
+
|
|
25
|
+
import type { SoilComponentTable, SoilMapUnitTable } from "#schema"
|
|
26
|
+
import type { SoilDelineation, SoilFeatureSource } from "#sdk/ingest"
|
|
27
|
+
import type { SurveyAreaAttributes } from "#sdk/survey-area"
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Re-exported so a fixture in another workspace builds its rings the same way this one does.
|
|
31
|
+
*/
|
|
32
|
+
export { rectangleRing, reversedRing as holeRing } from "@mailwoman/spatial"
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Where the fixture geometry sits — central Iowa, so the cells it produces are the ones a real build would use.
|
|
36
|
+
*/
|
|
37
|
+
export const FIXTURE_ORIGIN = { lat: 41.6, lon: -93.6 }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Degrees per fixture square side. About 1.6 km at this latitude — several resolution-9 cells across, so a fixture
|
|
41
|
+
* square has both an interior and a fringe.
|
|
42
|
+
*/
|
|
43
|
+
export const FIXTURE_SIDE = 0.015
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The fixture map units. Each one exists to exercise exactly one reading.
|
|
47
|
+
*/
|
|
48
|
+
export function fixtureMapUnits(areaSymbol = "XX001"): SoilMapUnitTable[] {
|
|
49
|
+
return [
|
|
50
|
+
{
|
|
51
|
+
mukey: "mu-mixed",
|
|
52
|
+
areasymbol: areaSymbol,
|
|
53
|
+
musym: "M1",
|
|
54
|
+
muname: "Fixture complex, 0 to 2 percent slopes",
|
|
55
|
+
mukind: "Complex",
|
|
56
|
+
mustatus: null,
|
|
57
|
+
farmlndcl: "Prime farmland if drained",
|
|
58
|
+
farmland_scope: "federal",
|
|
59
|
+
niccdcd: "2",
|
|
60
|
+
niccdcdpct: 45,
|
|
61
|
+
no_mapping: 0,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
mukey: "mu-class8",
|
|
65
|
+
areasymbol: areaSymbol,
|
|
66
|
+
musym: "M8",
|
|
67
|
+
muname: "Fixture badland",
|
|
68
|
+
mukind: "Consociation",
|
|
69
|
+
mustatus: null,
|
|
70
|
+
farmlndcl: "Not prime farmland",
|
|
71
|
+
farmland_scope: "none",
|
|
72
|
+
niccdcd: "8",
|
|
73
|
+
niccdcdpct: 100,
|
|
74
|
+
no_mapping: 0,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
mukey: "mu-water",
|
|
78
|
+
areasymbol: areaSymbol,
|
|
79
|
+
musym: "W",
|
|
80
|
+
muname: "Water",
|
|
81
|
+
mukind: "Consociation",
|
|
82
|
+
mustatus: null,
|
|
83
|
+
farmlndcl: null,
|
|
84
|
+
farmland_scope: "none",
|
|
85
|
+
niccdcd: null,
|
|
86
|
+
niccdcdpct: null,
|
|
87
|
+
no_mapping: 0,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
mukey: "mu-unrated",
|
|
91
|
+
areasymbol: areaSymbol,
|
|
92
|
+
musym: "U",
|
|
93
|
+
muname: "Fixture silt loam, unrated",
|
|
94
|
+
mukind: "Consociation",
|
|
95
|
+
mustatus: null,
|
|
96
|
+
farmlndcl: "Farmland of statewide importance",
|
|
97
|
+
farmland_scope: "state",
|
|
98
|
+
niccdcd: null,
|
|
99
|
+
niccdcdpct: null,
|
|
100
|
+
no_mapping: 0,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
mukey: "mu-notcom",
|
|
104
|
+
areasymbol: areaSymbol,
|
|
105
|
+
musym: "NOTCOM",
|
|
106
|
+
muname: "No Digital Data Available",
|
|
107
|
+
mukind: null,
|
|
108
|
+
mustatus: null,
|
|
109
|
+
farmlndcl: null,
|
|
110
|
+
farmland_scope: "none",
|
|
111
|
+
niccdcd: null,
|
|
112
|
+
niccdcdpct: null,
|
|
113
|
+
no_mapping: 1,
|
|
114
|
+
},
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The fixture components. `mu-mixed` is 45/35/20 across three classes, which is the case a winner-class schema would
|
|
120
|
+
* report as "class 2" and this one reports as a mixture.
|
|
121
|
+
*/
|
|
122
|
+
export function fixtureComponents(): SoilComponentTable[] {
|
|
123
|
+
return [
|
|
124
|
+
component("co-mixed-1", "mu-mixed", 45, "Series", "2", "e"),
|
|
125
|
+
component("co-mixed-2", "mu-mixed", 35, "Series", "3", "e"),
|
|
126
|
+
component("co-mixed-3", "mu-mixed", 20, "Series", "6", "s"),
|
|
127
|
+
component("co-class8", "mu-class8", 100, "Series", "8", "s"),
|
|
128
|
+
// A miscellaneous area with no rating: NOT RATEABLE, which is not the same as unrated and not the same as class 8.
|
|
129
|
+
component("co-water", "mu-water", 100, "Miscellaneous area", null, null),
|
|
130
|
+
// A named soil the survey did not rate: UNRATED.
|
|
131
|
+
component("co-unrated", "mu-unrated", 100, "Series", null, null),
|
|
132
|
+
// A minority component small enough to fall under the truncation floor once the lattice splits it.
|
|
133
|
+
component("co-tail", "mu-mixed", 1, "Series", "7", "e"),
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function component(
|
|
138
|
+
cokey: string,
|
|
139
|
+
mukey: string,
|
|
140
|
+
comppct: number,
|
|
141
|
+
compkind: string,
|
|
142
|
+
nirrcapcl: string | null,
|
|
143
|
+
nirrcapscl: string | null
|
|
144
|
+
): SoilComponentTable {
|
|
145
|
+
return {
|
|
146
|
+
cokey,
|
|
147
|
+
mukey,
|
|
148
|
+
comppct_r: comppct,
|
|
149
|
+
compname: cokey,
|
|
150
|
+
compkind,
|
|
151
|
+
nirrcapcl,
|
|
152
|
+
nirrcapscl,
|
|
153
|
+
irrcapcl: null,
|
|
154
|
+
irrcapscl: null,
|
|
155
|
+
nccpi_v3: null,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The declared domains a fixture build validates and stores, matching what the real `msdomdet.txt` ships.
|
|
161
|
+
*/
|
|
162
|
+
export function fixtureDomains(): SurveyAreaAttributes["domains"] {
|
|
163
|
+
const members: SurveyAreaAttributes["domains"] = []
|
|
164
|
+
|
|
165
|
+
for (const [index, code] of ["1", "2", "3", "4", "5", "6", "7", "8"].entries()) {
|
|
166
|
+
members.push({
|
|
167
|
+
domain: "capability_class",
|
|
168
|
+
code,
|
|
169
|
+
sequence: index + 1,
|
|
170
|
+
definition: `Soils in Class ${code} — fixture definition.`,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const [index, code] of ["e", "w", "s", "c"].entries()) {
|
|
175
|
+
members.push({ domain: "capability_subclass", code, sequence: index + 1, definition: `Fixture subclass ${code}.` })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return members
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The fixture delineations: a mixed square, a class-8 square, a water square, an unrated square, and a `NOTCOM` square,
|
|
183
|
+
* laid out left to right so each occupies its own ground.
|
|
184
|
+
*/
|
|
185
|
+
export function fixtureDelineations(areaSymbol = "XX001"): SoilDelineation[] {
|
|
186
|
+
const { lat, lon } = FIXTURE_ORIGIN
|
|
187
|
+
const mukeys = ["mu-mixed", "mu-class8", "mu-water", "mu-unrated", "mu-notcom"]
|
|
188
|
+
|
|
189
|
+
return mukeys.map((mukey, index) => ({
|
|
190
|
+
areaID: `${areaSymbol}:${index}`,
|
|
191
|
+
mukey,
|
|
192
|
+
areasymbol: areaSymbol,
|
|
193
|
+
polygons: [[rectangleRing(lon + index * FIXTURE_SIDE, lat, lon + (index + 1) * FIXTURE_SIDE, lat + FIXTURE_SIDE)]],
|
|
194
|
+
}))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The outline covering every fixture delineation, with margin — the survey area's own footprint.
|
|
199
|
+
*
|
|
200
|
+
* The margin is nearly a degree because the coverage test is CONSERVATIVE: `interiorCoverageCellSet` keeps only cells
|
|
201
|
+
* lying wholly inside the outline, and a resolution-6 cell is about 36 km across. An outline the size of the fixture
|
|
202
|
+
* squares yields zero interior cells and the build refuses — correctly, since an artifact with no coverage rows answers
|
|
203
|
+
* unknown everywhere while reporting success.
|
|
204
|
+
*/
|
|
205
|
+
export function fixtureOutline(margin = 0.75): { type: "Polygon"; coordinates: number[][][] } {
|
|
206
|
+
const { lat, lon } = FIXTURE_ORIGIN
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
type: "Polygon",
|
|
210
|
+
coordinates: [
|
|
211
|
+
rectangleRing(lon - margin, lat - margin, lon + 5 * FIXTURE_SIDE + margin, lat + FIXTURE_SIDE + margin),
|
|
212
|
+
],
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* A feature source over hand-built delineations.
|
|
218
|
+
*/
|
|
219
|
+
export function fixtureSource(delineations: SoilDelineation[], areaSymbol = "XX001"): SoilFeatureSource {
|
|
220
|
+
return {
|
|
221
|
+
areaSymbol,
|
|
222
|
+
declaredFeatureCount: delineations.length,
|
|
223
|
+
layer: `soilmu_a_${areaSymbol.toLowerCase()}`,
|
|
224
|
+
epsg: 4326,
|
|
225
|
+
origin: "fixture",
|
|
226
|
+
async *delineations() {
|
|
227
|
+
for (const delineation of delineations) {
|
|
228
|
+
yield delineation
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* One fixture survey area's attributes.
|
|
236
|
+
*
|
|
237
|
+
* `areaAcres` is left NULL on purpose: the area cross-check compares against what the AUTHORITY publishes, and a
|
|
238
|
+
* fixture that invented an acreage would be checking this package's arithmetic against itself.
|
|
239
|
+
*/
|
|
240
|
+
export function fixtureAttributes(areaSymbol = "XX001"): SurveyAreaAttributes {
|
|
241
|
+
return {
|
|
242
|
+
areasymbol: areaSymbol,
|
|
243
|
+
areaname: "Fixture County, Iowa",
|
|
244
|
+
saverest: "2025-09-09",
|
|
245
|
+
saversion: 1,
|
|
246
|
+
surveySourceDate: "1960",
|
|
247
|
+
surveySourceTitle: "Soil Survey of Fixture County, Iowa",
|
|
248
|
+
sourceScale: 15_840,
|
|
249
|
+
mappingScale: 12_000,
|
|
250
|
+
areaAcres: null,
|
|
251
|
+
mapUnits: fixtureMapUnits(areaSymbol),
|
|
252
|
+
components: fixtureComponents(),
|
|
253
|
+
domains: fixtureDomains(),
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* NRCS's own words, as data: the product identity, the licence sentence it ships inside every archive,
|
|
7
|
+
* the acknowledgement it asks for, and the caveats that decide what a reading may claim.
|
|
8
|
+
*
|
|
9
|
+
* THE RATING VOCABULARY IS NOT HERE, BECAUSE THE AUTHORITY SHIPS IT. `msdomdet.txt` inside each survey
|
|
10
|
+
* area's tabular export carries the declared domain for every `Choice` column, WITH the authority's own
|
|
11
|
+
* prose definition of each member — capability classes 1 through 8, subclasses `c`/`e`/`s`/`w`, the 28
|
|
12
|
+
* farmland classifications, the six component kinds. So the layer reads the domain out of the file it
|
|
13
|
+
* ingested rather than transcribing it from the National Soil Survey Handbook, and an out-of-domain value
|
|
14
|
+
* throws: an unknown code is a source-schema change, which is the event a reader most needs to hear
|
|
15
|
+
* about, and coercing it to a nearest neighbour or to NULL converts "the source changed" into "there is
|
|
16
|
+
* nothing here".
|
|
17
|
+
*
|
|
18
|
+
* THE FARMLAND VOCABULARY IS CONDITIONAL AND THE CONDITION IS THE CLAIM. 28 declared values, of which 24
|
|
19
|
+
* carry an "if" — `Prime farmland if drained`, `Prime farmland if irrigated and reclaimed of excess salts
|
|
20
|
+
* and sodium`. A boolean `arable` column would be this layer's invention rather than the authority's
|
|
21
|
+
* statement, so the string is stored whole.
|
|
22
|
+
*
|
|
23
|
+
* AND TWO OF ITS CATEGORIES TRAVEL WHILE THE OTHERS DO NOT. 7 CFR 657.5 defines prime farmland and unique
|
|
24
|
+
* farmland nationally against nine specific criteria; §657.5(c) and (d) hand "additional farmland of
|
|
25
|
+
* statewide importance" and "of local importance" to state and local agencies respectively. So
|
|
26
|
+
* `Farmland of statewide importance` in Iowa and in Georgia are not the same claim, and a consumer that
|
|
27
|
+
* pooled them into one rank would be pooling incompatible vocabularies. {@link FARMLAND_SCOPE} carries
|
|
28
|
+
* that distinction into the artifact rather than leaving it in a document.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The layer-name prefix. The suffix names the REGION the build covers, because the manifest's declared extent and the
|
|
33
|
+
* coverage rows describe the same set and that set is a list of published survey areas rather than "the United
|
|
34
|
+
* States".
|
|
35
|
+
*/
|
|
36
|
+
export const SOIL_LAYER_NAME_PREFIX = "soil-capability-nrcs-ssurgo-"
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The layer name for a build over `region`.
|
|
40
|
+
*/
|
|
41
|
+
export function soilLayerName(region: string): string {
|
|
42
|
+
return `${SOIL_LAYER_NAME_PREFIX}${region.toLowerCase()}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The pilot region — Iowa, whole, built survey area by survey area.
|
|
47
|
+
*/
|
|
48
|
+
export const SOIL_PILOT_REGION = "ia"
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The acknowledgement string the shipped FGDC metadata asks for, verbatim as the metadata gives the agency name. This
|
|
52
|
+
* is not decoration: the use constraints say the agency "should be acknowledged as the data source in products derived
|
|
53
|
+
* from these data", so it rides in `layer_manifest.attribution`.
|
|
54
|
+
*/
|
|
55
|
+
export const SSURGO_ATTRIBUTION = "U.S. Department of Agriculture, Natural Resources Conservation Service"
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The licence expression written into `layer_manifest.license`.
|
|
59
|
+
*
|
|
60
|
+
* A public-domain identifier, and the grant behind it is the producing agency's own sentence rather than a catalogue
|
|
61
|
+
* field: data.gov's entry points at `usa.gov/publicdomain/label/1.0/`, which redirects to a page that declines a
|
|
62
|
+
* blanket grant and tells the reader to check with the agency. The agency was checked at the strongest available place
|
|
63
|
+
* — the FGDC metadata NRCS ships inside the archive — and it says {@link SSURGO_PUBLIC_INFORMATION_SENTENCE}.
|
|
64
|
+
*/
|
|
65
|
+
export const SSURGO_LICENSE = "PDDL-1.0-USGov-NRCS"
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The sentence that makes this layer shippable, verbatim from the `useconst` element of the FGDC metadata inside every
|
|
69
|
+
* survey-area archive.
|
|
70
|
+
*
|
|
71
|
+
* The ingest asserts it is present in each area's metadata. A survey area whose use constraints no longer say this is a
|
|
72
|
+
* licence change, and a build that absorbed one would ship an artifact under terms nobody checked.
|
|
73
|
+
*/
|
|
74
|
+
export const SSURGO_PUBLIC_INFORMATION_SENTENCE = "This is public information"
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The use constraints in full, as the metadata states them. Carried so a reader can check the licence claim against the
|
|
78
|
+
* authority's own words rather than against this package's summary of them.
|
|
79
|
+
*/
|
|
80
|
+
export const SSURGO_USE_CONSTRAINTS =
|
|
81
|
+
"The U.S. Department of Agriculture, Natural Resources Conservation Service, should be acknowledged as the data " +
|
|
82
|
+
"source in products derived from these data. This data set is not designed for use as a primary regulatory tool " +
|
|
83
|
+
"in permitting or citing decisions, but may be used as a reference source. This is public information and may be " +
|
|
84
|
+
"interpreted by organizations, agencies, units of government, or others based on needs; however, they are " +
|
|
85
|
+
"responsible for the appropriate application."
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* What a reading is NOT, in the authority's own words. Carried on every reading, because a caller cannot see from a
|
|
89
|
+
* capability class that the survey declines to speak about a specific site.
|
|
90
|
+
*
|
|
91
|
+
* The first two sentences are why §3.1 of the survey forbids a point-level determination: the map is authoritative
|
|
92
|
+
* about an area at its own scale and explicitly declines to be authoritative about a point.
|
|
93
|
+
*/
|
|
94
|
+
export const SSURGO_PRODUCT_LIMITS: ReadonlyArray<string> = [
|
|
95
|
+
"The depicted soil boundaries, interpretations, and analysis derived from them do not eliminate the need for onsite sampling, testing, and detailed study of specific sites for intensive uses. Thus, these data and their interpretations are intended for planning purposes only.",
|
|
96
|
+
"Photographic or digital enlargement of these maps to scales greater than at which they were originally mapped can cause misinterpretation of the data. If enlarged, maps do not show the small areas of contrasting soils that could have been shown at a larger scale.",
|
|
97
|
+
"This data set is not designed for use as a primary regulatory tool in permitting or citing decisions, but may be used as a reference source.",
|
|
98
|
+
"Digital data files are periodically updated. Files are dated, and users are responsible for obtaining the latest version of the data.",
|
|
99
|
+
"The difference in positional accuracy between the soil boundaries and special soil features locations in the field and their digitized map locations is unknown.",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The coverage statement a `designated` basis rests on: what NRCS declares complete inside a published survey area.
|
|
104
|
+
*
|
|
105
|
+
* It is the mapping at the survey's own scale, NOT a site-specific determination — which is why the observation reports
|
|
106
|
+
* what the survey assigns to the map unit covering a location and never whether the land can be farmed.
|
|
107
|
+
*/
|
|
108
|
+
export const SSURGO_COVERAGE_STATEMENT =
|
|
109
|
+
"Soil surveys are published by survey area. Inside a published survey area the soil mapping is complete at the " +
|
|
110
|
+
"survey's own scale; land outside any published survey area has no soil map unit at all."
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Where the product's identity and cadence are published.
|
|
114
|
+
*/
|
|
115
|
+
export const SSURGO_STATEMENT_URL =
|
|
116
|
+
"https://www.nrcs.usda.gov/conservation-basics/natural-resource-concerns/soil/annual-soils-refresh"
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The dataset identifier written into `layer_manifest.source`.
|
|
120
|
+
*/
|
|
121
|
+
export const SSURGO_SOURCE = "nrcs.usda.gov/SSURGO"
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The projection every SSURGO survey-area shapefile declares. The `.prj` is an ESRI WKT naming `GCS_WGS_1984`, which
|
|
125
|
+
* GDAL resolves to this authority code — so no reprojection is needed before H3, and a survey area declaring anything
|
|
126
|
+
* else is a product change rather than a variation to absorb.
|
|
127
|
+
*/
|
|
128
|
+
export const SSURGO_SOURCE_EPSG = 4326
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Whether a farmland classification's criteria are set nationally or by a state or local agency.
|
|
132
|
+
*
|
|
133
|
+
* 7 CFR 657.5 defines prime and unique farmland against nine national criteria; (c) and (d) delegate statewide and
|
|
134
|
+
* local importance. A consumer comparing two states may compare the national categories and must not compare the
|
|
135
|
+
* delegated ones.
|
|
136
|
+
*/
|
|
137
|
+
export const FarmlandScope = {
|
|
138
|
+
/**
|
|
139
|
+
* Criteria set by 7 CFR 657.5(a)–(b). Comparable across the country.
|
|
140
|
+
*/
|
|
141
|
+
Federal: "federal",
|
|
142
|
+
/**
|
|
143
|
+
* Criteria "determined by the appropriate State agency or agencies" — 7 CFR 657.5(c).
|
|
144
|
+
*/
|
|
145
|
+
State: "state",
|
|
146
|
+
/**
|
|
147
|
+
* Criteria "identified by the local agency or agencies concerned" — 7 CFR 657.5(d).
|
|
148
|
+
*/
|
|
149
|
+
Local: "local",
|
|
150
|
+
/**
|
|
151
|
+
* The value states no farmland importance at all.
|
|
152
|
+
*/
|
|
153
|
+
None: "none",
|
|
154
|
+
} as const
|
|
155
|
+
|
|
156
|
+
export type FarmlandScope = (typeof FarmlandScope)[keyof typeof FarmlandScope]
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Which scope a declared `farmlndcl` value falls under, decided on the phrase the regulation itself uses.
|
|
160
|
+
*
|
|
161
|
+
* Matching on the phrase rather than on an enumerated list of the 28 values is deliberate: the domain grows — the
|
|
162
|
+
* conditional tail is generated by combining a base category with a condition — and a new `Farmland of statewide
|
|
163
|
+
* importance, if …` must land in {@link FarmlandScope.State} on the day it appears rather than silently defaulting to
|
|
164
|
+
* the comparable bucket.
|
|
165
|
+
*/
|
|
166
|
+
export function farmlandScope(value: string | null | undefined): FarmlandScope {
|
|
167
|
+
if (!value) return FarmlandScope.None
|
|
168
|
+
|
|
169
|
+
const lowered = value.toLowerCase()
|
|
170
|
+
|
|
171
|
+
// FIRST, because it CONTAINS the phrase the federal test looks for. `Not prime farmland` is a declared value stating
|
|
172
|
+
// no farmland importance at all, and a substring test that ran the other way round would call it federally comparable
|
|
173
|
+
// prime farmland — which it is the exact negation of. It is also the most common value in the domain: 192,120 of the
|
|
174
|
+
// 339,191 national map units carry it.
|
|
175
|
+
if (lowered.startsWith("not prime farmland")) return FarmlandScope.None
|
|
176
|
+
|
|
177
|
+
if (lowered.includes("statewide importance")) return FarmlandScope.State
|
|
178
|
+
|
|
179
|
+
if (lowered.includes("local importance")) return FarmlandScope.Local
|
|
180
|
+
|
|
181
|
+
if (lowered.includes("prime farmland") || lowered.includes("unique importance")) return FarmlandScope.Federal
|
|
182
|
+
|
|
183
|
+
return FarmlandScope.None
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Map-unit symbols NRCS uses for a delineation it has drawn and has no soil mapping behind.
|
|
188
|
+
*
|
|
189
|
+
* These are polygons rather than holes — the same wall-to-wall discipline that makes them separable from land outside a
|
|
190
|
+
* survey area entirely. Measured nationally through Soil Data Access: 37 map units carry `NOTCOM` (`No Digital Data
|
|
191
|
+
* Available`) and 7 carry `NOTPUB` (`Not Public Information`).
|
|
192
|
+
*/
|
|
193
|
+
export const SSURGO_NO_MAPPING_SYMBOLS: ReadonlySet<string> = new Set(["NOTCOM", "NOTPUB"])
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Map-unit names for the same case, where the symbol does not carry it. Measured nationally: 72 map units are named
|
|
197
|
+
* `Area not surveyed, access denied`.
|
|
198
|
+
*
|
|
199
|
+
* Matched case-insensitively on the whole name. A prefix match would catch a future `Area not surveyed, access denied —
|
|
200
|
+
* pending` and would also catch nothing else, but it would equally catch a real soil name beginning with those words,
|
|
201
|
+
* and there is no such thing to be gained by guessing.
|
|
202
|
+
*/
|
|
203
|
+
export const SSURGO_NO_MAPPING_NAMES: ReadonlySet<string> = new Set([
|
|
204
|
+
"area not surveyed, access denied",
|
|
205
|
+
"not public information",
|
|
206
|
+
"no digital data available",
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The interpretation rule whose overall value this layer carries, exactly as `cointerp.mrulename` spells it.
|
|
211
|
+
*
|
|
212
|
+
* Its `interphr` is an index in [0, 1] — measured 0.001 to 0.991 nationally, 0.022 to 0.990 on `IA153` — and it is
|
|
213
|
+
* never blended with the capability class: they are two ratings from one authority answering different questions, and
|
|
214
|
+
* combining them would produce a number NRCS does not publish.
|
|
215
|
+
*/
|
|
216
|
+
export const NCCPI_V3_RULE_NAME = "NCCPI - National Commodity Crop Productivity Index (Ver 3.0)"
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The interpretation depth at which a rule reports its own overall value. Sub-rules sit at greater depths and are the
|
|
220
|
+
* submodels (corn, soybeans, small grains, cotton), which this layer does not carry.
|
|
221
|
+
*/
|
|
222
|
+
export const COINTERP_OVERALL_RULE_DEPTH = "0"
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* How the per-cell shares were produced, recorded on every row.
|
|
226
|
+
*
|
|
227
|
+
* The survey names this as a required record because the weighting mixes two different things — polygon geometry, which
|
|
228
|
+
* the survey does know, and component percentages, which are a proportion without a location. "60% of this cell's area
|
|
229
|
+
* lies in map units whose components are class 2" and "the components in this cell sum to 60% class 2" are different
|
|
230
|
+
* claims, and a reader holding only a share cannot tell which one it is holding.
|
|
231
|
+
*/
|
|
232
|
+
export const SOIL_SHARE_WEIGHTING = "cell_area_x_comppct_r"
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The one-sentence expansion of {@link SOIL_SHARE_WEIGHTING}, stored beside it so the artifact explains itself.
|
|
236
|
+
*/
|
|
237
|
+
export const SOIL_SHARE_WEIGHTING_DESCRIPTION =
|
|
238
|
+
"Each map-unit delineation contributes the area of the cell it covers; that area is split across the delineation's " +
|
|
239
|
+
"components in proportion to comppct_r, the component's representative percentage of its map unit. Component " +
|
|
240
|
+
"percentages are a proportion without a location, so a share states how much of the cell's area lies in map units " +
|
|
241
|
+
"whose components carry a rating — not where within the cell that rating applies."
|
package/out/index.d.ts
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The `soil.db` reader — what the soil survey assigns at a coordinate, and on what basis.
|
|
7
|
+
*
|
|
8
|
+
* THREE ANSWERS, AND KEEPING THEM APART IS THE WHOLE JOB.
|
|
9
|
+
*
|
|
10
|
+
* 1. `designated` — the survey mapped this location and the cell's class distribution is the answer.
|
|
11
|
+
* 2. `designated_no_rating` — the survey mapped this location and rated nothing there. A cell that is
|
|
12
|
+
* 100% `unrated_share` or `notrateable_share` is `designated`-complete and carries no capability
|
|
13
|
+
* reading whatsoever, and that is not a corner case: 17.1% of national components carry no capability
|
|
14
|
+
* rating.
|
|
15
|
+
* 3. `unknown` — no coverage row. Outside any published survey area, or inside one where the polygon
|
|
16
|
+
* exists and the soil mapping behind it does not.
|
|
17
|
+
*
|
|
18
|
+
* READINGS 2 AND 3 LOOK THE SAME FROM A CLASS CODE AND ARE OPPOSITE ANSWERS FROM THE READER. A layer that
|
|
19
|
+
* could not tell them apart would report unmapped ground as unrated ground, which is one of the four
|
|
20
|
+
* absences this whole layer exists to keep separate.
|
|
21
|
+
*
|
|
22
|
+
* THE ANSWER IS A DISTRIBUTION, AND THE TOP CLASS ALWAYS ARRIVES WITH THE SHARE IT RESTS ON. NRCS's own
|
|
23
|
+
* `muaggatt` ships `niccdcd` beside `niccdcdpct` for exactly this reason, with an observed minimum of 2%.
|
|
24
|
+
* A caller that wants one class may take `topClass`; it cannot take it without also being handed
|
|
25
|
+
* `topClassShare`, because a 2% plurality and an 85% majority are different claims.
|
|
26
|
+
*
|
|
27
|
+
* NEITHER READING IS A STATEMENT ABOUT WHETHER THE LAND CAN BE FARMED. The layer reports what the soil
|
|
28
|
+
* survey assigns to the map unit covering a location, which is a fact about the map. NRCS states that its
|
|
29
|
+
* data "do not eliminate the need for onsite sampling, testing, and detailed study of specific sites for
|
|
30
|
+
* intensive uses" and are "intended for planning purposes only" — so `limits` carries the authority's own
|
|
31
|
+
* exclusions on every answer.
|
|
32
|
+
*
|
|
33
|
+
* THE PROBE IS ONE PRIMARY-KEY READ. The reduction is single-resolution and one row per cell, which is
|
|
34
|
+
* what makes it the spine key: a coordinate becomes a cell, the cell becomes a row, and the geometry tier
|
|
35
|
+
* underneath is never touched at read time. The unsimplified rings are there for a caller that wants to
|
|
36
|
+
* re-derive the claim, not for the probe.
|
|
37
|
+
*
|
|
38
|
+
* THE READER IS SYNCHRONOUS AND USES RAW PREPARED STATEMENTS, matching the resolution ladder's existing
|
|
39
|
+
* shape. The DDL that created these tables IS Kysely — see `schema.ts`.
|
|
40
|
+
*/
|
|
41
|
+
import { type CoverageCell, type LayerManifest } from "@mailwoman/core/layers";
|
|
42
|
+
export { FarmlandScope, farmlandScope, SSURGO_PRODUCT_LIMITS } from "#vocabulary";
|
|
43
|
+
/**
|
|
44
|
+
* What the layer can say about a coordinate.
|
|
45
|
+
*/
|
|
46
|
+
export declare const SoilReadingKind: {
|
|
47
|
+
/**
|
|
48
|
+
* The survey mapped this location and assigns at least one capability class here.
|
|
49
|
+
*/
|
|
50
|
+
readonly Designated: "designated";
|
|
51
|
+
/**
|
|
52
|
+
* The survey mapped this location and rated nothing here — every share is an absence share.
|
|
53
|
+
*/
|
|
54
|
+
readonly DesignatedNoRating: "designated_no_rating";
|
|
55
|
+
/**
|
|
56
|
+
* No coverage row. Unmapped by this authority, and never a low-capability reading.
|
|
57
|
+
*/
|
|
58
|
+
readonly Unknown: "unknown";
|
|
59
|
+
};
|
|
60
|
+
export type SoilReadingKind = (typeof SoilReadingKind)[keyof typeof SoilReadingKind];
|
|
61
|
+
/**
|
|
62
|
+
* The per-cell distribution, as a caller reads it.
|
|
63
|
+
*/
|
|
64
|
+
export interface SoilCapabilityDistribution {
|
|
65
|
+
/**
|
|
66
|
+
* The authority's class codes mapped to their area-weighted share, largest first.
|
|
67
|
+
*/
|
|
68
|
+
classShares: Record<string, number>;
|
|
69
|
+
/**
|
|
70
|
+
* Mapped soil components carrying a NULL rating — the survey did not rate them.
|
|
71
|
+
*/
|
|
72
|
+
unratedShare: number;
|
|
73
|
+
/**
|
|
74
|
+
* Miscellaneous areas the rating does not apply to.
|
|
75
|
+
*/
|
|
76
|
+
notRateableShare: number;
|
|
77
|
+
/**
|
|
78
|
+
* Polygons with no soil mapping behind them.
|
|
79
|
+
*/
|
|
80
|
+
noDataShare: number;
|
|
81
|
+
/**
|
|
82
|
+
* The truncated minority tail. The five shares sum to 1.
|
|
83
|
+
*/
|
|
84
|
+
otherShare: number;
|
|
85
|
+
/**
|
|
86
|
+
* How much of the cell any delineation covers. Below 1 at a survey-area edge.
|
|
87
|
+
*/
|
|
88
|
+
mappedShare: number;
|
|
89
|
+
/**
|
|
90
|
+
* The largest class share, and the share it rests on. Absent when the cell carries no class at all.
|
|
91
|
+
*/
|
|
92
|
+
topClass?: string;
|
|
93
|
+
topClassShare?: number;
|
|
94
|
+
/**
|
|
95
|
+
* Which weighting produced these shares.
|
|
96
|
+
*/
|
|
97
|
+
weighting: string;
|
|
98
|
+
/**
|
|
99
|
+
* How many delineations reached the cell.
|
|
100
|
+
*/
|
|
101
|
+
delineations: number;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* One survey area, as the layer holds it.
|
|
105
|
+
*/
|
|
106
|
+
export interface SoilSurveyAreaRecord {
|
|
107
|
+
areaSymbol: string;
|
|
108
|
+
areaName: string;
|
|
109
|
+
/**
|
|
110
|
+
* The refresh — when this version of the data was established.
|
|
111
|
+
*/
|
|
112
|
+
saverest: string;
|
|
113
|
+
/**
|
|
114
|
+
* The FIELD survey date, which is a different fact and is usually much older.
|
|
115
|
+
*/
|
|
116
|
+
surveySourceDate: string | null;
|
|
117
|
+
surveySourceTitle: string | null;
|
|
118
|
+
sourceScale: number | null;
|
|
119
|
+
mappingScale: number | null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* One reading, carrying everything a caller needs to re-derive it rather than take it.
|
|
123
|
+
*/
|
|
124
|
+
export interface SoilCapabilityReading {
|
|
125
|
+
kind: SoilReadingKind;
|
|
126
|
+
/**
|
|
127
|
+
* The cell's distribution. Present on both designated readings; absent on `unknown`.
|
|
128
|
+
*/
|
|
129
|
+
distribution?: SoilCapabilityDistribution;
|
|
130
|
+
/**
|
|
131
|
+
* The authority's own definition of the top class, from the domain it shipped.
|
|
132
|
+
*/
|
|
133
|
+
topClassDefinition?: string;
|
|
134
|
+
/**
|
|
135
|
+
* The survey area covering the location, with both its dates.
|
|
136
|
+
*/
|
|
137
|
+
surveyArea?: SoilSurveyAreaRecord;
|
|
138
|
+
/**
|
|
139
|
+
* The coverage row that licenses the reading, when there is one. Absent on `unknown`, which IS the absence.
|
|
140
|
+
*/
|
|
141
|
+
coverage?: CoverageCell & {
|
|
142
|
+
h3CellIndex: string;
|
|
143
|
+
resolution: number;
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* The index cell probed, for a receipt.
|
|
147
|
+
*/
|
|
148
|
+
indexCellIndex: string;
|
|
149
|
+
/**
|
|
150
|
+
* What the product does not cover, in the authority's own words. Carried on every reading.
|
|
151
|
+
*/
|
|
152
|
+
limits: ReadonlyArray<string>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The layer's identity, read once at open time.
|
|
156
|
+
*/
|
|
157
|
+
export interface SoilLayerIdentity {
|
|
158
|
+
manifest: LayerManifest;
|
|
159
|
+
indexResolution: number;
|
|
160
|
+
coverageResolution: number;
|
|
161
|
+
/**
|
|
162
|
+
* The survey areas the layer covers, in symbol order.
|
|
163
|
+
*/
|
|
164
|
+
surveyAreas: SoilSurveyAreaRecord[];
|
|
165
|
+
/**
|
|
166
|
+
* The class codes the layer's own vocabulary declares.
|
|
167
|
+
*/
|
|
168
|
+
classCodes: string[];
|
|
169
|
+
/**
|
|
170
|
+
* The weighting every stored share was produced under, and the sentence that says what it means.
|
|
171
|
+
*/
|
|
172
|
+
weighting: {
|
|
173
|
+
code: string;
|
|
174
|
+
description: string;
|
|
175
|
+
};
|
|
176
|
+
databasePath: string;
|
|
177
|
+
}
|
|
178
|
+
export interface SoilCapabilityLookupOptions {
|
|
179
|
+
databasePath: string;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Read a sealed `soil.db`.
|
|
183
|
+
*
|
|
184
|
+
* Everything that would make the reader answer a well-formed wrong thing is refused at CONSTRUCTION rather than at
|
|
185
|
+
* query time: a manifest naming a different product, a coverage table with no rows, a vocabulary with no classes. Each
|
|
186
|
+
* of those would otherwise present as a reader that simply always answers `unknown`, which on a receipt is
|
|
187
|
+
* indistinguishable from a region the authority genuinely has not surveyed.
|
|
188
|
+
*/
|
|
189
|
+
export declare class SoilCapabilityLookup implements Disposable {
|
|
190
|
+
#private;
|
|
191
|
+
readonly identity: SoilLayerIdentity;
|
|
192
|
+
constructor(options: SoilCapabilityLookupOptions);
|
|
193
|
+
/**
|
|
194
|
+
* What the soil survey assigns at this coordinate.
|
|
195
|
+
*/
|
|
196
|
+
lookup(latitude: number, longitude: number): SoilCapabilityReading;
|
|
197
|
+
[Symbol.dispose](): void;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=index.d.ts.map
|