@mailwoman/soil 9.4.0 → 10.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -116
- package/lib/index.ts +66 -98
- package/lib/paths.ts +24 -0
- package/lib/schema.ts +188 -120
- package/lib/vocabulary.ts +40 -107
- package/out/index.d.ts +52 -70
- package/out/index.d.ts.map +1 -1
- package/out/index.js +24 -72
- package/out/index.js.map +1 -1
- package/out/paths.d.ts +19 -0
- package/out/paths.d.ts.map +1 -0
- package/out/paths.js +21 -0
- package/out/paths.js.map +1 -0
- package/out/schema.d.ts +187 -119
- package/out/schema.d.ts.map +1 -1
- package/out/schema.js +31 -31
- package/out/schema.js.map +1 -1
- package/out/sdk/acquire.d.ts +15 -24
- package/out/sdk/acquire.d.ts.map +1 -1
- package/out/sdk/acquire.js +5 -20
- package/out/sdk/acquire.js.map +1 -1
- package/out/sdk/build-soil.d.ts +67 -70
- package/out/sdk/build-soil.d.ts.map +1 -1
- package/out/sdk/build-soil.js +65 -95
- package/out/sdk/build-soil.js.map +1 -1
- package/out/sdk/cell-tiers.d.ts +7 -19
- package/out/sdk/cell-tiers.d.ts.map +1 -1
- package/out/sdk/cell-tiers.js +19 -38
- package/out/sdk/cell-tiers.js.map +1 -1
- package/out/sdk/cells.d.ts +15 -41
- package/out/sdk/cells.d.ts.map +1 -1
- package/out/sdk/cells.js +11 -38
- package/out/sdk/cells.js.map +1 -1
- package/out/sdk/client.d.ts +23 -48
- package/out/sdk/client.d.ts.map +1 -1
- package/out/sdk/client.js +19 -63
- package/out/sdk/client.js.map +1 -1
- package/out/sdk/download.d.ts +24 -47
- package/out/sdk/download.d.ts.map +1 -1
- package/out/sdk/download.js +14 -54
- package/out/sdk/download.js.map +1 -1
- package/out/sdk/ingest/chunk.d.ts +77 -0
- package/out/sdk/ingest/chunk.d.ts.map +1 -0
- package/out/sdk/{ingest-chunk.js → ingest/chunk.js} +19 -17
- package/out/sdk/ingest/chunk.js.map +1 -0
- package/out/sdk/ingest/worker.d.ts +9 -0
- package/out/sdk/ingest/worker.d.ts.map +1 -0
- package/out/{scripts/ingest-chunk.js → sdk/ingest/worker.js} +11 -10
- package/out/sdk/ingest/worker.js.map +1 -0
- package/out/sdk/ingest.d.ts +40 -52
- package/out/sdk/ingest.d.ts.map +1 -1
- package/out/sdk/ingest.js +18 -61
- package/out/sdk/ingest.js.map +1 -1
- package/out/sdk/measure-resolutions.d.ts +5 -16
- package/out/sdk/measure-resolutions.d.ts.map +1 -1
- package/out/sdk/measure-resolutions.js +3 -15
- package/out/sdk/measure-resolutions.js.map +1 -1
- package/out/sdk/reduce.d.ts +33 -59
- package/out/sdk/reduce.d.ts.map +1 -1
- package/out/sdk/reduce.js +47 -78
- package/out/sdk/reduce.js.map +1 -1
- package/out/sdk/survey-area.d.ts +16 -48
- package/out/sdk/survey-area.d.ts.map +1 -1
- package/out/sdk/survey-area.js +33 -77
- package/out/sdk/survey-area.js.map +1 -1
- package/out/sdk/tabular.d.ts +32 -31
- package/out/sdk/tabular.d.ts.map +1 -1
- package/out/sdk/tabular.js +58 -56
- package/out/sdk/tabular.js.map +1 -1
- package/out/sdk/test-kit.d.ts +57 -0
- package/out/sdk/test-kit.d.ts.map +1 -0
- package/out/{test-kit.js → sdk/test-kit.js} +18 -39
- package/out/sdk/test-kit.js.map +1 -0
- package/out/sdk/verify.d.ts +15 -51
- package/out/sdk/verify.d.ts.map +1 -1
- package/out/sdk/verify.js +19 -77
- package/out/sdk/verify.js.map +1 -1
- package/out/vocabulary.d.ts +37 -103
- package/out/vocabulary.d.ts.map +1 -1
- package/out/vocabulary.js +34 -107
- package/out/vocabulary.js.map +1 -1
- package/package.json +36 -190
- package/{lib/sdk → sdk}/acquire.ts +17 -27
- package/{lib/sdk → sdk}/build-soil.ts +114 -128
- package/{lib/sdk → sdk}/cell-tiers.ts +20 -39
- package/{lib/sdk → sdk}/cells.ts +17 -43
- package/sdk/client.ts +147 -0
- package/sdk/download.ts +131 -0
- package/{lib/sdk/ingest-chunk.ts → sdk/ingest/chunk.ts} +34 -26
- package/{lib/scripts/ingest-chunk.ts → sdk/ingest/worker.ts} +10 -9
- package/sdk/ingest.ts +253 -0
- package/{lib/sdk → sdk}/measure-resolutions.ts +5 -16
- package/sdk/reduce.ts +344 -0
- package/{lib/sdk → sdk}/survey-area.ts +39 -83
- package/{lib/sdk → sdk}/tabular.ts +62 -59
- package/{lib → sdk}/test-kit.ts +18 -40
- package/{lib/sdk → sdk}/verify.ts +29 -85
- package/lib/sdk/client.ts +0 -184
- package/lib/sdk/download.ts +0 -161
- package/lib/sdk/index.ts +0 -20
- package/lib/sdk/ingest.ts +0 -278
- package/lib/sdk/reduce.ts +0 -375
- package/out/scripts/ingest-chunk.d.ts +0 -11
- package/out/scripts/ingest-chunk.d.ts.map +0 -1
- package/out/scripts/ingest-chunk.js.map +0 -1
- package/out/sdk/index.d.ts +0 -20
- package/out/sdk/index.d.ts.map +0 -1
- package/out/sdk/index.js +0 -20
- package/out/sdk/index.js.map +0 -1
- package/out/sdk/ingest-chunk.d.ts +0 -73
- package/out/sdk/ingest-chunk.d.ts.map +0 -1
- package/out/sdk/ingest-chunk.js.map +0 -1
- package/out/test-kit.d.ts +0 -79
- package/out/test-kit.d.ts.map +0 -1
- package/out/test-kit.js.map +0 -1
package/out/sdk/cells.d.ts
CHANGED
|
@@ -3,35 +3,11 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* The delineation-keyed cell index,
|
|
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.
|
|
6
|
+
* The delineation-keyed cell index, plus the partial-cell and mixed-top-class shares that choose its resolution.
|
|
31
7
|
*/
|
|
32
8
|
import { type FeatureCells, type MultiPolygonRings } from "@mailwoman/spatial";
|
|
33
9
|
/**
|
|
34
|
-
* The label this layer's classifier failures
|
|
10
|
+
* The label assigned to this layer's classifier failures.
|
|
35
11
|
*/
|
|
36
12
|
export declare const SOIL_CELL_LABEL = "soil cells";
|
|
37
13
|
/**
|
|
@@ -56,12 +32,13 @@ export interface SoilCellIndexMeasurement {
|
|
|
56
32
|
*/
|
|
57
33
|
partialCells: number;
|
|
58
34
|
/**
|
|
59
|
-
* `partialCells / touchedCells
|
|
35
|
+
* `partialCells / touchedCells`, the share of in-layer probes that cannot
|
|
36
|
+
* be answered using only the index.
|
|
60
37
|
*/
|
|
61
38
|
partialShare: number;
|
|
62
39
|
/**
|
|
63
|
-
* Whole cells after `compactCells
|
|
64
|
-
*
|
|
40
|
+
* Whole cells after `compactCells`, expected to be close to `wholeCells` here
|
|
41
|
+
* because small delineations do not produce a uniform interior.
|
|
65
42
|
*/
|
|
66
43
|
compactedWholeCells: number;
|
|
67
44
|
/**
|
|
@@ -69,8 +46,8 @@ export interface SoilCellIndexMeasurement {
|
|
|
69
46
|
*/
|
|
70
47
|
cellDelineationPairs: number;
|
|
71
48
|
/**
|
|
72
|
-
* The mean number of delineations reaching a cell
|
|
73
|
-
*
|
|
49
|
+
* The mean number of delineations reaching a cell measures how mixed a cell is
|
|
50
|
+
* before any rating is read and rises as the resolution coarsens.
|
|
74
51
|
*/
|
|
75
52
|
meanDelineationsPerCell: number;
|
|
76
53
|
/**
|
|
@@ -83,10 +60,9 @@ export interface SoilCellIndexMeasurement {
|
|
|
83
60
|
resolutions: number[];
|
|
84
61
|
}
|
|
85
62
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* over full indexes and round-tripping through the integer form at every step would cost more than the strings do.
|
|
63
|
+
* Accumulates one resolution's cell index over a stream of delineations,
|
|
64
|
+
* held as short-cell strings because `compactCells` needs full h3-js indexes
|
|
65
|
+
* and round-tripping through the integer form would cost more.
|
|
90
66
|
*/
|
|
91
67
|
export declare class SoilCellIndex {
|
|
92
68
|
#private;
|
|
@@ -97,16 +73,14 @@ export declare class SoilCellIndex {
|
|
|
97
73
|
*/
|
|
98
74
|
add(areaID: string, cells: FeatureCells): void;
|
|
99
75
|
/**
|
|
100
|
-
*
|
|
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.
|
|
76
|
+
* Compacts the whole-cell set and reports the measurement, applying compaction to the
|
|
77
|
+
* whole set only because a partial cell's parent would claim fringe ground.
|
|
104
78
|
*/
|
|
105
79
|
finish(): SoilCellIndexMeasurement;
|
|
106
80
|
}
|
|
107
81
|
/**
|
|
108
|
-
* The measurement as markdown table
|
|
109
|
-
*
|
|
82
|
+
* The measurement as markdown table rows, one line per element so a caller never
|
|
83
|
+
* has to split a joined string back apart.
|
|
110
84
|
*/
|
|
111
85
|
export declare function formatSoilResolutionRows(measurements: ReadonlyArray<SoilCellIndexMeasurement & {
|
|
112
86
|
mixedCellShare?: number;
|
package/out/sdk/cells.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cells.d.ts","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"cells.d.ts","sourceRoot":"","sources":["../../sdk/cells.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;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;;;OAGG;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;;;;GAIG;AACH,qBAAa,aAAa;;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAY3B,YAAY,UAAU,EAAE,MAAM,EAE7B;IAED;;OAEG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAa7C;IAgBD;;;OAGG;IACH,MAAM,IAAI,wBAAwB,CA8BjC;CACD;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
CHANGED
|
@@ -3,36 +3,12 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* The delineation-keyed cell index,
|
|
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.
|
|
6
|
+
* The delineation-keyed cell index, plus the partial-cell and mixed-top-class shares that choose its resolution.
|
|
31
7
|
*/
|
|
32
8
|
import { classifyFeatureCells, compactAcrossResolutions, shortCellToInt, } from "@mailwoman/spatial";
|
|
33
9
|
import { getResolution } from "h3-js";
|
|
34
10
|
/**
|
|
35
|
-
* The label this layer's classifier failures
|
|
11
|
+
* The label assigned to this layer's classifier failures.
|
|
36
12
|
*/
|
|
37
13
|
export const SOIL_CELL_LABEL = "soil cells";
|
|
38
14
|
/**
|
|
@@ -42,18 +18,17 @@ export function classifyDelineationCells(polygons, targetResolution, areaID) {
|
|
|
42
18
|
return classifyFeatureCells(polygons, targetResolution, areaID, SOIL_CELL_LABEL);
|
|
43
19
|
}
|
|
44
20
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* over full indexes and round-tripping through the integer form at every step would cost more than the strings do.
|
|
21
|
+
* Accumulates one resolution's cell index over a stream of delineations,
|
|
22
|
+
* held as short-cell strings because `compactCells` needs full h3-js indexes
|
|
23
|
+
* and round-tripping through the integer form would cost more.
|
|
49
24
|
*/
|
|
50
25
|
export class SoilCellIndex {
|
|
51
26
|
resolution;
|
|
52
27
|
#whole = new Set();
|
|
53
28
|
#touched = new Set();
|
|
54
29
|
/**
|
|
55
|
-
* `cell → delineation ids
|
|
56
|
-
* fringe
|
|
30
|
+
* `cell → delineation ids` for every touched cell, so the mean is over the
|
|
31
|
+
* real population rather than only the fringe.
|
|
57
32
|
*/
|
|
58
33
|
#byCell = new Map();
|
|
59
34
|
#coarsened = 0;
|
|
@@ -85,10 +60,8 @@ export class SoilCellIndex {
|
|
|
85
60
|
areas.add(areaID);
|
|
86
61
|
}
|
|
87
62
|
/**
|
|
88
|
-
*
|
|
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.
|
|
63
|
+
* Compacts the whole-cell set and reports the measurement, applying compaction to the
|
|
64
|
+
* whole set only because a partial cell's parent would claim fringe ground.
|
|
92
65
|
*/
|
|
93
66
|
finish() {
|
|
94
67
|
const compacted = compactAcrossResolutions(this.#whole);
|
|
@@ -117,8 +90,8 @@ export class SoilCellIndex {
|
|
|
117
90
|
}
|
|
118
91
|
}
|
|
119
92
|
/**
|
|
120
|
-
* The measurement as markdown table
|
|
121
|
-
*
|
|
93
|
+
* The measurement as markdown table rows, one line per element so a caller never
|
|
94
|
+
* has to split a joined string back apart.
|
|
122
95
|
*/
|
|
123
96
|
export function formatSoilResolutionRows(measurements) {
|
|
124
97
|
return [
|
package/out/sdk/cells.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cells.js","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"cells.js","sourceRoot":"","sources":["../../sdk/cells.ts"],"names":[],"mappings":"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"}
|
package/out/sdk/client.d.ts
CHANGED
|
@@ -2,46 +2,17 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
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
5
|
*/
|
|
33
6
|
import { APIClient, type APIClientConfig, type ClockLike } from "@mailwoman/core/api";
|
|
7
|
+
import type { PathBuilderLike } from "path-ts";
|
|
34
8
|
/**
|
|
35
|
-
*
|
|
9
|
+
* Sends anonymous requests to the Soil Data Access tabular query endpoint.
|
|
10
|
+
* Requests need no key or account.
|
|
36
11
|
*/
|
|
37
12
|
export declare const SDA_POST_REST_URL = "https://sdmdataaccess.nrcs.usda.gov/Tabular/post.rest";
|
|
38
13
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* NRCS publishes no rate limit for this service and returned no rate-limit header on any request, so this is courtesy
|
|
42
|
-
* pacing rather than a published ceiling — stated as such rather than dressed up as a measured limit. It costs an
|
|
43
|
-
* acquisition run nothing: a whole-state build makes one catalogue call, and the verification's per-point calls are
|
|
44
|
-
* measured at 1.8 s each anyway.
|
|
14
|
+
* Sets the minimum spacing between Soil Data Access requests.
|
|
15
|
+
* NRCS publishes no rate limit for the service.
|
|
45
16
|
*/
|
|
46
17
|
export declare const SDA_MIN_REQUEST_INTERVAL_MS = 500;
|
|
47
18
|
/**
|
|
@@ -51,7 +22,8 @@ export interface SurveyAreaCatalogEntry {
|
|
|
51
22
|
areasymbol: string;
|
|
52
23
|
areaname: string;
|
|
53
24
|
/**
|
|
54
|
-
* The version-established date as an ISO date
|
|
25
|
+
* The version-established date as an ISO date.
|
|
26
|
+
* The survey area's archive filename embeds it.
|
|
55
27
|
*/
|
|
56
28
|
saverest: string;
|
|
57
29
|
saversion: number;
|
|
@@ -61,35 +33,38 @@ export interface SurveyAreaCatalogEntry {
|
|
|
61
33
|
*/
|
|
62
34
|
export declare class SoilDataAccessClient extends APIClient<APIClientConfig> {
|
|
63
35
|
/**
|
|
64
|
-
*
|
|
36
|
+
* Runs one SQL query and returns its rows as strings, with NULL as an empty string.
|
|
65
37
|
*
|
|
66
|
-
* @throws {OGCServiceError} When the service answers with an exception report
|
|
67
|
-
*
|
|
38
|
+
* @throws {OGCServiceError} When the service answers with an exception report,
|
|
39
|
+
* including an HTTP 200 response.
|
|
40
|
+
* A server-side timeout uses that status.
|
|
68
41
|
*/
|
|
69
42
|
query(sql: string): Promise<string[][]>;
|
|
70
43
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
44
|
+
* Returns the published survey areas whose symbol starts with `prefix`,
|
|
45
|
+
* such as a state code or one whole area symbol.
|
|
73
46
|
*
|
|
74
|
-
* @throws {Error} When
|
|
75
|
-
*
|
|
47
|
+
* @throws {Error} When no survey area matches, because a build over an empty set
|
|
48
|
+
* would otherwise report success having written no rows.
|
|
76
49
|
*/
|
|
77
50
|
readSurveyAreaCatalog(prefix: string): Promise<SurveyAreaCatalogEntry[]>;
|
|
78
51
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* This is the second path the built artifact is checked against: same authority, different distribution channel, and
|
|
82
|
-
* geometry this package has never touched. Measured at 1.807 s per point, so a few hundred points is minutes.
|
|
52
|
+
* Returns the map unit key the service's own geometry assigns at a point, or `undefined`,
|
|
53
|
+
* as a cross-check against the authority through a channel this package never processed.
|
|
83
54
|
*/
|
|
84
55
|
mukeyAtPoint(latitude: number, longitude: number): Promise<string | undefined>;
|
|
85
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Overrides the clock, the HTTP cache directory and the request spacing used
|
|
59
|
+
* by {@link createSoilDataAccessClient}.
|
|
60
|
+
*/
|
|
86
61
|
export interface CreateSoilDataAccessClientOptions {
|
|
87
62
|
clock?: ClockLike;
|
|
88
|
-
cacheDirectory?:
|
|
63
|
+
cacheDirectory?: PathBuilderLike;
|
|
89
64
|
minRequestIntervalMs?: number;
|
|
90
65
|
}
|
|
91
66
|
/**
|
|
92
|
-
*
|
|
67
|
+
* Creates a {@link SoilDataAccessClient} with retries, a 12-hour disk cache and the default request pacing.
|
|
93
68
|
*/
|
|
94
69
|
export declare function createSoilDataAccessClient(options?: CreateSoilDataAccessClientOptions): SoilDataAccessClient;
|
|
95
70
|
//# sourceMappingURL=client.d.ts.map
|
package/out/sdk/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../sdk/client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,SAAS,EAA+B,MAAM,qBAAqB,CAAA;AAGlH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAK9C;;;GAGG;AACH,eAAO,MAAM,iBAAiB,0DAA0D,CAAA;AAExF;;;GAGG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAI9C;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAEhB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CACjB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,SAAS,CAAC,eAAe,CAAC;IACnE;;;;;;OAMG;IACU,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAuBnD;IAED;;;;;;OAMG;IACU,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAmBpF;IAED;;;OAGG;IACU,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM1F;CACD;AAED;;;GAGG;AACH,MAAM,WAAW,iCAAiC;IACjD,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,cAAc,CAAC,EAAE,eAAe,CAAA;IAChC,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,iCAAsC,GAAG,oBAAoB,CAahH"}
|
package/out/sdk/client.js
CHANGED
|
@@ -2,86 +2,44 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
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
5
|
*/
|
|
33
6
|
import { APIClient, assertNoOGCServiceException } from "@mailwoman/core/api";
|
|
34
7
|
import { buildDiskStorage } from "@mailwoman/core/api/disk-storage";
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
8
|
+
import { parseJSONStrict, stringifyJSON } from "@mailwoman/core/json";
|
|
9
|
+
import { soilDatabasePath } from "#paths";
|
|
37
10
|
import { saverestToISODate } from "#sdk/tabular";
|
|
38
|
-
// Re-exported so a caller branching on this client's failures needs exactly one import.
|
|
39
11
|
/**
|
|
40
|
-
*
|
|
12
|
+
* Sends anonymous requests to the Soil Data Access tabular query endpoint.
|
|
13
|
+
* Requests need no key or account.
|
|
41
14
|
*/
|
|
42
15
|
export const SDA_POST_REST_URL = "https://sdmdataaccess.nrcs.usda.gov/Tabular/post.rest";
|
|
43
16
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* NRCS publishes no rate limit for this service and returned no rate-limit header on any request, so this is courtesy
|
|
47
|
-
* pacing rather than a published ceiling — stated as such rather than dressed up as a measured limit. It costs an
|
|
48
|
-
* acquisition run nothing: a whole-state build makes one catalogue call, and the verification's per-point calls are
|
|
49
|
-
* measured at 1.8 s each anyway.
|
|
17
|
+
* Sets the minimum spacing between Soil Data Access requests.
|
|
18
|
+
* NRCS publishes no rate limit for the service.
|
|
50
19
|
*/
|
|
51
20
|
export const SDA_MIN_REQUEST_INTERVAL_MS = 500;
|
|
52
|
-
/**
|
|
53
|
-
* How long a cached Soil Data Access response stays fresh.
|
|
54
|
-
*
|
|
55
|
-
* Twelve hours, chosen against the product's cadence rather than a wall-clock intuition: NRCS performs ONE coordinated
|
|
56
|
-
* Annual Soils Refresh, on October 1. Grouping `sacatalog` by year of `saverest` returns 2016: 1, 2025: 3,323, 2026: 56
|
|
57
|
-
* — 98.3% of survey areas carry a single version date from one refresh rather than a per-area drift. A shorter TTL buys
|
|
58
|
-
* nothing.
|
|
59
|
-
*/
|
|
60
21
|
const SDA_CACHE_TTL_MS = 12 * 60 * 60 * 1000;
|
|
61
22
|
/**
|
|
62
23
|
* A client for Soil Data Access.
|
|
63
24
|
*/
|
|
64
25
|
export class SoilDataAccessClient extends APIClient {
|
|
65
26
|
/**
|
|
66
|
-
*
|
|
27
|
+
* Runs one SQL query and returns its rows as strings, with NULL as an empty string.
|
|
67
28
|
*
|
|
68
|
-
* @throws {OGCServiceError} When the service answers with an exception report
|
|
69
|
-
*
|
|
29
|
+
* @throws {OGCServiceError} When the service answers with an exception report,
|
|
30
|
+
* including an HTTP 200 response.
|
|
31
|
+
* A server-side timeout uses that status.
|
|
70
32
|
*/
|
|
71
33
|
async query(sql) {
|
|
72
34
|
const { data } = await this.fetch({
|
|
73
35
|
method: "POST",
|
|
74
36
|
url: SDA_POST_REST_URL,
|
|
75
|
-
// TEXT, not JSON, and that is the whole trap. A JSON response type hands a failure body to a JSON parser,
|
|
76
|
-
// which either throws something unrelated to what went wrong or — on a 200 — yields nothing at all.
|
|
77
37
|
responseType: "text",
|
|
78
38
|
headers: { "Content-Type": "application/json" },
|
|
79
39
|
data: { SERVICE: "query", FORMAT: "JSON", QUERY: sql },
|
|
80
40
|
});
|
|
81
41
|
assertNoOGCServiceException(data, `soil data access (query: ${sql.slice(0, 200)})`);
|
|
82
42
|
const parsed = parseJSONStrict(data);
|
|
83
|
-
// An answer with NO rows is `{}` rather than `{"Table":[]}`, so an absent `Table` is a real empty result and not a
|
|
84
|
-
// read failure — the exception check above has already separated the two.
|
|
85
43
|
if (parsed.Table === undefined)
|
|
86
44
|
return [];
|
|
87
45
|
if (!Array.isArray(parsed.Table)) {
|
|
@@ -90,17 +48,17 @@ export class SoilDataAccessClient extends APIClient {
|
|
|
90
48
|
return parsed.Table.map((row) => row.map((value) => (value === null ? "" : String(value))));
|
|
91
49
|
}
|
|
92
50
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
51
|
+
* Returns the published survey areas whose symbol starts with `prefix`,
|
|
52
|
+
* such as a state code or one whole area symbol.
|
|
95
53
|
*
|
|
96
|
-
* @throws {Error} When
|
|
97
|
-
*
|
|
54
|
+
* @throws {Error} When no survey area matches, because a build over an empty set
|
|
55
|
+
* would otherwise report success having written no rows.
|
|
98
56
|
*/
|
|
99
57
|
async readSurveyAreaCatalog(prefix) {
|
|
100
58
|
const escaped = prefix.replaceAll("'", "''");
|
|
101
59
|
const rows = await this.query(`SELECT areasymbol, areaname, saverest, saversion FROM sacatalog WHERE areasymbol LIKE '${escaped}%' ORDER BY areasymbol`);
|
|
102
60
|
if (!rows.length) {
|
|
103
|
-
throw new Error(`soil data access: the catalogue holds no survey area whose symbol starts with ${
|
|
61
|
+
throw new Error(`soil data access: the catalogue holds no survey area whose symbol starts with ${stringifyJSON(prefix)} — a build over an empty set would report success having written nothing`);
|
|
104
62
|
}
|
|
105
63
|
return rows.map((row) => ({
|
|
106
64
|
areasymbol: row[0],
|
|
@@ -110,10 +68,8 @@ export class SoilDataAccessClient extends APIClient {
|
|
|
110
68
|
}));
|
|
111
69
|
}
|
|
112
70
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* This is the second path the built artifact is checked against: same authority, different distribution channel, and
|
|
116
|
-
* geometry this package has never touched. Measured at 1.807 s per point, so a few hundred points is minutes.
|
|
71
|
+
* Returns the map unit key the service's own geometry assigns at a point, or `undefined`,
|
|
72
|
+
* as a cross-check against the authority through a channel this package never processed.
|
|
117
73
|
*/
|
|
118
74
|
async mukeyAtPoint(latitude, longitude) {
|
|
119
75
|
const rows = await this.query(`SELECT mukey FROM SDA_Get_Mukey_from_intersection_with_WktWgs84('point(${longitude} ${latitude})')`);
|
|
@@ -121,7 +77,7 @@ export class SoilDataAccessClient extends APIClient {
|
|
|
121
77
|
}
|
|
122
78
|
}
|
|
123
79
|
/**
|
|
124
|
-
*
|
|
80
|
+
* Creates a {@link SoilDataAccessClient} with retries, a 12-hour disk cache and the default request pacing.
|
|
125
81
|
*/
|
|
126
82
|
export function createSoilDataAccessClient(options = {}) {
|
|
127
83
|
return new SoilDataAccessClient({
|
|
@@ -132,7 +88,7 @@ export function createSoilDataAccessClient(options = {}) {
|
|
|
132
88
|
caching: {
|
|
133
89
|
ttl: SDA_CACHE_TTL_MS,
|
|
134
90
|
storage: buildDiskStorage({
|
|
135
|
-
directory: options.cacheDirectory ??
|
|
91
|
+
directory: (options.cacheDirectory ?? soilDatabasePath("cache", "http")).toString(),
|
|
136
92
|
}),
|
|
137
93
|
},
|
|
138
94
|
});
|
package/out/sdk/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../sdk/client.ts"],"names":[],"mappings":"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"}
|
package/out/sdk/download.d.ts
CHANGED
|
@@ -2,56 +2,32 @@
|
|
|
2
2
|
* @copyright Sister Software
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
|
-
*
|
|
6
|
-
* Acquire one survey area's published archive — 13 to 41 MB streamed to disk and unzipped.
|
|
7
|
-
*
|
|
8
|
-
* THE TRANSFER ITSELF LIVES IN `@mailwoman/core/utils`, and `streamToDisk` carries why a file transfer of
|
|
9
|
-
* this size keeps raw `fetch` instead of going through `APIClient`, plus the `.part`-rename rule. What is
|
|
10
|
-
* soil's, and stays here, is the URL shape, the cache key, and the two facts below that the shared
|
|
11
|
-
* transfer is told rather than assumes: the progress stride and what a 400 means. The METADATA reads
|
|
12
|
-
* around this one do go through `APIClient` — see `client.ts`.
|
|
13
|
-
*
|
|
14
|
-
* FRESHNESS IS `sacatalog.saverest`, NEVER A LENGTH PROBE, AND THE HOST LEAVES NO CHOICE. It answers `HEAD`
|
|
15
|
-
* with HTTP 405 (`allow: GET`) and IGNORES `Range`: a request with `Range: bytes=0-0` returned HTTP 200 and
|
|
16
|
-
* transferred the whole 27,598,377 bytes in 7.23 s. So "check the size first" starts a real download. The
|
|
17
|
-
* cache is keyed on the version date the tabular service reports instead, and a vintage already on disk is
|
|
18
|
-
* never re-fetched. The `Range` behaviour is PATH-SPECIFIC rather than host-wide — `/DataAvailability/`
|
|
19
|
-
* does answer 206 — so a client must probe per path rather than conclude from one.
|
|
20
|
-
*
|
|
21
|
-
* THE FILENAME EMBEDS THE VERSION DATE AND A WRONG ONE IS AN HTTP 400. Not a 404: asking for a date the
|
|
22
|
-
* host does not hold reads as a malformed request rather than a missing file, which is why the date comes
|
|
23
|
-
* from the catalogue rather than from a guess. The square brackets must be sent literally, so the URL is
|
|
24
|
-
* built with them percent-encoded.
|
|
25
|
-
*
|
|
26
|
-
* TWO CACHE VARIANTS EXIST AND THE BARE ONE IS WANTED. `wss_SSA_IA153_[2025-09-09].zip` is 25,474,922 bytes;
|
|
27
|
-
* `wss_SSA_IA153_soildb_IA_2003_[2025-09-09].zip` is 27,598,377 and differs only by an EMPTY Microsoft Access
|
|
28
|
-
* template container for a workflow this program does not use. Confirmed on a second area (`IA015`:
|
|
29
|
-
* 38,981,269 against 41,104,724 bytes) and on a third that ships no template at all (`TX299`, 13,455,641
|
|
30
|
-
* bytes, 97 files, no `.mdb`).
|
|
31
5
|
*/
|
|
6
|
+
import { PathBuilder, type PathBuilderLike } from "path-ts";
|
|
32
7
|
/**
|
|
33
|
-
*
|
|
34
|
-
* which lists `GET /{CacheName}/{FileName}`.
|
|
8
|
+
* Points to the Web Soil Survey download cache that serves survey-area archives.
|
|
35
9
|
*/
|
|
36
10
|
export declare const WSS_SSA_CACHE_URL = "https://websoilsurvey.sc.egov.usda.gov/DSD/Download/Cache/SSA";
|
|
37
11
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* The brackets are percent-encoded rather than sent raw: they are not valid in a URL path, and a client that sends them
|
|
41
|
-
* literally depends on the fetcher tolerating them.
|
|
12
|
+
* Returns the archive URL for one survey area at one version date, with the brackets
|
|
13
|
+
* around the date percent-encoded because they are not valid in a URL path.
|
|
42
14
|
*/
|
|
43
15
|
export declare function surveyAreaArchiveURL(areaSymbol: string, versionDate: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Configures {@link downloadSurveyArea}, which caches the archive and its
|
|
18
|
+
* extracted tree under `cacheRoot/<versionDate>`.
|
|
19
|
+
*/
|
|
44
20
|
export interface DownloadSurveyAreaOptions {
|
|
45
21
|
areaSymbol: string;
|
|
46
22
|
/**
|
|
47
|
-
* The version date from `sacatalog.saverest`, as `YYYY-MM-DD`.
|
|
23
|
+
* The survey area's version date from `sacatalog.saverest`, formatted as `YYYY-MM-DD`.
|
|
48
24
|
*/
|
|
49
25
|
versionDate: string;
|
|
50
26
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
27
|
+
* The cache directory, where each version date gets its own subdirectory so a new
|
|
28
|
+
* vintage never overwrites an old one and a repeat run downloads no file.
|
|
53
29
|
*/
|
|
54
|
-
cacheRoot:
|
|
30
|
+
cacheRoot: PathBuilderLike;
|
|
55
31
|
onProgress?: (message: string) => void;
|
|
56
32
|
}
|
|
57
33
|
/**
|
|
@@ -61,24 +37,25 @@ export interface SurveyAreaArchive {
|
|
|
61
37
|
areaSymbol: string;
|
|
62
38
|
versionDate: string;
|
|
63
39
|
/**
|
|
64
|
-
* The extracted `<areasymbol>/` directory
|
|
40
|
+
* The extracted `<areasymbol>/` directory.
|
|
41
|
+
* It contains `spatial/` and `tabular/`.
|
|
65
42
|
*/
|
|
66
|
-
root:
|
|
67
|
-
spatialDirectory:
|
|
68
|
-
tabularDirectory:
|
|
43
|
+
root: PathBuilder;
|
|
44
|
+
spatialDirectory: PathBuilder;
|
|
45
|
+
tabularDirectory: PathBuilder;
|
|
69
46
|
/**
|
|
70
|
-
* The
|
|
47
|
+
* The downloaded ZIP archive, kept so a repeat run skips the transfer and the bytes can be rechecked.
|
|
71
48
|
*/
|
|
72
|
-
archivePath:
|
|
49
|
+
archivePath: PathBuilder;
|
|
73
50
|
}
|
|
74
51
|
/**
|
|
75
|
-
*
|
|
52
|
+
* Downloads and unzips one survey area into the cache, skipping completed steps.
|
|
53
|
+
* Returns the paths to its files.
|
|
76
54
|
*
|
|
77
|
-
*
|
|
78
|
-
* complete archive — the same discipline the database build uses, for the same reason.
|
|
55
|
+
* The download goes through a `.part` file so an interrupted transfer never looks like a complete archive.
|
|
79
56
|
*
|
|
80
|
-
* @throws {Error} When the host answers anything but 200, or when the extracted
|
|
81
|
-
*
|
|
57
|
+
* @throws {Error} When the host answers anything but 200, or when the extracted
|
|
58
|
+
* tree lacks the `spatial` or `tabular` directory.
|
|
82
59
|
*/
|
|
83
60
|
export declare function downloadSurveyArea(options: DownloadSurveyAreaOptions): Promise<SurveyAreaArchive>;
|
|
84
61
|
//# sourceMappingURL=download.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../sdk/download.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAA;AAE3D;;GAEG;AACH,eAAO,MAAM,iBAAiB,kEAAkE,CAAA;AAEhG;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;GAGG;AACH,MAAM,WAAW,yBAAyB;IACzC,UAAU,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,SAAS,EAAE,eAAe,CAAA;IAC1B,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC;AAMD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IAEnB;;;OAGG;IACH,IAAI,EAAE,WAAW,CAAA;IACjB,gBAAgB,EAAE,WAAW,CAAA;IAC7B,gBAAgB,EAAE,WAAW,CAAA;IAE7B;;OAEG;IACH,WAAW,EAAE,WAAW,CAAA;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAmDvG"}
|