@mailwoman/tiger 1.0.0 → 4.13.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/sdk/schema.ts ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @copyright Sister Software.
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * TIGER SQLite schema, as a string so it loads in both `tsx` source mode and the compiled CLI
7
+ * (`tsc` doesn't copy `.sql` assets into `out/`). Geometry is GeoJSON text — NOT SpatiaLite. The
8
+ * prior SpatiaLite path (`load_extension` of a hardcoded macOS dylib + WKB `GEOM` columns) never
9
+ * loaded on Linux; plain text keeps the build dependency-free and `node:sqlite`-native (read it
10
+ * back with `JSON.parse`).
11
+ */
12
+
13
+ import { sql, type Generated, type Kysely } from "kysely"
14
+
15
+ /** Kysely row type for `tabblock20`. Geometry is a GeoJSON string. */
16
+ export interface TIGERBlockTable {
17
+ GEOID: string
18
+ state_code: string
19
+ county_code: string
20
+ tract_code: string
21
+ block_group_code: string
22
+ block_code: string
23
+ urbanized_area_code: string | null
24
+ urban_rural_code: string | null
25
+ housing_unit_count: number
26
+ land_area_sqm: number
27
+ water_area_sqm: number
28
+ population: number
29
+ geometry: string
30
+ }
31
+
32
+ /**
33
+ * Kysely row type for `pl_block` — Census 2020 P.L. 94-171 table P2 (Hispanic-or-Latino by race),
34
+ * one row per tabulation block, keyed on the same 15-char `GEOID` as {@link TIGERBlockTable}. The
35
+ * eight category columns partition `pop_total`.
36
+ */
37
+ export interface PLBlockTable {
38
+ GEOID: string
39
+ pop_total: number
40
+ hispanic: number
41
+ white: number
42
+ black: number
43
+ aian: number
44
+ asian: number
45
+ nhpi: number
46
+ other: number
47
+ multi: number
48
+ }
49
+
50
+ /** Kysely row type for `tiger_streets` (ADDRFEAT — named street segments + ZIPs, per county). */
51
+ export interface TIGERStreetTable {
52
+ linearid: string
53
+ fullname: string
54
+ zipl: string | null
55
+ zipr: string | null
56
+ statefp: string
57
+ }
58
+
59
+ /** Kysely row type for `tiger_places` (PLACE — incorporated/census places, per state). */
60
+ export interface TIGERPlaceTable {
61
+ geoid: string
62
+ name: string
63
+ statefp: string
64
+ lsad: string | null
65
+ namelsad: string | null
66
+ classfp: string | null
67
+ }
68
+
69
+ /** The TIGER database schema, for `new DatabaseClient<TIGERDatabase>(...)`. */
70
+ export interface TIGERDatabase {
71
+ tabblock20: TIGERBlockTable
72
+ pl_block: PLBlockTable
73
+ tiger_streets: TIGERStreetTable
74
+ tiger_places: TIGERPlaceTable
75
+ }
76
+
77
+ /** Marker so callers can opt into `Generated` columns later without importing kysely here. */
78
+ export type { Generated }
79
+
80
+ /**
81
+ * Build-tuning PRAGMAs, run raw before any table is created (`page_size`/`auto_vacuum` only take
82
+ * effect on an empty DB, and PRAGMA has no Kysely builder). The consumer execs this, then calls
83
+ * {@link initializeTIGERSchema} for the tables + indexes.
84
+ */
85
+ export const TIGER_PRAGMAS = /* sql */ `
86
+ PRAGMA auto_vacuum = INCREMENTAL;
87
+ PRAGMA page_size = 4096;
88
+ PRAGMA cache_size = 10000;
89
+ PRAGMA journal_mode = WAL;
90
+ `
91
+
92
+ /**
93
+ * Create the TIGER tables + indexes via the Kysely schema-builder (the house idiom). Idempotent
94
+ * (`IF NOT EXISTS`). Pass a {@link DatabaseClient} (or any `Kysely`) over the TIGER DB; run
95
+ * {@link TIGER_PRAGMAS} first. `us_state`/`tract` aren't in {@link TIGERDatabase} (created here but
96
+ * not queried via Kysely) — `createTable` takes any table name, so that's fine.
97
+ *
98
+ * The `text(N)` length hints in the prior raw DDL were documentary only (SQLite uses TEXT affinity
99
+ * regardless); the lengths live on the {@link TIGERBlockTable} interface instead.
100
+ */
101
+ export async function initializeTIGERSchema(db: Kysely<TIGERDatabase>): Promise<void> {
102
+ await db.schema
103
+ .createTable("us_state")
104
+ .ifNotExists()
105
+ .addColumn("state_code", "text", (c) => c.primaryKey().notNull())
106
+ .addColumn("abbreviation", "text", (c) => c.notNull())
107
+ .addColumn("display_name", "text", (c) => c.notNull())
108
+ .addColumn("geometry", "text", (c) => c.notNull())
109
+ .execute()
110
+
111
+ await db.schema
112
+ .createTable("tract")
113
+ .ifNotExists()
114
+ .addColumn("GEOID", "text", (c) => c.primaryKey().notNull())
115
+ .addColumn("state_code", "text", (c) => c.notNull())
116
+ .addColumn("county_code", "text", (c) => c.notNull())
117
+ .addColumn("tract_code", "text", (c) => c.notNull())
118
+ .addColumn("geometry", "text", (c) => c.notNull())
119
+ .execute()
120
+
121
+ await db.schema
122
+ .createTable("tabblock20")
123
+ .ifNotExists()
124
+ .addColumn("GEOID", "text", (c) => c.primaryKey().notNull())
125
+ .addColumn("state_code", "text", (c) => c.notNull())
126
+ .addColumn("county_code", "text", (c) => c.notNull())
127
+ .addColumn("tract_code", "text", (c) => c.notNull())
128
+ .addColumn("block_group_code", "text", (c) => c.notNull())
129
+ .addColumn("block_code", "text", (c) => c.notNull())
130
+ .addColumn("urbanized_area_code", "text")
131
+ .addColumn("urban_rural_code", "text")
132
+ .addColumn("housing_unit_count", "integer", (c) => c.notNull())
133
+ .addColumn("land_area_sqm", "integer", (c) => c.notNull())
134
+ .addColumn("water_area_sqm", "integer", (c) => c.notNull())
135
+ .addColumn("population", "integer", (c) => c.notNull())
136
+ .addColumn("geometry", "text", (c) => c.notNull())
137
+ .execute()
138
+
139
+ // No index on GEOID alone — it's the PRIMARY KEY, which already carries a unique index. The prior
140
+ // schema's idx_tabblock20_geoid duplicated that for nothing (double insert cost, double footprint).
141
+ await db.schema.createIndex("idx_tabblock20_state_code").ifNotExists().on("tabblock20").column("state_code").execute()
142
+ await db.schema
143
+ .createIndex("idx_tabblock20_state_county")
144
+ .ifNotExists()
145
+ .on("tabblock20")
146
+ .columns(["state_code", "county_code"])
147
+ .execute()
148
+ await db.schema
149
+ .createIndex("idx_tabblock20_state_county_tract")
150
+ .ifNotExists()
151
+ .on("tabblock20")
152
+ .columns(["state_code", "county_code", "tract_code"])
153
+ .execute()
154
+ await db.schema.createIndex("idx_tabblock20_population").ifNotExists().on("tabblock20").column("population").execute()
155
+
156
+ await db.schema
157
+ .createTable("pl_block")
158
+ .ifNotExists()
159
+ .addColumn("GEOID", "text", (c) => c.primaryKey().notNull())
160
+ .addColumn("pop_total", "integer", (c) => c.notNull())
161
+ .addColumn("hispanic", "integer", (c) => c.notNull())
162
+ .addColumn("white", "integer", (c) => c.notNull())
163
+ .addColumn("black", "integer", (c) => c.notNull())
164
+ .addColumn("aian", "integer", (c) => c.notNull())
165
+ .addColumn("asian", "integer", (c) => c.notNull())
166
+ .addColumn("nhpi", "integer", (c) => c.notNull())
167
+ .addColumn("other", "integer", (c) => c.notNull())
168
+ .addColumn("multi", "integer", (c) => c.notNull())
169
+ // pl_block is small (no geometry) and always probed by its GEOID PK (1:1 join to tabblock20), so
170
+ // cluster it WITHOUT ROWID — one B-tree probe per join, no separate rowid + PK-index pair.
171
+ .modifyEnd(sql`without rowid`)
172
+ .execute()
173
+
174
+ await db.schema
175
+ .createTable("tiger_streets")
176
+ .ifNotExists()
177
+ .addColumn("linearid", "text", (c) => c.notNull())
178
+ .addColumn("fullname", "text", (c) => c.notNull())
179
+ .addColumn("zipl", "text")
180
+ .addColumn("zipr", "text")
181
+ .addColumn("statefp", "text", (c) => c.notNull())
182
+ .execute()
183
+ await db.schema.createIndex("idx_tiger_streets_statefp").ifNotExists().on("tiger_streets").column("statefp").execute()
184
+ await db.schema
185
+ .createIndex("idx_tiger_streets_linearid")
186
+ .ifNotExists()
187
+ .on("tiger_streets")
188
+ .column("linearid")
189
+ .execute()
190
+
191
+ await db.schema
192
+ .createTable("tiger_places")
193
+ .ifNotExists()
194
+ .addColumn("geoid", "text", (c) => c.notNull())
195
+ .addColumn("name", "text", (c) => c.notNull())
196
+ .addColumn("statefp", "text", (c) => c.notNull())
197
+ .addColumn("lsad", "text")
198
+ .addColumn("namelsad", "text")
199
+ .addColumn("classfp", "text")
200
+ .execute()
201
+ await db.schema.createIndex("idx_tiger_places_statefp").ifNotExists().on("tiger_places").column("statefp").execute()
202
+ await db.schema.createIndex("idx_tiger_places_geoid").ifNotExists().on("tiger_places").column("geoid").execute()
203
+ }
@@ -1,161 +0,0 @@
1
- -- @copyright Sister Software.
2
- -- @license AGPL-3.0
3
- -- @author Teffen Ellis, et al.
4
- -- @file TIGER SQLite3 Database Initialization Script
5
-
6
- PRAGMA auto_vacuum = FULL;
7
- PRAGMA synchronous = FULL;
8
- PRAGMA locking_mode = EXCLUSIVE;
9
- PRAGMA page_size = 4096;
10
- PRAGMA cache_size = 10000;
11
-
12
- SELECT load_extension('/opt/homebrew/opt/libspatialite/lib/mod_spatialite.dylib');
13
- SELECT InitSpatialMetadata();
14
-
15
- --#region US States
16
-
17
- CREATE TABLE "us_state" (
18
- "state_code" text(2) PRIMARY KEY NOT NULL,
19
- "abbreviation" text NOT NULL,
20
- "display_name" text NOT NULL
21
- );
22
-
23
- SELECT AddGeometryColumn('us_state', 'GEOM', 4326, 'MultiPolygon', 2, 1);
24
- SELECT CreateSpatialIndex('us_state', 'GEOM');
25
-
26
-
27
- CREATE UNIQUE INDEX "idx_us_state_code" ON "us_state" (
28
- "state_code"
29
- );
30
-
31
- CREATE UNIQUE INDEX "idx_us_state_abbreviation" ON "us_state" (
32
- "abbreviation"
33
- );
34
-
35
- CREATE UNIQUE INDEX "idx_us_state_display_name" ON "us_state" (
36
- "display_name"
37
- );
38
-
39
- --#endregion
40
-
41
- --#region Tracts
42
-
43
- CREATE TABLE "tract" (
44
- "GEOID" text(11) PRIMARY KEY NOT NULL,
45
- "state_code" text(2) NOT NULL,
46
- "county_code" text(3) NOT NULL,
47
- "county_sub_division_code" text(5) NOT NULL,
48
- "tract_code" text(6) NOT NULL
49
- );
50
-
51
- SELECT AddGeometryColumn('tract', 'GEOM', 4326, 'MultiPolygon', 2, 1);
52
- SELECT CreateSpatialIndex('tract', 'GEOM');
53
-
54
- --#endregion
55
-
56
-
57
- --#region Tabulated Blocks
58
-
59
- CREATE TABLE "tabblock20" (
60
- "GEOID" text(15) PRIMARY KEY NOT NULL,
61
- "state_code" text(2) NOT NULL,
62
- "county_code" text(3) NOT NULL,
63
- "county_sub_division_code" text(5) NOT NULL,
64
- "tract_code" text(6) NOT NULL,
65
- "block_group_code" text(1) NOT NULL,
66
- "block_code" text(4) NOT NULL,
67
- "urbanized_area_code" text(5),
68
- "urban_rural_code" text(1),
69
- "housing_unit_count" integer NOT NULL,
70
- "land_area_sqm" integer NOT NULL,
71
- "water_area_sqm" integer NOT NULL,
72
- "population" integer NOT NULL
73
- );
74
-
75
- SELECT AddGeometryColumn('tabblock20', 'GEOM', 4326, 'MultiPolygon', 2, 1);
76
- SELECT CreateSpatialIndex('tabblock20', 'GEOM');
77
-
78
-
79
- CREATE UNIQUE INDEX "idx_tabblock20_geoid" ON "tabblock20" (
80
- "GEOID"
81
- );
82
-
83
- CREATE INDEX "idx_tabblock20_state_code" ON "tabblock20" (
84
- "state_code"
85
- );
86
-
87
- CREATE INDEX "idx_tabblock20_county_code" ON "tabblock20" (
88
- "county_code"
89
- );
90
-
91
- CREATE INDEX "idx_tabblock20_county_sub_division_code" ON "tabblock20" (
92
- "county_sub_division_code"
93
- );
94
-
95
- CREATE INDEX "idx_tabblock20_tract_code" ON "tabblock20" (
96
- "tract_code"
97
- );
98
-
99
- CREATE INDEX "idx_tabblock20_block_group_code" ON "tabblock20" (
100
- "block_group_code"
101
- );
102
-
103
- CREATE INDEX "idx_tabblock20_block_code" ON "tabblock20" (
104
- "block_code"
105
- );
106
-
107
- CREATE INDEX "idx_tabblock20_urbanized_area_code" ON "tabblock20" (
108
- "urbanized_area_code"
109
- );
110
-
111
- CREATE INDEX "idx_tabblock20_urban_rural_code" ON "tabblock20" (
112
- "urban_rural_code"
113
- );
114
-
115
- CREATE INDEX "idx_tabblock20_housing_unit_count" ON "tabblock20" (
116
- "housing_unit_count"
117
- );
118
-
119
- CREATE INDEX "idx_tabblock20_land_area_sqm" ON "tabblock20" (
120
- "land_area_sqm"
121
- );
122
-
123
- CREATE INDEX "idx_tabblock20_water_area_sqm" ON "tabblock20" (
124
- "water_area_sqm"
125
- );
126
-
127
- CREATE INDEX "idx_tabblock20_population" ON "tabblock20" (
128
- "population"
129
- );
130
-
131
- CREATE INDEX "idx_tabblock20_state_code_county_code" ON "tabblock20" (
132
- "state_code",
133
- "county_code"
134
- );
135
-
136
- CREATE INDEX "idx_tabblock20_state_code_county_code_tract_code" ON "tabblock20" (
137
- "state_code",
138
- "county_code",
139
- "tract_code"
140
- );
141
-
142
- CREATE INDEX "idx_tabblock20_state_code_county_code_tract_code_block_group_code" ON "tabblock20" (
143
- "state_code",
144
- "county_code",
145
- "tract_code",
146
- "block_group_code"
147
- );
148
-
149
- CREATE INDEX "idx_tabblock20_state_code_county_code_tract_code_block_group_code_block_code" ON "tabblock20" (
150
- "state_code",
151
- "county_code",
152
- "tract_code",
153
- "block_group_code",
154
- "block_code"
155
- );
156
-
157
- --#endregion
158
-
159
-
160
-
161
-