@hebcal/geo-sqlite 3.6.0 → 3.7.1

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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hebcal/geo-sqlite v3.6.0 */
1
+ /*! @hebcal/geo-sqlite v3.7.1 */
2
2
  'use strict';
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -52,18 +52,76 @@ const ZIPCODE_SQL = `SELECT CityMixedCase,State,Latitude,Longitude,TimeZone,DayL
52
52
  FROM ZIPCodes_Primary WHERE ZipCode = ?`;
53
53
  const ZIPCODE_ALL_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving
54
54
  FROM ZIPCodes_Primary`;
55
- const ZIP_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving
55
+ const ZIP_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving,Population
56
56
  FROM ZIPCodes_Primary
57
57
  WHERE ZipCode LIKE ?
58
58
  ORDER BY Population DESC
59
59
  LIMIT 10`;
60
+ const ZIP_FULLTEXT_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving,Population
61
+ FROM ZIPCodes_CityFullText
62
+ WHERE CityMixedCase MATCH ?
63
+ ORDER BY Population DESC
64
+ LIMIT 15`;
60
65
  const GEONAME_COMPLETE_SQL = `SELECT geonameid, asciiname, admin1, country,
61
66
  population, latitude, longitude, timezone
62
67
  FROM geoname_fulltext
63
68
  WHERE longname MATCH ?
64
69
  GROUP BY geonameid
65
70
  ORDER BY population DESC
66
- LIMIT 10`;
71
+ LIMIT 15`;
72
+ const stateNames = {
73
+ 'AK': 'Alaska',
74
+ 'AL': 'Alabama',
75
+ 'AR': 'Arkansas',
76
+ 'AZ': 'Arizona',
77
+ 'CA': 'California',
78
+ 'CO': 'Colorado',
79
+ 'CT': 'Connecticut',
80
+ 'DC': 'Washington, D.C.',
81
+ 'DE': 'Delaware',
82
+ 'FL': 'Florida',
83
+ 'GA': 'Georgia',
84
+ 'HI': 'Hawaii',
85
+ 'IA': 'Iowa',
86
+ 'ID': 'Idaho',
87
+ 'IL': 'Illinois',
88
+ 'IN': 'Indiana',
89
+ 'KS': 'Kansas',
90
+ 'KY': 'Kentucky',
91
+ 'LA': 'Louisiana',
92
+ 'MA': 'Massachusetts',
93
+ 'MD': 'Maryland',
94
+ 'ME': 'Maine',
95
+ 'MI': 'Michigan',
96
+ 'MN': 'Minnesota',
97
+ 'MO': 'Missouri',
98
+ 'MS': 'Mississippi',
99
+ 'MT': 'Montana',
100
+ 'NC': 'North Carolina',
101
+ 'ND': 'North Dakota',
102
+ 'NE': 'Nebraska',
103
+ 'NH': 'New Hampshire',
104
+ 'NJ': 'New Jersey',
105
+ 'NM': 'New Mexico',
106
+ 'NV': 'Nevada',
107
+ 'NY': 'New York',
108
+ 'OH': 'Ohio',
109
+ 'OK': 'Oklahoma',
110
+ 'OR': 'Oregon',
111
+ 'PA': 'Pennsylvania',
112
+ 'RI': 'Rhode Island',
113
+ 'SC': 'South Carolina',
114
+ 'SD': 'South Dakota',
115
+ 'TN': 'Tennessee',
116
+ 'TX': 'Texas',
117
+ 'UT': 'Utah',
118
+ 'VA': 'Virginia',
119
+ 'VT': 'Vermont',
120
+ 'WA': 'Washington',
121
+ 'WI': 'Wisconsin',
122
+ 'WV': 'West Virginia',
123
+ 'WY': 'Wyoming'
124
+ };
67
125
  /** Wrapper around sqlite databases */
68
126
 
