text-to-map 1.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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -0
  3. package/dist/bin/address-points-import-only.d.ts +1 -0
  4. package/dist/bin/address-points-import-only.js +14 -0
  5. package/dist/bin/address-points.d.ts +1 -0
  6. package/dist/bin/address-points.js +14 -0
  7. package/dist/bin/clear-all.d.ts +1 -0
  8. package/dist/bin/clear-all.js +5 -0
  9. package/dist/bin/generate-syntax-diagrams.d.ts +1 -0
  10. package/dist/bin/generate-syntax-diagrams.js +19 -0
  11. package/dist/bin/parse-ordinance.d.ts +1 -0
  12. package/dist/bin/parse-ordinance.js +20 -0
  13. package/dist/bin/schools.d.ts +1 -0
  14. package/dist/bin/schools.js +8 -0
  15. package/dist/bin/streets.d.ts +1 -0
  16. package/dist/bin/streets.js +14 -0
  17. package/dist/db/address-points.d.ts +38 -0
  18. package/dist/db/address-points.js +312 -0
  19. package/dist/db/db.d.ts +23 -0
  20. package/dist/db/db.js +90 -0
  21. package/dist/db/founders.d.ts +9 -0
  22. package/dist/db/founders.js +191 -0
  23. package/dist/db/meta.d.ts +3 -0
  24. package/dist/db/meta.js +17 -0
  25. package/dist/db/schools.d.ts +6 -0
  26. package/dist/db/schools.js +61 -0
  27. package/dist/db/street-sync.d.ts +5 -0
  28. package/dist/db/street-sync.js +18 -0
  29. package/dist/db/types.d.ts +34 -0
  30. package/dist/db/types.js +12 -0
  31. package/dist/index.d.ts +4 -0
  32. package/dist/index.js +4 -0
  33. package/dist/open-data-sync/address-points.d.ts +4 -0
  34. package/dist/open-data-sync/address-points.js +73 -0
  35. package/dist/open-data-sync/schools.d.ts +3 -0
  36. package/dist/open-data-sync/schools.js +308 -0
  37. package/dist/open-data-sync/streets.d.ts +2 -0
  38. package/dist/open-data-sync/streets.js +77 -0
  39. package/dist/smd-to-csv/json-to-csv.d.ts +1 -0
  40. package/dist/smd-to-csv/json-to-csv.js +153 -0
  41. package/dist/smd-to-csv/ordinance-to-csv-rules.d.ts +1 -0
  42. package/dist/smd-to-csv/ordinance-to-csv-rules.js +31 -0
  43. package/dist/smd-to-csv/txt-to-json.d.ts +1 -0
  44. package/dist/smd-to-csv/txt-to-json.js +102 -0
  45. package/dist/street-markdown/municipality.d.ts +5 -0
  46. package/dist/street-markdown/municipality.js +44 -0
  47. package/dist/street-markdown/roman-numerals.d.ts +1 -0
  48. package/dist/street-markdown/roman-numerals.js +67 -0
  49. package/dist/street-markdown/smd-line-parser.d.ts +4 -0
  50. package/dist/street-markdown/smd-line-parser.js +29 -0
  51. package/dist/street-markdown/smd-parser.d.ts +21 -0
  52. package/dist/street-markdown/smd-parser.js +343 -0
  53. package/dist/street-markdown/smd.d.ts +9 -0
  54. package/dist/street-markdown/smd.js +182 -0
  55. package/dist/street-markdown/token-definition.d.ts +23 -0
  56. package/dist/street-markdown/token-definition.js +154 -0
  57. package/dist/street-markdown/types.d.ts +83 -0
  58. package/dist/street-markdown/types.js +18 -0
  59. package/dist/utils/addressBuilder.d.ts +8 -0
  60. package/dist/utils/addressBuilder.js +101 -0
  61. package/dist/utils/atom.d.ts +2 -0
  62. package/dist/utils/atom.js +65 -0
  63. package/dist/utils/helpers.d.ts +34 -0
  64. package/dist/utils/helpers.js +157 -0
  65. package/package.json +82 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Marek Lisy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,20 @@
