@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,434 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * Build `soil.db` — the sealed polygon layer, from the survey areas NRCS publishes.
7
+ *
8
+ * THE ACCUMULATION IS IN SQL, NOT IN A MAP. Classification is per delineation and shared with the
9
+ * resolution measurement, but where the touches GO differs on purpose: the measuring instrument holds
10
+ * them in memory because it is comparing candidate resolutions in one pass, and the builder streams them
11
+ * into a temporary table because memory has to stay flat in row count. A polygon layer's touches
12
+ * outnumber its features, and Iowa is 99 survey areas.
13
+ *
14
+ * ABSENCE IS NO ROW, IN EVERY TABLE. Land outside a published survey area gets no `layer_coverage` row and
15
+ * no summary row — never a zero, never an empty histogram. Inside a published area the coverage row says
16
+ * `designated` at completeness 1.0, because NRCS declares its own mapping complete for those areas at its
17
+ * own scale; a coverage cell reached ONLY by `NOTCOM` and access-denied polygons gets no row either,
18
+ * because the polygon exists and the soil mapping behind it does not.
19
+ *
20
+ * A COVERAGE ROW LICENSES ONLY THAT THE AUTHORITY MAPPED HERE. The reading is the class distribution, and
21
+ * a cell that is 100% `unrated_share` is `designated`-complete and carries no capability reading
22
+ * whatsoever. That pairing is not a corner case: 17.1% of national components carry no capability rating,
23
+ * and for the irrigated rating the figure is 85.1%.
24
+ *
25
+ * THE AREA CROSS-CHECK USES THE AUTHORITY'S OWN PUBLISHED FIGURE. `legend.areaacres` is what NRCS states
26
+ * the survey area covers — 378,800 acres for `IA153`, which is 1,532.9 km² against the 1,532.5 km² an
27
+ * independent projected measurement of the same shapefile reports. Comparing the spherical ring sum
28
+ * against it catches a hole read as an exterior ring, whose absence is silent: such a polygon is
29
+ * well-formed and simply answers "inside" for ground the authority did not map.
30
+ */
31
+ import { readFileSize } from "@mailwoman/core/fs/readers";
32
+ import { areaAgreementFrom, createLayerCoverageTable, createLayerManifestTable, designatedCoverageCells, polygonLayerManifest, writeLayerCoverage, writeLayerManifest, } from "@mailwoman/core/layers";
33
+ import { resolveModulePath } from "@mailwoman/core/module/resolvers";
34
+ import { ingestChunkArguments, mergeCountsInto, runChunkProcess } from "@mailwoman/core/utils";
35
+ import { arealPolygons, geometryContains, interiorCoverageCells, shortCellToInt, } from "@mailwoman/spatial";
36
+ import { buildSealedArtifact } from "@mailwoman/sqlite/sealed-build";
37
+ import { cellToLatLng } from "h3-js";
38
+ import { createSoilTables } from "#schema";
39
+ import { reduceCells, resolveCells } from "#sdk/cell-tiers";
40
+ import { ingestSoilChunk } from "#sdk/ingest-chunk";
41
+ import { WEIGHT_LATTICE_DEPTH } from "#sdk/reduce";
42
+ import { SOIL_SHARE_WEIGHTING, SOIL_SHARE_WEIGHTING_DESCRIPTION, soilLayerName, SSURGO_ATTRIBUTION, SSURGO_LICENSE, SSURGO_SOURCE, } from "#vocabulary";
43
+ /**
44
+ * Schema version of the domain tables. Bumped when a column changes meaning, never for an added column a reader can
45
+ * ignore.
46
+ */
47
+ export const SOIL_SCHEMA_VERSION = 1;
48
+ /**
49
+ * Delineation ids per chunk process.
50
+ *
51
+ * Sized against the ceiling the flood layer measured rather than guessed: single-process runs over that product died
52
+ * after roughly 510,000 and 798,000 features as h3's WASM heap fragmented. 100,000 leaves five times that margin, and
53
+ * the cost of a smaller number is one interpreter start per chunk. Iowa's largest survey area holds well under it, so
54
+ * on this build the bound costs one process per area — which is what makes an area's failure nameable.
55
+ */
56
+ export const DEFAULT_CHUNK_SIZE = 100_000;
57
+ /**
58
+ * Square metres in an acre — the unit `legend.areaacres` publishes in.
59
+ */
60
+ const M2_PER_ACRE = 4046.8564224;
61
+ /**
62
+ * The relative gap between the ring-area total and the authority's own published acreage that fails the build.
63
+ *
64
+ * Two percent. The comparison is a spherical ring area against a figure NRCS itself warns "may differ from that
65
+ * measured using GIS software due to different measuring techniques and rounding practices, or due to the fact that the
66
+ * value has been adjusted so that the sum total of all map units in the legend equals that listed for soil survey area"
67
+ * — so an exact test would be brittle. Two percent sits far above the 0.03% `IA153` measures and far below the error a
68
+ * hole-blind read produces, which the zoning survey measured at 4.1% over a whole national layer.
69
+ */
70
+ const AREA_TOLERANCE = 0.02;
71
+ /**
72
+ * Build the layer.
73
+ *
74
+ * @throws {Error} On a delineation the classifier refuses, a streamed count that disagrees with the shapefile's own
75
+ * declaration, an area total that disagrees with the authority's published acreage, or a set of outlines that yields
76
+ * no interior coverage cell at all.
77
+ */
78
+ export async function buildSoilDatabase(options) {
79
+ if (options.coverageResolution >= options.indexResolution) {
80
+ throw new Error(`soil build: the coverage resolution (${options.coverageResolution}) must be coarser than the index resolution (${options.indexResolution}) — a row's coverage cell is the PARENT of its index cell`);
81
+ }
82
+ if (!options.areas.length) {
83
+ throw new Error("soil build: no survey areas were supplied — an empty build writes an artifact that answers unknown everywhere while reporting success");
84
+ }
85
+ const built = await buildSealedArtifact({
86
+ out: options.out,
87
+ createTables: async (kdb) => {
88
+ await createSoilTables(kdb);
89
+ await createLayerManifestTable(kdb);
90
+ await createLayerCoverageTable(kdb);
91
+ // The touch table exists only for this build and is dropped before the artifact is sealed. No primary key while
92
+ // loading: the resolution queries below read it through indexes created once the load is done, and a clustered
93
+ // key would sort every insert against an ingest order nothing controls.
94
+ kdb.exec("CREATE TABLE build_cell_touch (h3_cell INTEGER NOT NULL, resolution INTEGER NOT NULL, area_id TEXT NOT NULL, is_full INTEGER NOT NULL)");
95
+ },
96
+ ingest: async (kdb) => {
97
+ // Attributes FIRST because the ingest needs one thing out of them — which map units have no soil mapping behind
98
+ // them — and because a delineation whose map unit is missing must fail while the artifact is still empty rather
99
+ // than after millions of geometry rows are written.
100
+ writeAttributes(kdb, options.areas);
101
+ if (!options.inProcess)
102
+ return undefined;
103
+ return aggregateChunks(await ingestInProcess(kdb, options));
104
+ },
105
+ ...(options.inProcess
106
+ ? {}
107
+ : { batched: async (tmpPath) => aggregateChunks(await runBatchedIngest(tmpPath, options)) }),
108
+ finish: async (kdb, ingested) => {
109
+ assertDelineationCounts(options.areas, ingested);
110
+ const area = assertAreaAgreement(options.areas, ingested);
111
+ options.onProgress?.(`${ingested.delineations.toLocaleString()} delineations written · resolving cells`);
112
+ const cells = resolveCells(kdb, options.indexResolution);
113
+ options.onProgress?.(`${cells.wholeRows.toLocaleString()} whole (compacted) · ${cells.partialRows.toLocaleString()} partial · reducing`);
114
+ const reduced = reduceCells(kdb, options.indexResolution, options.onProgress);
115
+ const coverage = buildCoverageCells(options, ingested);
116
+ await writeLayerCoverage(kdb, coverage.cells);
117
+ writeSurveyAreaRows(kdb, options, coverage.cellsByArea);
118
+ writeVocabularyRows(kdb, options.areas);
119
+ // THE SPINE KEY NAMES THE TABLE A CONSUMER JOINS ON, table-qualified, per the layer contract. For this layer
120
+ // that is the REDUCTION rather than the containment index: `soil_capability_cell` holds one row per cell at one
121
+ // resolution, which `soil_map_unit_cell` does not — it is keyed `(cell, delineation)` and is mixed-resolution by
122
+ // construction, so it is a tier the reader walks rather than a key a consumer joins.
123
+ await writeLayerManifest(kdb, polygonLayerManifest(options, {
124
+ name: soilLayerName(options.region),
125
+ schemaVersion: SOIL_SCHEMA_VERSION,
126
+ license: SSURGO_LICENSE,
127
+ attribution: SSURGO_ATTRIBUTION,
128
+ source: SSURGO_SOURCE,
129
+ cellColumn: "soil_capability_cell.h3_cell",
130
+ }));
131
+ kdb.exec("DROP TABLE build_cell_touch");
132
+ const totalCellRows = cells.wholeRows + cells.partialRows;
133
+ return {
134
+ out: options.out,
135
+ region: options.region,
136
+ surveyAreas: options.areas.length,
137
+ delineations: ingested.delineations,
138
+ mapUnits: options.areas.reduce((sum, input) => sum + input.attributes.mapUnits.length, 0),
139
+ components: options.areas.reduce((sum, input) => sum + input.attributes.components.length, 0),
140
+ indexResolution: options.indexResolution,
141
+ coverageResolution: options.coverageResolution,
142
+ wholeCellRows: cells.wholeRows,
143
+ partialCellRows: cells.partialRows,
144
+ storedPartialShare: totalCellRows ? cells.partialRows / totalCellRows : 0,
145
+ capabilityCells: reduced.cells,
146
+ sampledCells: reduced.sampled,
147
+ topClassUnderHalfCells: reduced.topClassUnderHalf,
148
+ topClassUnderHalfShare: reduced.cells ? reduced.topClassUnderHalf / reduced.cells : 0,
149
+ classlessCells: reduced.classless,
150
+ unsampledCells: reduced.unsampled,
151
+ meanDelineationsPerCell: reduced.cells ? reduced.candidatePairs / reduced.cells : 0,
152
+ coarsenedFeatures: ingested.coarsened,
153
+ storedResolutions: cells.resolutions,
154
+ coverageCells: coverage.cells.length,
155
+ coverageCellsWithoutMapping: coverage.withoutMapping,
156
+ area,
157
+ };
158
+ },
159
+ });
160
+ return { ...built, sizeBytes: await readFileSize(options.out) };
161
+ }
162
+ /**
163
+ * Add up what the chunks reported.
164
+ *
165
+ * Exported for its own test: the coverage-cell arithmetic is the one part of the batched path a fixture build cannot
166
+ * reach, and getting it wrong produces a well-formed artifact that under-reports how many delineations a cell holds.
167
+ */
168
+ export function aggregateChunks(chunks) {
169
+ const byArea = new Map();
170
+ const observedByCoverageCell = new Map();
171
+ const mappedByCoverageCell = new Map();
172
+ let delineations = 0;
173
+ let coarsened = 0;
174
+ let nestedM2 = 0;
175
+ let allExteriorM2 = 0;
176
+ for (const chunk of chunks) {
177
+ delineations += chunk.delineations;
178
+ coarsened += chunk.coarsened;
179
+ nestedM2 += chunk.area.nestedM2;
180
+ allExteriorM2 += chunk.area.allExteriorM2;
181
+ byArea.set(chunk.areaSymbol, (byArea.get(chunk.areaSymbol) ?? 0) + chunk.delineations);
182
+ // A coverage cell straddles chunk boundaries — a range of feature ids is not a region, and a coverage cell can
183
+ // straddle two survey areas — so the counts ADD rather than replace.
184
+ mergeCountsInto(observedByCoverageCell, chunk.observedByCoverageCell);
185
+ mergeCountsInto(mappedByCoverageCell, chunk.mappedByCoverageCell);
186
+ }
187
+ return { delineations, coarsened, byArea, observedByCoverageCell, mappedByCoverageCell, nestedM2, allExteriorM2 };
188
+ }
189
+ /**
190
+ * Refuse a build that streamed fewer delineations than a survey area declares.
191
+ *
192
+ * A short read builds a smaller county and reports success, which is the partial result that must throw.
193
+ */
194
+ function assertDelineationCounts(areas, streamed) {
195
+ for (const area of areas) {
196
+ const declared = area.declaredFeatureCount ?? area.source?.declaredFeatureCount;
197
+ if (declared === undefined)
198
+ continue;
199
+ const actual = streamed.byArea.get(area.attributes.areasymbol) ?? 0;
200
+ if (actual !== declared) {
201
+ throw new Error(`soil build: ${area.attributes.areasymbol} streamed ${actual} delineations, its shapefile declares ${declared} — a short read builds a smaller survey area and reports success`);
202
+ }
203
+ }
204
+ }
205
+ /**
206
+ * Refuse an artifact whose rings do not add up to the acreage the authority publishes.
207
+ *
208
+ * The message carries the hole-blind total beside the nested one, because the gap between them is the diagnosis: a hole
209
+ * read as an exterior ring answers "inside" for every point in it. A build over survey areas that publish no acreage
210
+ * has no witness — the reading's own type says so, and the `known` count is what a receipt names.
211
+ */
212
+ function assertAreaAgreement(areas, streamed) {
213
+ let publishedAcres = 0;
214
+ let known = 0;
215
+ for (const input of areas) {
216
+ if (input.attributes.areaAcres !== null) {
217
+ publishedAcres += input.attributes.areaAcres;
218
+ known++;
219
+ }
220
+ }
221
+ const area = areaAgreementFrom({ nestedM2: streamed.nestedM2, allExteriorM2: streamed.allExteriorM2 }, known ? publishedAcres * M2_PER_ACRE : undefined);
222
+ if (area.witness === "source" && area.relativeGap > AREA_TOLERANCE) {
223
+ throw new Error(`soil build: the encoded rings total ${area.nestedKM2.toFixed(1)} km² against the ${area.sourceKM2.toFixed(1)} km² the authority publishes for these ${known} survey areas ` +
224
+ `(${(area.relativeGap * 100).toFixed(2)}% apart, tolerance ${(AREA_TOLERANCE * 100).toFixed(0)}%). Read without their holes the same rings total ` +
225
+ `${area.allExteriorKM2.toFixed(1)} km², so compare the two: a hole-blind read answers "inside" for every point in a hole`);
226
+ }
227
+ return area;
228
+ }
229
+ /**
230
+ * Write every survey area's map units, components and vocabulary before any geometry is streamed.
231
+ */
232
+ function writeAttributes(database, areas) {
233
+ const insertMapUnit = database.prepare("INSERT OR REPLACE INTO soil_map_unit (mukey, areasymbol, musym, muname, mukind, mustatus, farmlndcl, farmland_scope, niccdcd, niccdcdpct, no_mapping) " +
234
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
235
+ const insertComponent = database.prepare("INSERT OR REPLACE INTO soil_component (cokey, mukey, comppct_r, compname, compkind, nirrcapcl, nirrcapscl, irrcapcl, irrcapscl, nccpi_v3) " +
236
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
237
+ database.exec("BEGIN");
238
+ for (const area of areas) {
239
+ for (const mapUnit of area.attributes.mapUnits) {
240
+ insertMapUnit.run(mapUnit.mukey, mapUnit.areasymbol, mapUnit.musym, mapUnit.muname, mapUnit.mukind, mapUnit.mustatus, mapUnit.farmlndcl, mapUnit.farmland_scope, mapUnit.niccdcd, mapUnit.niccdcdpct, mapUnit.no_mapping);
241
+ }
242
+ for (const component of area.attributes.components) {
243
+ insertComponent.run(component.cokey, component.mukey, component.comppct_r, component.compname, component.compkind, component.nirrcapcl, component.nirrcapscl, component.irrcapcl, component.irrcapscl, component.nccpi_v3);
244
+ }
245
+ }
246
+ database.exec("COMMIT");
247
+ }
248
+ /**
249
+ * The map units with no soil mapping behind them, from what was just written.
250
+ */
251
+ function noMappingMukeys(areas) {
252
+ const mukeys = new Set();
253
+ for (const area of areas) {
254
+ for (const mapUnit of area.attributes.mapUnits) {
255
+ if (mapUnit.no_mapping) {
256
+ mukeys.add(mapUnit.mukey);
257
+ }
258
+ }
259
+ }
260
+ return mukeys;
261
+ }
262
+ /**
263
+ * The in-process ingest — one chunk per survey area, all in this interpreter. Fixtures only.
264
+ */
265
+ async function ingestInProcess(database, options) {
266
+ const noMapping = noMappingMukeys(options.areas);
267
+ const chunks = [];
268
+ for (const area of options.areas) {
269
+ if (!area.source) {
270
+ throw new Error(`soil build: ${area.attributes.areasymbol} was built in-process without a feature source — the in-process path is for fixtures, which supply one`);
271
+ }
272
+ chunks.push(await ingestSoilChunk(database, {
273
+ source: area.source,
274
+ indexResolution: options.indexResolution,
275
+ coverageResolution: options.coverageResolution,
276
+ noMappingMukeys: noMapping,
277
+ ...(options.onProgress ? { onProgress: options.onProgress } : {}),
278
+ }));
279
+ }
280
+ return chunks;
281
+ }
282
+ /**
283
+ * Run the ingest as a sequence of bounded child processes, one per range of one survey area's FIDs. The shared chunk
284
+ * contract — the parent's no-handle rule, and the fail-loud handling of a chunk that dies or prints nothing — lives
285
+ * with `ingestChunkArguments` and `runChunkProcess`.
286
+ */
287
+ async function runBatchedIngest(tmpPath, options) {
288
+ const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;
289
+ const script = resolveModulePath("@mailwoman/soil/scripts/ingest-chunk");
290
+ const noMapping = [...noMappingMukeys(options.areas)];
291
+ const chunks = [];
292
+ for (const area of options.areas) {
293
+ if (!area.shapefilePath) {
294
+ throw new Error(`soil build: ${area.attributes.areasymbol} has no shapefile path, so no chunk process can open it`);
295
+ }
296
+ const declared = area.declaredFeatureCount ?? 0;
297
+ for (let from = 0; from < Math.max(declared, 1); from += chunkSize) {
298
+ const to = Math.min(from + chunkSize - 1, declared - 1);
299
+ options.onProgress?.(`${area.attributes.areasymbol}: FID ${from}–${to}`);
300
+ chunks.push(await runChunkProcess({
301
+ script,
302
+ context: "soil build",
303
+ subject: `chunk ${area.attributes.areasymbol} FID ${from}–${to}`,
304
+ args: ingestChunkArguments({
305
+ database: tmpPath,
306
+ args: [
307
+ "--shapefile",
308
+ area.shapefilePath,
309
+ "--area-symbol",
310
+ area.attributes.areasymbol,
311
+ "--fid-from",
312
+ String(from),
313
+ "--fid-to",
314
+ String(to),
315
+ "--no-mapping-mukeys",
316
+ noMapping.join(","),
317
+ ],
318
+ indexResolution: options.indexResolution,
319
+ coverageResolution: options.coverageResolution,
320
+ }),
321
+ }));
322
+ }
323
+ }
324
+ return chunks;
325
+ }
326
+ /**
327
+ * The coverage rows: one per interior cell of the built footprint that soil mapping actually reaches, and none outside.
328
+ *
329
+ * THE INTERIOR TEST RUNS ONCE OVER THE UNION OF EVERY OUTLINE BUILT, NOT PER SURVEY AREA, and the difference is most of
330
+ * a state. The test is conservative — it keeps only cells lying WHOLLY inside — so applied per area it drops every cell
331
+ * a county border crosses. Measured on Polk County alone at resolution 6: 20 interior cells against the roughly 42 the
332
+ * county spans by area, so more than half of it would read `unknown` while sitting inside a survey the build had
333
+ * ingested. Run over the union, only the OUTER border of the built set is dropped, which is the honest edge: beyond it
334
+ * lies ground this artifact does not hold.
335
+ *
336
+ * The conservatism itself stays. A cell wrongly called interior would state that an authority determined a location it
337
+ * never looked at, and a point in the dropped strip reading `unknown` is the truthful answer for ground the built set
338
+ * may or may not reach.
339
+ *
340
+ * `observed_rows` counts the delineations reaching the cell, which is what the contract's column means. A cell reached
341
+ * only by `NOTCOM` and access-denied polygons gets NO ROW — the polygon exists, the soil mapping behind it does not,
342
+ * and the survey's §3.2 puts that case with the absences rather than with the coverage.
343
+ */
344
+ function buildCoverageCells(options, streamed) {
345
+ const footprint = {
346
+ type: "MultiPolygon",
347
+ coordinates: options.areas.flatMap((input) => outlinePolygons(input.outline)),
348
+ };
349
+ const interior = interiorCoverageCells(footprint, options.coverageResolution);
350
+ if (!interior.length) {
351
+ throw new Error(`soil build: the ${options.areas.length} built outline(s) yield no interior cell at resolution ${options.coverageResolution} — the artifact would carry no coverage rows and answer "unknown" everywhere while reporting success`);
352
+ }
353
+ const mapped = (h3Cell) => (streamed.mappedByCoverageCell.get(h3Cell) ?? 0) > 0;
354
+ const cells = designatedCoverageCells(interior.map((cell) => shortCellToInt(cell)), streamed.observedByCoverageCell, { include: mapped });
355
+ const cellsByArea = new Map();
356
+ let withoutMapping = 0;
357
+ for (const cell of interior) {
358
+ if (!mapped(shortCellToInt(cell))) {
359
+ withoutMapping++;
360
+ continue;
361
+ }
362
+ // Attributed by the cell's CENTRE, so each row is counted for exactly one survey area even where the cell straddles
363
+ // two. The count is a per-area receipt, not part of the coverage claim — the claim is the row set itself.
364
+ const [latitude, longitude] = cellToLatLng(cell);
365
+ const owner = options.areas.find((input) => geometryContains(input.outline, longitude, latitude));
366
+ if (owner) {
367
+ const symbol = owner.attributes.areasymbol;
368
+ cellsByArea.set(symbol, (cellsByArea.get(symbol) ?? 0) + 1);
369
+ }
370
+ }
371
+ return { cells, cellsByArea, withoutMapping };
372
+ }
373
+ /**
374
+ * One outline's polygons, in the `MultiPolygon` coordinate shape, whichever areal type it arrived as.
375
+ *
376
+ * @throws {TypeError} When the outline is not areal. A survey area whose footprint cannot be read would silently
377
+ * contribute nothing to the union, and the coverage over it would simply be absent.
378
+ */
379
+ function outlinePolygons(outline) {
380
+ const polygons = arealPolygons(outline);
381
+ if (polygons)
382
+ return polygons;
383
+ throw new TypeError(`soil build: a survey-area outline is a ${outline.type}, which bounds no area — its coverage would be silently absent rather than refused`);
384
+ }
385
+ /**
386
+ * Insert one row per survey area.
387
+ */
388
+ function writeSurveyAreaRows(database, options, cellsByArea) {
389
+ const insert = database.prepare("INSERT OR REPLACE INTO soil_survey_area (areasymbol, areaname, saverest, saversion, survey_source_date, survey_source_title, source_scale, mapping_scale, area_acres, min_lat, min_lon, max_lat, max_lon, coverage_cells, coverage_resolution) " +
390
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
391
+ database.exec("BEGIN");
392
+ for (const area of options.areas) {
393
+ const bounds = database
394
+ .prepare("SELECT min(min_lat) AS min_lat, min(min_lon) AS min_lon, max(max_lat) AS max_lat, max(max_lon) AS max_lon FROM soil_map_unit_area WHERE areasymbol = ?")
395
+ .get(area.attributes.areasymbol);
396
+ const row = {
397
+ areasymbol: area.attributes.areasymbol,
398
+ areaname: area.attributes.areaname,
399
+ saverest: area.attributes.saverest,
400
+ saversion: area.attributes.saversion,
401
+ survey_source_date: area.attributes.surveySourceDate,
402
+ survey_source_title: area.attributes.surveySourceTitle,
403
+ source_scale: area.attributes.sourceScale,
404
+ mapping_scale: area.attributes.mappingScale,
405
+ area_acres: area.attributes.areaAcres,
406
+ min_lat: bounds.min_lat ?? 0,
407
+ min_lon: bounds.min_lon ?? 0,
408
+ max_lat: bounds.max_lat ?? 0,
409
+ max_lon: bounds.max_lon ?? 0,
410
+ coverage_cells: cellsByArea.get(area.attributes.areasymbol) ?? 0,
411
+ coverage_resolution: options.coverageResolution,
412
+ };
413
+ insert.run(row.areasymbol, row.areaname, row.saverest, row.saversion, row.survey_source_date, row.survey_source_title, row.source_scale, row.mapping_scale, row.area_acres, row.min_lat, row.min_lon, row.max_lat, row.max_lon, row.coverage_cells, row.coverage_resolution);
414
+ }
415
+ database.exec("COMMIT");
416
+ }
417
+ /**
418
+ * Insert the authority's declared domains, plus the weighting the shares were produced under.
419
+ *
420
+ * The weighting rides in the vocabulary table as well as on every row: the row-level copy is what a consumer reads, and
421
+ * this one carries the sentence that says what it MEANS, which no column can.
422
+ */
423
+ function writeVocabularyRows(database, areas) {
424
+ const insert = database.prepare("INSERT OR REPLACE INTO soil_vocabulary (domain, code, definition, sequence) VALUES (?, ?, ?, ?)");
425
+ database.exec("BEGIN");
426
+ for (const area of areas) {
427
+ for (const member of area.attributes.domains) {
428
+ insert.run(member.domain, member.code, member.definition, member.sequence);
429
+ }
430
+ }
431
+ insert.run("share_weighting", SOIL_SHARE_WEIGHTING, SOIL_SHARE_WEIGHTING_DESCRIPTION, WEIGHT_LATTICE_DEPTH);
432
+ database.exec("COMMIT");
433
+ }
434
+ //# sourceMappingURL=build-soil.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-soil.js","sourceRoot":"","sources":["../../lib/sdk/build-soil.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * The two phases between the streamed touches and the artifact a consumer reads: the stored containment
7
+ * index, and the reduction above it.
8
+ *
9
+ * BOTH READ THE TOUCH TABLE, AND ONLY ONE OF THEM COMPACTS. `resolveCells` writes the tiers a probe walks
10
+ * and collapses uniform interiors parent-ward; `reduceCells` reads the UNCOMPACTED touches, because a
11
+ * compacted parent no longer names the cells the reduction has to answer.
12
+ *
13
+ * THE REDUCTION IS THE SLOW PHASE AND ITS MEMORY IS BOUNDED BY CONSTRUCTION. A lattice of 49 point tests
14
+ * per sampled cell, over a delineation cache that is cleared whole rather than evicted one entry at a
15
+ * time — see {@link GEOMETRY_CACHE_ENTRIES}. Memory stays flat in row count, which is the property the poi
16
+ * build lost when a reader materialized instead of streaming.
17
+ */
18
+ import type { DatabaseClient } from "@mailwoman/sqlite/client";
19
+ import { type SoilDatabase } from "#schema";
20
+ /**
21
+ * Resolve the touch table into the stored containment index.
22
+ *
23
+ * Compaction happens HERE and only on the whole side. It is expected to yield close to nothing on this layer, which is
24
+ * the inversion the survey predicts: compaction needs a uniform interior, and 85.4% of `IA153`'s delineations are
25
+ * smaller than one resolution-9 cell.
26
+ */
27
+ export declare function resolveCells(database: DatabaseClient<SoilDatabase>, indexResolution: number): {
28
+ wholeRows: number;
29
+ partialRows: number;
30
+ resolutions: number[];
31
+ };
32
+ /**
33
+ * Reduce the touch table into `soil_capability_cell`.
34
+ *
35
+ * Reads the touch table rather than `soil_map_unit_cell` on purpose: the stored index is compacted on the whole side,
36
+ * so a compacted parent no longer names the cells the reduction has to answer. The touch table is the uncompacted truth
37
+ * about which delineation reaches which cell.
38
+ */
39
+ export declare function reduceCells(database: DatabaseClient<SoilDatabase>, indexResolution: number, onProgress?: (message: string) => void): {
40
+ cells: number;
41
+ sampled: number;
42
+ topClassUnderHalf: number;
43
+ classless: number;
44
+ unsampled: number;
45
+ candidatePairs: number;
46
+ };
47
+ //# sourceMappingURL=cell-tiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cell-tiers.d.ts","sourceRoot":"","sources":["../../lib/sdk/cell-tiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAG9D,OAAO,EAAqD,KAAK,YAAY,EAAE,MAAM,SAAS,CAAA;AAG9F;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,cAAc,CAAC,YAAY,CAAC,EACtC,eAAe,EAAE,MAAM,GACrB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CA4DnE;AA8BD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,cAAc,CAAC,YAAY,CAAC,EACtC,eAAe,EAAE,MAAM,EACvB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACpC;IACF,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;IACzB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;CACtB,CAoHA"}