@dra2020/dra-types 1.4.9 → 1.5.11
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/dist/all.d.ts +3 -0
- package/dist/csv.d.ts +40 -0
- package/dist/dra-types.d.ts +0 -58
- package/dist/dra-types.js +289 -80
- package/dist/dra-types.js.map +1 -1
- package/dist/stats.d.ts +45 -0
- package/dist/vfeature.d.ts +20 -0
- package/lib/all.ts +3 -0
- package/lib/bucketmap.ts +3 -0
- package/lib/csv.ts +515 -0
- package/lib/dra-types.ts +0 -592
- package/lib/schemas.ts +3 -0
- package/lib/stats.ts +203 -0
- package/lib/vfeature.ts +105 -0
- package/package.json +4 -4
package/lib/csv.ts
ADDED
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// Public libraries
|
|
2
|
+
import * as Util from '@dra2020/util';
|
|
3
|
+
|
|
4
|
+
// Local library
|
|
5
|
+
import * as VF from './vfeature';
|
|
6
|
+
|
|
7
|
+
// Used internally to index into District Properties Array
|
|
8
|
+
export type BlockMap = { [id: string]: number };
|
|
9
|
+
|
|
10
|
+
// Used more generically and allows string districtIDs
|
|
11
|
+
export type BlockMapping = { [id: string]: string };
|
|
12
|
+
|
|
13
|
+
let reNumeric = /^(\D*)(\d*)(\D*)$/;
|
|
14
|
+
let reDistrictNumber = /^\d+$/;
|
|
15
|
+
let reDistrictNumeric = /^\d/;
|
|
16
|
+
|
|
17
|
+
// Normalize any numeric part to have no padded leading zeros
|
|
18
|
+
export function canonicalDistrictID(districtID: string): string
|
|
19
|
+
{
|
|
20
|
+
let a = reNumeric.exec(districtID);
|
|
21
|
+
if (a && a.length == 4)
|
|
22
|
+
{
|
|
23
|
+
if (a[2].length > 0)
|
|
24
|
+
a[2] = String(Number(a[2]));
|
|
25
|
+
districtID = `${a[1]}${a[2]}${a[3]}`;
|
|
26
|
+
}
|
|
27
|
+
return districtID;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Normalize any numeric part to have four digits with padded leading zeros
|
|
31
|
+
// so alphabetic sorting will result in correct numeric sort for mixed alphanumber
|
|
32
|
+
// district labels.
|
|
33
|
+
export function canonicalSortingDistrictID(districtID: string): string
|
|
34
|
+
{
|
|
35
|
+
let a = reNumeric.exec(districtID);
|
|
36
|
+
if (a && a.length == 4)
|
|
37
|
+
{
|
|
38
|
+
let s = a[2];
|
|
39
|
+
if (s.length > 0)
|
|
40
|
+
{
|
|
41
|
+
switch (s.length)
|
|
42
|
+
{
|
|
43
|
+
case 1: s = `000${s}`; break;
|
|
44
|
+
case 2: s = `00${s}`; break;
|
|
45
|
+
case 3: s = `0${s}`; break;
|
|
46
|
+
}
|
|
47
|
+
a[2] = s;
|
|
48
|
+
}
|
|
49
|
+
districtID = `${a[1]}${a[2]}${a[3]}`;
|
|
50
|
+
}
|
|
51
|
+
return districtID;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Return numeric part of districtID (or -1 if there is none)
|
|
55
|
+
export function canonicalNumericFromDistrictID(districtID: string): number
|
|
56
|
+
{
|
|
57
|
+
let a = reNumeric.exec(districtID);
|
|
58
|
+
if (a && a.length == 4)
|
|
59
|
+
{
|
|
60
|
+
let s = a[2];
|
|
61
|
+
if (s.length > 0)
|
|
62
|
+
return Number(s);
|
|
63
|
+
}
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function canonicalDistrictIDFromNumber(districtID: string, n: number): string
|
|
68
|
+
{
|
|
69
|
+
let a = reNumeric.exec(districtID);
|
|
70
|
+
if (a && a.length == 4)
|
|
71
|
+
{
|
|
72
|
+
a[2] = String(n);
|
|
73
|
+
districtID = `${a[1]}${a[2]}${a[3]}`;
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
districtID = String(n);
|
|
77
|
+
return districtID;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Numbers start at 1
|
|
81
|
+
export type DistrictOrder = { [districtID: string]: number };
|
|
82
|
+
|
|
83
|
+
// If purely numeric districtIDs and we are missing some number of IDs less than
|
|
84
|
+
export function canonicalDistrictIDGapFill(keys: string[]): string[]
|
|
85
|
+
{
|
|
86
|
+
if (keys == null || keys.length == 0) return keys;
|
|
87
|
+
let nonNumeric = keys.find((s: string) => !reDistrictNumber.test(s)) !== undefined;
|
|
88
|
+
if (nonNumeric) return keys;
|
|
89
|
+
let max = Number(keys[keys.length-1]);
|
|
90
|
+
if (max == keys.length || (max - keys.length) > keys.length) return keys; // no gaps or too many gaps
|
|
91
|
+
|
|
92
|
+
// OK, finally going to fill some gaps
|
|
93
|
+
for (let i: number = 0; i < keys.length; i++)
|
|
94
|
+
{
|
|
95
|
+
let here = Number(keys[i]);
|
|
96
|
+
while (here > i+1)
|
|
97
|
+
{
|
|
98
|
+
keys.splice(i, 0, canonicalSortingDistrictID(String(i+1)));
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return keys;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function canonicalDistrictIDOrdering(order: DistrictOrder): DistrictOrder
|
|
107
|
+
{
|
|
108
|
+
let keys = Object.keys(order);
|
|
109
|
+
let i: number;
|
|
110
|
+
let a: any = [];
|
|
111
|
+
let template: string = undefined;
|
|
112
|
+
|
|
113
|
+
keys = keys.map((s: string) => canonicalSortingDistrictID(s));
|
|
114
|
+
keys = canonicalDistrictIDGapFill(keys);
|
|
115
|
+
keys.sort();
|
|
116
|
+
order = {};
|
|
117
|
+
for (i = 0; i < keys.length; i++)
|
|
118
|
+
order[canonicalDistrictID(keys[i])] = i+1;
|
|
119
|
+
|
|
120
|
+
// Remove water districts
|
|
121
|
+
if (order['ZZZ']) delete order['ZZZ'];
|
|
122
|
+
if (order['ZZ']) delete order['ZZ'];
|
|
123
|
+
|
|
124
|
+
return order;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface OneCSVLine
|
|
128
|
+
{
|
|
129
|
+
geoid: string;
|
|
130
|
+
districtID: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let reArray = [
|
|
134
|
+
/^(\d\d[^\s,"']*)[\s]*,[\s]*([^\s'"]+)[\s]*$/,
|
|
135
|
+
/^["'](\d\d[^"']*)["'][\s]*,[\s]*["']([^"']*)["'][\s]*$/,
|
|
136
|
+
/^(\d\d[^\s,]*)[\s]*,[\s]*["']([^"']*)["'][\s]*$/,
|
|
137
|
+
/^["'](\d\d[^"']*)["'][\s]*,[\s]*([^\s]+)[\s]*$/,
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
export function parseCSVLine(line: string): OneCSVLine
|
|
141
|
+
{
|
|
142
|
+
if (line == null || line == '') return null;
|
|
143
|
+
for (let i: number = 0; i < reArray.length; i++)
|
|
144
|
+
{
|
|
145
|
+
let a = reArray[i].exec(line);
|
|
146
|
+
if (a && a.length === 3)
|
|
147
|
+
return { geoid: a[1], districtID: a[2] };
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ConvertResult
|
|
153
|
+
{
|
|
154
|
+
inBlockMap: BlockMapping;
|
|
155
|
+
inStateMap: BlockMapping;
|
|
156
|
+
outValid: boolean;
|
|
157
|
+
outState: string;
|
|
158
|
+
outMap: BlockMapping;
|
|
159
|
+
outOrder: DistrictOrder;
|
|
160
|
+
outDistrictToSplit: VF.DistrictToSplitBlock;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function blockmapToState(blockMap: BlockMapping): string
|
|
164
|
+
{
|
|
165
|
+
for (var id in blockMap) if (blockMap.hasOwnProperty(id))
|
|
166
|
+
return geoidToState(id);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// blockToVTD:
|
|
171
|
+
// Take BlockMapping (simple map of GEOID to districtID) and a per-state map of block-level GEOID to VTD
|
|
172
|
+
// and return the output mapping of VTD to districtID, as well a data structure that describes any VTD's
|
|
173
|
+
// that need to be split between districtIDs. Also returns the DistrictOrder structure that defines the
|
|
174
|
+
// districtIDs that were used by the file.
|
|
175
|
+
//
|
|
176
|
+
// The state (as specified by the first two digits of the GEOID) is also determined. If the GEOID's do
|
|
177
|
+
// not all specify the same state, the mapping is considered invalid and the outValid flag is set to false.
|
|
178
|
+
//
|
|
179
|
+
|
|
180
|
+
export function blockmapToVTDmap(blockMap: BlockMapping, stateMap: BlockMapping): ConvertResult
|
|
181
|
+
{
|
|
182
|
+
let res: ConvertResult = {
|
|
183
|
+
inBlockMap: blockMap,
|
|
184
|
+
inStateMap: stateMap,
|
|
185
|
+
outValid: true,
|
|
186
|
+
outState: null,
|
|
187
|
+
outMap: {},
|
|
188
|
+
outOrder: {},
|
|
189
|
+
outDistrictToSplit: {}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
let bmGather: { [geoid: string]: { [district: string]: { [blockid: string]: boolean } } } = {};
|
|
193
|
+
let revMap: BlockMapping = {};
|
|
194
|
+
let id: string;
|
|
195
|
+
|
|
196
|
+
if (stateMap)
|
|
197
|
+
for (id in stateMap) if (stateMap.hasOwnProperty(id))
|
|
198
|
+
revMap[stateMap[id]] = null;
|
|
199
|
+
|
|
200
|
+
// First aggregate into features across all the blocks
|
|
201
|
+
for (id in blockMap) if (blockMap.hasOwnProperty(id))
|
|
202
|
+
{
|
|
203
|
+
let state = geoidToState(id);
|
|
204
|
+
if (res.outState == null)
|
|
205
|
+
res.outState = state;
|
|
206
|
+
else if (res.outState !== state)
|
|
207
|
+
{
|
|
208
|
+
res.outValid = false;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
let districtID: string = canonicalDistrictID(blockMap[id]);
|
|
213
|
+
|
|
214
|
+
// Just ignore ZZZ (water) blocks
|
|
215
|
+
if (districtID === 'ZZZ')
|
|
216
|
+
continue;
|
|
217
|
+
|
|
218
|
+
let n: number = id.length;
|
|
219
|
+
let geoid: string;
|
|
220
|
+
|
|
221
|
+
// Simple test for block id (vs. voting district or block group) id
|
|
222
|
+
if (n >= 15)
|
|
223
|
+
{
|
|
224
|
+
if (stateMap && stateMap[id] !== undefined)
|
|
225
|
+
geoid = stateMap[id];
|
|
226
|
+
else
|
|
227
|
+
{
|
|
228
|
+
geoid = id.substr(0, 12); // heuristic for mapping blockID to blockgroupID
|
|
229
|
+
if (revMap[geoid] === undefined)
|
|
230
|
+
{
|
|
231
|
+
res.outValid = false;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else
|
|
237
|
+
geoid = id;
|
|
238
|
+
|
|
239
|
+
if (res.outOrder[districtID] === undefined)
|
|
240
|
+
res.outOrder[districtID] = 0;
|
|
241
|
+
|
|
242
|
+
let districtToBlocks: { [districtID: string]: { [blockid: string]: boolean } } = bmGather[geoid];
|
|
243
|
+
if (districtToBlocks === undefined)
|
|
244
|
+
bmGather[geoid] = { [districtID]: { [id]: true } };
|
|
245
|
+
else
|
|
246
|
+
{
|
|
247
|
+
let thisDistrict: { [blockid: string]: boolean } = districtToBlocks[districtID];
|
|
248
|
+
if (thisDistrict === undefined)
|
|
249
|
+
{
|
|
250
|
+
thisDistrict = { };
|
|
251
|
+
districtToBlocks[districtID] = thisDistrict;
|
|
252
|
+
}
|
|
253
|
+
thisDistrict[id] = true;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Now determine actual mapping of blocks to features, looking for split features
|
|
258
|
+
for (let geoid in bmGather) if (bmGather.hasOwnProperty(geoid))
|
|
259
|
+
{
|
|
260
|
+
let districtToBlocks = bmGather[geoid];
|
|
261
|
+
if (Util.countKeys(districtToBlocks) == 1)
|
|
262
|
+
{
|
|
263
|
+
res.outMap[geoid] = Util.nthKey(districtToBlocks);
|
|
264
|
+
}
|
|
265
|
+
else
|
|
266
|
+
{
|
|
267
|
+
for (let districtID in districtToBlocks) if (districtToBlocks.hasOwnProperty(districtID))
|
|
268
|
+
{
|
|
269
|
+
let split: VF.SplitBlock = { state: '', datasource: '', geoid: geoid, blocks: Object.keys(districtToBlocks[districtID]) };
|
|
270
|
+
let splits = res.outDistrictToSplit[districtID];
|
|
271
|
+
if (splits === undefined)
|
|
272
|
+
{
|
|
273
|
+
splits = [];
|
|
274
|
+
res.outDistrictToSplit[districtID] = splits;
|
|
275
|
+
}
|
|
276
|
+
splits.push(split);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
res.outOrder = canonicalDistrictIDOrdering(res.outOrder);
|
|
282
|
+
|
|
283
|
+
return res;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export const GEOIDToState: any = {
|
|
287
|
+
'01': 'AL',
|
|
288
|
+
'02': 'AK',
|
|
289
|
+
'04': 'AZ',
|
|
290
|
+
'05': 'AR',
|
|
291
|
+
'06': 'CA',
|
|
292
|
+
'08': 'CO',
|
|
293
|
+
'09': 'CT',
|
|
294
|
+
'10': 'DE',
|
|
295
|
+
'12': 'FL',
|
|
296
|
+
'13': 'GA',
|
|
297
|
+
'15': 'HI',
|
|
298
|
+
'16': 'ID',
|
|
299
|
+
'17': 'IL',
|
|
300
|
+
'18': 'IN',
|
|
301
|
+
'19': 'IA',
|
|
302
|
+
'20': 'KS',
|
|
303
|
+
'21': 'KY',
|
|
304
|
+
'22': 'LA',
|
|
305
|
+
'23': 'ME',
|
|
306
|
+
'24': 'MD',
|
|
307
|
+
'25': 'MA',
|
|
308
|
+
'26': 'MI',
|
|
309
|
+
'27': 'MN',
|
|
310
|
+
'28': 'MS',
|
|
311
|
+
'29': 'MO',
|
|
312
|
+
'30': 'MT',
|
|
313
|
+
'31': 'NE',
|
|
314
|
+
'32': 'NV',
|
|
315
|
+
'33': 'NH',
|
|
316
|
+
'34': 'NJ',
|
|
317
|
+
'35': 'NM',
|
|
318
|
+
'36': 'NY',
|
|
319
|
+
'37': 'NC',
|
|
320
|
+
'38': 'ND',
|
|
321
|
+
'39': 'OH',
|
|
322
|
+
'40': 'OK',
|
|
323
|
+
'41': 'OR',
|
|
324
|
+
'42': 'PA',
|
|
325
|
+
'44': 'RI',
|
|
326
|
+
'45': 'SC',
|
|
327
|
+
'46': 'SD',
|
|
328
|
+
'47': 'TN',
|
|
329
|
+
'48': 'TX',
|
|
330
|
+
'49': 'UT',
|
|
331
|
+
'50': 'VT',
|
|
332
|
+
'51': 'VA',
|
|
333
|
+
'53': 'WA',
|
|
334
|
+
'54': 'WV',
|
|
335
|
+
'55': 'WI',
|
|
336
|
+
'56': 'WY',
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export const StateToGEOID: any = {
|
|
340
|
+
'AL': '01',
|
|
341
|
+
'AK': '02',
|
|
342
|
+
'AZ': '04',
|
|
343
|
+
'AR': '05',
|
|
344
|
+
'CA': '06',
|
|
345
|
+
'CO': '08',
|
|
346
|
+
'CT': '09',
|
|
347
|
+
'DE': '10',
|
|
348
|
+
'FL': '12',
|
|
349
|
+
'GA': '13',
|
|
350
|
+
'HI': '15',
|
|
351
|
+
'ID': '16',
|
|
352
|
+
'IL': '17',
|
|
353
|
+
'IN': '18',
|
|
354
|
+
'IA': '19',
|
|
355
|
+
'KS': '20',
|
|
356
|
+
'KY': '21',
|
|
357
|
+
'LA': '22',
|
|
358
|
+
'ME': '23',
|
|
359
|
+
'MD': '24',
|
|
360
|
+
'MA': '25',
|
|
361
|
+
'MI': '26',
|
|
362
|
+
'MN': '27',
|
|
363
|
+
'MS': '28',
|
|
364
|
+
'MO': '29',
|
|
365
|
+
'MT': '30',
|
|
366
|
+
'NE': '31',
|
|
367
|
+
'NV': '32',
|
|
368
|
+
'NH': '33',
|
|
369
|
+
'NJ': '34',
|
|
370
|
+
'NM': '35',
|
|
371
|
+
'NY': '36',
|
|
372
|
+
'NC': '37',
|
|
373
|
+
'ND': '38',
|
|
374
|
+
'OH': '39',
|
|
375
|
+
'OK': '40',
|
|
376
|
+
'OR': '41',
|
|
377
|
+
'PA': '42',
|
|
378
|
+
'RI': '44',
|
|
379
|
+
'SC': '45',
|
|
380
|
+
'SD': '46',
|
|
381
|
+
'TN': '47',
|
|
382
|
+
'TX': '48',
|
|
383
|
+
'UT': '49',
|
|
384
|
+
'VT': '50',
|
|
385
|
+
'VA': '51',
|
|
386
|
+
'WA': '53',
|
|
387
|
+
'WV': '54',
|
|
388
|
+
'WI': '55',
|
|
389
|
+
'WY': '56',
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
export function geoidToState(geoid: string): string
|
|
393
|
+
{
|
|
394
|
+
let re = /^(..).*$/;
|
|
395
|
+
|
|
396
|
+
let a = re.exec(geoid);
|
|
397
|
+
if (a == null || a.length != 2) return null;
|
|
398
|
+
return GEOIDToState[a[1]];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export type StateUrls = (
|
|
402
|
+
'alabama' |
|
|
403
|
+
'alaska' |
|
|
404
|
+
'arizona' |
|
|
405
|
+
'arkansas' |
|
|
406
|
+
'california' |
|
|
407
|
+
'colorado' |
|
|
408
|
+
'connecticut' |
|
|
409
|
+
'delaware' |
|
|
410
|
+
'florida' |
|
|
411
|
+
'georgia' |
|
|
412
|
+
'hawaii' |
|
|
413
|
+
'idaho' |
|
|
414
|
+
'illinois' |
|
|
415
|
+
'indiana' |
|
|
416
|
+
'iowa' |
|
|
417
|
+
'kansas' |
|
|
418
|
+
'kentucky' |
|
|
419
|
+
'louisiana' |
|
|
420
|
+
'maine' |
|
|
421
|
+
'maryland' |
|
|
422
|
+
'massachusetts' |
|
|
423
|
+
'michigan' |
|
|
424
|
+
'minnesota' |
|
|
425
|
+
'mississippi' |
|
|
426
|
+
'missouri' |
|
|
427
|
+
'montana' |
|
|
428
|
+
'nebraska' |
|
|
429
|
+
'nevada' |
|
|
430
|
+
'new-hampshire' |
|
|
431
|
+
'new-jersey' |
|
|
432
|
+
'new-mexico' |
|
|
433
|
+
'new-york' |
|
|
434
|
+
'north-carolina' |
|
|
435
|
+
'north-dakota' |
|
|
436
|
+
'ohio' |
|
|
437
|
+
'oklahoma' |
|
|
438
|
+
'oregon' |
|
|
439
|
+
'pennsylvania' |
|
|
440
|
+
'rhode-island' |
|
|
441
|
+
'south-carolina' |
|
|
442
|
+
'south-dakota' |
|
|
443
|
+
'tennessee' |
|
|
444
|
+
'texas' |
|
|
445
|
+
'utah' |
|
|
446
|
+
'vermont' |
|
|
447
|
+
'virginia' |
|
|
448
|
+
'washington' |
|
|
449
|
+
'west-virginia' |
|
|
450
|
+
'wisconsin' |
|
|
451
|
+
'wyoming'
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
export type ValidStateUrlsType =
|
|
455
|
+
{
|
|
456
|
+
readonly [stateUrl in StateUrls]: boolean;
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
const ValidStateUrls: ValidStateUrlsType = {
|
|
460
|
+
'alabama': true,
|
|
461
|
+
'alaska': true,
|
|
462
|
+
'arizona': true,
|
|
463
|
+
'arkansas': true,
|
|
464
|
+
'california': true,
|
|
465
|
+
'colorado': true,
|
|
466
|
+
'connecticut': true,
|
|
467
|
+
'delaware': true,
|
|
468
|
+
'florida': true,
|
|
469
|
+
'georgia': true,
|
|
470
|
+
'hawaii': true,
|
|
471
|
+
'idaho': true,
|
|
472
|
+
'illinois': true,
|
|
473
|
+
'indiana': true,
|
|
474
|
+
'iowa': true,
|
|
475
|
+
'kansas': true,
|
|
476
|
+
'kentucky': true,
|
|
477
|
+
'louisiana': true,
|
|
478
|
+
'maine': true,
|
|
479
|
+
'maryland': true,
|
|
480
|
+
'massachusetts': true,
|
|
481
|
+
'michigan': true,
|
|
482
|
+
'minnesota': true,
|
|
483
|
+
'mississippi': true,
|
|
484
|
+
'missouri': true,
|
|
485
|
+
'montana': true,
|
|
486
|
+
'nebraska': true,
|
|
487
|
+
'nevada': true,
|
|
488
|
+
'new-hampshire': true,
|
|
489
|
+
'new-jersey': true,
|
|
490
|
+
'new-mexico': true,
|
|
491
|
+
'new-york': true,
|
|
492
|
+
'north-carolina': true,
|
|
493
|
+
'north-dakota': true,
|
|
494
|
+
'ohio': true,
|
|
495
|
+
'oklahoma': true,
|
|
496
|
+
'oregon': true,
|
|
497
|
+
'pennsylvania': true,
|
|
498
|
+
'rhode-island': true,
|
|
499
|
+
'south-carolina': true,
|
|
500
|
+
'south-dakota': true,
|
|
501
|
+
'tennessee': true,
|
|
502
|
+
'texas': true,
|
|
503
|
+
'utah': true,
|
|
504
|
+
'vermont': true,
|
|
505
|
+
'virginia': true,
|
|
506
|
+
'washington': true,
|
|
507
|
+
'west-virginia': true,
|
|
508
|
+
'wisconsin': true,
|
|
509
|
+
'wyoming': true,
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
export function isStateUrl(s: any): s is StateUrls
|
|
513
|
+
{
|
|
514
|
+
return (typeof s === 'string' && s in ValidStateUrls);
|
|
515
|
+
}
|