@mailwoman/soil 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -0
- package/lib/index.ts +474 -0
- package/lib/schema.ts +493 -0
- package/lib/scripts/ingest-chunk.ts +47 -0
- package/lib/sdk/acquire.ts +124 -0
- package/lib/sdk/build-soil.ts +778 -0
- package/lib/sdk/cell-tiers.ts +310 -0
- package/lib/sdk/cells.ts +224 -0
- package/lib/sdk/client.ts +273 -0
- package/lib/sdk/download.ts +161 -0
- package/lib/sdk/index.ts +20 -0
- package/lib/sdk/ingest-chunk.ts +182 -0
- package/lib/sdk/ingest.ts +271 -0
- package/lib/sdk/measure-resolutions.ts +81 -0
- package/lib/sdk/reduce.ts +375 -0
- package/lib/sdk/survey-area.ts +467 -0
- package/lib/sdk/tabular.ts +296 -0
- package/lib/sdk/verify.ts +369 -0
- package/lib/test-kit.ts +255 -0
- package/lib/vocabulary.ts +241 -0
- package/out/index.d.ts +199 -0
- package/out/index.d.ts.map +1 -0
- package/out/index.js +255 -0
- package/out/index.js.map +1 -0
- package/out/schema.d.ts +369 -0
- package/out/schema.d.ts.map +1 -0
- package/out/schema.js +186 -0
- package/out/schema.js.map +1 -0
- package/out/scripts/ingest-chunk.d.ts +11 -0
- package/out/scripts/ingest-chunk.d.ts.map +1 -0
- package/out/scripts/ingest-chunk.js +42 -0
- package/out/scripts/ingest-chunk.js.map +1 -0
- package/out/sdk/acquire.d.ts +60 -0
- package/out/sdk/acquire.d.ts.map +1 -0
- package/out/sdk/acquire.js +74 -0
- package/out/sdk/acquire.js.map +1 -0
- package/out/sdk/build-soil.d.ts +197 -0
- package/out/sdk/build-soil.d.ts.map +1 -0
- package/out/sdk/build-soil.js +434 -0
- package/out/sdk/build-soil.js.map +1 -0
- package/out/sdk/cell-tiers.d.ts +47 -0
- package/out/sdk/cell-tiers.d.ts.map +1 -0
- package/out/sdk/cell-tiers.js +194 -0
- package/out/sdk/cell-tiers.js.map +1 -0
- package/out/sdk/cells.d.ts +118 -0
- package/out/sdk/cells.d.ts.map +1 -0
- package/out/sdk/cells.js +140 -0
- package/out/sdk/cells.js.map +1 -0
- package/out/sdk/client.d.ts +120 -0
- package/out/sdk/client.d.ts.map +1 -0
- package/out/sdk/client.js +214 -0
- package/out/sdk/client.js.map +1 -0
- package/out/sdk/download.d.ts +84 -0
- package/out/sdk/download.d.ts.map +1 -0
- package/out/sdk/download.js +117 -0
- package/out/sdk/download.js.map +1 -0
- package/out/sdk/index.d.ts +20 -0
- package/out/sdk/index.d.ts.map +1 -0
- package/out/sdk/index.js +20 -0
- package/out/sdk/index.js.map +1 -0
- package/out/sdk/ingest-chunk.d.ts +73 -0
- package/out/sdk/ingest-chunk.d.ts.map +1 -0
- package/out/sdk/ingest-chunk.js +98 -0
- package/out/sdk/ingest-chunk.js.map +1 -0
- package/out/sdk/ingest.d.ts +132 -0
- package/out/sdk/ingest.d.ts.map +1 -0
- package/out/sdk/ingest.js +163 -0
- package/out/sdk/ingest.js.map +1 -0
- package/out/sdk/measure-resolutions.d.ts +39 -0
- package/out/sdk/measure-resolutions.d.ts.map +1 -0
- package/out/sdk/measure-resolutions.js +52 -0
- package/out/sdk/measure-resolutions.js.map +1 -0
- package/out/sdk/reduce.d.ts +122 -0
- package/out/sdk/reduce.d.ts.map +1 -0
- package/out/sdk/reduce.js +266 -0
- package/out/sdk/reduce.js.map +1 -0
- package/out/sdk/survey-area.d.ts +105 -0
- package/out/sdk/survey-area.d.ts.map +1 -0
- package/out/sdk/survey-area.js +346 -0
- package/out/sdk/survey-area.js.map +1 -0
- package/out/sdk/tabular.d.ts +106 -0
- package/out/sdk/tabular.d.ts.map +1 -0
- package/out/sdk/tabular.js +194 -0
- package/out/sdk/tabular.js.map +1 -0
- package/out/sdk/verify.d.ts +135 -0
- package/out/sdk/verify.d.ts.map +1 -0
- package/out/sdk/verify.js +221 -0
- package/out/sdk/verify.js.map +1 -0
- package/out/test-kit.d.ts +80 -0
- package/out/test-kit.d.ts.map +1 -0
- package/out/test-kit.js +228 -0
- package/out/test-kit.js.map +1 -0
- package/out/vocabulary.d.ts +179 -0
- package/out/vocabulary.d.ts.map +1 -0
- package/out/vocabulary.js +215 -0
- package/out/vocabulary.js.map +1 -0
- package/package.json +276 -0
|
@@ -0,0 +1,194 @@
|
|
|
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 { expandShortCellInt, shortCellToInt } from "@mailwoman/spatial";
|
|
19
|
+
import { beginBatched } from "@mailwoman/sqlite/batched";
|
|
20
|
+
import { compactCells, getResolution } from "h3-js";
|
|
21
|
+
import { SoilCellContainment } from "#schema";
|
|
22
|
+
import { mapUnitProfile, reduceCell } from "#sdk/reduce";
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the touch table into the stored containment index.
|
|
25
|
+
*
|
|
26
|
+
* Compaction happens HERE and only on the whole side. It is expected to yield close to nothing on this layer, which is
|
|
27
|
+
* the inversion the survey predicts: compaction needs a uniform interior, and 85.4% of `IA153`'s delineations are
|
|
28
|
+
* smaller than one resolution-9 cell.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveCells(database, indexResolution) {
|
|
31
|
+
database.exec("CREATE INDEX build_cell_touch_area_cell ON build_cell_touch (area_id, resolution, is_full, h3_cell)");
|
|
32
|
+
const groups = database
|
|
33
|
+
.prepare("SELECT DISTINCT area_id, resolution FROM build_cell_touch WHERE is_full = 1")
|
|
34
|
+
.all();
|
|
35
|
+
const insertCell = database.prepare("INSERT OR REPLACE INTO soil_map_unit_cell (h3_cell, resolution, area_id, containment) VALUES (?, ?, ?, ?)");
|
|
36
|
+
const resolutions = new Set();
|
|
37
|
+
let wholeRows = 0;
|
|
38
|
+
// One group per (delineation, resolution): `compactCells` takes a single resolution, and an adaptively-indexed layer
|
|
39
|
+
// has several. Pooling them throws; compacting only the target-resolution group would silently drop every coarsened
|
|
40
|
+
// delineation's interior.
|
|
41
|
+
database.exec("BEGIN");
|
|
42
|
+
for (const { area_id: areaID, resolution } of groups) {
|
|
43
|
+
const whole = database
|
|
44
|
+
.prepare("SELECT DISTINCT h3_cell FROM build_cell_touch WHERE area_id = ? AND resolution = ? AND is_full = 1")
|
|
45
|
+
.all(areaID, resolution);
|
|
46
|
+
for (const cell of compactCells(whole.map((row) => expandShortCellInt(row.h3_cell, resolution)))) {
|
|
47
|
+
const cellResolution = getResolution(cell);
|
|
48
|
+
resolutions.add(cellResolution);
|
|
49
|
+
insertCell.run(shortCellToInt(cell), cellResolution, areaID, SoilCellContainment.Whole);
|
|
50
|
+
wholeRows++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
database.exec("COMMIT");
|
|
54
|
+
// The partial rows are every touch that is not whole for its own delineation. `INSERT OR REPLACE` above already put
|
|
55
|
+
// the whole rows in, and the primary key is `(h3_cell, area_id)`, so this insert must skip them explicitly rather
|
|
56
|
+
// than rely on the key: a partial row replacing a whole one would demote an answered cell to a ray cast.
|
|
57
|
+
database.exec("INSERT OR IGNORE INTO soil_map_unit_cell (h3_cell, resolution, area_id, containment) " +
|
|
58
|
+
"SELECT DISTINCT t.h3_cell, t.resolution, t.area_id, 'partial' FROM build_cell_touch t WHERE t.is_full = 0");
|
|
59
|
+
const partialRows = database.prepare("SELECT count(*) AS n FROM soil_map_unit_cell WHERE containment = 'partial'").get().n;
|
|
60
|
+
for (const row of database.prepare("SELECT DISTINCT resolution FROM soil_map_unit_cell").all()) {
|
|
61
|
+
resolutions.add(row.resolution);
|
|
62
|
+
}
|
|
63
|
+
resolutions.add(indexResolution);
|
|
64
|
+
return { wholeRows, partialRows, resolutions: [...resolutions].toSorted((left, right) => left - right) };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cells reduced per progress report. The reduction is the slow phase — a lattice of 49 point tests per sampled cell —
|
|
68
|
+
* so it reports often enough that a long run is visibly alive.
|
|
69
|
+
*/
|
|
70
|
+
const REDUCE_PROGRESS_STRIDE = 50_000;
|
|
71
|
+
/**
|
|
72
|
+
* How many delineations the reduction keeps in memory at once.
|
|
73
|
+
*
|
|
74
|
+
* Sized to bound the phase rather than to hold everything: the pilot region's median delineation encodes to roughly 1.4
|
|
75
|
+
* kB, so 200,000 of them is a few hundred megabytes — comfortable, and far below the 2.5 million a whole state holds.
|
|
76
|
+
* Memory stays flat in row count, which is the property the poi build lost when a reader materialized instead of
|
|
77
|
+
* streaming.
|
|
78
|
+
*/
|
|
79
|
+
const GEOMETRY_CACHE_ENTRIES = 200_000;
|
|
80
|
+
/**
|
|
81
|
+
* Reduce the touch table into `soil_capability_cell`.
|
|
82
|
+
*
|
|
83
|
+
* Reads the touch table rather than `soil_map_unit_cell` on purpose: the stored index is compacted on the whole side,
|
|
84
|
+
* so a compacted parent no longer names the cells the reduction has to answer. The touch table is the uncompacted truth
|
|
85
|
+
* about which delineation reaches which cell.
|
|
86
|
+
*/
|
|
87
|
+
export function reduceCells(database, indexResolution, onProgress) {
|
|
88
|
+
const profiles = readMapUnitProfiles(database);
|
|
89
|
+
const insert = database.prepare("INSERT OR REPLACE INTO soil_capability_cell (h3_cell, class_shares, unrated_share, notrateable_share, nodata_share, other_share, mapped_share, top_class, top_class_share, weighting, delineations) " +
|
|
90
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
|
91
|
+
const selectArea = database.prepare("SELECT mukey, min_lat, min_lon, max_lat, max_lon, rings FROM soil_map_unit_area WHERE area_id = ?");
|
|
92
|
+
const rows = database
|
|
93
|
+
.prepare("SELECT h3_cell, resolution, area_id, is_full FROM build_cell_touch ORDER BY resolution, h3_cell, area_id")
|
|
94
|
+
.iterate();
|
|
95
|
+
let cells = 0;
|
|
96
|
+
let sampled = 0;
|
|
97
|
+
let topClassUnderHalf = 0;
|
|
98
|
+
let classless = 0;
|
|
99
|
+
let unsampled = 0;
|
|
100
|
+
let candidatePairs = 0;
|
|
101
|
+
let currentCell;
|
|
102
|
+
let currentResolution = indexResolution;
|
|
103
|
+
let candidates = [];
|
|
104
|
+
// A delineation is named by every cell it reaches — 5.35 of them per cell at resolution 9 on the pilot region — so a
|
|
105
|
+
// naive read fetches each ring blob once per touch. At Iowa's scale that is millions of blob reads of ground already
|
|
106
|
+
// in memory. The cache is bounded and CLEARED WHOLE when it fills rather than evicted one at a time: h3 cell integers
|
|
107
|
+
// carry their ancestry in their high bits, so a scan in `h3_cell` order visits neighbours together and a cleared cache
|
|
108
|
+
// refills with the delineations the next run of cells actually names.
|
|
109
|
+
const geometry = new Map();
|
|
110
|
+
const batch = beginBatched(database, { rowsPerCommit: REDUCE_PROGRESS_STRIDE });
|
|
111
|
+
const flush = () => {
|
|
112
|
+
if (currentCell === undefined || !candidates.length)
|
|
113
|
+
return;
|
|
114
|
+
const cell = expandShortCellInt(currentCell, currentResolution);
|
|
115
|
+
const reducedCell = reduceCell(cell, currentResolution, candidates, profiles, currentCell);
|
|
116
|
+
candidatePairs += candidates.length;
|
|
117
|
+
if (reducedCell.row.mapped_share <= 0) {
|
|
118
|
+
unsampled++;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
cells++;
|
|
122
|
+
if (reducedCell.sampled) {
|
|
123
|
+
sampled++;
|
|
124
|
+
}
|
|
125
|
+
if (reducedCell.topClassUnderHalf) {
|
|
126
|
+
topClassUnderHalf++;
|
|
127
|
+
}
|
|
128
|
+
if (!reducedCell.row.top_class) {
|
|
129
|
+
classless++;
|
|
130
|
+
}
|
|
131
|
+
insertRow(insert, reducedCell.row);
|
|
132
|
+
if (batch.rowWritten()) {
|
|
133
|
+
onProgress?.(`${cells.toLocaleString()} cells reduced`);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
for (const row of rows) {
|
|
137
|
+
if (row.h3_cell !== currentCell || row.resolution !== currentResolution) {
|
|
138
|
+
flush();
|
|
139
|
+
currentCell = row.h3_cell;
|
|
140
|
+
currentResolution = row.resolution;
|
|
141
|
+
candidates = [];
|
|
142
|
+
}
|
|
143
|
+
let area = geometry.get(row.area_id);
|
|
144
|
+
if (!area) {
|
|
145
|
+
area = selectArea.get(row.area_id);
|
|
146
|
+
if (!area) {
|
|
147
|
+
throw new Error(`soil build: the touch table names delineation ${row.area_id}, which soil_map_unit_area does not hold — the ingest and the reduction disagree about what was written`);
|
|
148
|
+
}
|
|
149
|
+
if (geometry.size >= GEOMETRY_CACHE_ENTRIES) {
|
|
150
|
+
geometry.clear();
|
|
151
|
+
}
|
|
152
|
+
geometry.set(row.area_id, area);
|
|
153
|
+
}
|
|
154
|
+
candidates.push({
|
|
155
|
+
areaID: row.area_id,
|
|
156
|
+
mukey: area.mukey,
|
|
157
|
+
containment: row.is_full ? SoilCellContainment.Whole : SoilCellContainment.Partial,
|
|
158
|
+
minLat: area.min_lat,
|
|
159
|
+
minLon: area.min_lon,
|
|
160
|
+
maxLat: area.max_lat,
|
|
161
|
+
maxLon: area.max_lon,
|
|
162
|
+
rings: area.rings,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
flush();
|
|
166
|
+
batch.commit();
|
|
167
|
+
return { cells, sampled, topClassUnderHalf, classless, unsampled, candidatePairs };
|
|
168
|
+
}
|
|
169
|
+
function insertRow(statement, row) {
|
|
170
|
+
statement.run(row.h3_cell, row.class_shares, row.unrated_share, row.notrateable_share, row.nodata_share, row.other_share, row.mapped_share, row.top_class, row.top_class_share, row.weighting, row.delineations);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Every map unit's per-unit-area profile, computed once and reused for every cell it reaches.
|
|
174
|
+
*/
|
|
175
|
+
function readMapUnitProfiles(database) {
|
|
176
|
+
const componentsByMukey = new Map();
|
|
177
|
+
for (const row of database
|
|
178
|
+
.prepare("SELECT mukey, comppct_r, compkind, nirrcapcl FROM soil_component")
|
|
179
|
+
.all()) {
|
|
180
|
+
const list = componentsByMukey.get(row.mukey);
|
|
181
|
+
if (list) {
|
|
182
|
+
list.push(row);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
componentsByMukey.set(row.mukey, [row]);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const profiles = new Map();
|
|
189
|
+
for (const row of database.prepare("SELECT mukey, no_mapping FROM soil_map_unit").all()) {
|
|
190
|
+
profiles.set(row.mukey, mapUnitProfile(row, componentsByMukey.get(row.mukey) ?? []));
|
|
191
|
+
}
|
|
192
|
+
return profiles;
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=cell-tiers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cell-tiers.js","sourceRoot":"","sources":["../../lib/sdk/cell-tiers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The delineation-keyed cell index, and the two numbers the index resolution is chosen from.
|
|
7
|
+
*
|
|
8
|
+
* THE CLASSIFIER ITSELF LIVES IN `@mailwoman/spatial`, because the traps it guards are properties of
|
|
9
|
+
* h3-js rather than of SSURGO: a centre-containment polyfill drops every polygon smaller than a cell, an
|
|
10
|
+
* exhausted WASM allocator reports success and returns zeros, and the allocator is sized from the
|
|
11
|
+
* bounding box. The layer contract states all three as requirements on every polygon builder. What is
|
|
12
|
+
* soil-shaped is the ACCUMULATOR below, which keys on the delineation rather than on a hazard class,
|
|
13
|
+
* because the reduction weights by the area a delineation covers.
|
|
14
|
+
*
|
|
15
|
+
* EXPECT THE `partial` SHARE TO INVERT AGAINST THE FLOOD LAYER, AND DO NOT READ THAT AS A DEFECT. Flood
|
|
16
|
+
* polygons are large against their cells, so most cells fall wholly inside one zone and `compactCells`
|
|
17
|
+
* collapses long uniform interiors. Soil delineations are the opposite: 85.4% of `IA153`'s 17,966 of them
|
|
18
|
+
* are smaller than one resolution-9 cell, and the median is 24,863 m² against a 105,333 m² cell. Small
|
|
19
|
+
* polygons against large cells means most cells are crossed by a boundary — so the `partial` share should
|
|
20
|
+
* be HIGH, `compactCells` should yield close to nothing, and the index alone will rarely answer a point
|
|
21
|
+
* probe. That is not an argument against storing the geometry; it is the argument for why this layer
|
|
22
|
+
* carries the reduced `soil_capability_cell` alongside the index rather than relying on the index the way
|
|
23
|
+
* the flood layer can.
|
|
24
|
+
*
|
|
25
|
+
* TWO NUMBERS GET REPORTED AT EACH CANDIDATE RESOLUTION, AND THEY MOVE IN OPPOSITE DIRECTIONS. The
|
|
26
|
+
* `partial` cell share says whether the containment index answers most probes alone. The share of cells
|
|
27
|
+
* whose top class holds less than half the cell says whether the layer is answering or hedging — the
|
|
28
|
+
* cell-grain analogue of NRCS's own `niccdcdpct` distribution, which reads 3.3% below half nationally.
|
|
29
|
+
* Going coarser improves the first and worsens the second, and picking between them is what the
|
|
30
|
+
* measurement is for.
|
|
31
|
+
*/
|
|
32
|
+
import { type FeatureCells, type MultiPolygonRings } from "@mailwoman/spatial";
|
|
33
|
+
/**
|
|
34
|
+
* The label this layer's classifier failures carry.
|
|
35
|
+
*/
|
|
36
|
+
export declare const SOIL_CELL_LABEL = "soil cells";
|
|
37
|
+
/**
|
|
38
|
+
* Classify one delineation — {@link classifyFeatureCells} with this layer's label bound.
|
|
39
|
+
*/
|
|
40
|
+
export declare function classifyDelineationCells(polygons: MultiPolygonRings, targetResolution: number, areaID: string): FeatureCells;
|
|
41
|
+
/**
|
|
42
|
+
* What one resolution's index came out as.
|
|
43
|
+
*/
|
|
44
|
+
export interface SoilCellIndexMeasurement {
|
|
45
|
+
resolution: number;
|
|
46
|
+
/**
|
|
47
|
+
* Cells the layer reaches at all.
|
|
48
|
+
*/
|
|
49
|
+
touchedCells: number;
|
|
50
|
+
/**
|
|
51
|
+
* Cells lying wholly inside a single delineation, before compaction.
|
|
52
|
+
*/
|
|
53
|
+
wholeCells: number;
|
|
54
|
+
/**
|
|
55
|
+
* Cells a delineation boundary crosses.
|
|
56
|
+
*/
|
|
57
|
+
partialCells: number;
|
|
58
|
+
/**
|
|
59
|
+
* `partialCells / touchedCells` — the share of in-layer probes that cannot be answered from the index alone.
|
|
60
|
+
*/
|
|
61
|
+
partialShare: number;
|
|
62
|
+
/**
|
|
63
|
+
* Whole cells after `compactCells`. Expected to be close to `wholeCells` here rather than far below it: compaction
|
|
64
|
+
* needs a uniform interior, and small delineations do not produce one.
|
|
65
|
+
*/
|
|
66
|
+
compactedWholeCells: number;
|
|
67
|
+
/**
|
|
68
|
+
* `(cell, delineation)` pairs the index stores.
|
|
69
|
+
*/
|
|
70
|
+
cellDelineationPairs: number;
|
|
71
|
+
/**
|
|
72
|
+
* The mean number of delineations reaching a cell — the direct measure of how mixed a cell is before any rating is
|
|
73
|
+
* read, and the number that rises as the resolution coarsens.
|
|
74
|
+
*/
|
|
75
|
+
meanDelineationsPerCell: number;
|
|
76
|
+
/**
|
|
77
|
+
* Delineations whose bounding box forced a coarser resolution than the target.
|
|
78
|
+
*/
|
|
79
|
+
coarsenedFeatures: number;
|
|
80
|
+
/**
|
|
81
|
+
* The resolutions actually present, finest last.
|
|
82
|
+
*/
|
|
83
|
+
resolutions: number[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Accumulate one resolution's cell index over a stream of delineations.
|
|
87
|
+
*
|
|
88
|
+
* Held as short-cell STRINGS rather than the integers the tables store, because `compactCells` is an h3-js function
|
|
89
|
+
* over full indexes and round-tripping through the integer form at every step would cost more than the strings do.
|
|
90
|
+
*/
|
|
91
|
+
export declare class SoilCellIndex {
|
|
92
|
+
#private;
|
|
93
|
+
readonly resolution: number;
|
|
94
|
+
constructor(resolution: number);
|
|
95
|
+
/**
|
|
96
|
+
* Fold one delineation's classification in.
|
|
97
|
+
*/
|
|
98
|
+
add(areaID: string, cells: FeatureCells): void;
|
|
99
|
+
/**
|
|
100
|
+
* Compact the whole-cell set and report the measurement.
|
|
101
|
+
*
|
|
102
|
+
* Compaction is applied to the WHOLE set only — a partial cell's parent is not partial in any useful sense, and
|
|
103
|
+
* compacting it would claim the fringe covers ground it does not.
|
|
104
|
+
*/
|
|
105
|
+
finish(): SoilCellIndexMeasurement;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The measurement as markdown table ROWS — what a build receipt carries, one line per element so a caller printing them
|
|
109
|
+
* never has to split a joined string back apart.
|
|
110
|
+
*/
|
|
111
|
+
export declare function formatSoilResolutionRows(measurements: ReadonlyArray<SoilCellIndexMeasurement & {
|
|
112
|
+
mixedCellShare?: number;
|
|
113
|
+
}>): string[];
|
|
114
|
+
/**
|
|
115
|
+
* The cell integer a short-cell string stores as.
|
|
116
|
+
*/
|
|
117
|
+
export declare function cellToShortInt(cell: string): number;
|
|
118
|
+
//# sourceMappingURL=cells.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cells.d.ts","sourceRoot":"","sources":["../../lib/sdk/cells.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAIN,KAAK,YAAY,EAEjB,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAA;AAG3B;;GAEG;AACH,eAAO,MAAM,eAAe,eAAe,CAAA;AAE3C;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,QAAQ,EAAE,iBAAiB,EAC3B,gBAAgB,EAAE,MAAM,EACxB,MAAM,EAAE,MAAM,GACZ,YAAY,CAEd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACxC,UAAU,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;OAEG;IACH,oBAAoB,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,uBAAuB,EAAE,MAAM,CAAA;IAC/B;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB;;OAEG;IACH,WAAW,EAAE,MAAM,EAAE,CAAA;CACrB;AAED;;;;;GAKG;AACH,qBAAa,aAAa;;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;gBAYf,UAAU,EAAE,MAAM;IAI9B;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IA6B9C;;;;;OAKG;IACH,MAAM,IAAI,wBAAwB;CA+BlC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACvC,YAAY,EAAE,aAAa,CAAC,wBAAwB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACjF,MAAM,EAAE,CAaV;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
package/out/sdk/cells.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* The delineation-keyed cell index, and the two numbers the index resolution is chosen from.
|
|
7
|
+
*
|
|
8
|
+
* THE CLASSIFIER ITSELF LIVES IN `@mailwoman/spatial`, because the traps it guards are properties of
|
|
9
|
+
* h3-js rather than of SSURGO: a centre-containment polyfill drops every polygon smaller than a cell, an
|
|
10
|
+
* exhausted WASM allocator reports success and returns zeros, and the allocator is sized from the
|
|
11
|
+
* bounding box. The layer contract states all three as requirements on every polygon builder. What is
|
|
12
|
+
* soil-shaped is the ACCUMULATOR below, which keys on the delineation rather than on a hazard class,
|
|
13
|
+
* because the reduction weights by the area a delineation covers.
|
|
14
|
+
*
|
|
15
|
+
* EXPECT THE `partial` SHARE TO INVERT AGAINST THE FLOOD LAYER, AND DO NOT READ THAT AS A DEFECT. Flood
|
|
16
|
+
* polygons are large against their cells, so most cells fall wholly inside one zone and `compactCells`
|
|
17
|
+
* collapses long uniform interiors. Soil delineations are the opposite: 85.4% of `IA153`'s 17,966 of them
|
|
18
|
+
* are smaller than one resolution-9 cell, and the median is 24,863 m² against a 105,333 m² cell. Small
|
|
19
|
+
* polygons against large cells means most cells are crossed by a boundary — so the `partial` share should
|
|
20
|
+
* be HIGH, `compactCells` should yield close to nothing, and the index alone will rarely answer a point
|
|
21
|
+
* probe. That is not an argument against storing the geometry; it is the argument for why this layer
|
|
22
|
+
* carries the reduced `soil_capability_cell` alongside the index rather than relying on the index the way
|
|
23
|
+
* the flood layer can.
|
|
24
|
+
*
|
|
25
|
+
* TWO NUMBERS GET REPORTED AT EACH CANDIDATE RESOLUTION, AND THEY MOVE IN OPPOSITE DIRECTIONS. The
|
|
26
|
+
* `partial` cell share says whether the containment index answers most probes alone. The share of cells
|
|
27
|
+
* whose top class holds less than half the cell says whether the layer is answering or hedging — the
|
|
28
|
+
* cell-grain analogue of NRCS's own `niccdcdpct` distribution, which reads 3.3% below half nationally.
|
|
29
|
+
* Going coarser improves the first and worsens the second, and picking between them is what the
|
|
30
|
+
* measurement is for.
|
|
31
|
+
*/
|
|
32
|
+
import { classifyFeatureCells, compactAcrossResolutions, shortCellToInt, } from "@mailwoman/spatial";
|
|
33
|
+
import { getResolution } from "h3-js";
|
|
34
|
+
/**
|
|
35
|
+
* The label this layer's classifier failures carry.
|
|
36
|
+
*/
|
|
37
|
+
export const SOIL_CELL_LABEL = "soil cells";
|
|
38
|
+
/**
|
|
39
|
+
* Classify one delineation — {@link classifyFeatureCells} with this layer's label bound.
|
|
40
|
+
*/
|
|
41
|
+
export function classifyDelineationCells(polygons, targetResolution, areaID) {
|
|
42
|
+
return classifyFeatureCells(polygons, targetResolution, areaID, SOIL_CELL_LABEL);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Accumulate one resolution's cell index over a stream of delineations.
|
|
46
|
+
*
|
|
47
|
+
* Held as short-cell STRINGS rather than the integers the tables store, because `compactCells` is an h3-js function
|
|
48
|
+
* over full indexes and round-tripping through the integer form at every step would cost more than the strings do.
|
|
49
|
+
*/
|
|
50
|
+
export class SoilCellIndex {
|
|
51
|
+
resolution;
|
|
52
|
+
#whole = new Set();
|
|
53
|
+
#touched = new Set();
|
|
54
|
+
/**
|
|
55
|
+
* `cell → delineation ids`. Every touched cell, so the mean below is over the real population rather than over the
|
|
56
|
+
* fringe alone.
|
|
57
|
+
*/
|
|
58
|
+
#byCell = new Map();
|
|
59
|
+
#coarsened = 0;
|
|
60
|
+
constructor(resolution) {
|
|
61
|
+
this.resolution = resolution;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Fold one delineation's classification in.
|
|
65
|
+
*/
|
|
66
|
+
add(areaID, cells) {
|
|
67
|
+
if (cells.resolution !== this.resolution) {
|
|
68
|
+
this.#coarsened++;
|
|
69
|
+
}
|
|
70
|
+
for (const cell of cells.whole) {
|
|
71
|
+
this.#whole.add(cell);
|
|
72
|
+
this.#record(cell, areaID);
|
|
73
|
+
}
|
|
74
|
+
for (const cell of cells.partial) {
|
|
75
|
+
this.#record(cell, areaID);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
#record(cell, areaID) {
|
|
79
|
+
this.#touched.add(cell);
|
|
80
|
+
let areas = this.#byCell.get(cell);
|
|
81
|
+
if (!areas) {
|
|
82
|
+
areas = new Set();
|
|
83
|
+
this.#byCell.set(cell, areas);
|
|
84
|
+
}
|
|
85
|
+
areas.add(areaID);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compact the whole-cell set and report the measurement.
|
|
89
|
+
*
|
|
90
|
+
* Compaction is applied to the WHOLE set only — a partial cell's parent is not partial in any useful sense, and
|
|
91
|
+
* compacting it would claim the fringe covers ground it does not.
|
|
92
|
+
*/
|
|
93
|
+
finish() {
|
|
94
|
+
const compacted = compactAcrossResolutions(this.#whole);
|
|
95
|
+
const resolutions = new Set();
|
|
96
|
+
for (const cell of this.#touched) {
|
|
97
|
+
resolutions.add(getResolution(cell));
|
|
98
|
+
}
|
|
99
|
+
let pairs = 0;
|
|
100
|
+
for (const areas of this.#byCell.values()) {
|
|
101
|
+
pairs += areas.size;
|
|
102
|
+
}
|
|
103
|
+
const touched = this.#touched.size;
|
|
104
|
+
const partial = touched - this.#whole.size;
|
|
105
|
+
return {
|
|
106
|
+
resolution: this.resolution,
|
|
107
|
+
touchedCells: touched,
|
|
108
|
+
wholeCells: this.#whole.size,
|
|
109
|
+
partialCells: partial,
|
|
110
|
+
partialShare: touched ? partial / touched : 0,
|
|
111
|
+
compactedWholeCells: compacted.length,
|
|
112
|
+
cellDelineationPairs: pairs,
|
|
113
|
+
meanDelineationsPerCell: touched ? pairs / touched : 0,
|
|
114
|
+
coarsenedFeatures: this.#coarsened,
|
|
115
|
+
resolutions: [...resolutions].toSorted((left, right) => left - right),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The measurement as markdown table ROWS — what a build receipt carries, one line per element so a caller printing them
|
|
121
|
+
* never has to split a joined string back apart.
|
|
122
|
+
*/
|
|
123
|
+
export function formatSoilResolutionRows(measurements) {
|
|
124
|
+
return [
|
|
125
|
+
"| res | touched cells | whole | partial | partial share | whole after compaction | (cell, delineation) pairs | mean delineations/cell | top class under half |",
|
|
126
|
+
"| --- | ------------- | ----- | ------- | ------------- | ---------------------- | ------------------------- | ---------------------- | -------------------- |",
|
|
127
|
+
...measurements.map((m) => `| ${m.resolution} | ${m.touchedCells.toLocaleString()} | ${m.wholeCells.toLocaleString()} | ` +
|
|
128
|
+
`${m.partialCells.toLocaleString()} | ${(m.partialShare * 100).toFixed(1)}% | ` +
|
|
129
|
+
`${m.compactedWholeCells.toLocaleString()} | ${m.cellDelineationPairs.toLocaleString()} | ` +
|
|
130
|
+
`${m.meanDelineationsPerCell.toFixed(2)} | ` +
|
|
131
|
+
`${m.mixedCellShare === undefined ? "—" : `${(m.mixedCellShare * 100).toFixed(1)}%`} |`),
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The cell integer a short-cell string stores as.
|
|
136
|
+
*/
|
|
137
|
+
export function cellToShortInt(cell) {
|
|
138
|
+
return shortCellToInt(cell);
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=cells.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cells.js","sourceRoot":"","sources":["../../lib/sdk/cells.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Soil Data Access — NRCS's live SQL service, and the two things this layer asks it: which survey areas
|
|
7
|
+
* exist with what version date, and which map unit covers a point.
|
|
8
|
+
*
|
|
9
|
+
* THIS IS AN API REQUEST AND IT GOES THROUGH {@linkcode APIClient}. Small bodies, repeated calls, a
|
|
10
|
+
* third-party host with a server-side query timeout and no published rate limit — the pacing, bounded
|
|
11
|
+
* retry, response caching and `ResourceError` mapping are exactly what it needs. The survey-area
|
|
12
|
+
* ARCHIVES are not: they are 13 to 41 MB file transfers, they stream to disk on raw `fetch`, and
|
|
13
|
+
* `download.ts` says so in place.
|
|
14
|
+
*
|
|
15
|
+
* FAILURES COME BACK AS XML, INCLUDING ON A TIMEOUT, AND A JSON-ONLY PARSER MIS-READS THEM. A bad column,
|
|
16
|
+
* a blocked query and a query that exceeded the server's own timeout all return an OGC
|
|
17
|
+
* `ServiceExceptionReport` document. Measured messages: `Invalid query: Invalid column name
|
|
18
|
+
* 'nosuchcolumn'.` (HTTP 400), `Invalid query - access denied.`, and `Your query timed out.` — and the
|
|
19
|
+
* last one arrives on an HTTP 200. So every response is read as TEXT and checked for the report before
|
|
20
|
+
* anything tries to parse it as JSON. A client that branched on the status code alone would read a
|
|
21
|
+
* timeout as a successful empty answer, which is the exact shape of lie this program keeps writing down.
|
|
22
|
+
*
|
|
23
|
+
* SCHEMA INTROSPECTION IS REFUSED, SO THE COLUMN NAMES ARE THE PUBLISHED DATA DICTIONARY'S.
|
|
24
|
+
* `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS` answers `Invalid query - access denied.` The
|
|
25
|
+
* columns this file names were each verified by querying them successfully.
|
|
26
|
+
*
|
|
27
|
+
* FRESHNESS IS `sacatalog.saverest` AND NEVER A LENGTH PROBE. The download host answers `HEAD` with HTTP
|
|
28
|
+
* 405 and IGNORES `Range` — a request with `Range: bytes=0-0` returned HTTP 200 and transferred the whole
|
|
29
|
+
* 27,598,377 bytes — so "just check the size" starts a real download. The tabular service answers the
|
|
30
|
+
* freshness question directly instead, and the version date it returns is what the archive's filename
|
|
31
|
+
* embeds.
|
|
32
|
+
*/
|
|
33
|
+
import { APIClient, type APIClientConfig, type ClockLike } from "@mailwoman/core/api";
|
|
34
|
+
export { isTransientResourceError } from "@mailwoman/core/api";
|
|
35
|
+
export { ResourceError } from "@mailwoman/core/errors";
|
|
36
|
+
/**
|
|
37
|
+
* The tabular endpoint. Anonymous: no key, no account, and no rate-limit header on any observed response.
|
|
38
|
+
*/
|
|
39
|
+
export declare const SDA_POST_REST_URL = "https://sdmdataaccess.nrcs.usda.gov/Tabular/post.rest";
|
|
40
|
+
/**
|
|
41
|
+
* Minimum spacing between Soil Data Access requests, in milliseconds.
|
|
42
|
+
*
|
|
43
|
+
* NRCS publishes no rate limit for this service and returned no rate-limit header on any request, so this is courtesy
|
|
44
|
+
* pacing rather than a published ceiling — stated as such rather than dressed up as a measured limit. It costs an
|
|
45
|
+
* acquisition run nothing: a whole-state build makes one catalogue call, and the verification's per-point calls are
|
|
46
|
+
* measured at 1.8 s each anyway.
|
|
47
|
+
*/
|
|
48
|
+
export declare const SDA_MIN_REQUEST_INTERVAL_MS = 500;
|
|
49
|
+
/**
|
|
50
|
+
* The error a `ServiceExceptionReport` becomes.
|
|
51
|
+
*
|
|
52
|
+
* Its own class rather than a bare `Error`, because the three failures it carries need different responses from a
|
|
53
|
+
* caller: a timeout is worth narrowing the query for, an invalid column is a schema change, and access denied is a
|
|
54
|
+
* query the service will never run.
|
|
55
|
+
*/
|
|
56
|
+
export declare class SoilDataAccessError extends Error {
|
|
57
|
+
readonly serviceException: string;
|
|
58
|
+
constructor(serviceException: string, query: string);
|
|
59
|
+
/**
|
|
60
|
+
* Did the service exceed its own query timeout? There is no published figure for it, so the message is the only
|
|
61
|
+
* signal — and it arrives on an HTTP 200.
|
|
62
|
+
*/
|
|
63
|
+
get timedOut(): boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The `<ServiceException>` text inside an OGC exception report, or `undefined` when the body is not one.
|
|
67
|
+
*
|
|
68
|
+
* Split from the request so the detection is testable against captured bodies. Both shapes below were taken from the
|
|
69
|
+
* live service: the report arrives with an XML declaration and an `xmlns` of `http://www.opengis.net/ogc`.
|
|
70
|
+
*/
|
|
71
|
+
export declare function readServiceException(body: string): string | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* One published survey area, as the catalogue reports it.
|
|
74
|
+
*/
|
|
75
|
+
export interface SurveyAreaCatalogEntry {
|
|
76
|
+
areasymbol: string;
|
|
77
|
+
areaname: string;
|
|
78
|
+
/**
|
|
79
|
+
* The version-established date as an ISO date — what the archive's filename embeds.
|
|
80
|
+
*/
|
|
81
|
+
saverest: string;
|
|
82
|
+
saversion: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A client for Soil Data Access.
|
|
86
|
+
*/
|
|
87
|
+
export declare class SoilDataAccessClient extends APIClient<APIClientConfig> {
|
|
88
|
+
/**
|
|
89
|
+
* Run one query and return its rows.
|
|
90
|
+
*
|
|
91
|
+
* @throws {SoilDataAccessError} When the service answers with an exception report — including on an HTTP 200, which
|
|
92
|
+
* is what a server-side timeout does.
|
|
93
|
+
*/
|
|
94
|
+
query(sql: string): Promise<string[][]>;
|
|
95
|
+
/**
|
|
96
|
+
* The published survey areas whose symbol starts with `prefix` — a state code for a state-scoped build, or a whole
|
|
97
|
+
* symbol for the single-area rung.
|
|
98
|
+
*
|
|
99
|
+
* @throws {Error} When the catalogue returns nothing. An empty catalogue for a prefix a caller named is either a typo
|
|
100
|
+
* or a service change, and building zero survey areas while reporting success is the shape this refuses.
|
|
101
|
+
*/
|
|
102
|
+
readSurveyAreaCatalog(prefix: string): Promise<SurveyAreaCatalogEntry[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Which map unit the service's OWN geometry assigns at a point, or `undefined` where it assigns none.
|
|
105
|
+
*
|
|
106
|
+
* This is the second path the built artifact is checked against: same authority, different distribution channel, and
|
|
107
|
+
* geometry this package has never touched. Measured at 1.807 s per point, so a few hundred points is minutes.
|
|
108
|
+
*/
|
|
109
|
+
mukeyAtPoint(latitude: number, longitude: number): Promise<string | undefined>;
|
|
110
|
+
}
|
|
111
|
+
export interface CreateSoilDataAccessClientOptions {
|
|
112
|
+
clock?: ClockLike;
|
|
113
|
+
cacheDirectory?: string;
|
|
114
|
+
minRequestIntervalMs?: number;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Build a {@link SoilDataAccessClient} with the disk cache and pacing this package's acquisition path expects.
|
|
118
|
+
*/
|
|
119
|
+
export declare function createSoilDataAccessClient(options?: CreateSoilDataAccessClientOptions): SoilDataAccessClient;
|
|
120
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../lib/sdk/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAA;AASrF,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAEtD;;GAEG;AACH,eAAO,MAAM,iBAAiB,0DAA0D,CAAA;AAExF;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAY9C;;;;;;GAMG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC7C,SAAgB,gBAAgB,EAAE,MAAM,CAAA;gBAE5B,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IASnD;;;OAGG;IACH,IAAW,QAAQ,IAAI,OAAO,CAE7B;CACD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AA4CD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS,CAAC,eAAe,CAAC;IACnE;;;;;OAKG;IACU,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IA8BpD;;;;;;OAMG;IACU,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC;IAqBrF;;;;;OAKG;IACU,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAO3F;AAED,MAAM,WAAW,iCAAiC;IACjD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,iCAAsC,GAAG,oBAAoB,CAahH"}
|