@hebcal/geo-sqlite 3.7.1 → 4.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.
@@ -2,21 +2,22 @@
2
2
 
3
3
  const {buildGeonamesSqlite} = require('@hebcal/geo-sqlite');
4
4
 
5
- if (process.argv.length != 8) {
6
- const infiles = 'countryInfo.txt cities5000.txt cities-patch.txt admin1CodesASCII.txt IL.txt';
5
+ const argv = process.argv.slice(2);
6
+ if (argv.length !== 7) {
7
+ const infiles = 'countryInfo.txt cities5000.txt cities-patch.txt admin1CodesASCII.txt IL.txt IL-alternatenames.txt';
7
8
  console.error(`Usage: build-geonames-sqlite geonames.sqlite3 ${infiles}`);
8
9
  process.exit(1);
9
10
  }
10
11
 
11
- const [dbFilename, countryInfo, cities, citiesPatch, admin1CodesASCII, il] = process.argv.slice(2);
12
-
13
- buildGeonamesSqlite(
14
- dbFilename,
15
- countryInfo,
16
- cities,
17
- citiesPatch,
18
- admin1CodesASCII,
19
- il,
20
- ).then(() => {
12
+ const filenames = {
13
+ dbFilename: argv[0],
14
+ countryInfotxt: argv[1],
15
+ cities5000txt: argv[2],
16
+ citiesPatch: argv[3],
17
+ admin1CodesASCIItxt: argv[4],
18
+ ILtxt: argv[5],
19
+ ILalternate: argv[6],
20
+ };
21
+ buildGeonamesSqlite(filenames).then(() => {
21
22
  console.log('Done!');
22
23
  });
package/dist/index.js CHANGED
@@ -1,10 +1,11 @@
1
- /*! @hebcal/geo-sqlite v3.7.1 */
1
+ /*! @hebcal/geo-sqlite v4.0.0 */
2
2
  'use strict';
3
3
 
4
4
  Object.defineProperty(exports, '__esModule', { value: true });
5
5
 
6
6
  var Database = require('better-sqlite3');
7
7
  var core = require('@hebcal/core');
8
+ require('@hebcal/cities');
8
9
  var pino = require('pino');
9
10
  var events = require('events');
10
11
  var fs = require('fs');
@@ -141,14 +142,32 @@ class GeoDb {
141
142
  fileMustExist: true
142
143
  });
143
144
  this.zipStmt = this.zipsDb.prepare(ZIPCODE_SQL);
145
+ /** @type {Map<string, Location>} */
146
+
144
147
  this.zipCache = new Map();
145
148
  this.geonamesStmt = this.geonamesDb.prepare(GEONAME_SQL);
149
+ /** @type {Map<number, Location>} */
150
+
146
151
  this.geonamesCache = new Map();
152
+ /** @type {Map<string, Location>} */
153
+
147
154
  this.legacyCities = new Map();
148
155
 
149
156
  for (const [name, id] of Object.entries(city2geonameid)) {
150
157
  this.legacyCities.set(GeoDb.munge(name), id);
151
158
  }
159
+
160
+ const stmt = this.geonamesDb.prepare(`SELECT ISO, Country FROM country WHERE Country <> ''`);
161
+ const rows = stmt.all();
162
+ const map = new Map();
163
+
164
+ for (const row of rows) {
165
+ map.set(row.ISO, row.Country);
166
+ }
167
+ /** @type {Map<string, string>} */
168
+
169
+
170
+ this.countryNames = map;
152
171
  }
153
172
  /** Closes database handles */
154
173
 
@@ -174,19 +193,20 @@ class GeoDb {
174
193
 
175
194
 
176
195
  lookupZip(zip) {
177
- const found = this.zipCache.get(zip);
196
+ const zip5 = zip.trim().substring(0, 5);
197
+ const found = this.zipCache.get(zip5);
178
198
  if (typeof found !== 'undefined') return found;
179
- const result = this.zipStmt.get(zip);
199
+ const result = this.zipStmt.get(zip5);
180
200
 
181
201
  if (!result) {
182
- if (this.logger) this.logger.warn(`GeoDb: unknown zipcode=${zip}`);
183
- this.zipCache.set(zip, null);
202
+ if (this.logger) this.logger.warn(`GeoDb: unknown zipcode=${zip5}`);
203
+ this.zipCache.set(zip5, null);
184
204
  return null;
185
205
  }
186
206
 
187
- result.ZipCode = String(zip);
207
+ result.ZipCode = String(zip5);
188
208
  const location = this.makeZipLocation(result);
189
- this.zipCache.set(zip, location);
209
+ this.zipCache.set(zip5, location);
190
210
  return location;
191
211
  }
192
212
  /**
@@ -272,6 +292,12 @@ class GeoDb {
272
292
  if (geonameid) {
273
293
  return this.lookupGeoname(geonameid);
274
294
  } else {
295
+ const location = core.Location.lookup(cityName);
296
+
297
+ if (location) {
298
+ return location;
299
+ }
300
+
275
301
  if (this.logger) this.logger.warn(`GeoDb: unknown city=${cityName}`);
276
302
  return null;
277
303
  }
@@ -420,20 +446,25 @@ class GeoDb {
420
446
 
421
447
  /**
422
448
  * Builds `geonames.sqlite3` from files downloaded from geonames.org
423
- * @param {string} dbFilename
424
- * @param {string} countryInfotxt
425
- * @param {string} cities5000txt
426
- * @param {string} citiesPatch
427
- * @param {string} admin1CodesASCIItxt
428
- * @param {string} ILtxt
449
+ * @param {any} opts
429
450
  */
430
451
 
431
- async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, citiesPatch, admin1CodesASCIItxt, ILtxt) {
452
+ async function buildGeonamesSqlite(opts) {
453
+ const dbFilename = opts.dbFilename;
454
+ const countryInfotxt = opts.countryInfotxt;
455
+ const cities5000txt = opts.cities5000txt;
456
+ const citiesPatch = opts.citiesPatch;
457
+ const admin1CodesASCIItxt = opts.admin1CodesASCIItxt;
458
+ const ILtxt = opts.ILtxt;
459
+ const ILalternate = opts.ILalternate;
432
460
  const logger = pino__default["default"]({
433
461
  // level: argv.quiet ? 'warn' : 'info',
434
- prettyPrint: {
435
- translateTime: true,
436
- ignore: 'pid,hostname'
462
+ transport: {
463
+ target: 'pino-pretty',
464
+ options: {
465
+ translateTime: 'SYS:standard',
466
+ ignore: 'pid,hostname'
467
+ }
437
468
  }
438
469
  });
439
470
  const db = new Database__default["default"](dbFilename);
@@ -498,8 +529,41 @@ async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, ci
498
529
  WHERE geonameid = 4140963;`, `UPDATE admin1
499
530
  SET name = 'Washington, D.C.', asciiname = 'Washington, D.C.'
500
531
  WHERE key = 'US.DC';`);
501
- doSql(logger, db, `DROP TABLE IF EXISTS geoname_he`, `CREATE TABLE geoname_he AS SELECT * FROM geoname LIMIT 0`);
502
- await doFile(logger, db, ILtxt, 'geoname_he', 19, filterPlacesHebrew);
532
+ doSql(logger, db, `DROP TABLE IF EXISTS alternatenames`, `CREATE TABLE alternatenames (
533
+ id int PRIMARY KEY,
534
+ geonameid int NOT NULL,
535
+ isolanguage varchar(7),
536
+ name varchar(400),
537
+ isPreferredName tinyint,
538
+ isShortName tinyint,
539
+ isColloquial tinyint,
540
+ isHistoric tinyint,
541
+ periodFrom NULL,
542
+ periodTo NULL
543
+ );`);
544
+ await doFile(logger, db, ILalternate, 'alternatenames', 10, a => {
545
+ if (a[2] === 'he' || a[2] === 'en') {
546
+ if (a[2] === 'he') {
547
+ a[3] = a[3].replace(/‘/g, '׳');
548
+ a[3] = a[3].replace(/’/g, '׳');
549
+ a[3] = a[3].replace(/\'/g, '׳');
550
+ a[3] = core.Locale.hebrewStripNikkud(a[3]);
551
+ } else {
552
+ a[3] = a[3].replace(/‘/g, '\'');
553
+ a[3] = a[3].replace(/’/g, '\'');
554
+ }
555
+
556
+ return true;
557
+ }
558
+
559
+ return false;
560
+ }); // remove duplicates from alternatenames
561
+
562
+ doSql(logger, db, `DROP TABLE IF EXISTS altnames`, `CREATE TABLE altnames
563
+ AS SELECT geonameid, isolanguage, name
564
+ FROM alternatenames
565
+ GROUP BY 1, 2, 3
566
+ `);
503
567
  doSql(logger, db, `update admin1 set name='',asciiname='' where key like 'PS.%';`, `update country set country = '' where iso = 'PS';`, `delete from geoname where geonameid = 7303419;`);
504
568
  doSql(logger, db, `DROP TABLE IF EXISTS geoname_fulltext`, `CREATE VIRTUAL TABLE geoname_fulltext
505
569
  USING fts3(geonameid int, longname text,
@@ -530,47 +594,23 @@ async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, ci
530
594
  AND g.country = c.ISO
531
595
  AND g.country||'.'||g.admin1 = a.key
532
596
  `, `INSERT INTO geoname_fulltext
533
- SELECT g.geonameid, g.name||', ישראל',
534
- g.name, '', 'ישראל',
597
+ SELECT g.geonameid, alt.name||', ישראל',
598
+ alt.name, '', 'ישראל',
535
599
  g.population, g.latitude, g.longitude, g.timezone
536
- FROM geoname_he g, admin1 a, country c
537
- WHERE g.country = c.ISO
538
- AND g.country||'.'||g.admin1 = a.key
600
+ FROM geoname g, country c, altnames alt
601
+ WHERE g.country = 'IL'
602
+ AND alt.isolanguage = 'he'
603
+ AND g.geonameid = alt.geonameid
539
604
  `);
540
605
  db.close();
541
606
  return Promise.resolve(true);
542
607
  }
543
- /**
544
- * @param {string[]} a
545
- * @return {boolean}
546
- */
547
-
548
- function filterPlacesHebrew(a) {
549
- if (a[6] != 'P' || a[7] != 'PPL' && a[7] != 'STLMT') {
550
- return false;
551
- }
552
-
553
- const alternatenames = a[3].split(',');
554
-
555
- for (const name of alternatenames) {
556
- const firstchar = name[0];
557
-
558
- if (firstchar >= '\u05D0' && firstchar <= '\u05EA') {
559
- a[1] = core.Locale.hebrewStripNikkud(name); // replace 'name' field with Hebrew
560
-
561
- return true;
562
- }
563
- }
564
-
565
- return false;
566
- }
567
608
  /**
568
609
  * @param {pino.Logger} logger
569
610
  * @param {Database} db
570
611
  * @param {...string} sqls
571
612
  */
572
613
 
573
-
574
614
  function doSql(logger, db, ...sqls) {
575
615
  for (let i = 0; i < sqls.length; i++) {
576
616
  logger.info(sqls[i]);
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- /*! @hebcal/geo-sqlite v3.7.1 */
1
+ /*! @hebcal/geo-sqlite v4.0.0 */
2
2
  import Database from 'better-sqlite3';
3
3
  import { Location, Locale } from '@hebcal/core';
4
+ import '@hebcal/cities';
4
5
  import pino from 'pino';
5
6
  import events from 'events';
6
7
  import fs from 'fs';
@@ -129,14 +130,32 @@ class GeoDb {
129
130
  fileMustExist: true
130
131
  });
131
132
  this.zipStmt = this.zipsDb.prepare(ZIPCODE_SQL);
133
+ /** @type {Map<string, Location>} */
134
+
132
135
  this.zipCache = new Map();
133
136
  this.geonamesStmt = this.geonamesDb.prepare(GEONAME_SQL);
137
+ /** @type {Map<number, Location>} */
138
+
134
139
  this.geonamesCache = new Map();
140
+ /** @type {Map<string, Location>} */
141
+
135
142
  this.legacyCities = new Map();
136
143
 
137
144
  for (const [name, id] of Object.entries(city2geonameid)) {
138
145
  this.legacyCities.set(GeoDb.munge(name), id);
139
146
  }
147
+
148
+ const stmt = this.geonamesDb.prepare(`SELECT ISO, Country FROM country WHERE Country <> ''`);
149
+ const rows = stmt.all();
150
+ const map = new Map();
151
+
152
+ for (const row of rows) {
153
+ map.set(row.ISO, row.Country);
154
+ }
155
+ /** @type {Map<string, string>} */
156
+
157
+
158
+ this.countryNames = map;
140
159
  }
141
160
  /** Closes database handles */
142
161
 
@@ -162,19 +181,20 @@ class GeoDb {
162
181
 
163
182
 
164
183
  lookupZip(zip) {
165
- const found = this.zipCache.get(zip);
184
+ const zip5 = zip.trim().substring(0, 5);
185
+ const found = this.zipCache.get(zip5);
166
186
  if (typeof found !== 'undefined') return found;
167
- const result = this.zipStmt.get(zip);
187
+ const result = this.zipStmt.get(zip5);
168
188
 
169
189
  if (!result) {
170
- if (this.logger) this.logger.warn(`GeoDb: unknown zipcode=${zip}`);
171
- this.zipCache.set(zip, null);
190
+ if (this.logger) this.logger.warn(`GeoDb: unknown zipcode=${zip5}`);
191
+ this.zipCache.set(zip5, null);
172
192
  return null;
173
193
  }
174
194
 
175
- result.ZipCode = String(zip);
195
+ result.ZipCode = String(zip5);
176
196
  const location = this.makeZipLocation(result);
177
- this.zipCache.set(zip, location);
197
+ this.zipCache.set(zip5, location);
178
198
  return location;
179
199
  }
180
200
  /**
@@ -260,6 +280,12 @@ class GeoDb {
260
280
  if (geonameid) {
261
281
  return this.lookupGeoname(geonameid);
262
282
  } else {
283
+ const location = Location.lookup(cityName);
284
+
285
+ if (location) {
286
+ return location;
287
+ }
288
+
263
289
  if (this.logger) this.logger.warn(`GeoDb: unknown city=${cityName}`);
264
290
  return null;
265
291
  }
@@ -408,20 +434,25 @@ class GeoDb {
408
434
 
409
435
  /**
410
436
  * Builds `geonames.sqlite3` from files downloaded from geonames.org
411
- * @param {string} dbFilename
412
- * @param {string} countryInfotxt
413
- * @param {string} cities5000txt
414
- * @param {string} citiesPatch
415
- * @param {string} admin1CodesASCIItxt
416
- * @param {string} ILtxt
437
+ * @param {any} opts
417
438
  */
418
439
 
419
- async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, citiesPatch, admin1CodesASCIItxt, ILtxt) {
440
+ async function buildGeonamesSqlite(opts) {
441
+ const dbFilename = opts.dbFilename;
442
+ const countryInfotxt = opts.countryInfotxt;
443
+ const cities5000txt = opts.cities5000txt;
444
+ const citiesPatch = opts.citiesPatch;
445
+ const admin1CodesASCIItxt = opts.admin1CodesASCIItxt;
446
+ const ILtxt = opts.ILtxt;
447
+ const ILalternate = opts.ILalternate;
420
448
  const logger = pino({
421
449
  // level: argv.quiet ? 'warn' : 'info',
422
- prettyPrint: {
423
- translateTime: true,
424
- ignore: 'pid,hostname'
450
+ transport: {
451
+ target: 'pino-pretty',
452
+ options: {
453
+ translateTime: 'SYS:standard',
454
+ ignore: 'pid,hostname'
455
+ }
425
456
  }
426
457
  });
427
458
  const db = new Database(dbFilename);
@@ -486,8 +517,41 @@ async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, ci
486
517
  WHERE geonameid = 4140963;`, `UPDATE admin1
487
518
  SET name = 'Washington, D.C.', asciiname = 'Washington, D.C.'
488
519
  WHERE key = 'US.DC';`);
489
- doSql(logger, db, `DROP TABLE IF EXISTS geoname_he`, `CREATE TABLE geoname_he AS SELECT * FROM geoname LIMIT 0`);
490
- await doFile(logger, db, ILtxt, 'geoname_he', 19, filterPlacesHebrew);
520
+ doSql(logger, db, `DROP TABLE IF EXISTS alternatenames`, `CREATE TABLE alternatenames (
521
+ id int PRIMARY KEY,
522
+ geonameid int NOT NULL,
523
+ isolanguage varchar(7),
524
+ name varchar(400),
525
+ isPreferredName tinyint,
526
+ isShortName tinyint,
527
+ isColloquial tinyint,
528
+ isHistoric tinyint,
529
+ periodFrom NULL,
530
+ periodTo NULL
531
+ );`);
532
+ await doFile(logger, db, ILalternate, 'alternatenames', 10, a => {
533
+ if (a[2] === 'he' || a[2] === 'en') {
534
+ if (a[2] === 'he') {
535
+ a[3] = a[3].replace(/‘/g, '׳');
536
+ a[3] = a[3].replace(/’/g, '׳');
537
+ a[3] = a[3].replace(/\'/g, '׳');
538
+ a[3] = Locale.hebrewStripNikkud(a[3]);
539
+ } else {
540
+ a[3] = a[3].replace(/‘/g, '\'');
541
+ a[3] = a[3].replace(/’/g, '\'');
542
+ }
543
+
544
+ return true;
545
+ }
546
+
547
+ return false;
548
+ }); // remove duplicates from alternatenames
549
+
550
+ doSql(logger, db, `DROP TABLE IF EXISTS altnames`, `CREATE TABLE altnames
551
+ AS SELECT geonameid, isolanguage, name
552
+ FROM alternatenames
553
+ GROUP BY 1, 2, 3
554
+ `);
491
555
  doSql(logger, db, `update admin1 set name='',asciiname='' where key like 'PS.%';`, `update country set country = '' where iso = 'PS';`, `delete from geoname where geonameid = 7303419;`);
492
556
  doSql(logger, db, `DROP TABLE IF EXISTS geoname_fulltext`, `CREATE VIRTUAL TABLE geoname_fulltext
493
557
  USING fts3(geonameid int, longname text,
@@ -518,47 +582,23 @@ async function buildGeonamesSqlite(dbFilename, countryInfotxt, cities5000txt, ci
518
582
  AND g.country = c.ISO
519
583
  AND g.country||'.'||g.admin1 = a.key
520
584
  `, `INSERT INTO geoname_fulltext
521
- SELECT g.geonameid, g.name||', ישראל',
522
- g.name, '', 'ישראל',
585
+ SELECT g.geonameid, alt.name||', ישראל',
586
+ alt.name, '', 'ישראל',
523
587
  g.population, g.latitude, g.longitude, g.timezone
524
- FROM geoname_he g, admin1 a, country c
525
- WHERE g.country = c.ISO
526
- AND g.country||'.'||g.admin1 = a.key
588
+ FROM geoname g, country c, altnames alt
589
+ WHERE g.country = 'IL'
590
+ AND alt.isolanguage = 'he'
591
+ AND g.geonameid = alt.geonameid
527
592
  `);
528
593
  db.close();
529
594
  return Promise.resolve(true);
530
595
  }
531
- /**
532
- * @param {string[]} a
533
- * @return {boolean}
534
- */
535
-
536
- function filterPlacesHebrew(a) {
537
- if (a[6] != 'P' || a[7] != 'PPL' && a[7] != 'STLMT') {
538
- return false;
539
- }
540
-
541
- const alternatenames = a[3].split(',');
542
-
543
- for (const name of alternatenames) {
544
- const firstchar = name[0];
545
-
546
- if (firstchar >= '\u05D0' && firstchar <= '\u05EA') {
547
- a[1] = Locale.hebrewStripNikkud(name); // replace 'name' field with Hebrew
548
-
549
- return true;
550
- }
551
- }
552
-
553
- return false;
554
- }
555
596
  /**
556
597
  * @param {pino.Logger} logger
557
598
  * @param {Database} db
558
599
  * @param {...string} sqls
559
600
  */
560
601
 
561
-
562
602
  function doSql(logger, db, ...sqls) {
563
603
  for (let i = 0; i < sqls.length; i++) {
564
604
  logger.info(sqls[i]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hebcal/geo-sqlite",
3
- "version": "3.7.1",
3
+ "version": "4.0.0",
4
4
  "author": "Michael J. Radwin (https://github.com/mjradwin)",
5
5
  "keywords": [
6
6
  "hebcal"
@@ -28,10 +28,11 @@
28
28
  "geo-sqlite.d.ts"
29
29
  ],
30
30
  "dependencies": {
31
- "@hebcal/core": "^3.32.1",
32
- "better-sqlite3": "^7.4.6",
33
- "pino": "^7.6.3",
34
- "pino-pretty": "^7.3.0"
31
+ "@hebcal/cities": "^3.1.1",
32
+ "@hebcal/core": "^3.33.3",
33
+ "better-sqlite3": "^7.5.0",
34
+ "pino": "^7.8.0",
35
+ "pino-pretty": "^7.5.3"
35
36
  },
36
37
  "scripts": {
37
38
  "build": "rollup -c",
@@ -45,34 +46,25 @@
45
46
  ],
46
47
  "require": [
47
48
  "@babel/register",
48
- "@babel/polyfill"
49
+ "regenerator-runtime/runtime"
49
50
  ],
50
- "babel": {
51
- "testOptions": {
52
- "presets": [
53
- "@babel/env"
54
- ]
55
- }
56
- },
57
51
  "inherit": true,
58
52
  "verbose": true
59
53
  },
60
54
  "license": "BSD-2-Clause",
61
55
  "devDependencies": {
62
- "@ava/babel": "^2.0.0",
63
- "@babel/core": "^7.16.7",
64
- "@babel/polyfill": "^7.12.1",
65
- "@babel/preset-env": "^7.16.8",
66
- "@babel/register": "^7.16.8",
67
- "@rollup/plugin-babel": "^5.3.0",
68
- "@rollup/plugin-commonjs": "^21.0.1",
56
+ "@babel/core": "^7.17.5",
57
+ "@babel/preset-env": "^7.16.11",
58
+ "@babel/register": "^7.17.0",
59
+ "@rollup/plugin-babel": "^5.3.1",
60
+ "@rollup/plugin-commonjs": "^21.0.2",
69
61
  "@rollup/plugin-json": "^4.1.0",
70
62
  "@rollup/plugin-node-resolve": "^13.1.3",
71
- "ava": "^3.15.0",
72
- "eslint": "^8.6.0",
63
+ "ava": "^4.0.1",
64
+ "eslint": "^8.10.0",
73
65
  "eslint-config-google": "^0.14.0",
74
- "jsdoc": "^3.6.7",
75
- "jsdoc-to-markdown": "^7.1.0",
76
- "rollup": "^2.63.0"
66
+ "jsdoc": "^3.6.10",
67
+ "jsdoc-to-markdown": "^7.1.1",
68
+ "rollup": "^2.69.0"
77
69
  }
78
70
  }