1
+ # Text To Map
2
+ Text To Map usiluje o lepší, strojově zpracovatelné využití částí vyhlášek s výčtem ulic a dalších lokací. Jde o rozšiřitelnou sadu konceptů a nástrojů, které zajistí hladký převod výčtu ulic a jejich rozsahů v lidsky srozumitelném jazyce do strojově zpracovatelného, uchopitelného formátu.
3
+
4
+ ## Odkazy
5
+ - [text-to-map-frontend](https://github.com/maral/text-to-map-frontend) - mapa zobrazující výstupy této knihovny
6
+
7
+ ## Živě na webu
8
+ - [Spádové oblasti - Praha](https://mareklisy.cz/spadovosti-praha/)
9
+
10
+ ## Návod na použití
11
+ 1. `npm install`
12
+ 2. `npm run address-points` - stažení a import adresních bodů z RÚIAN
13
+ 3. `npm run schools` - stažení a import dat o školách a zřizovatelích z rejstříku MŠMT
14
+ 4. `npm run streets` - dotažení všech ulic z RÚIAN (nepovinné, ale zmizí tím warningy o neexistujících ulicích ve vyhlášce v případech, kdy jsou ve vyhlášce ulice bez jediného adresního bodu)
15
+ 5. `npm run parse-ordinance /some/path/input.txt /path/output.json` - parsování vyhlášky a namapování adresních míst na spádové školy
16
+
17
+ Pro vyzkoušení je možné použít již vyčištěné vyhlášky ze složky *examples*.
18
+
19
+
20
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/E1E5JOMLT)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { importAllLatestAddressPoints } from "../open-data-sync/address-points";
2
+ console.time("importOnly");
3
+ importAllLatestAddressPoints({})
4
+ .then(() => {
5
+ console.timeEnd("importOnly");
6
+ })
7
+ .catch((error) => {
8
+ if (error.hasOwnProperty("stack")) {
9
+ console.log(error, error.stack);
10
+ }
11
+ else {
12
+ console.log(error);
13
+ }
14
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { downloadAndImportAllLatestAddressPoints } from "../open-data-sync/address-points";
2
+ console.time("downloadAndImportAllLatestAddressPoints");
3
+ downloadAndImportAllLatestAddressPoints({})
4
+ .then(() => {
5
+ console.timeEnd("downloadAndImportAllLatestAddressPoints");
6
+ })
7
+ .catch((error) => {
8
+ if (error.hasOwnProperty("stack")) {
9
+ console.log(error, error.stack);
10
+ }
11
+ else {
12
+ console.log(error);
13
+ }
14
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import { deleteDb } from "../open-data-sync/address-points";
2
+ import { deleteSchoolsXmlFile } from "../open-data-sync/schools";
3
+ deleteDb();
4
+ deleteSchoolsXmlFile();
5
+ console.log("Everything cleared, ready for a new sync.");
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { writeFileSync } from "fs";
2
+ import { createSyntaxDiagramsCode } from "chevrotain";
3
+ import { smdParser } from "../street-markdown/smd-line-parser";
4
+ // extract the serialized grammar.
5
+ const serializedGrammar = smdParser.getSerializedGastProductions();
6
+ // create the HTML Text
7
+ const htmlText = `
8
+ <!DOCTYPE html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="UTF-8">
12
+ <title>SMD street line Syntax Diagrams</title>
13
+ </head>
14
+ <body>
15
+ ${createSyntaxDiagramsCode(serializedGrammar)}
16
+ </body>
17
+ </html>`;
18
+ // Write the HTML file to disk
19
+ writeFileSync("generated_diagrams.html", htmlText);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { readFileSync, writeFileSync } from "fs";
2
+ import { parseOrdinanceToAddressPoints } from "../street-markdown/smd";
3
+ // take first node argument as a file name
4
+ if (process.argv.length < 3) {
5
+ console.error("Missing file name argument");
6
+ process.exit(1);
7
+ }
8
+ const fileName = process.argv[2];
9
+ const fileContent = readFileSync(fileName);
10
+ const lines = fileContent.toString().split("\n");
11
+ const addressPoints = parseOrdinanceToAddressPoints(lines);
12
+ if (process.argv.length >= 4) {
13
+ const outputFileName = process.argv[3];
14
+ const output = JSON.stringify(addressPoints);
15
+ console.log(`Writing output to ${outputFileName}`);
16
+ writeFileSync(outputFileName, output);
17
+ }
18
+ else {
19
+ console.log(JSON.stringify(addressPoints));
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { downloadAndImportAllSchools } from "../open-data-sync/schools";
2
+ downloadAndImportAllSchools({})
3
+ .then(() => {
4
+ console.log("Completed");
5
+ })
6
+ .catch((error) => {
7
+ console.log(error);
8
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { downloadAndImportAllStreets } from "../open-data-sync/streets";
2
+ console.time("downloadAndImportAllStreets");
3
+ downloadAndImportAllStreets({})
4
+ .then(() => {
5
+ console.timeEnd("downloadAndImportAllStreets");
6
+ })
7
+ .catch((error) => {
8
+ if (error.hasOwnProperty("stack")) {
9
+ console.log(error, error.stack);
10
+ }
11
+ else {
12
+ console.log(error);
13
+ }
14
+ });
@@ -0,0 +1,38 @@
1
+ import { AddressPoint, FullStreetNumber, RangeSpec, SeriesType, SmdLine, WholeMunicipalitySmdLine } from "../street-markdown/types";
2
+ import { Founder, Municipality } from "./types";
3
+ export declare const Column: {
4
+ admCode: number;
5
+ cityCode: number;
6
+ cityName: number;
7
+ districtCode: number;
8
+ districtName: number;
9
+ pragueDistrictCode: number;
10
+ pragueDistrictName: number;
11
+ municipalityPartCode: number;
12
+ municipalityPartName: number;
13
+ streetCode: number;
14
+ streetName: number;
15
+ objectType: number;
16
+ houseNumber: number;
17
+ orientationalNumber: number;
18
+ orientationalNumberLetter: number;
19
+ postalCode: number;
20
+ yCoordinate: number;
21
+ xCoordinate: number;
22
+ validFrom: number;
23
+ };
24
+ export declare const importParsedLine: (data: string[]) => number;
25
+ export declare const commitAddressPoints: () => number;
26
+ export declare const insertCities: (buffer: string[][]) => number;
27
+ export declare const insertDistricts: (buffer: string[][]) => number;
28
+ export declare const insertMunicipalityParts: (buffer: string[][]) => number;
29
+ export declare const insertStreets: (buffer: string[][]) => number;
30
+ export declare const getAddressPointById: (addressPointId: number) => AddressPoint | null;
31
+ export declare const checkStreetExists: (streetName: string, founder: Founder) => {
32
+ exists: boolean;
33
+ errors: string[];
34
+ };
35
+ export declare const findAddressPoints: (smdLine: SmdLine | WholeMunicipalitySmdLine, municipality: Municipality) => AddressPoint[];
36
+ export declare const isInRange: (number: number | null, letter: string | null, range: RangeSpec) => boolean;
37
+ export declare const fitsType: (number: number | null, type: SeriesType) => boolean;
38
+ export declare const equalsFullStreetNumber: (fullStreetNumber: FullStreetNumber, addressPoint: AddressPoint) => boolean;
@@ -0,0 +1,312 @@
1
+ import { extractKeyValuesPairs, getDb, insertMultipleRows, nonEmptyOrNull, } from "./db";
2
+ import jtsk2wgs84 from "@arodax/jtsk2wgs84";
3
+ import { AddressPointType, isNegativeSeriesSpec, isRange, isSeriesSpecArray, isWholeMunicipalitySmdLine, SeriesType, } from "../street-markdown/types";
4
+ import { MunicipalityType } from "./types";
5
+ import { findClosestString } from "../utils/helpers";
6
+ import { getFounderCityCode } from "./founders";
7
+ import { buildInline } from "../utils/addressBuilder";
8
+ const buffer = [];
9
+ const MaxBufferSize = 1000;
10
+ const DescriptiveType = "č.p.";
11
+ const RegistrationType = "č.ev.";
12
+ const ObjectTypes = {
13
+ [DescriptiveType]: 1,
14
+ [RegistrationType]: 2,
15
+ };
16
+ export const Column = {
17
+ admCode: 0,
18
+ cityCode: 1,
19
+ cityName: 2,
20
+ districtCode: 3,
21
+ districtName: 4,
22
+ pragueDistrictCode: 5,
23
+ pragueDistrictName: 6,
24
+ municipalityPartCode: 7,
25
+ municipalityPartName: 8,
26
+ streetCode: 9,
27
+ streetName: 10,
28
+ objectType: 11,
29
+ houseNumber: 12,
30
+ orientationalNumber: 13,
31
+ orientationalNumberLetter: 14,
32
+ postalCode: 15,
33
+ yCoordinate: 16,
34
+ xCoordinate: 17,
35
+ validFrom: 18,
36
+ };
37
+ export const importParsedLine = (data) => {
38
+ buffer.push(data);
39
+ if (buffer.length >= MaxBufferSize) {
40
+ return commitAddressPoints();
41
+ }
42
+ return 0;
43
+ };
44
+ export const commitAddressPoints = () => {
45
+ if (buffer.length === 0) {
46
+ return 0;
47
+ }
48
+ insertCities(buffer);
49
+ insertDistricts(buffer);
50
+ insertMunicipalityParts(buffer);
51
+ insertStreets(buffer);
52
+ const db = getDb();
53
+ const placeHolders = new Array(buffer.length)
54
+ .fill("(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
55
+ .join(", ");
56
+ const insertStatement = db.prepare(`INSERT OR IGNORE INTO address_point
57
+ (id, street_code, object_type_id, descriptive_number, orientational_number, orientational_number_letter, city_code, city_district_code, municipality_part_code, postal_code, jtsk_x, jtsk_y, wgs84_latitude, wgs84_longitude)
58
+ VALUES ${placeHolders}`);
59
+ const params = [];
60
+ buffer.forEach((data) => {
61
+ let latOrNull, lonOrNull;
62
+ latOrNull = null;
63
+ lonOrNull = null;
64
+ if (data[Column.xCoordinate] && data[Column.yCoordinate]) {
65
+ const { lat, lon } = jtsk2wgs84(parseFloat(data[Column.xCoordinate]), parseFloat(data[Column.yCoordinate]));
66
+ [latOrNull, lonOrNull] = [lat.toString(), lon.toString()];
67
+ }
68
+ params.push(data[Column.admCode], nonEmptyOrNull(data[Column.streetCode]), ObjectTypes[data[Column.objectType]], nonEmptyOrNull(data[Column.houseNumber]), nonEmptyOrNull(data[Column.orientationalNumber]), nonEmptyOrNull(data[Column.orientationalNumberLetter]), data[Column.cityCode], nonEmptyOrNull(data[Column.districtCode]), nonEmptyOrNull(data[Column.municipalityPartCode]), data[Column.postalCode], nonEmptyOrNull(data[Column.xCoordinate]), nonEmptyOrNull(data[Column.yCoordinate]), latOrNull, lonOrNull);
69
+ });
70
+ buffer.length = 0;
71
+ return insertStatement.run(params).changes;
72
+ };
73
+ export const insertCities = (buffer) => {
74
+ return insertMultipleRows(extractKeyValuesPairs(buffer, Column.cityCode, [Column.cityName]), "city", ["code", "name"]);
75
+ };
76
+ export const insertDistricts = (buffer) => {
77
+ return insertMultipleRows(extractKeyValuesPairs(buffer, Column.districtCode, [
78
+ Column.cityCode,
79
+ Column.districtName,
80
+ ]), "city_district", ["code", "city_code", "name"]);
81
+ };
82
+ export const insertMunicipalityParts = (buffer) => {
83
+ return insertMultipleRows(extractKeyValuesPairs(buffer, Column.municipalityPartCode, [
84
+ Column.municipalityPartName,
85
+ ]), "municipality_part", ["code", "name"]);
86
+ };
87
+ export const insertStreets = (buffer) => {
88
+ return insertMultipleRows(extractKeyValuesPairs(buffer, Column.streetCode, [
89
+ Column.cityCode,
90
+ Column.streetName,
91
+ ]), "street", ["code", "city_code", "name"]);
92
+ };
93
+ const addressPointSelect = `
94
+ SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
95
+ a.orientational_number, a.orientational_number_letter, c.name AS city_name,
96
+ m.name AS municipality_part_name, d.name AS district_name, a.postal_code,
97
+ a.wgs84_latitude, a.wgs84_longitude
98
+ FROM address_point a
99
+ LEFT JOIN street s ON a.street_code = s.code
100
+ INNER JOIN object_type o ON o.id = a.object_type_id
101
+ INNER JOIN city c ON c.code = a.city_code
102
+ LEFT JOIN city_district d ON a.city_district_code = d.code
103
+ LEFT JOIN municipality_part m ON a.municipality_part_code = m.code`;
104
+ export const getAddressPointById = (addressPointId) => {
105
+ const db = getDb();
106
+ const statement = db.prepare(`${addressPointSelect}
107
+ WHERE a.id = ?`);
108
+ const row = statement.get(addressPointId);
109
+ if (!row) {
110
+ return null;
111
+ }
112
+ return rowToAddressPoint(row);
113
+ };
114
+ let lastFounder = null;
115
+ let allStreets = [];
116
+ export const checkStreetExists = (streetName, founder) => {
117
+ const db = getDb();
118
+ const errors = [];
119
+ // we check the whole city
120
+ const cityCode = getFounderCityCode(founder);
121
+ const statement = db.prepare(`
122
+ SELECT name AS street_name
123
+ FROM street
124
+ WHERE city_code = ? AND name = ? COLLATE NOCASE`);
125
+ const row = statement.get(cityCode, streetName);
126
+ if (row) {
127
+ if (row.street_name !== streetName) {
128
+ // errors.push(
129
+ // `Street '${streetName}' has wrong case, correct case: '${row.street_name}'.`
130
+ // );
131
+ }
132
+ return { exists: true, errors };
133
+ }
134
+ if (lastFounder !== founder) {
135
+ allStreets = getAllStreets(cityCode);
136
+ lastFounder = founder;
137
+ }
138
+ const match = allStreets.find((s) => s.toLocaleLowerCase("cs-CZ") === streetName.toLocaleLowerCase("cs-CZ"));
139
+ let exists = false;
140
+ if (match) {
141
+ exists = true;
142
+ if (streetName !== match) {
143
+ // errors.push(
144
+ // `Street '${streetName}' has wrong case, correct case: '${match}'.`
145
+ // );
146
+ }
147
+ exists = true;
148
+ }
149
+ else {
150
+ const closest = findClosestString(streetName, allStreets);
151
+ errors.push(`Street '${streetName}' does not exist, did you mean '${closest}'?`);
152
+ }
153
+ return { exists: false, errors };
154
+ };
155
+ const getAllStreets = (cityCode) => {
156
+ const db = getDb();
157
+ const statement = db.prepare(`
158
+ SELECT s.name AS street_name
159
+ FROM street s
160
+ WHERE city_code = ?`);
161
+ const rows = statement.all(cityCode);
162
+ return rows.map((row) => row.street_name);
163
+ };
164
+ export const findAddressPoints = (smdLine, municipality) => {
165
+ const db = getDb();
166
+ const streetJoinCondition = isWholeMunicipalitySmdLine(smdLine)
167
+ ? "LEFT JOIN street s ON a.street_code = s.code"
168
+ : "JOIN street s ON a.street_code = s.code AND s.name = ? COLLATE NOCASE";
169
+ const statement = db.prepare(`
170
+ SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
171
+ a.orientational_number, a.orientational_number_letter, c.name AS city_name,
172
+ m.name AS municipality_part_name, d.name AS district_name, a.postal_code,
173
+ a.wgs84_latitude, a.wgs84_longitude
174
+ FROM address_point a
175
+ ${streetJoinCondition}
176
+ INNER JOIN object_type o ON o.id = a.object_type_id
177
+ INNER JOIN city c ON c.code = a.city_code
178
+ LEFT JOIN city_district d ON a.city_district_code = d.code
179
+ LEFT JOIN municipality_part m ON a.municipality_part_code = m.code
180
+ WHERE ${getMunicipalityWhere("a", municipality)}`);
181
+ const params = isWholeMunicipalitySmdLine(smdLine)
182
+ ? [municipality.code]
183
+ : [smdLine.street, municipality.code];
184
+ const filteredAddressPoints = statement.all(...params).map(rowToAddressPoint);
185
+ if (isWholeMunicipalitySmdLine(smdLine)) {
186
+ return filteredAddressPoints;
187
+ }
188
+ const result = [];
189
+ const numberSpec = smdLine.numberSpec;
190
+ if (isSeriesSpecArray(numberSpec)) {
191
+ numberSpec.forEach((seriesSpec) => {
192
+ result.push(...filterAddressPointsByRanges(filteredAddressPoints, seriesSpec));
193
+ });
194
+ // when no number specs are present, we take everything
195
+ if (numberSpec.length === 0) {
196
+ result.push(...filteredAddressPoints);
197
+ }
198
+ }
199
+ else if (isNegativeSeriesSpec(numberSpec)) {
200
+ // negative number spec (e.g. odd numbers except 13-17)
201
+ const toExclude = filterAddressPointsByRanges(filteredAddressPoints, numberSpec);
202
+ result.push(...filteredAddressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
203
+ }
204
+ return result;
205
+ };
206
+ const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
207
+ const result = [];
208
+ seriesSpec.ranges.forEach((range) => {
209
+ result.push(...addressPoints.filter((addressPoint) => {
210
+ var _a;
211
+ if (isRange(range)) {
212
+ const number = getNumberByType(seriesSpec.type, addressPoint);
213
+ return (isInRange(number, seriesSpec.type !== SeriesType.Descriptive
214
+ ? (_a = addressPoint.orientationalNumberLetter) !== null && _a !== void 0 ? _a : null
215
+ : null, range) && fitsType(number, seriesSpec.type));
216
+ }
217
+ else {
218
+ const fullStreetNumber = range;
219
+ return equalsFullStreetNumber(fullStreetNumber, addressPoint);
220
+ }
221
+ }));
222
+ });
223
+ // when no series specs are present, we take everything that fits the type
224
+ if (seriesSpec.ranges.length === 0) {
225
+ result.push(...addressPoints.filter((addressPoint) => fitsType(getNumberByType(seriesSpec.type, addressPoint), seriesSpec.type)));
226
+ }
227
+ return result;
228
+ };
229
+ export const isInRange = (number, letter, range) => {
230
+ if (number === null) {
231
+ return !range.from && !range.to;
232
+ }
233
+ // explainer: either the lower bound is not set, or it is lower than the number
234
+ // if it's equal to the number, we also have to check the letter - if it's not set, we don't care
235
+ // if from.letter is set, we need to have a letter set and it has to be greater or equal:
236
+ // number without letter counts lower than number with letter 'a' (e.g. 23 < 23a)
237
+ const satisfiesFrom = !range.from ||
238
+ number > range.from.number ||
239
+ (number === range.from.number &&
240
+ (!range.from.letter || (!!letter && letter >= range.from.letter)));
241
+ const satisfiesTo = !range.to ||
242
+ number < range.to.number ||
243
+ (number === range.to.number &&
244
+ (!letter ||
245
+ (!!range.to.letter && !!letter && letter <= range.to.letter)));
246
+ return satisfiesFrom && satisfiesTo;
247
+ };
248
+ export const fitsType = (number, type) => {
249
+ return (type === SeriesType.Descriptive ||
250
+ type === SeriesType.All ||
251
+ (type === SeriesType.Odd && number % 2 === 1) ||
252
+ (type === SeriesType.Even && number % 2 === 0));
253
+ };
254
+ export const equalsFullStreetNumber = (fullStreetNumber, addressPoint) => {
255
+ return (fullStreetNumber.descriptiveNumber.number ===
256
+ addressPoint.descriptiveNumber &&
257
+ fullStreetNumber.orientationalNumber.number ===
258
+ addressPoint.orientationalNumber &&
259
+ ((!fullStreetNumber.orientationalNumber.letter &&
260
+ !addressPoint.orientationalNumberLetter) ||
261
+ fullStreetNumber.orientationalNumber.letter ===
262
+ addressPoint.orientationalNumberLetter));
263
+ };
264
+ const getNumberByType = (type, addressPoint) => {
265
+ var _a;
266
+ return type === SeriesType.Descriptive
267
+ ? addressPoint.descriptiveNumber
268
+ : (_a = addressPoint.orientationalNumber) !== null && _a !== void 0 ? _a : null;
269
+ };
270
+ const getMunicipalityWhere = (alias, municipality) => {
271
+ return municipality.type === MunicipalityType.City
272
+ ? `${alias}.city_code = ?`
273
+ : `${alias}.city_district_code = ?`;
274
+ };
275
+ const createAddress = (result) => {
276
+ var _a;
277
+ const orientationalPart = result.orientational_number
278
+ ? `/${result.orientational_number}${(_a = result.orientational_number_letter) !== null && _a !== void 0 ? _a : ""}`
279
+ : "";
280
+ return `${result.street_name} ${result.descriptive_number}${orientationalPart}, ${result.city_name}`;
281
+ };
282
+ const rowToAddressPoint = (row) => {
283
+ const point = {
284
+ id: row.id,
285
+ address: "",
286
+ type: row.object_type_name === DescriptiveType
287
+ ? AddressPointType.Descriptive
288
+ : AddressPointType.Registration,
289
+ descriptiveNumber: row.descriptive_number,
290
+ city: row.city_name,
291
+ postalCode: row.postal_code,
292
+ lat: row.wgs84_latitude,
293
+ lng: row.wgs84_longitude,
294
+ };
295
+ if (row.street_name !== null) {
296
+ point.street = row.street_name;
297
+ }
298
+ if (row.orientational_number !== null) {
299
+ point.orientationalNumber = row.orientational_number;
300
+ }
301
+ if (row.orientational_number_letter !== null) {
302
+ point.orientationalNumberLetter = row.orientational_number_letter;
303
+ }
304
+ if (row.district_name !== null) {
305
+ point.district = row.district_name;
306
+ }
307
+ if (row.municipality_part_name !== null) {
308
+ point.municipalityPart = row.municipality_part_name;
309
+ }
310
+ point.address = buildInline(point);
311
+ return point;
312
+ };
@@ -0,0 +1,23 @@
1
+ import Database from "better-sqlite3";
2
+ interface DbConfig {
3
+ initFilePath?: string;
4
+ filePath?: string;
5
+ verbose?: boolean;
6
+ }
7
+ export declare const setDbConfig: (config: DbConfig) => void;
8
+ export declare const getDb: (config?: DbConfig) => Database.Database;
9
+ export declare const nonEmptyOrNull: (value: string) => string | null;
10
+ /**
11
+ * Efficiently insert multiple rows. If preventDuplicatesByFirstColumn is true, the first
12
+ * column should be unique (PK or UNIQUE).
13
+ */
14
+ export declare const insertMultipleRows: (rows: string[][], table: string, columnNames: string[], preventDuplicatesByFirstColumn?: boolean) => number;
15
+ /**
16
+ * Insert a single row and return the autoincremented ID.
17
+ */
18
+ export declare const insertAutoincrementRow: (row: string[], table: string, columnNames: string[]) => number;
19
+ export declare const deleteMultipleRows: (keys: string[], table: string, keyColumnName: string) => void;
20
+ export declare const clearDuplicates: (rows: string[][], table: string, columnNames: string[]) => string[][];
21
+ export declare const disconnect: () => void;
22
+ export declare const extractKeyValuesPairs: (array: string[][], keyIndex: number, valuesIndices: number[]) => string[][];
23
+ export {};
package/dist/db/db.js ADDED
@@ -0,0 +1,90 @@
1
+ import Database from "better-sqlite3";
2
+ import { existsSync, copyFileSync } from "fs";
3
+ import { join } from "path";
4
+ let _db;
5
+ const defaults = {
6
+ filePath: "address_points.db",
7
+ initFilePath: join("src", "address_points_init.db"),
8
+ verbose: false,
9
+ };
10
+ export const setDbConfig = (config) => {
11
+ _db = undefined;
12
+ getDb(config);
13
+ };
14
+ export const getDb = (config = {}) => {
15
+ const options = Object.assign(Object.assign({}, defaults), config);
16
+ if (typeof _db === "undefined") {
17
+ if (!existsSync(options.filePath)) {
18
+ copyFileSync(options.initFilePath, options.filePath);
19
+ }
20
+ const dbSettings = options.verbose ? { verbose: console.log } : {};
21
+ _db = new Database(options.filePath, dbSettings);
22
+ _db.pragma("journal_mode = WAL");
23
+ }
24
+ return _db;
25
+ };
26
+ export const nonEmptyOrNull = (value) => {
27
+ return value ? value : null;
28
+ };
29
+ /**
30
+ * Efficiently insert multiple rows. If preventDuplicatesByFirstColumn is true, the first
31
+ * column should be unique (PK or UNIQUE).
32
+ */
33
+ export const insertMultipleRows = (rows, table, columnNames, preventDuplicatesByFirstColumn = true) => {
34
+ const db = getDb();
35
+ if (rows.length === 0) {
36
+ return 0;
37
+ }
38
+ if (preventDuplicatesByFirstColumn) {
39
+ rows = clearDuplicates(rows, table, columnNames);
40
+ if (rows.length === 0) {
41
+ return 0;
42
+ }
43
+ }
44
+ const insertPlaceholders = generateRepetitiveString(`(${generatePlaceholders(columnNames.length)})`, ",", rows.length);
45
+ const insertStatement = db.prepare(`INSERT INTO ${table} (${columnNames.join(",")}) VALUES ${insertPlaceholders}`);
46
+ return insertStatement.run(rows.flat()).changes;
47
+ };
48
+ /**
49
+ * Insert a single row and return the autoincremented ID.
50
+ */
51
+ export const insertAutoincrementRow = (row, table, columnNames) => {
52
+ const db = getDb();
53
+ const insertStatement = db.prepare(`INSERT INTO ${table} (${columnNames.join(",")}) VALUES (${generatePlaceholders(columnNames.length)})`);
54
+ return Number(insertStatement.run(row).lastInsertRowid);
55
+ };
56
+ export const deleteMultipleRows = (keys, table, keyColumnName) => {
57
+ const db = getDb();
58
+ if (keys.length === 0) {
59
+ return;
60
+ }
61
+ db.prepare(`DELETE FROM ${table} WHERE ${keyColumnName} IN (${generatePlaceholders(keys.length)})`).run(keys);
62
+ };
63
+ export const clearDuplicates = (rows, table, columnNames) => {
64
+ const db = getDb();
65
+ const selectPlaceholders = generatePlaceholders(rows.length);
66
+ const selectStatement = db.prepare(`SELECT ${columnNames[0]} FROM ${table} WHERE ${columnNames[0]} IN (${selectPlaceholders})`);
67
+ const existing = selectStatement
68
+ .pluck()
69
+ .all(rows.map((row) => row[0]))
70
+ .map((key) => key.toString());
71
+ return rows.filter((row) => !existing.includes(row[0]));
72
+ };
73
+ export const disconnect = () => {
74
+ getDb().close();
75
+ };
76
+ const generateRepetitiveString = (value, glue, n) => {
77
+ return new Array(n).fill(value).join(glue);
78
+ };
79
+ const generatePlaceholders = (n) => {
80
+ return generateRepetitiveString("?", ",", n);
81
+ };
82
+ export const extractKeyValuesPairs = (array, keyIndex, valuesIndices) => {
83
+ const object = array.reduce((acc, data) => {
84
+ if (data[keyIndex]) {
85
+ acc[data[keyIndex]] = valuesIndices.map((index) => nonEmptyOrNull(data[index]));
86
+ }
87
+ return acc;
88
+ }, {});
89
+ return Object.keys(object).map((key) => [key, ...object[key]]);
90
+ };
@@ -0,0 +1,9 @@
1
+ import { Founder, MunicipalityType } from "./types";
2
+ import { DbMunicipalityResult } from "../street-markdown/types";
3
+ export declare const insertFounders: (founders: Founder[]) => number;
4
+ export declare const findFounder: (name: string) => {
5
+ founder: Founder;
6
+ errors: string[];
7
+ };
8
+ export declare const getFounderCityCode: (founder: Founder) => number;
9
+ export declare const findMunicipalityByNameAndType: (name: string, type: MunicipalityType) => DbMunicipalityResult;