@hebcal/geo-sqlite 3.6.2 → 3.7.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hebcal/geo-sqlite v3.6.2 */
1
+ /*! @hebcal/geo-sqlite v3.7.0 */
2
2
  'use strict';
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
@@ -57,13 +57,71 @@ 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
@@ -235,34 +315,23 @@ class GeoDb {
235
315
  this.zipCompStmt = this.zipsDb.prepare(ZIP_COMPLETE_SQL);
236
316
  }
237
317
 
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
- });
318
+ return this.zipCompStmt.all(qraw + '%').map(GeoDb.zipResultToObj);
253
319
  } else {
254
320
  if (!this.geonamesCompStmt) {
255
321
  this.geonamesCompStmt = this.geonamesDb.prepare(GEONAME_COMPLETE_SQL);
256
322
  }
257
323
 
258
324
  qraw = qraw.replace(/\"/g, '""');
259
- return this.geonamesCompStmt.all(`"${qraw}*"`).map(res => {
325
+ const geoRows = this.geonamesCompStmt.all(`"${qraw}*"`);
326
+ const geoMatches = geoRows.map(res => {
260
327
  const country = res.country || '';
261
328
  const admin1 = res.admin1 || '';
262
329
  const obj = {
263
330
  id: res.geonameid,
264
331
  value: core.Location.geonameCityDescr(res.asciiname, admin1, country),
265
332
  asciiname: res.asciiname,
333
+ admin1,
334
+ country,
266
335
  latitude: res.latitude,
267
336
  longitude: res.longitude,
268
337
  timezone: res.timezone,
@@ -281,6 +350,32 @@ class GeoDb {
281
350
  obj.tokens = Array.from(new Set(res.asciiname.split(' ').concat(admin1.split(' '), country.split(' '))));
282
351
  return obj;
283
352
  });
353
+
354
+ if (!this.zipFulltextCompStmt) {
355
+ this.zipFulltextCompStmt = this.zipsDb.prepare(ZIP_FULLTEXT_COMPLETE_SQL);
356
+ }
357
+
358
+ const zipRows = this.zipFulltextCompStmt.all(`"${qraw}*"`);
359
+ const zipMatches = zipRows.map(GeoDb.zipResultToObj);
360
+ const map = new Map();
361
+
362
+ for (const obj of zipMatches) {
363
+ const key = [obj.asciiname, stateNames[obj.admin1], obj.country].join('|');
364
+
365
+ if (!map.has(key)) {
366
+ map.set(key, obj);
367
+ }
368
+ } // GeoNames takes priority over USA ZIP code matches
369
+
370
+
371
+ for (const obj of geoMatches) {
372
+ const key = [obj.asciiname, obj.admin1, obj.country].join('|');
373
+ map.set(key, obj);
374
+ }
375
+
376
+ const values = Array.from(map.values());
377
+ values.sort((a, b) => b.population - a.population);
378
+ return values.slice(0, 10);
284
379
  }
285
380
  }
286
381
  /** Reads entire ZIP database and caches in-memory */
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! @hebcal/geo-sqlite v3.6.2 */
1
+ /*! @hebcal/geo-sqlite v3.7.0 */
2
2
  import Database from 'better-sqlite3';
3
3
  import { Location, Locale } from '@hebcal/core';
4
4
  import pino from 'pino';
@@ -45,13 +45,71 @@ 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
@@ -223,34 +303,23 @@ class GeoDb {
223
303
  this.zipCompStmt = this.zipsDb.prepare(ZIP_COMPLETE_SQL);
224
304
  }
225
305
 
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
- });
306
+ return this.zipCompStmt.all(qraw + '%').map(GeoDb.zipResultToObj);
241
307
  } else {
242
308
  if (!this.geonamesCompStmt) {
243
309
  this.geonamesCompStmt = this.geonamesDb.prepare(GEONAME_COMPLETE_SQL);
244
310
  }
245
311
 
246
312
  qraw = qraw.replace(/\"/g, '""');
247
- return this.geonamesCompStmt.all(`"${qraw}*"`).map(res => {
313
+ const geoRows = this.geonamesCompStmt.all(`"${qraw}*"`);
314
+ const geoMatches = geoRows.map(res => {
248
315
  const country = res.country || '';
249
316
  const admin1 = res.admin1 || '';
250
317
  const obj = {
251
318
  id: res.geonameid,
252
319
  value: Location.geonameCityDescr(res.asciiname, admin1, country),
253
320
  asciiname: res.asciiname,
321
+ admin1,
322
+ country,
254
323
  latitude: res.latitude,
255
324
  longitude: res.longitude,
256
325
  timezone: res.timezone,
@@ -269,6 +338,32 @@ class GeoDb {
269
338
  obj.tokens = Array.from(new Set(res.asciiname.split(' ').concat(admin1.split(' '), country.split(' '))));
270
339
  return obj;
271
340
  });
341
+
342
+ if (!this.zipFulltextCompStmt) {
343
+ this.zipFulltextCompStmt = this.zipsDb.prepare(ZIP_FULLTEXT_COMPLETE_SQL);
344
+ }
345
+
346
+ const zipRows = this.zipFulltextCompStmt.all(`"${qraw}*"`);
347
+ const zipMatches = zipRows.map(GeoDb.zipResultToObj);
348
+ const map = new Map();
349
+
350
+ for (const obj of zipMatches) {
351
+ const key = [obj.asciiname, stateNames[obj.admin1], obj.country].join('|');
352
+
353
+ if (!map.has(key)) {
354
+ map.set(key, obj);
355
+ }
356
+ } // GeoNames takes priority over USA ZIP code matches
357
+
358
+
359
+ for (const obj of geoMatches) {
360
+ const key = [obj.asciiname, obj.admin1, obj.country].join('|');
361
+ map.set(key, obj);
362
+ }
363
+
364
+ const values = Array.from(map.values());
365
+ values.sort((a, b) => b.population - a.population);
366
+ return values.slice(0, 10);
272
367
  }
273
368
  }
274
369
  /** 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.2",
3
+ "version": "3.7.0",
4
4
  "author": "Michael J. Radwin (https://github.com/mjradwin)",
5
5
  "keywords": [
6
6
  "hebcal"
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);