@mailwoman/tiger 9.1.0 → 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/out/sdk/download.d.ts +0 -7
- package/out/sdk/download.d.ts.map +1 -1
- package/out/sdk/download.js +0 -18
- package/out/sdk/download.js.map +1 -1
- package/out/sdk/fetch.d.ts.map +1 -1
- package/out/sdk/fetch.js +11 -8
- package/out/sdk/fetch.js.map +1 -1
- package/out/sdk/redistricting.d.ts.map +1 -1
- package/out/sdk/redistricting.js +1 -5
- package/out/sdk/redistricting.js.map +1 -1
- package/out/sdk/state/TIGERTabulatedBlockEntity.d.ts.map +1 -1
- package/out/sdk/state/TIGERTabulatedBlockEntity.js +1 -1
- package/out/sdk/state/TIGERTabulatedBlockEntity.js.map +1 -1
- package/out/sdk/state/TIGERTractEntity.d.ts +1 -1
- package/out/sdk/state/TIGERTractEntity.d.ts.map +1 -1
- package/out/sdk/state/block-operations.d.ts +1 -1
- package/out/sdk/state/block-operations.d.ts.map +1 -1
- package/out/sdk/state/block-operations.js.map +1 -1
- package/out/tools/index.d.ts +1 -5
- package/out/tools/index.d.ts.map +1 -1
- package/out/tools/index.js +1 -5
- package/out/tools/index.js.map +1 -1
- package/out/tools/race-dots-map.d.ts.map +1 -1
- package/out/tools/race-dots-map.js +2 -1
- package/out/tools/race-dots-map.js.map +1 -1
- package/out/tools/race-dots.js +2 -2
- package/out/tools/race-dots.js.map +1 -1
- package/out/tools/serve-range.d.ts.map +1 -1
- package/out/tools/serve-range.js +2 -1
- package/out/tools/serve-range.js.map +1 -1
- package/out/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -7
- package/sdk/download.ts +0 -22
- package/sdk/fetch.ts +11 -7
- package/sdk/redistricting.ts +1 -5
- package/sdk/state/TIGERTabulatedBlockEntity.ts +2 -1
- package/sdk/state/TIGERTractEntity.ts +1 -1
- package/sdk/state/block-operations.ts +2 -1
- package/tools/index.ts +1 -5
- package/tools/race-dots-map.ts +3 -1
- package/tools/race-dots.ts +2 -2
- package/tools/serve-range.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/tiger",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "US Census TIGER/Line data processing and analysis.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"census",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "https://github.com/sister-software/mailwoman.git",
|
|
24
|
-
"directory": "tiger"
|
|
24
|
+
"directory": "packages/tiger"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"out/",
|
|
@@ -33,9 +33,16 @@
|
|
|
33
33
|
"!*.test.tsx",
|
|
34
34
|
"!**/*.test.ts",
|
|
35
35
|
"!**/*.test.tsx",
|
|
36
|
-
"!__snapshots__/"
|
|
36
|
+
"!__snapshots__/",
|
|
37
|
+
"!test/**"
|
|
37
38
|
],
|
|
38
39
|
"type": "module",
|
|
40
|
+
"imports": {
|
|
41
|
+
"#index": {
|
|
42
|
+
"types": "./out/index.d.ts",
|
|
43
|
+
"default": "./out/index.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
39
46
|
"exports": {
|
|
40
47
|
"./package.json": "./package.json",
|
|
41
48
|
"./sdk": {
|
|
@@ -67,18 +74,23 @@
|
|
|
67
74
|
"types": "./out/index.d.ts",
|
|
68
75
|
"default": "./out/index.js"
|
|
69
76
|
}
|
|
77
|
+
},
|
|
78
|
+
"imports": {
|
|
79
|
+
"#index": {
|
|
80
|
+
"types": "./out/index.d.ts",
|
|
81
|
+
"default": "./out/index.js"
|
|
82
|
+
}
|
|
70
83
|
}
|
|
71
84
|
},
|
|
72
85
|
"scripts": {
|
|
73
86
|
"test": "node ./out/sdk/TIGERService.test.js"
|
|
74
87
|
},
|
|
75
88
|
"dependencies": {
|
|
76
|
-
"@mailwoman/core": "9.
|
|
77
|
-
"@mailwoman/spatial": "9.
|
|
89
|
+
"@mailwoman/core": "9.2.0",
|
|
90
|
+
"@mailwoman/spatial": "9.2.0",
|
|
78
91
|
"@turf/boolean-contains": "^7.3.5",
|
|
79
92
|
"kysely": "^0.29.4",
|
|
80
|
-
"
|
|
81
|
-
"spliterator": "^6.1.0",
|
|
93
|
+
"spliterator": "^6.2.0",
|
|
82
94
|
"type-fest": "^5.8.0"
|
|
83
95
|
},
|
|
84
96
|
"devDependencies": {
|
package/sdk/download.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* never holds it: a web stream piped to a write stream.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { spawn } from "node:child_process"
|
|
18
17
|
import { createWriteStream, existsSync } from "node:fs"
|
|
19
18
|
import { rename } from "node:fs/promises"
|
|
20
19
|
import { Readable } from "node:stream"
|
|
@@ -22,27 +21,6 @@ import { pipeline } from "node:stream/promises"
|
|
|
22
21
|
|
|
23
22
|
import { verifyZipIntegrity } from "@mailwoman/core/fs/zip"
|
|
24
23
|
|
|
25
|
-
/**
|
|
26
|
-
* Run a command and resolve its stdout, rejecting with the captured stderr on a non-zero exit.
|
|
27
|
-
*
|
|
28
|
-
* Used for the `unzip -tq` integrity probe below — a shelled-out check rather than a library one because the archives
|
|
29
|
-
* are large and `unzip -t` streams them without materializing the contents.
|
|
30
|
-
*/
|
|
31
|
-
export function runCapture(cmd: string, args: string[]): Promise<string> {
|
|
32
|
-
return new Promise((resolve, reject) => {
|
|
33
|
-
const child = spawn(cmd, args)
|
|
34
|
-
let out = ""
|
|
35
|
-
let err = ""
|
|
36
|
-
child.stdout.on("data", (d) => (out += d))
|
|
37
|
-
child.stderr.on("data", (d) => (err += d))
|
|
38
|
-
child.on("error", reject)
|
|
39
|
-
|
|
40
|
-
child.on("close", (code) =>
|
|
41
|
-
code === 0 ? resolve(out) : reject(new Error(`${cmd} exited ${code}: ${err.slice(0, 500)}`))
|
|
42
|
-
)
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
24
|
/**
|
|
47
25
|
* Download `url` to `dest` unless a VALID copy is already there. Returns `true` when the cache was reused, `false` when
|
|
48
26
|
* a download happened.
|
package/sdk/fetch.ts
CHANGED
|
@@ -25,13 +25,11 @@
|
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
import { spawn } from "node:child_process"
|
|
28
|
-
import {
|
|
29
|
-
import { mkdir, rename } from "node:fs/promises"
|
|
28
|
+
import { mkdir } from "node:fs/promises"
|
|
30
29
|
import { dirname, join } from "node:path"
|
|
31
30
|
import { DatabaseSync } from "node:sqlite"
|
|
32
|
-
import { Readable } from "node:stream"
|
|
33
|
-
import { pipeline } from "node:stream/promises"
|
|
34
31
|
|
|
32
|
+
import { APIClient, pluckResponseData } from "@mailwoman/core/api"
|
|
35
33
|
import { extractZipEntries } from "@mailwoman/core/fs/zip"
|
|
36
34
|
import { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
37
35
|
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
@@ -176,10 +174,11 @@ function buildRow(level: TIGERFetchLevel, p: Record<string, unknown>, geometry:
|
|
|
176
174
|
* Scrape the ADDRFEAT directory listing for a state's county FIPS codes.
|
|
177
175
|
*/
|
|
178
176
|
async function discoverCounties(state: string, vintage: number): Promise<string[]> {
|
|
179
|
-
|
|
177
|
+
// The LISTING only — a small HTML index. The per-county archives below stay on raw `fetch`, streaming to disk.
|
|
178
|
+
const html = await new APIClient({ displayName: "tiger-listing", retry: true })
|
|
179
|
+
.fetch<string>({ url: `${CENSUS_HOST}/geo/tiger/TIGER${vintage}/ADDRFEAT/`, responseType: "text" })
|
|
180
|
+
.then(pluckResponseData)
|
|
180
181
|
|
|
181
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} listing ADDRFEAT for vintage ${vintage}`)
|
|
182
|
-
const html = await res.text()
|
|
183
182
|
const re = new RegExp(`tl_${vintage}_${state}(\\d{3})_addrfeat\\.zip`, "g")
|
|
184
183
|
const counties = new Set<string>()
|
|
185
184
|
|
|
@@ -222,6 +221,11 @@ export async function* fetchTIGER(options: FetchTIGEROptions): AsyncGenerator<Fe
|
|
|
222
221
|
const kdb = new DatabaseClient<TIGERDatabase>({ database: db })
|
|
223
222
|
await initializeTIGERSchema(kdb)
|
|
224
223
|
|
|
224
|
+
/**
|
|
225
|
+
* `level` picks the table AND the row shape, but nothing ties them at the type level: {@link buildRow} returns the
|
|
226
|
+
* union and Kysely needs the arm's concrete row. The narrowings below are sound only because `buildRow` switches on
|
|
227
|
+
* the SAME `level` — keep the two switches in step, or a row shape reaches the wrong table.
|
|
228
|
+
*/
|
|
225
229
|
const insertBatch = async (rows: Row[]): Promise<void> => {
|
|
226
230
|
if (level === "tabblock20") {
|
|
227
231
|
await kdb
|
package/sdk/redistricting.ts
CHANGED
|
@@ -16,13 +16,9 @@
|
|
|
16
16
|
* https://www2.census.gov/programs-surveys/decennial/2020/data/01-Redistricting_File--PL_94-171/
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
import { createWriteStream, existsSync } from "node:fs"
|
|
21
|
-
import { mkdir, rename } from "node:fs/promises"
|
|
19
|
+
import { mkdir } from "node:fs/promises"
|
|
22
20
|
import { dirname, join } from "node:path"
|
|
23
21
|
import { DatabaseSync } from "node:sqlite"
|
|
24
|
-
import { Readable } from "node:stream"
|
|
25
|
-
import { pipeline } from "node:stream/promises"
|
|
26
22
|
|
|
27
23
|
import { extractZipEntries } from "@mailwoman/core/fs/zip"
|
|
28
24
|
import { DatabaseClient } from "@mailwoman/core/kysley/client"
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { GeometryLiteral, MultiPolygonLiteral } from "@mailwoman/spatial"
|
|
8
8
|
import { wellKnownGeometryToGeoJSON } from "@mailwoman/spatial/sdk"
|
|
9
|
+
|
|
9
10
|
import type {
|
|
10
11
|
FIPSStateCode,
|
|
11
12
|
FIPSBlockGeoID,
|
|
@@ -13,7 +14,7 @@ import type {
|
|
|
13
14
|
TIGERBlockFeature,
|
|
14
15
|
TIGERBlockFeatureCollection,
|
|
15
16
|
TIGERTabulatedBlockProperties,
|
|
16
|
-
} from "
|
|
17
|
+
} from "#index"
|
|
17
18
|
|
|
18
19
|
export interface StateBlockIntersectionCriteria {
|
|
19
20
|
stateCode: FIPSStateCode
|
package/tools/index.ts
CHANGED
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
* @license AGPL-3.0
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*
|
|
6
|
-
* TIGER operator
|
|
7
|
-
* `race-dots-map` (see the 2026-07-09 scripts→Pastel spec). No argv, no `process.exit`: commands
|
|
8
|
-
* own parsing, rendering, and exit codes. Heavy deps (`@turf/boolean-contains`,
|
|
9
|
-
* `@protomaps/basemaps`) are lazy-imported inside their entry fns. NOT `sdk/` — that submodule
|
|
10
|
-
* means data acquisition.
|
|
6
|
+
* TIGER operator tool exports.
|
|
11
7
|
*/
|
|
12
8
|
|
|
13
9
|
export * from "./race-dots.ts"
|
package/tools/race-dots-map.ts
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
import { writeFileSync } from "node:fs"
|
|
21
21
|
|
|
22
|
+
import { tempRootPath } from "@mailwoman/core/utils"
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* Options for {@linkcode raceDotsMap}.
|
|
24
26
|
*/
|
|
@@ -104,7 +106,7 @@ export async function raceDotsMap(
|
|
|
104
106
|
report?: (line: string) => void
|
|
105
107
|
): Promise<RaceDotsMapResult> {
|
|
106
108
|
const PMTILES_URL = options.pmtilesURL || "http://localhost:8899/race-dots-oc.pmtiles"
|
|
107
|
-
const OUT = options.out || "
|
|
109
|
+
const OUT = options.out || tempRootPath("race-dots-oc.html")
|
|
108
110
|
const PER = options.per ?? 5
|
|
109
111
|
const PER_PHRASE = PER === 1 ? "one dot is one person" : `one dot ≈ ${PER} people`
|
|
110
112
|
const TITLE = options.title || `Race in Orange County, CA — ${PER_PHRASE} (2020 Census)`
|
package/tools/race-dots.ts
CHANGED
|
@@ -25,7 +25,7 @@ import { createWriteStream } from "node:fs"
|
|
|
25
25
|
import { DatabaseSync } from "node:sqlite"
|
|
26
26
|
|
|
27
27
|
import { tryParsingJSON } from "@mailwoman/core/objects"
|
|
28
|
-
import { dataRootPath } from "@mailwoman/core/utils"
|
|
28
|
+
import { dataRootPath, tempRootPath } from "@mailwoman/core/utils"
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Attempts to place a dot inside its polygon by rejection sampling before giving up on it.
|
|
@@ -112,7 +112,7 @@ export async function raceDots(
|
|
|
112
112
|
report?: (line: string) => void
|
|
113
113
|
): Promise<RaceDotsResult> {
|
|
114
114
|
const DB = options.db || dataRootPath("tiger", "tiger-oc.db")
|
|
115
|
-
const OUT = options.out || "
|
|
115
|
+
const OUT = options.out || tempRootPath("race-dots.ndjson")
|
|
116
116
|
const PER = options.per ?? 10 // people represented by one dot
|
|
117
117
|
const LAYER = options.layer || "dots"
|
|
118
118
|
|
package/tools/serve-range.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { createReadStream, statSync, type Stats } from "node:fs"
|
|
|
15
15
|
import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http"
|
|
16
16
|
import { extname, join, normalize } from "node:path"
|
|
17
17
|
|
|
18
|
+
import { mailwomanTempRoot } from "@mailwoman/core/utils"
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* Options for {@linkcode serveWithRangeSupport}.
|
|
20
22
|
*/
|
|
@@ -55,7 +57,7 @@ export function serveWithRangeSupport(
|
|
|
55
57
|
options: ServeRangeOptions = {},
|
|
56
58
|
report?: (line: string) => void
|
|
57
59
|
): Promise<RangeServer> {
|
|
58
|
-
const dir = options.dir ||
|
|
60
|
+
const dir = options.dir || mailwomanTempRoot()
|
|
59
61
|
const port = options.port ?? 8899
|
|
60
62
|
|
|
61
63
|
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|