@mailwoman/tiger 8.0.0 → 8.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/constants.ts +12 -12
- package/county.ts +5 -2
- package/files.ts +4 -1
- package/geoid.ts +11 -9
- package/out/constants.d.ts +5 -5
- package/out/constants.d.ts.map +1 -1
- package/out/constants.js +0 -5
- package/out/constants.js.map +1 -1
- package/out/county.d.ts +5 -2
- package/out/county.d.ts.map +1 -1
- package/out/county.js +3 -2
- package/out/county.js.map +1 -1
- package/out/files.d.ts +1 -1
- package/out/files.d.ts.map +1 -1
- package/out/files.js +0 -1
- package/out/files.js.map +1 -1
- package/out/geoid.d.ts +14 -16
- package/out/geoid.d.ts.map +1 -1
- package/out/geoid.js +1 -2
- package/out/geoid.js.map +1 -1
- package/out/sdk/fetch.d.ts +18 -6
- package/out/sdk/fetch.d.ts.map +1 -1
- package/out/sdk/fetch.js +11 -5
- package/out/sdk/fetch.js.map +1 -1
- package/out/sdk/redistricting.d.ts +18 -6
- package/out/sdk/redistricting.d.ts.map +1 -1
- package/out/sdk/redistricting.js +12 -6
- package/out/sdk/redistricting.js.map +1 -1
- package/out/sdk/schema.d.ts +14 -7
- package/out/sdk/schema.d.ts.map +1 -1
- package/out/sdk/schema.js +4 -0
- package/out/sdk/schema.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/TIGERTractEntity.js +1 -1
- package/out/sdk/state/TIGERTractEntity.js.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 +0 -1
- package/out/sdk/state/block-operations.js.map +1 -1
- package/out/state.d.ts +343 -113
- package/out/state.d.ts.map +1 -1
- package/out/state.js +174 -57
- package/out/state.js.map +1 -1
- package/out/tabulation-block.d.ts +3 -3
- package/out/tabulation-block.d.ts.map +1 -1
- package/out/tabulation-block.js +1 -3
- package/out/tabulation-block.js.map +1 -1
- package/out/tools/race-dots-map.d.ts +30 -10
- package/out/tools/race-dots-map.d.ts.map +1 -1
- package/out/tools/race-dots-map.js +6 -2
- package/out/tools/race-dots-map.js.map +1 -1
- package/out/tools/race-dots.d.ts +24 -8
- package/out/tools/race-dots.d.ts.map +1 -1
- package/out/tools/race-dots.js +15 -5
- package/out/tools/race-dots.js.map +1 -1
- package/out/tools/serve-range.d.ts +12 -4
- package/out/tools/serve-range.d.ts.map +1 -1
- package/out/tools/serve-range.js.map +1 -1
- package/out/tract.d.ts +2 -2
- package/out/tract.d.ts.map +1 -1
- package/out/tract.js +1 -2
- package/out/tract.js.map +1 -1
- package/out/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/sdk/fetch.ts +36 -11
- package/sdk/redistricting.ts +39 -12
- package/sdk/schema.ts +21 -7
- package/sdk/state/TIGERTractEntity.ts +1 -1
- package/sdk/state/block-operations.ts +7 -6
- package/state.ts +175 -57
- package/tabulation-block.ts +3 -3
- package/tools/race-dots-map.ts +35 -11
- package/tools/race-dots.ts +52 -12
- package/tools/serve-range.ts +16 -4
- package/tract.ts +2 -2
package/state.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { GeoFeature, MultiPolygonLiteral } from "@mailwoman/spatial"
|
|
8
8
|
|
|
9
|
-
import { GeoIDPart } from "./geoid.ts"
|
|
9
|
+
import type { GeoIDPart } from "./geoid.ts"
|
|
10
10
|
import type { TIGERTabulatedBlockProperties } from "./tabulation-block.ts"
|
|
11
11
|
|
|
12
12
|
//#region FIPS Codes
|
|
@@ -21,157 +21,259 @@ import type { TIGERTabulatedBlockProperties } from "./tabulation-block.ts"
|
|
|
21
21
|
* @see {@linkcode AdminLevel1Code} for a combined set of states and territories.
|
|
22
22
|
*/
|
|
23
23
|
export const FIPSStateCode = {
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* @title Alabama
|
|
26
|
+
*/
|
|
25
27
|
AL: "01",
|
|
26
28
|
|
|
27
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* @title Alaska
|
|
31
|
+
*/
|
|
28
32
|
AK: "02",
|
|
29
33
|
|
|
30
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* @title Arizona
|
|
36
|
+
*/
|
|
31
37
|
AZ: "04",
|
|
32
38
|
|
|
33
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* @title Arkansas
|
|
41
|
+
*/
|
|
34
42
|
AR: "05",
|
|
35
43
|
|
|
36
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* @title California
|
|
46
|
+
*/
|
|
37
47
|
CA: "06",
|
|
38
48
|
|
|
39
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* @title Colorado
|
|
51
|
+
*/
|
|
40
52
|
CO: "08",
|
|
41
53
|
|
|
42
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* @title Connecticut
|
|
56
|
+
*/
|
|
43
57
|
CT: "09",
|
|
44
58
|
|
|
45
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* @title Delaware
|
|
61
|
+
*/
|
|
46
62
|
DE: "10",
|
|
47
63
|
|
|
48
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* @title District Of Columbia
|
|
66
|
+
*/
|
|
49
67
|
DC: "11",
|
|
50
68
|
|
|
51
|
-
/**
|
|
69
|
+
/**
|
|
70
|
+
* @title Florida
|
|
71
|
+
*/
|
|
52
72
|
FL: "12",
|
|
53
73
|
|
|
54
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* @title Georgia
|
|
76
|
+
*/
|
|
55
77
|
GA: "13",
|
|
56
78
|
|
|
57
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* @title Hawaii
|
|
81
|
+
*/
|
|
58
82
|
HI: "15",
|
|
59
83
|
|
|
60
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* @title Idaho
|
|
86
|
+
*/
|
|
61
87
|
ID: "16",
|
|
62
88
|
|
|
63
|
-
/**
|
|
89
|
+
/**
|
|
90
|
+
* @title Illinois
|
|
91
|
+
*/
|
|
64
92
|
IL: "17",
|
|
65
93
|
|
|
66
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* @title Indiana
|
|
96
|
+
*/
|
|
67
97
|
IN: "18",
|
|
68
98
|
|
|
69
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* @title Iowa
|
|
101
|
+
*/
|
|
70
102
|
IA: "19",
|
|
71
103
|
|
|
72
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* @title Kansas
|
|
106
|
+
*/
|
|
73
107
|
KS: "20",
|
|
74
108
|
|
|
75
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* @title Kentucky
|
|
111
|
+
*/
|
|
76
112
|
KY: "21",
|
|
77
113
|
|
|
78
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* @title Louisiana
|
|
116
|
+
*/
|
|
79
117
|
LA: "22",
|
|
80
118
|
|
|
81
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* @title Maine
|
|
121
|
+
*/
|
|
82
122
|
ME: "23",
|
|
83
123
|
|
|
84
|
-
/**
|
|
124
|
+
/**
|
|
125
|
+
* @title Maryland
|
|
126
|
+
*/
|
|
85
127
|
MD: "24",
|
|
86
128
|
|
|
87
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* @title Massachusetts
|
|
131
|
+
*/
|
|
88
132
|
MA: "25",
|
|
89
133
|
|
|
90
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* @title Michigan
|
|
136
|
+
*/
|
|
91
137
|
MI: "26",
|
|
92
138
|
|
|
93
|
-
/**
|
|
139
|
+
/**
|
|
140
|
+
* @title Minnesota
|
|
141
|
+
*/
|
|
94
142
|
MN: "27",
|
|
95
143
|
|
|
96
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* @title Mississippi
|
|
146
|
+
*/
|
|
97
147
|
MS: "28",
|
|
98
148
|
|
|
99
|
-
/**
|
|
149
|
+
/**
|
|
150
|
+
* @title Missouri
|
|
151
|
+
*/
|
|
100
152
|
MO: "29",
|
|
101
153
|
|
|
102
|
-
/**
|
|
154
|
+
/**
|
|
155
|
+
* @title Montana
|
|
156
|
+
*/
|
|
103
157
|
MT: "30",
|
|
104
158
|
|
|
105
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* @title Nebraska
|
|
161
|
+
*/
|
|
106
162
|
NE: "31",
|
|
107
163
|
|
|
108
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* @title Nevada
|
|
166
|
+
*/
|
|
109
167
|
NV: "32",
|
|
110
168
|
|
|
111
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* @title New Hampshire
|
|
171
|
+
*/
|
|
112
172
|
NH: "33",
|
|
113
173
|
|
|
114
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* @title New Jersey
|
|
176
|
+
*/
|
|
115
177
|
NJ: "34",
|
|
116
178
|
|
|
117
|
-
/**
|
|
179
|
+
/**
|
|
180
|
+
* @title New Mexico
|
|
181
|
+
*/
|
|
118
182
|
NM: "35",
|
|
119
183
|
|
|
120
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* @title New York
|
|
186
|
+
*/
|
|
121
187
|
NY: "36",
|
|
122
188
|
|
|
123
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* @title North Carolina
|
|
191
|
+
*/
|
|
124
192
|
NC: "37",
|
|
125
193
|
|
|
126
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* @title North Dakota
|
|
196
|
+
*/
|
|
127
197
|
ND: "38",
|
|
128
198
|
|
|
129
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* @title Ohio
|
|
201
|
+
*/
|
|
130
202
|
OH: "39",
|
|
131
203
|
|
|
132
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* @title Oklahoma
|
|
206
|
+
*/
|
|
133
207
|
OK: "40",
|
|
134
208
|
|
|
135
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* @title Oregon
|
|
211
|
+
*/
|
|
136
212
|
OR: "41",
|
|
137
213
|
|
|
138
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* @title Pennsylvania
|
|
216
|
+
*/
|
|
139
217
|
PA: "42",
|
|
140
218
|
|
|
141
|
-
/**
|
|
219
|
+
/**
|
|
220
|
+
* @title Rhode Island
|
|
221
|
+
*/
|
|
142
222
|
RI: "44",
|
|
143
223
|
|
|
144
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* @title South Carolina
|
|
226
|
+
*/
|
|
145
227
|
SC: "45",
|
|
146
228
|
|
|
147
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* @title South Dakota
|
|
231
|
+
*/
|
|
148
232
|
SD: "46",
|
|
149
233
|
|
|
150
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* @title Tennessee
|
|
236
|
+
*/
|
|
151
237
|
TN: "47",
|
|
152
238
|
|
|
153
|
-
/**
|
|
239
|
+
/**
|
|
240
|
+
* @title Texas
|
|
241
|
+
*/
|
|
154
242
|
TX: "48",
|
|
155
243
|
|
|
156
|
-
/**
|
|
244
|
+
/**
|
|
245
|
+
* @title Utah
|
|
246
|
+
*/
|
|
157
247
|
UT: "49",
|
|
158
248
|
|
|
159
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* @title Vermont
|
|
251
|
+
*/
|
|
160
252
|
VT: "50",
|
|
161
253
|
|
|
162
|
-
/**
|
|
254
|
+
/**
|
|
255
|
+
* @title Virginia
|
|
256
|
+
*/
|
|
163
257
|
VA: "51",
|
|
164
258
|
|
|
165
|
-
/**
|
|
259
|
+
/**
|
|
260
|
+
* @title Washington
|
|
261
|
+
*/
|
|
166
262
|
WA: "53",
|
|
167
263
|
|
|
168
|
-
/**
|
|
264
|
+
/**
|
|
265
|
+
* @title West Virginia
|
|
266
|
+
*/
|
|
169
267
|
WV: "54",
|
|
170
268
|
|
|
171
|
-
/**
|
|
269
|
+
/**
|
|
270
|
+
* @title Wisconsin
|
|
271
|
+
*/
|
|
172
272
|
WI: "55",
|
|
173
273
|
|
|
174
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* @title Wyoming
|
|
276
|
+
*/
|
|
175
277
|
WY: "56",
|
|
176
278
|
} as const
|
|
177
279
|
|
|
@@ -194,19 +296,29 @@ export const FIPSTerritoryCode = {
|
|
|
194
296
|
*/
|
|
195
297
|
JA: "74",
|
|
196
298
|
|
|
197
|
-
/**
|
|
299
|
+
/**
|
|
300
|
+
* @title American Samoa
|
|
301
|
+
*/
|
|
198
302
|
AS: "60",
|
|
199
303
|
|
|
200
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* @title Guam
|
|
306
|
+
*/
|
|
201
307
|
GU: "66",
|
|
202
308
|
|
|
203
|
-
/**
|
|
309
|
+
/**
|
|
310
|
+
* @title Northern Mariana Islands
|
|
311
|
+
*/
|
|
204
312
|
MP: "69",
|
|
205
313
|
|
|
206
|
-
/**
|
|
314
|
+
/**
|
|
315
|
+
* @title Puerto Rico
|
|
316
|
+
*/
|
|
207
317
|
PR: "72",
|
|
208
318
|
|
|
209
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* @title Virgin Islands
|
|
321
|
+
*/
|
|
210
322
|
VI: "78",
|
|
211
323
|
} as const
|
|
212
324
|
|
|
@@ -344,6 +456,9 @@ export const StateAbbreviation = {
|
|
|
344
456
|
|
|
345
457
|
export type StateAbbreviation = (typeof StateAbbreviation)[keyof typeof StateAbbreviation]
|
|
346
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Postal abbreviations for US territories, kept apart from the states so callers can include or exclude them.
|
|
461
|
+
*/
|
|
347
462
|
export const TerritoryAbbreviation = {
|
|
348
463
|
"American Samoa": "AS",
|
|
349
464
|
"Johnston Atoll": "JA",
|
|
@@ -364,6 +479,9 @@ export type TerritoryAbbreviation = (typeof TerritoryAbbreviation)[keyof typeof
|
|
|
364
479
|
*/
|
|
365
480
|
export type AdminLevel1Abbreviation = StateAbbreviation | TerritoryAbbreviation
|
|
366
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Every admin-level-1 postal abbreviation — the fifty states, DC, and the territories together.
|
|
484
|
+
*/
|
|
367
485
|
export const AdminLevel1Abbreviation = {
|
|
368
486
|
...StateAbbreviation,
|
|
369
487
|
...TerritoryAbbreviation,
|
package/tabulation-block.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @author Teffen Ellis, et al.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import type { MultiPolygonLiteral } from "@mailwoman/spatial"
|
|
8
8
|
|
|
9
|
-
import { TIGERProperty,
|
|
10
|
-
import { GeoIDPart,
|
|
9
|
+
import type { TIGERProperty, TIGERPropertyRecord } from "./constants.ts"
|
|
10
|
+
import type { GeoIDPart, FIPSBlockGeoID, ParsedGeoIDBlockLevel } from "./geoid.ts"
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The properties of a tabulated block from the TIGER/Line dataset.
|
package/tools/race-dots-map.ts
CHANGED
|
@@ -19,25 +19,43 @@
|
|
|
19
19
|
|
|
20
20
|
import { writeFileSync } from "node:fs"
|
|
21
21
|
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@linkcode raceDotsMap}.
|
|
24
|
+
*/
|
|
23
25
|
export interface RaceDotsMapOptions {
|
|
24
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Dots tileset URL the page reads client-side. Default `http://localhost:8899/race-dots-oc.pmtiles`.
|
|
28
|
+
*/
|
|
25
29
|
pmtilesURL?: string
|
|
26
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Output HTML path. Default `/tmp/race-dots-oc.html`.
|
|
32
|
+
*/
|
|
27
33
|
out?: string
|
|
28
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* People represented by one dot (title/legend copy only). Default 5.
|
|
36
|
+
*/
|
|
29
37
|
per?: number
|
|
30
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Page title. Default derives from `per`.
|
|
40
|
+
*/
|
|
31
41
|
title?: string
|
|
32
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Initial map center longitude. Default -117.83 (Orange County, CA).
|
|
44
|
+
*/
|
|
33
45
|
lng?: number
|
|
34
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Initial map center latitude. Default 33.68.
|
|
48
|
+
*/
|
|
35
49
|
lat?: number
|
|
36
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Initial map zoom. Default 9.4.
|
|
52
|
+
*/
|
|
37
53
|
zoom?: number
|
|
38
54
|
}
|
|
39
55
|
|
|
40
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Result of {@linkcode raceDotsMap}.
|
|
58
|
+
*/
|
|
41
59
|
export interface RaceDotsMapResult {
|
|
42
60
|
outPath: string
|
|
43
61
|
pmtilesURL: string
|
|
@@ -53,7 +71,9 @@ const BASEMAP_TILEJSON_URL = "https://tiles.sister.software/basemap-v4.json"
|
|
|
53
71
|
const GLYPHS_URL = "https://protomaps.github.io/basemaps-assets/fonts/{fontstack}/{range}.pbf"
|
|
54
72
|
const SPRITE_URL = "https://protomaps.github.io/basemaps-assets/sprites/v4/light"
|
|
55
73
|
|
|
56
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Cooper Center "Racial Dot Map" palette, by `pl_block`/P2 category.
|
|
76
|
+
*/
|
|
57
77
|
const CATEGORY_COLOR: Record<string, string> = {
|
|
58
78
|
white: "#1f78b4", // blue
|
|
59
79
|
black: "#33a02c", // green
|
|
@@ -64,6 +84,7 @@ const CATEGORY_COLOR: Record<string, string> = {
|
|
|
64
84
|
other: "#b15928", // brown
|
|
65
85
|
multi: "#9e9e9e", // grey
|
|
66
86
|
}
|
|
87
|
+
|
|
67
88
|
const CATEGORY_LABEL: Record<string, string> = {
|
|
68
89
|
white: "White (non-Hispanic)",
|
|
69
90
|
black: "Black (non-Hispanic)",
|
|
@@ -75,7 +96,9 @@ const CATEGORY_LABEL: Record<string, string> = {
|
|
|
75
96
|
multi: "Two or more races",
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Race-dots MapLibre page writer — see the module doc.
|
|
101
|
+
*/
|
|
79
102
|
export async function raceDotsMap(
|
|
80
103
|
options: RaceDotsMapOptions = {},
|
|
81
104
|
report?: (line: string) => void
|
|
@@ -97,6 +120,7 @@ export async function raceDotsMap(
|
|
|
97
120
|
for (const [cat, color] of Object.entries(CATEGORY_COLOR)) {
|
|
98
121
|
colorMatch.push(cat, color)
|
|
99
122
|
}
|
|
123
|
+
|
|
100
124
|
colorMatch.push("#9e9e9e")
|
|
101
125
|
|
|
102
126
|
const style = {
|
package/tools/race-dots.ts
CHANGED
|
@@ -26,32 +26,54 @@ import { DatabaseSync } from "node:sqlite"
|
|
|
26
26
|
|
|
27
27
|
import { dataRootPath } from "@mailwoman/core/utils"
|
|
28
28
|
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Attempts to place a dot inside its polygon by rejection sampling before giving up on it.
|
|
31
|
+
*/
|
|
32
|
+
const MAX_PLACEMENT_TRIES = 60
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options for {@linkcode raceDots}.
|
|
36
|
+
*/
|
|
30
37
|
export interface RaceDotsOptions {
|
|
31
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* TIGER SQLite DB (`tabblock20` ⋈ `pl_block`). Default `$MAILWOMAN_DATA_ROOT/tiger/tiger-oc.db`.
|
|
40
|
+
*/
|
|
32
41
|
db?: string
|
|
33
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Output NDJSON path. Default `/tmp/race-dots.ndjson`.
|
|
44
|
+
*/
|
|
34
45
|
out?: string
|
|
35
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* People represented by one dot. Default 10.
|
|
48
|
+
*/
|
|
36
49
|
per?: number
|
|
37
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Tippecanoe layer name. Default `dots`.
|
|
52
|
+
*/
|
|
38
53
|
layer?: string
|
|
39
54
|
}
|
|
40
55
|
|
|
41
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Result of {@linkcode raceDots}.
|
|
58
|
+
*/
|
|
42
59
|
export interface RaceDotsResult {
|
|
43
60
|
outPath: string
|
|
44
61
|
dots: number
|
|
45
62
|
skipped: number
|
|
46
63
|
blocks: number
|
|
47
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Dots emitted per P2 category.
|
|
66
|
+
*/
|
|
48
67
|
totals: Record<string, number>
|
|
49
68
|
}
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The eight P2 categories (columns in pl_block) that partition each block's population.
|
|
72
|
+
*/
|
|
52
73
|
const CATEGORIES = ["hispanic", "white", "black", "asian", "aian", "nhpi", "other", "multi"] as const
|
|
53
74
|
|
|
54
75
|
type Ring = number[][]
|
|
76
|
+
|
|
55
77
|
type PolygonCoords = Ring[]
|
|
56
78
|
|
|
57
79
|
function bbox(rings: PolygonCoords): [number, number, number, number] {
|
|
@@ -81,7 +103,9 @@ function bbox(rings: PolygonCoords): [number, number, number, number] {
|
|
|
81
103
|
return [minX, minY, maxX, maxY]
|
|
82
104
|
}
|
|
83
105
|
|
|
84
|
-
/**
|
|
106
|
+
/**
|
|
107
|
+
* Race-by-dot-density NDJSON builder — see the module doc.
|
|
108
|
+
*/
|
|
85
109
|
export async function raceDots(
|
|
86
110
|
options: RaceDotsOptions = {},
|
|
87
111
|
report?: (line: string) => void
|
|
@@ -103,15 +127,18 @@ export async function raceDots(
|
|
|
103
127
|
while (pick < polys.length - 1 && (r -= areas[pick]!) > 0) {
|
|
104
128
|
pick++
|
|
105
129
|
}
|
|
130
|
+
|
|
106
131
|
const poly = polys[pick]!
|
|
132
|
+
|
|
107
133
|
const polyFeature = {
|
|
108
134
|
type: "Feature" as const,
|
|
109
135
|
geometry: { type: "Polygon" as const, coordinates: poly },
|
|
110
136
|
properties: {},
|
|
111
137
|
}
|
|
138
|
+
|
|
112
139
|
const [minX, minY, maxX, maxY] = bbox(poly)
|
|
113
140
|
|
|
114
|
-
for (let tries = 0; tries <
|
|
141
|
+
for (let tries = 0; tries < MAX_PLACEMENT_TRIES; tries++) {
|
|
115
142
|
const x = minX + Math.random() * (maxX - minX)
|
|
116
143
|
const y = minY + Math.random() * (maxY - minY)
|
|
117
144
|
const pt = { type: "Feature" as const, geometry: { type: "Point" as const, coordinates: [x, y] }, properties: {} }
|
|
@@ -123,6 +150,7 @@ export async function raceDots(
|
|
|
123
150
|
}
|
|
124
151
|
|
|
125
152
|
const db = new DatabaseSync(DB, { readOnly: true })
|
|
153
|
+
|
|
126
154
|
const rows = db
|
|
127
155
|
.prepare(
|
|
128
156
|
`SELECT b.geometry AS geometry, ${CATEGORIES.map((c) => `p.${c} AS ${c}`).join(", ")}
|
|
@@ -130,10 +158,12 @@ export async function raceDots(
|
|
|
130
158
|
WHERE p.pop_total > 0`
|
|
131
159
|
)
|
|
132
160
|
.all() as Array<{ geometry: string } & Record<(typeof CATEGORIES)[number], number>>
|
|
161
|
+
|
|
133
162
|
db.close()
|
|
134
163
|
|
|
135
164
|
const out = createWriteStream(OUT)
|
|
136
165
|
const totals = new Map<string, number>()
|
|
166
|
+
|
|
137
167
|
let dots = 0,
|
|
138
168
|
skipped = 0
|
|
139
169
|
|
|
@@ -145,13 +175,16 @@ export async function raceDots(
|
|
|
145
175
|
} catch {
|
|
146
176
|
continue
|
|
147
177
|
}
|
|
178
|
+
|
|
148
179
|
const polys: PolygonCoords[] =
|
|
149
180
|
geom.type === "Polygon" ? [geom.coordinates as PolygonCoords] : (geom.coordinates as PolygonCoords[])
|
|
181
|
+
|
|
150
182
|
const areas = polys.map((p) => {
|
|
151
183
|
const [a, b, c, d] = bbox(p)
|
|
152
184
|
|
|
153
185
|
return Math.max((c - a) * (d - b), 1e-12)
|
|
154
186
|
})
|
|
187
|
+
|
|
155
188
|
const totalArea = areas.reduce((s, a) => s + a, 0)
|
|
156
189
|
|
|
157
190
|
for (const cat of CATEGORIES) {
|
|
@@ -166,8 +199,10 @@ export async function raceDots(
|
|
|
166
199
|
|
|
167
200
|
if (!pt) {
|
|
168
201
|
skipped++
|
|
202
|
+
|
|
169
203
|
continue
|
|
170
204
|
}
|
|
205
|
+
|
|
171
206
|
out.write(
|
|
172
207
|
JSON.stringify({
|
|
173
208
|
type: "Feature",
|
|
@@ -176,6 +211,7 @@ export async function raceDots(
|
|
|
176
211
|
geometry: { type: "Point", coordinates: [Math.round(pt[0] * 1e5) / 1e5, Math.round(pt[1] * 1e5) / 1e5] },
|
|
177
212
|
}) + "\n"
|
|
178
213
|
)
|
|
214
|
+
|
|
179
215
|
dots++
|
|
180
216
|
totals.set(cat, (totals.get(cat) ?? 0) + 1)
|
|
181
217
|
}
|
|
@@ -183,10 +219,14 @@ export async function raceDots(
|
|
|
183
219
|
}
|
|
184
220
|
|
|
185
221
|
out.end()
|
|
186
|
-
|
|
222
|
+
|
|
223
|
+
await new Promise<void>((resolve) => {
|
|
224
|
+
out.on("finish", () => resolve())
|
|
225
|
+
})
|
|
226
|
+
|
|
187
227
|
report?.(`[done] ${dots} dots from ${rows.length} blocks (1 dot ≈ ${PER} people); ${skipped} skipped`)
|
|
188
228
|
|
|
189
|
-
for (const [cat, n] of [...totals.entries()].
|
|
229
|
+
for (const [cat, n] of [...totals.entries()].toSorted((a, b) => b[1] - a[1])) {
|
|
190
230
|
report?.(` ${n.toString().padStart(7)} ${cat}`)
|
|
191
231
|
}
|
|
192
232
|
|
package/tools/serve-range.ts
CHANGED
|
@@ -15,15 +15,23 @@ 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
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Options for {@linkcode serveWithRangeSupport}.
|
|
20
|
+
*/
|
|
19
21
|
export interface ServeRangeOptions {
|
|
20
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Directory to serve. Default `/tmp`.
|
|
24
|
+
*/
|
|
21
25
|
dir?: string
|
|
22
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Port to listen on. Default 8899.
|
|
28
|
+
*/
|
|
23
29
|
port?: number
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* A running range-capable static server, resolved once listening.
|
|
34
|
+
*/
|
|
27
35
|
export interface RangeServer {
|
|
28
36
|
dir: string
|
|
29
37
|
port: number
|
|
@@ -62,6 +70,7 @@ export function serveWithRangeSupport(
|
|
|
62
70
|
|
|
63
71
|
return res.end("not found")
|
|
64
72
|
}
|
|
73
|
+
|
|
65
74
|
const type = TYPES[extname(path)] || "application/octet-stream"
|
|
66
75
|
const base = { "Content-Type": type, "Accept-Ranges": "bytes", "Access-Control-Allow-Origin": "*" }
|
|
67
76
|
const range = req.headers.range
|
|
@@ -70,11 +79,13 @@ export function serveWithRangeSupport(
|
|
|
70
79
|
if (m) {
|
|
71
80
|
const start = Number(m[1])
|
|
72
81
|
const end = m[2] ? Number(m[2]) : st.size - 1
|
|
82
|
+
|
|
73
83
|
res.writeHead(206, {
|
|
74
84
|
...base,
|
|
75
85
|
"Content-Range": `bytes ${start}-${end}/${st.size}`,
|
|
76
86
|
"Content-Length": end - start + 1,
|
|
77
87
|
})
|
|
88
|
+
|
|
78
89
|
createReadStream(path, { start, end }).pipe(res)
|
|
79
90
|
} else {
|
|
80
91
|
res.writeHead(200, { ...base, "Content-Length": st.size })
|
|
@@ -84,6 +95,7 @@ export function serveWithRangeSupport(
|
|
|
84
95
|
|
|
85
96
|
return new Promise<RangeServer>((resolve, reject) => {
|
|
86
97
|
server.once("error", reject)
|
|
98
|
+
|
|
87
99
|
server.listen(port, () => {
|
|
88
100
|
report?.(`range server: ${dir} on http://localhost:${port}`)
|
|
89
101
|
resolve({ dir, port, server, close: () => server.close() })
|