@mailwoman/tiger 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +3 -0
  2. package/block-group.ts +151 -0
  3. package/class-code.ts +1026 -0
  4. package/constants.ts +252 -0
  5. package/county-subdivision.ts +154 -0
  6. package/county.ts +135 -0
  7. package/files.ts +251 -0
  8. package/functional-status.ts +64 -0
  9. package/geoid.ts +678 -0
  10. package/index.ts +18 -0
  11. package/legal-statistical-area.ts +222 -0
  12. package/out/block-group.d.ts +135 -0
  13. package/out/block-group.d.ts.map +1 -0
  14. package/out/block-group.js +7 -0
  15. package/out/block-group.js.map +1 -0
  16. package/out/class-code.d.ts +863 -0
  17. package/out/class-code.d.ts.map +1 -0
  18. package/out/class-code.js +864 -0
  19. package/out/class-code.js.map +1 -0
  20. package/out/constants.d.ts +220 -0
  21. package/out/constants.d.ts.map +1 -0
  22. package/out/constants.js +98 -0
  23. package/out/constants.js.map +1 -0
  24. package/out/county-subdivision.d.ts +139 -0
  25. package/out/county-subdivision.d.ts.map +1 -0
  26. package/out/county-subdivision.js +7 -0
  27. package/out/county-subdivision.js.map +1 -0
  28. package/out/county.d.ts +120 -0
  29. package/out/county.d.ts.map +1 -0
  30. package/out/county.js +9 -0
  31. package/out/county.js.map +1 -0
  32. package/out/files.d.ts +118 -0
  33. package/out/files.d.ts.map +1 -0
  34. package/out/files.js +142 -0
  35. package/out/files.js.map +1 -0
  36. package/out/functional-status.d.ts +64 -0
  37. package/out/functional-status.d.ts.map +1 -0
  38. package/out/functional-status.js +65 -0
  39. package/out/functional-status.js.map +1 -0
  40. package/out/geoid.d.ts +438 -0
  41. package/out/geoid.d.ts.map +1 -0
  42. package/out/geoid.js +255 -0
  43. package/out/geoid.js.map +1 -0
  44. package/out/index.d.ts +18 -0
  45. package/out/index.d.ts.map +1 -0
  46. package/out/index.js +18 -0
  47. package/out/index.js.map +1 -0
  48. package/out/legal-statistical-area.d.ts +222 -0
  49. package/out/legal-statistical-area.d.ts.map +1 -0
  50. package/out/legal-statistical-area.js +223 -0
  51. package/out/legal-statistical-area.js.map +1 -0
  52. package/out/sdk/fetch.d.ts.map +1 -0
  53. package/out/sdk/fetch.js.map +1 -0
  54. package/out/sdk/index.d.ts +7 -0
  55. package/out/sdk/index.d.ts.map +1 -0
  56. package/out/sdk/index.js +7 -0
  57. package/out/sdk/index.js.map +1 -0
  58. package/out/sdk/redistricting.d.ts.map +1 -0
  59. package/out/sdk/redistricting.js.map +1 -0
  60. package/out/sdk/schema.d.ts.map +1 -0
  61. package/out/sdk/schema.js.map +1 -0
  62. package/out/sdk/state/TIGERTabulatedBlockEntity.d.ts +13 -0
  63. package/out/sdk/state/TIGERTabulatedBlockEntity.d.ts.map +1 -0
  64. package/out/sdk/state/TIGERTabulatedBlockEntity.js +22 -0
  65. package/out/sdk/state/TIGERTabulatedBlockEntity.js.map +1 -0
  66. package/out/sdk/state/TIGERTractEntity.d.ts +31 -0
  67. package/out/sdk/state/TIGERTractEntity.d.ts.map +1 -0
  68. package/out/sdk/state/TIGERTractEntity.js +7 -0
  69. package/out/sdk/state/TIGERTractEntity.js.map +1 -0
  70. package/out/sdk/state/block-operations.d.ts +36 -0
  71. package/out/sdk/state/block-operations.d.ts.map +1 -0
  72. package/out/sdk/state/block-operations.js +37 -0
  73. package/out/sdk/state/block-operations.js.map +1 -0
  74. package/out/sdk/state/index.d.ts +9 -0
  75. package/out/sdk/state/index.d.ts.map +1 -0
  76. package/out/sdk/state/index.js +9 -0
  77. package/out/sdk/state/index.js.map +1 -0
  78. package/out/state.d.ts +595 -0
  79. package/out/state.d.ts.map +1 -0
  80. package/out/state.js +384 -0
  81. package/out/state.js.map +1 -0
  82. package/out/tabulation-block.d.ts +43 -0
  83. package/out/tabulation-block.d.ts.map +1 -0
  84. package/out/tabulation-block.js +9 -0
  85. package/out/tabulation-block.js.map +1 -0
  86. package/out/tract.d.ts +36 -0
  87. package/out/tract.d.ts.map +1 -0
  88. package/out/tract.js +8 -0
  89. package/out/tract.js.map +1 -0
  90. package/out/tsconfig.tsbuildinfo +1 -0
  91. package/package.json +41 -0
  92. package/sdk/data/nation/index.json +14642 -0
  93. package/sdk/index.ts +7 -0
  94. package/sdk/initialize.sql +161 -0
  95. package/sdk/state/TIGERTabulatedBlockEntity.ts +25 -0
  96. package/sdk/state/TIGERTractEntity.ts +34 -0
  97. package/sdk/state/block-operations.ts +69 -0
  98. package/sdk/state/index.ts +9 -0
  99. package/state.ts +553 -0
  100. package/tabulation-block.ts +68 -0
  101. package/tract.ts +52 -0
  102. package/tsconfig.json +16 -0
  103. package/typedoc.json +5 -0