69
127
  class GeoDb {
@@ -218,6 +276,28 @@ class GeoDb {
218
276
  return null;
219
277
  }
220
278
  }
279
+ /**
280
+ * @private
281
+ * @param {any[]} res
282
+ * @return {Object[]}
283
+ */
284
+
285
+
286
+ static zipResultToObj(res) {
287
+ const obj = {
288
+ id: String(res.ZipCode),
289
+ value: `${res.CityMixedCase}, ${res.State} ${res.ZipCode}`,
290
+ admin1: res.State,
291
+ asciiname: res.CityMixedCase,
292
+ country: 'United States',
293
+ latitude: res.Latitude,
294
+ longitude: res.Longitude,
295
+ timezone: core.Location.getUsaTzid(res.State, res.TimeZone, res.DayLightSaving),
296
+ population: res.Population,
297
+ geo: 'zip'
298
+ };
299
+ return obj;
300
+ }
221
301
  /**
222
302
  * Generates autocomplete results based on a query string
223
303
  * @param {string} qraw
@@ -226,43 +306,37 @@ class GeoDb {
226
306
 
227
307
 
228
308
  autoComplete(qraw) {
309
+ qraw = qraw.trim();
310
+
229
311
  if (qraw.length === 0) {
230
312
  return [];
231
313
  }
232
314
 
233
- if (qraw.charCodeAt(0) >= 48 && qraw.charCodeAt(0) <= 57) {
315
+ const firstCharCode = qraw.charCodeAt(0);
316
+
317
+ if (firstCharCode >= 48 && firstCharCode <= 57) {
234
318
  if (!this.zipCompStmt) {
235
319
  this.zipCompStmt = this.zipsDb.prepare(ZIP_COMPLETE_SQL);
236
320
  }
237
321
 
238
- return this.zipCompStmt.all(qraw + '%').map(res => {
239
- const obj = {
240
- id: String(res.ZipCode),
241
- value: `${res.CityMixedCase}, ${res.State} ${res.ZipCode}`,
242
- admin1: res.State,
243
- asciiname: res.CityMixedCase,
244
- country: 'United States',
245
- latitude: res.Latitude,
246
- longitude: res.Longitude,
247
- timezone: core.Location.getUsaTzid(res.State, res.TimeZone, res.DayLightSaving),
248
- population: res.Population,
249
- geo: 'zip'
250
- };
251
- return obj;
252
- });
322
+ const zip5 = qraw.substring(0, 5);
323
+ return this.zipCompStmt.all(zip5 + '%').map(GeoDb.zipResultToObj);
253
324
  } else {
254
325
  if (!this.geonamesCompStmt) {
255
326
  this.geonamesCompStmt = this.geonamesDb.prepare(GEONAME_COMPLETE_SQL);
256
327
  }
257
328
 
258
329
  qraw = qraw.replace(/\"/g, '""');
259
- return this.geonamesCompStmt.all(`"${qraw}*"`).map(res => {
330
+ const geoRows = this.geonamesCompStmt.all(`"${qraw}*"`);
331
+ const geoMatches = geoRows.map(res => {
260
332
  const country = res.country || '';
261
333
  const admin1 = res.admin1 || '';
262
334
  const obj = {
263
335
  id: res.geonameid,
264
336
  value: core.Location.geonameCityDescr(res.asciiname, admin1, country),
265
337
  asciiname: res.asciiname,
338
+ admin1,
339
+ country,
266
340
  latitude: res.latitude,
267
341
  longitude: res.longitude,
268
342
  timezone: res.timezone,
@@ -281,6 +355,32 @@ class GeoDb {
281
355
  obj.tokens = Array.from(new Set(res.asciiname.split(' ').concat(admin1.split(' '), country.split(' '))));
282
356
  return obj;
283
357
  });
358
+
359
+ if (!this.zipFulltextCompStmt) {
360
+ this.zipFulltextCompStmt = this.zipsDb.prepare(ZIP_FULLTEXT_COMPLETE_SQL);
361
+ }
362
+
363
+ const zipRows = this.zipFulltextCompStmt.all(`"${qraw}*"`);
364
+ const zipMatches = zipRows.map(GeoDb.zipResultToObj);
365
+ const map = new Map();
366
+
367
+ for (const obj of zipMatches) {
368
+ const key = [obj.asciiname, stateNames[obj.admin1], obj.country].join('|');
369
+
370
+ if (!map.has(key)) {
371
+ map.set(key, obj);
372
+ }
373
+ } // GeoNames takes priority over USA ZIP code matches
374
+
375
+
376
+ for (const obj of geoMatches) {
377
+ const key = [obj.asciiname, obj.admin1, obj.country].join('|');
378
+ map.set(key, obj);
379
+ }
380
+
381
+ const values = Array.from(map.values());
382
+ values.sort((a, b) => b.population - a.population);
383
+ return values.slice(0, 10);
284
384
  }
285
385
  }
286
386
  /** Reads entire ZIP database and caches in-memory */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hebcal/geo-sqlite v3.6.0 */
