@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.
Files changed (97) hide show
  1. package/README.md +221 -0
  2. package/lib/index.ts +474 -0
  3. package/lib/schema.ts +493 -0
  4. package/lib/scripts/ingest-chunk.ts +47 -0
  5. package/lib/sdk/acquire.ts +124 -0
  6. package/lib/sdk/build-soil.ts +778 -0
  7. package/lib/sdk/cell-tiers.ts +310 -0
  8. package/lib/sdk/cells.ts +224 -0
  9. package/lib/sdk/client.ts +273 -0
  10. package/lib/sdk/download.ts +161 -0
  11. package/lib/sdk/index.ts +20 -0
  12. package/lib/sdk/ingest-chunk.ts +182 -0
  13. package/lib/sdk/ingest.ts +271 -0
  14. package/lib/sdk/measure-resolutions.ts +81 -0
  15. package/lib/sdk/reduce.ts +375 -0
  16. package/lib/sdk/survey-area.ts +467 -0
  17. package/lib/sdk/tabular.ts +296 -0
  18. package/lib/sdk/verify.ts +369 -0
  19. package/lib/test-kit.ts +255 -0
  20. package/lib/vocabulary.ts +241 -0
  21. package/out/index.d.ts +199 -0
  22. package/out/index.d.ts.map +1 -0
  23. package/out/index.js +255 -0
  24. package/out/index.js.map +1 -0
  25. package/out/schema.d.ts +369 -0
  26. package/out/schema.d.ts.map +1 -0
  27. package/out/schema.js +186 -0
  28. package/out/schema.js.map +1 -0
  29. package/out/scripts/ingest-chunk.d.ts +11 -0
  30. package/out/scripts/ingest-chunk.d.ts.map +1 -0
  31. package/out/scripts/ingest-chunk.js +42 -0
  32. package/out/scripts/ingest-chunk.js.map +1 -0
  33. package/out/sdk/acquire.d.ts +60 -0
  34. package/out/sdk/acquire.d.ts.map +1 -0
  35. package/out/sdk/acquire.js +74 -0
  36. package/out/sdk/acquire.js.map +1 -0
  37. package/out/sdk/build-soil.d.ts +197 -0
  38. package/out/sdk/build-soil.d.ts.map +1 -0
  39. package/out/sdk/build-soil.js +434 -0
  40. package/out/sdk/build-soil.js.map +1 -0
  41. package/out/sdk/cell-tiers.d.ts +47 -0
  42. package/out/sdk/cell-tiers.d.ts.map +1 -0
  43. package/out/sdk/cell-tiers.js +194 -0
  44. package/out/sdk/cell-tiers.js.map +1 -0
  45. package/out/sdk/cells.d.ts +118 -0
  46. package/out/sdk/cells.d.ts.map +1 -0
  47. package/out/sdk/cells.js +140 -0
  48. package/out/sdk/cells.js.map +1 -0
  49. package/out/sdk/client.d.ts +120 -0
  50. package/out/sdk/client.d.ts.map +1 -0
  51. package/out/sdk/client.js +214 -0
  52. package/out/sdk/client.js.map +1 -0
  53. package/out/sdk/download.d.ts +84 -0
  54. package/out/sdk/download.d.ts.map +1 -0
  55. package/out/sdk/download.js +117 -0
  56. package/out/sdk/download.js.map +1 -0
  57. package/out/sdk/index.d.ts +20 -0
  58. package/out/sdk/index.d.ts.map +1 -0
  59. package/out/sdk/index.js +20 -0
  60. package/out/sdk/index.js.map +1 -0
  61. package/out/sdk/ingest-chunk.d.ts +73 -0
  62. package/out/sdk/ingest-chunk.d.ts.map +1 -0
  63. package/out/sdk/ingest-chunk.js +98 -0
  64. package/out/sdk/ingest-chunk.js.map +1 -0
  65. package/out/sdk/ingest.d.ts +132 -0
  66. package/out/sdk/ingest.d.ts.map +1 -0
  67. package/out/sdk/ingest.js +163 -0
  68. package/out/sdk/ingest.js.map +1 -0
  69. package/out/sdk/measure-resolutions.d.ts +39 -0
  70. package/out/sdk/measure-resolutions.d.ts.map +1 -0
  71. package/out/sdk/measure-resolutions.js +52 -0
  72. package/out/sdk/measure-resolutions.js.map +1 -0
  73. package/out/sdk/reduce.d.ts +122 -0
  74. package/out/sdk/reduce.d.ts.map +1 -0
  75. package/out/sdk/reduce.js +266 -0
  76. package/out/sdk/reduce.js.map +1 -0
  77. package/out/sdk/survey-area.d.ts +105 -0
  78. package/out/sdk/survey-area.d.ts.map +1 -0
  79. package/out/sdk/survey-area.js +346 -0
  80. package/out/sdk/survey-area.js.map +1 -0
  81. package/out/sdk/tabular.d.ts +106 -0
  82. package/out/sdk/tabular.d.ts.map +1 -0
  83. package/out/sdk/tabular.js +194 -0
  84. package/out/sdk/tabular.js.map +1 -0
  85. package/out/sdk/verify.d.ts +135 -0
  86. package/out/sdk/verify.d.ts.map +1 -0
  87. package/out/sdk/verify.js +221 -0
  88. package/out/sdk/verify.js.map +1 -0
  89. package/out/test-kit.d.ts +80 -0
  90. package/out/test-kit.d.ts.map +1 -0
  91. package/out/test-kit.js +228 -0
  92. package/out/test-kit.js.map +1 -0
  93. package/out/vocabulary.d.ts +179 -0
  94. package/out/vocabulary.d.ts.map +1 -0
  95. package/out/vocabulary.js +215 -0
  96. package/out/vocabulary.js.map +1 -0
  97. package/package.json +276 -0
@@ -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