package/geoid.ts ADDED
@@ -0,0 +1,678 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ import type { Tagged } from "type-fest"
8
+ import { AdminLevel1Code, FIPSStateCode } from "./state.js"
9
+
10
+ //#region Tagged FIPS types
11
+
12
+ /**
13
+ * A 4-digit number assigned by the Census to uniquely identify a block.
14
+ *
15
+ * ```txt
16
+ * (State Code)━━┳┓ ┏┳┳┳┳┳━━(Tract Code)
17
+ * 482012231001050
18
+ * ┗╋┛ ┗┻┻┻━(Block Code)
19
+ * (County Code)
20
+ * ```
21
+ *
22
+ * @category Census
23
+ * @category FIPS
24
+ * @type {string}
25
+ * @see {@linkcode FIPSBlockGroupCode} for a less specific level.
26
+ * @see {@linkcode FIPSBlockGeoID} for the full GeoID.
27
+ * @pattern ^\d{4}$
28
+ * @minLength 4
29
+ * @maxLength 4
30
+ * @title FIPS Block Code
31
+ */
32
+ export type FIPSBlockCode = Tagged<string, "FIPSBlockCode">
33
+
34
+ /**
35
+ * A 15-digit number assigned by the Census to uniquely identify a block.
36
+ *
37
+ * ```txt
38
+ * (State Code)━━┳┓ ┏┳┳┳┳┳━━(Tract Code)
39
+ * 482012231001050
40
+ * ┗╋┛ ┗┻┻┻━(Block Code)
41
+ * (County Code)
42
+ * ```
43
+ *
44
+ * @category Census
45
+ * @category FIPS
46
+ * @type {string}
47
+ * @see {@linkcode FIPSBlockCode} for the 4-digit version.
48
+ * @pattern ^\d{15}$
49
+ * @minLength 15
50
+ * @maxLength 15
51
+ * @title FIPS Block Geo ID
52
+ */
53
+ export type FIPSBlockGeoID = Tagged<string, "FIPSBlockGeoID">
54
+
55
+ /**
56
+ * A 1-digit number assigned by the Census to uniquely identify a block group.
57
+ *
58
+ * ```txt
59
+ * (State Code)━━┳┓ ┏┳┳┳┳┳━━(Tract Code)
60
+ * 060133740002###
61
+ * ┗╋┛ ┗━(Block Group Code)
62
+ * (County Code)
63
+ * ```
64
+ *
65
+ * @category Census
66
+ * @category FIPS
67
+ * @type {string}
68
+ * @see {@linkcode FIPSBlockCode} for a more specific level.
69
+ * @see {@linkcode FIPSTractCode} for a less specific level.
70
+ * @pattern ^\d{1}$
71
+ * @minLength 1
72
+ * @maxLength 1
73
+ * @title FIPS Block Group Code
74
+ */
75
+ export type FIPSBlockGroupCode = Tagged<string, "FIPSBlockGroupCode">
76
+
77
+ /**
78
+ * A 6-digit number assigned by the Census to uniquely identify a tract.
79
+ *
80
+ * ```txt
81
+ * (State Code)━━┳┓ ┏┳┳┳┳┳━━(Tract Code)
82
+ * 06013374000####
83
+ * ┗╋┛
84
+ * (County Code)
85
+ * ```
86
+ *
87
+ * @category Census
88
+ * @category FIPS
89
+ * @type {string}
90
+ * @see {@linkcode FIPSBlockGroupCode} for a more specific level.
91
+ * @see {@linkcode FIPSCountySubDivisionCode} for a less specific level.
92
+ * @pattern ^\d{6}$
93
+ * @minLength 6
94
+ * @maxLength 6
95
+ * @title FIPS Tract Code
96
+ */
97
+ export type FIPSTractCode = Tagged<string, "FIPSTractCode">
98
+
99
+ /**
100
+ * An 11-digit number assigned by the Census to uniquely identify a tract.
101
+ *
102
+ * ```txt
103
+ * (State Code)━━┳┓ ┏┳┳┳┳┳━━(Tract Code)
104
+ * 06013374000####
105
+ * ┗╋┛
106
+ * (County Code)
107
+ * ```
108
+ *
109
+ * @category Census
110
+ * @category FIPS
111
+ * @type {string}
112
+ * @see {@linkcode FIPSBlockCode} for the 4-digit version.
113
+ * @pattern ^\d{6}$
114
+ * @minLength 6
115
+ * @maxLength 6
116
+ * @title FIPS Tract Geo ID
117
+ */
118
+ export type FIPSTractGeoID = Tagged<string, "FIPSTractGeoID">
119
+
120
+ /**
121
+ * A 5-digit number assigned by the Census to uniquely identify a county sub-division.
122
+ *
123
+ * ```txt
124
+ * (State Code)━━┳┓ ┏┳┳┳┳━━(County Sub-Division Code)
125
+ * 0601337400#####
126
+ * ┗╋┛
127
+ * (County Code)
128
+ * ```
129
+ *
130
+ * @category Census
131
+ * @category FIPS
132
+ * @type {string}
133
+ * @see {@linkcode FIPSTractCode} for a more specific level.
134
+ * @see {@linkcode FIPSCountyCode} for a less specific level.
135
+ * @pattern ^\d{5}$
136
+ * @minLength 5
137
+ * @maxLength 5
138
+ * @title FIPS County Sub-Division Code
139
+ */
140
+ export type FIPSCountySubDivisionCode = Tagged<string, "FIPSCountySubDivisionCode">
141
+
142
+ /**
143
+ * A 3-digit number assigned by the Census to uniquely identify a county.
144
+ *
145
+ * ```txt
146
+ * (State Code)━━┳┓
147
+ * 06013##########
148
+ * ┗┻┻━(County Code)
149
+ * ```
150
+ *
151
+ * @category Census
152
+ * @category FIPS
153
+ * @type {string}
154
+ * @see {@linkcode FIPSCountySubDivisionCode} for a more specific level.
155
+ * @see {@linkcode AdminLevel1Code} for a less specific level.
156
+ * @pattern ^\d{3}$
157
+ * @minLength 3
158
+ * @maxLength 3
159
+ * @title FIPS County Code
160
+ */
161
+ export type FIPSCountyCode = Tagged<string, "FIPSCountyCode">
162
+
163
+ /**
164
+ * A 5-digit number assigned by the Census to uniquely identify a place.
165
+ *
166
+ * ```txt
167
+ * (State Code)━━┳┓
168
+ * 4835000########
169
+ * ┗┻┻┻┻━(Place Code)
170
+ * ```
171
+ *
172
+ * @category Census
173
+ * @category FIPS
174
+ * @type {string}
175
+ * @minLength 5
176
+ * @maxLength 5
177
+ * @pattern ^\d{5}$
178
+ * @title FIPS Place Code
179
+ */
180
+ export type FIPSPlaceCode = Tagged<string, "FIPSPlaceCode">
181
+
182
+ /**
183
+ * A 2-digit number assigned by the Census to uniquely identify a congressional district.
184
+ *
185
+ * ```txt
186
+ * (State Code)━━┳┓
187
+ * 0902···········
188
+ * ┗┻━(Congressional District Code)
189
+ * ```
190
+ *
191
+ * @category Census
192
+ * @category FIPS
193
+ * @type {string}
194
+ * @see {@linkcode AdminLevel1Code} for a less specific level.
195
+ * @see {@linkcode FIPSPlaceCode} for a more specific level.
196
+ * @pattern ^\d{2}$
197
+ * @minLength 2
198
+ * @maxLength 2
199
+ * @title FIPS Congressional District Code
200
+ */
201
+ export type FIPSCongressionalDistrictCode = Tagged<string, "FIPSConressionalDistrictCode">
202
+
203
+ //#endregion
204
+
205
+ //#region GeoID Parsing Setup
206
+
207
+ /**
208
+ * A part of a GeoID to it's respective name.
209
+ *
210
+ * @internal
211
+ */
212
+ export enum GeoIDPart {
213
+ /**
214
+ * The state code part of a GeoID.
215
+ *
216
+ * Sometimes considered the administrative area level 1 code.
217
+ */
218
+ State = "state_code",
219
+ /**
220
+ * The county code part of a GeoID.
221
+ *
222
+ * Sometimes considered the administrative area level 2 code.
223
+ */
224
+ County = "county_code",
225
+ /**
226
+ * The county subdivision code part of a GeoID.
227
+ *
228
+ * Sometimes considered the administrative area level 3 code.
229
+ */
230
+ CountySubDivision = "county_sub_division_code",
231
+ /**
232
+ * The congressional district code part of a GeoID.
233
+ */
234
+ CongressionalDistrict = "congressional_district_code",
235
+ /**
236
+ * A place as defined by the Census, such as a city or town.
237
+ */
238
+ Place = "place_code",
239
+ /**
240
+ * The tract code part of a GeoID. The third most granular part.
241
+ */
242
+ Tract = "tract_code",
243
+ /**
244
+ * The block group code part of a GeoID. The second most granular part.
245
+ */
246
+ BlockGroup = "block_group_code",
247
+ /**
248
+ * The block code part of a GeoID. The most granular part.
249
+ */
250
+ Block = "block_code",
251
+ }
252
+
253
+ /**
254
+ * Mapping of GeoID parts to their respective FIPS codes types.
255
+ *
256
+ * @internal
257
+ */
258
+ export type GeoIDPartMapping = {
259
+ [GeoIDPart.State]: FIPSStateCode
260
+ [GeoIDPart.County]: FIPSCountyCode
261
+ [GeoIDPart.CountySubDivision]: FIPSCountySubDivisionCode
262
+ [GeoIDPart.CongressionalDistrict]: FIPSCongressionalDistrictCode
263
+ [GeoIDPart.Place]: FIPSPlaceCode
264
+ [GeoIDPart.Tract]: FIPSTractCode
265
+ [GeoIDPart.BlockGroup]: FIPSBlockGroupCode
266
+ [GeoIDPart.Block]: FIPSBlockCode
267
+ }
268
+
269
+ /**
270
+ * Record of GeoID parts and their respective lengths.
271
+ *
272
+ * Note that the length of a GeoID is the sum of the lengths of its parts.
273
+ *
274
+ * @internal
275
+ */
276
+ export const GeoIDPartLength = {
277
+ [GeoIDPart.State]: 2,
278
+ [GeoIDPart.County]: 3,
279
+ [GeoIDPart.CountySubDivision]: 5,
280
+ [GeoIDPart.CongressionalDistrict]: 2,
281
+ [GeoIDPart.Place]: 5,
282
+ [GeoIDPart.Tract]: 6,
283
+ [GeoIDPart.BlockGroup]: 1,
284
+ [GeoIDPart.Block]: 4,
285
+ } as const satisfies Record<GeoIDPart, number>
286
+
287
+ /**
288
+ * A GeoID parsed to the block level. The most granular level.
289
+ *
290
+ * @title Parsed GeoID Block Level
291
+ * @public
292
+ */
293
+ export interface ParsedGeoIDBlockLevel {
294
+ [GeoIDPart.State]: FIPSStateCode
295
+ [GeoIDPart.County]: FIPSCountyCode
296
+ [GeoIDPart.CountySubDivision]: FIPSCountySubDivisionCode
297
+ [GeoIDPart.Tract]: FIPSTractCode
298
+ [GeoIDPart.Place]: FIPSPlaceCode
299
+ [GeoIDPart.BlockGroup]: FIPSBlockGroupCode
300
+ [GeoIDPart.Block]: FIPSBlockCode
301
+ [GeoIDPart.BlockGroup]: FIPSBlockGroupCode
302
+ [GeoIDPart.CongressionalDistrict]: FIPSCongressionalDistrictCode
303
+ }
304
+
305
+ /**
306
+ * A GeoID parsed to the block group level. The second most granular level.
307
+ *
308
+ * @internal
309
+ */
310
+ export type ParsedGeoIDBlockGroupLevel = {
311
+ [GeoIDPart.State]: AdminLevel1Code
312
+ [GeoIDPart.County]: FIPSCountyCode
313
+ [GeoIDPart.CountySubDivision]: FIPSCountySubDivisionCode
314
+ [GeoIDPart.Tract]: FIPSTractCode
315
+ [GeoIDPart.BlockGroup]: FIPSBlockGroupCode
316
+ [GeoIDPart.Block]: undefined
317
+ }
318
+
319
+ /**
320
+ * A GeoID parsed to the tract level. The third most granular level.
321
+ *
322
+ * @internal
323
+ */
324
+ export type ParsedGeoIDTractLevel = {
325
+ [GeoIDPart.State]: AdminLevel1Code
326
+ [GeoIDPart.County]: FIPSCountyCode
327
+ [GeoIDPart.CountySubDivision]: FIPSCountySubDivisionCode
328
+ [GeoIDPart.Tract]: FIPSTractCode
329
+ [GeoIDPart.BlockGroup]: undefined
330
+ [GeoIDPart.Block]: undefined
331
+ }
332
+
333
+ /**
334
+ * A GeoID parsed to a partial level.
335
+ *
336
+ * @internal
337
+ */
338
+ export type ParsedGeoIDPartial = { [P in GeoIDPart]?: string }
339
+
340
+ /**
341
+ * A GeoID parsed to the county-subdivision level. The fourth most granular level.
342
+ *
343
+ * @internal
344
+ */
345
+ export type ParsedGeoIDCountySubDivisionLevel = {
346
+ [GeoIDPart.State]: AdminLevel1Code
347
+ [GeoIDPart.County]: FIPSCountyCode
348
+ [GeoIDPart.CountySubDivision]: FIPSCountySubDivisionCode
349
+ [GeoIDPart.Tract]: undefined
350
+ [GeoIDPart.BlockGroup]: undefined
351
+ [GeoIDPart.Block]: undefined
352
+ }
353
+
354
+ /**
355
+ * A GeoID parsed to the county level. The fifth most granular level.
356
+ *
357
+ * @internal
358
+ */
359
+ export type ParsedGeoIDCountyLevel = {
360
+ [GeoIDPart.State]: AdminLevel1Code
361
+ [GeoIDPart.County]: FIPSCountyCode
362
+ [GeoIDPart.CountySubDivision]: undefined
363
+ [GeoIDPart.Tract]: undefined
364
+ [GeoIDPart.BlockGroup]: undefined
365
+ [GeoIDPart.Block]: undefined
366
+ }
367
+
368
+ /**
369
+ * A GeoID parsed to the state level. The least granular level.
370
+ *
371
+ * @internal
372
+ */
373
+ export type ParsedGeoIDStateLevel = {
374
+ [GeoIDPart.State]: AdminLevel1Code
375
+ [GeoIDPart.County]: undefined
376
+ [GeoIDPart.CountySubDivision]: undefined
377
+ [GeoIDPart.Tract]: undefined
378
+ [GeoIDPart.BlockGroup]: undefined
379
+ [GeoIDPart.Block]: undefined
380
+ }
381
+
382
+ /**
383
+ * A GeoID parsed to a specific level.
384
+ *
385
+ * @internal
386
+ */
387
+ export type ParsedGeoID =
388
+ | ParsedGeoIDBlockLevel
389
+ | ParsedGeoIDBlockGroupLevel
390
+ | ParsedGeoIDTractLevel
391
+ | ParsedGeoIDCountySubDivisionLevel
392
+ | ParsedGeoIDCountyLevel
393
+ | ParsedGeoIDStateLevel
394
+
395
+ export class GeoIDParsingError extends Error {
396
+ constructor(message: string) {
397
+ super(message)
398
+ this.name = "GeoIDParsingError"
399
+ }
400
+ }
401
+
402
+ //#endregion
403
+
404
+ //#region GeoID Input Matchers
405
+
406
+ export class GeoIDInputMatcher<T extends GeoIDPart[] = GeoIDPart[]> {
407
+ public pattern: RegExp
408
+
409
+ public components: T
410
+
411
+ /**
412
+ * The specific length of a valid GeoID input.
413
+ */
414
+ public length: number
415
+
416
+ constructor(...geoIDComponents: T) {
417
+ this.components = geoIDComponents
418
+ this.length = 0
419
+
420
+ const matchedGroups = this.components.map((component) => {
421
+ const groupLength = GeoIDPartLength[component]
422
+ this.length += groupLength
423
+
424
+ return `(\\d{${groupLength}})`
425
+ })
426
+
427
+ this.pattern = new RegExp("^" + matchedGroups.join(""))
428
+ }
429
+
430
+ public parse<R extends ParsedGeoIDPartial>(input: string) {
431
+ const match = input.match(this.pattern)
432
+
433
+ if (!match) return null
434
+
435
+ const result = {} as R
436
+
437
+ for (let i = 0; i < this.components.length; i++) {
438
+ result[this.components[i]!] = match[i + 1]!
439
+ }
440
+
441
+ return result as R
442
+ }
443
+
444
+ public test(input: string) {
445
+ return this.pattern.test(input)
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Record of GeoID input matchers, i.e. a pattern and parser for each grouping of GeoID components.
451
+ *
452
+ * @see {@linkcode GeoIDComponentMatchers} for the component-level matchers.
453
+ * @internal
454
+ */
455
+ export const GeoIDInputMatchers = {
456
+ [GeoIDPart.Block]: new GeoIDInputMatcher(
457
+ // ---
458
+ GeoIDPart.State,
459
+ GeoIDPart.County,
460
+ GeoIDPart.Tract,
461
+ GeoIDPart.Block
462
+ ),
463
+ [GeoIDPart.BlockGroup]: new GeoIDInputMatcher(
464
+ // ---
465
+ GeoIDPart.State,
466
+ GeoIDPart.County,
467
+ GeoIDPart.Tract,
468
+ GeoIDPart.BlockGroup
469
+ ),
470
+ [GeoIDPart.Tract]: new GeoIDInputMatcher(
471
+ // ---
472
+ GeoIDPart.State,
473
+ GeoIDPart.County,
474
+ GeoIDPart.Tract
475
+ ),
476
+ [GeoIDPart.Place]: new GeoIDInputMatcher(
477
+ // ---
478
+ GeoIDPart.State,
479
+ GeoIDPart.County,
480
+ GeoIDPart.Place
481
+ ),
482
+ [GeoIDPart.CountySubDivision]: new GeoIDInputMatcher(
483
+ // ---
484
+ GeoIDPart.State,
485
+ GeoIDPart.County,
486
+ GeoIDPart.CountySubDivision
487
+ ),
488
+
489
+ [GeoIDPart.County]: new GeoIDInputMatcher(
490
+ // ---
491
+ GeoIDPart.State,
492
+ GeoIDPart.County
493
+ ),
494
+ [GeoIDPart.CongressionalDistrict]: new GeoIDInputMatcher(
495
+ // ---
496
+ GeoIDPart.State,
497
+ GeoIDPart.CongressionalDistrict
498
+ ),
499
+ [GeoIDPart.State]: new GeoIDInputMatcher(GeoIDPart.State),
500
+ } as const satisfies Record<GeoIDPart, GeoIDInputMatcher>
501
+
502
+ /**
503
+ * A record of GeoID component matchers, i.e. a pattern and parser for each GeoID component.
504
+ *
505
+ * @see {@linkcode GeoIDInputMatchers} for the input-level matchers.
506
+ * @internal
507
+ */
508
+ const GeoIDComponentMatchers = Object.fromEntries(
509
+ Object.values(GeoIDPart).map((value) => {
510
+ return [value, new GeoIDInputMatcher(value)]
511
+ })
512
+ ) as unknown as Record<GeoIDPart, GeoIDInputMatcher>
513
+
514
+ /**
515
+ * Type-predicate for checking if a value appears to be a valid GeoID component.
516
+ */
517
+ export function isGeoIDComponent<T extends GeoIDPart>(component: T, input: unknown): input is GeoIDPartMapping[T] {
518
+ if (!input) return false
519
+ if (typeof input !== "string") return false
520
+
521
+ const matcher = GeoIDComponentMatchers[component]
522
+
523
+ return matcher.test(input)
524
+ }
525
+
526
+ //#endregion
527
+
528
+ //#region GeoID Parsing
529
+
530
+ /**
531
+ * GeoID input matchers sorted by length in descending order, such that the most specific matchers
532
+ * are first.
533
+ */
534
+ const OrderedGeoIDInputMatchers = Object.values(GeoIDInputMatchers).sort((a, b) => b.length - a.length)
535
+
536
+ /**
537
+ * Given a block GeoID, parse it into its components.
538
+ *
539
+ * @category Census
540
+ * @category FIPS
541
+ */
542
+ export function parseFIPSBlockGeoID(input: FIPSBlockGeoID): ParsedGeoIDBlockLevel {
543
+ const parsedBlock = GeoIDInputMatchers[GeoIDPart.Block].parse(input)!
544
+ const { congressional_district_code } = GeoIDInputMatchers[GeoIDPart.CongressionalDistrict].parse(input)!
545
+ const { county_sub_division_code } = GeoIDInputMatchers[GeoIDPart.CountySubDivision].parse(input)!
546
+
547
+ const block_group_code = parsedBlock.block_code!.slice(0, 1) as FIPSBlockGroupCode
548
+
549
+ const parsed: { [P in keyof ParsedGeoIDBlockLevel]?: string } = {
550
+ ...parsedBlock,
551
+ congressional_district_code,
552
+ county_sub_division_code,
553
+ block_group_code,
554
+ }
555
+
556
+ return parsed as ParsedGeoIDBlockLevel
557
+ }
558
+
559
+ /**
560
+ * Given a tract GeoID, parse it into its components.
561
+ *
562
+ * @category Census
563
+ * @category FIPS
564
+ */
565
+ export function parseFIPSTractGeoID(input: FIPSTractGeoID): ParsedGeoIDTractLevel {
566
+ const parsedTract = GeoIDInputMatchers[GeoIDPart.Tract].parse<ParsedGeoIDTractLevel>(input)!
567
+ const { county_sub_division_code } = GeoIDInputMatchers[GeoIDPart.CountySubDivision].parse(input)!
568
+
569
+ const parsed: { [P in keyof ParsedGeoIDTractLevel]?: string } = {
570
+ ...parsedTract,
571
+ [GeoIDPart.CountySubDivision]: county_sub_division_code!,
572
+ }
573
+
574
+ return parsed as ParsedGeoIDTractLevel
575
+ }
576
+
577
+ /**
578
+ * Given a GeoID, parse it into its components.
579
+ *
580
+ * @category Census
581
+ * @category FIPS
582
+ */
583
+ export function parseGeoID(input: FIPSBlockGeoID): ParsedGeoIDBlockLevel
584
+ export function parseGeoID(input: FIPSBlockGroupCode): ParsedGeoIDBlockGroupLevel
585
+ export function parseGeoID(input: FIPSTractCode): ParsedGeoIDTractLevel
586
+ export function parseGeoID(input: FIPSCountySubDivisionCode): ParsedGeoIDCountySubDivisionLevel
587
+ export function parseGeoID(input: FIPSCountyCode): ParsedGeoIDCountyLevel
588
+ export function parseGeoID(input: AdminLevel1Code): ParsedGeoIDStateLevel
589
+ export function parseGeoID(input: unknown): ParsedGeoID | null
590
+ export function parseGeoID(input: unknown): ParsedGeoID | null {
591
+ if (!input) return null
592
+
593
+ if (typeof input === "number") input = input.toString()
594
+ if (typeof input !== "string") return null
595
+
596
+ if (input.length === GeoIDInputMatchers[GeoIDPart.Block].length) {
597
+ return parseFIPSBlockGeoID(input as FIPSBlockGeoID)
598
+ }
599
+
600
+ if (input.length === GeoIDInputMatchers[GeoIDPart.Tract].length) {
601
+ return parseFIPSTractGeoID(input as FIPSTractGeoID)
602
+ }
603
+
604
+ for (const matcher of OrderedGeoIDInputMatchers) {
605
+ // Do we even have a chance of matching?
606
+ if (matcher.length !== input.length) continue
607
+
608
+ const match = matcher.parse(input) as ParsedGeoID | null
609
+
610
+ if (!match) continue
611
+
612
+ Object.assign(match, {
613
+ toJSON: () => match,
614
+ })
615
+
616
+ return match as ParsedGeoID
617
+ }
618
+
619
+ return null
620
+ }
621
+
622
+ //#endregion
623
+
624
+ //#region GeoID Formatting
625
+
626
+ /**
627
+ * Format a parsed block GeoID back into a string.
628
+ *
629
+ * @category Census
630
+ * @category FIPS
631
+ */
632
+ export function formatGeoID(input: ParsedGeoIDBlockLevel): FIPSBlockGeoID
633
+ /**
634
+ * Format a parsed GeoID back into a string.
635
+ *
636
+ * @category Census
637
+ * @category FIPS
638
+ */
639
+ export function formatGeoID(input: ParsedGeoIDPartial): string
640
+ export function formatGeoID(input: unknown): string | null {
641
+ if (!input || typeof input !== "object") return null
642
+
643
+ const {
644
+ // ---
645
+ block_code,
646
+ block_group_code,
647
+ tract_code,
648
+ state_code,
649
+ county_sub_division_code,
650
+ county_code,
651
+ } = input as ParsedGeoIDPartial
652
+
653
+ if (block_code) {
654
+ return `${state_code}${county_code}${tract_code}${block_code}`
655
+ }
656
+
657
+ if (block_group_code) {
658
+ return `${state_code}${county_code}${tract_code}${block_group_code}`
659
+ }
660
+
661
+ if (tract_code) {
662
+ return `${state_code}${county_code}${tract_code}`
663
+ }
664
+
665
+ if (county_sub_division_code) {
666
+ return `${state_code}${county_code}${county_sub_division_code}`
667
+ }
668
+
669
+ if (county_code) {
670
+ return `${state_code}${county_code}`
671
+ }
672
+
673
+ if (state_code) return state_code
674
+
675
+ return null
676
+ }
677
+
678
+ //#endregion
package/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ */
6
+
7
+ export * from "./block-group.js"
8
+ export * from "./class-code.js"
9
+ export * from "./constants.js"
10
+ export * from "./county-subdivision.js"
11
+ export * from "./county.js"
12
+ export * from "./files.js"
13
+ export * from "./functional-status.js"
14
+ export * from "./geoid.js"
15
+ export * from "./legal-statistical-area.js"
16
+ export * from "./state.js"
17
+ export * from "./tabulation-block.js"
18
+ export * from "./tract.js"