1
+ /*! @hebcal/geo-sqlite v3.7.1 */
2
2
  import Database from 'better-sqlite3';
3
3
  import { Location, Locale } from '@hebcal/core';
4
4
  import pino from 'pino';
@@ -40,18 +40,76 @@ const ZIPCODE_SQL = `SELECT CityMixedCase,State,Latitude,Longitude,TimeZone,DayL
40
40
  FROM ZIPCodes_Primary WHERE ZipCode = ?`;
41
41
  const ZIPCODE_ALL_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving
42
42
  FROM ZIPCodes_Primary`;
43
- const ZIP_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving
43
+ const ZIP_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving,Population
44
44
  FROM ZIPCodes_Primary
45
45
  WHERE ZipCode LIKE ?
46
46
  ORDER BY Population DESC
47
47
  LIMIT 10`;
48
+ const ZIP_FULLTEXT_COMPLETE_SQL = `SELECT ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving,Population
49
+ FROM ZIPCodes_CityFullText
50
+ WHERE CityMixedCase MATCH ?
51
+ ORDER BY Population DESC
52
+ LIMIT 15`;
48
53
  const GEONAME_COMPLETE_SQL = `SELECT geonameid, asciiname, admin1, country,
49
54
  population, latitude, longitude, timezone
50
55
  FROM geoname_fulltext
51
56
  WHERE longname MATCH ?
52
57
  GROUP BY geonameid
53
58
  ORDER BY population DESC
54
- LIMIT 10`;
59
+ LIMIT 15`;
60
+ const stateNames = {
61
+ 'AK': 'Alaska',
62
+ 'AL': 'Alabama',
63
+ 'AR': 'Arkansas',
64
+ 'AZ': 'Arizona',
65
+ 'CA': 'California',
66
+ 'CO': 'Colorado',
67
+ 'CT': 'Connecticut',
68
+ 'DC': 'Washington, D.C.',
69
+ 'DE': 'Delaware',
70
+ 'FL': 'Florida',
71
+ 'GA': 'Georgia',
72
+ 'HI': 'Hawaii',
73
+ 'IA': 'Iowa',
74
+ 'ID': 'Idaho',
75
+ 'IL': 'Illinois',
76
+ 'IN': 'Indiana',
77
+ 'KS': 'Kansas',
78
+ 'KY': 'Kentucky',
79
+ 'LA': 'Louisiana',
80
+ 'MA': 'Massachusetts',
81
+ 'MD': 'Maryland',
82
+ 'ME': 'Maine',
83
+ 'MI': 'Michigan',
84
+ 'MN': 'Minnesota',
85
+ 'MO': 'Missouri',
86
+ 'MS': 'Mississippi',
87
+ 'MT': 'Montana',
88
+ 'NC': 'North Carolina',
89
+ 'ND': 'North Dakota',
90
+ 'NE': 'Nebraska',
91
+ 'NH': 'New Hampshire',
92
+ 'NJ': 'New Jersey',
93
+ 'NM': 'New Mexico',
94
+ 'NV': 'Nevada',
95
+ 'NY': 'New York',
96
+ 'OH': 'Ohio',
97
+ 'OK': 'Oklahoma',
98
+ 'OR': 'Oregon',
99
+ 'PA': 'Pennsylvania',
100
+ 'RI': 'Rhode Island',
101
+ 'SC': 'South Carolina',
102
+ 'SD': 'South Dakota',
103
+ 'TN': 'Tennessee',
104
+ 'TX': 'Texas',
105
+ 'UT': 'Utah',
106
+ 'VA': 'Virginia',
107
+ 'VT': 'Vermont',
108
+ 'WA': 'Washington',
109
+ 'WI': 'Wisconsin',
110
+ 'WV': 'West Virginia',
111
+ 'WY': 'Wyoming'
112
+ };
55
113
  /** Wrapper around sqlite databases */
56
114
 
57
115
  class GeoDb {
@@ -206,6 +264,28 @@ class GeoDb {
206
264
  return null;
207
265
  }
208
266
  }
267
+ /**
268
+ * @private
269
+ * @param {any[]} res
270
+ * @return {Object[]}
271
+ */
272
+
273
+
274
+ static zipResultToObj(res) {
275
+ const obj = {
276
+ id: String(res.ZipCode),
277
+ value: `${res.CityMixedCase}, ${res.State} ${res.ZipCode}`,
278
+ admin1: res.State,
279
+ asciiname: res.CityMixedCase,
280
+ country: 'United States',
281
+ latitude: res.Latitude,
282
+ longitude: res.Longitude,
283
+ timezone: Location.getUsaTzid(res.State, res.TimeZone, res.DayLightSaving),
284
+ population: res.Population,
285
+ geo: 'zip'
286
+ };
287
+ return obj;
288
+ }
209
289
  /**
210
290
  * Generates autocomplete results based on a query string
211
291
  * @param {string} qraw
@@ -214,43 +294,37 @@ class GeoDb {
214
294
 
215
295
 
216
296
  autoComplete(qraw) {
297
+ qraw = qraw.trim();
298
+
217
299
  if (qraw.length === 0) {
218
300
  return [];
219
301
  }
220
302
 
221
- if (qraw.charCodeAt(0) >= 48 && qraw.charCodeAt(0) <= 57) {
303
+ const firstCharCode = qraw.charCodeAt(0);
304
+
305
+ if (firstCharCode >= 48 && firstCharCode <= 57) {
222
306
  if (!this.zipCompStmt) {
223
307
  this.zipCompStmt = this.zipsDb.prepare(ZIP_COMPLETE_SQL);
224
308
  }
225
309
 
226
- return this.zipCompStmt.all(qraw + '%').map(res => {
227
- const obj = {
228
- id: String(res.ZipCode),
229
- value: `${res.CityMixedCase}, ${res.State} ${res.ZipCode}`,
230
- admin1: res.State,
231
- asciiname: res.CityMixedCase,
232
- country: 'United States',
233
- latitude: res.Latitude,
234
- longitude: res.Longitude,
235
- timezone: Location.getUsaTzid(res.State, res.TimeZone, res.DayLightSaving),
236
- population: res.Population,
237
- geo: 'zip'
238
- };
239
- return obj;
240
- });
310
+ const zip5 = qraw.substring(0, 5);
311
+ return this.zipCompStmt.all(zip5 + '%').map(GeoDb.zipResultToObj);
241
312
  } else {
242
313
  if (!this.geonamesCompStmt) {
243
314
  this.geonamesCompStmt = this.geonamesDb.prepare(GEONAME_COMPLETE_SQL);
244
315
  }
245
316
 
246
317
  qraw = qraw.replace(/\"/g, '""');
247
- return this.geonamesCompStmt.all(`"${qraw}*"`).map(res => {
318
+ const geoRows = this.geonamesCompStmt.all(`"${qraw}*"`);
319
+ const geoMatches = geoRows.map(res => {
248
320
  const country = res.country || '';
249
321
  const admin1 = res.admin1 || '';
250
322
  const obj = {
251
323
  id: res.geonameid,
252
324
  value: Location.geonameCityDescr(res.asciiname, admin1, country),
253
325
  asciiname: res.asciiname,
326
+ admin1,
327
+ country,
254
328
  latitude: res.latitude,
255
329
  longitude: res.longitude,
256
330
  timezone: res.timezone,
@@ -269,6 +343,32 @@ class GeoDb {
269
343
  obj.tokens = Array.from(new Set(res.asciiname.split(' ').concat(admin1.split(' '), country.split(' '))));
270
344
  return obj;
271
345
  });
346
+
347
+ if (!this.zipFulltextCompStmt) {
348
+ this.zipFulltextCompStmt = this.zipsDb.prepare(ZIP_FULLTEXT_COMPLETE_SQL);
349
+ }
350
+
351
+ const zipRows = this.zipFulltextCompStmt.all(`"${qraw}*"`);
352
+ const zipMatches = zipRows.map(GeoDb.zipResultToObj);
353
+ const map = new Map();
354
+
355
+ for (const obj of zipMatches) {
356
+ const key = [obj.asciiname, stateNames[obj.admin1], obj.country].join('|');
357
+
358
+ if (!map.has(key)) {
359
+ map.set(key, obj);
360
+ }
361
+ } // GeoNames takes priority over USA ZIP code matches
362
+
363
+
364
+ for (const obj of geoMatches) {
365
+ const key = [obj.asciiname, obj.admin1, obj.country].join('|');
366
+ map.set(key, obj);
367
+ }
368
+
369
+ const values = Array.from(map.values());
370
+ values.sort((a, b) => b.population - a.population);
371
+ return values.slice(0, 10);
272
372
  }
273
373
  }
274
374
  /** Reads entire ZIP database and caches in-memory */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hebcal/geo-sqlite",
3
- "version": "3.6.0",
3
+ "version": "3.7.1",
4
4
  "author": "Michael J. Radwin (https://github.com/mjradwin)",
5
5
  "keywords": [
6
6
  "hebcal"
@@ -28,9 +28,9 @@
28
28
  "geo-sqlite.d.ts"
29
29
  ],
30
30
  "dependencies": {
31
- "@hebcal/core": "^3.32.0",
31
+ "@hebcal/core": "^3.32.1",
32
32
  "better-sqlite3": "^7.4.6",
33
- "pino": "^7.6.2",
33
+ "pino": "^7.6.3",
34
34
  "pino-pretty": "^7.3.0"
35
35
  },
36
36
  "scripts": {
@@ -62,8 +62,8 @@
62
62
  "@ava/babel": "^2.0.0",
63
63
  "@babel/core": "^7.16.7",
64
64
  "@babel/polyfill": "^7.12.1",
65
- "@babel/preset-env": "^7.16.7",
66
- "@babel/register": "^7.16.7",
65
+ "@babel/preset-env": "^7.16.8",
66
+ "@babel/register": "^7.16.8",
67
67
  "@rollup/plugin-babel": "^5.3.0",
68
68
  "@rollup/plugin-commonjs": "^21.0.1",
69
69
  "@rollup/plugin-json": "^4.1.0",
package/zips-dummy.sql CHANGED
@@ -8,3 +8,6 @@ CREATE TABLE ZIPCodes_Primary (
8
8
  DayLightSaving char(1) NULL,
9
9
  Population int NULL
10
10
  );
11
+
12
+ CREATE VIRTUAL TABLE ZIPCodes_CityFullText
13
+ USING fts3(ZipCode,CityMixedCase,State,Latitude,Longitude,TimeZone,DayLightSaving,Population);