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.
- package/LICENSE +21 -0
- package/README.md +20 -0
- package/dist/bin/address-points-import-only.d.ts +1 -0
- package/dist/bin/address-points-import-only.js +14 -0
- package/dist/bin/address-points.d.ts +1 -0
- package/dist/bin/address-points.js +14 -0
- package/dist/bin/clear-all.d.ts +1 -0
- package/dist/bin/clear-all.js +5 -0
- package/dist/bin/generate-syntax-diagrams.d.ts +1 -0
- package/dist/bin/generate-syntax-diagrams.js +19 -0
- package/dist/bin/parse-ordinance.d.ts +1 -0
- package/dist/bin/parse-ordinance.js +20 -0
- package/dist/bin/schools.d.ts +1 -0
- package/dist/bin/schools.js +8 -0
- package/dist/bin/streets.d.ts +1 -0
- package/dist/bin/streets.js +14 -0
- package/dist/db/address-points.d.ts +38 -0
- package/dist/db/address-points.js +312 -0
- package/dist/db/db.d.ts +23 -0
- package/dist/db/db.js +90 -0
- package/dist/db/founders.d.ts +9 -0
- package/dist/db/founders.js +191 -0
- package/dist/db/meta.d.ts +3 -0
- package/dist/db/meta.js +17 -0
- package/dist/db/schools.d.ts +6 -0
- package/dist/db/schools.js +61 -0
- package/dist/db/street-sync.d.ts +5 -0
- package/dist/db/street-sync.js +18 -0
- package/dist/db/types.d.ts +34 -0
- package/dist/db/types.js +12 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/open-data-sync/address-points.d.ts +4 -0
- package/dist/open-data-sync/address-points.js +73 -0
- package/dist/open-data-sync/schools.d.ts +3 -0
- package/dist/open-data-sync/schools.js +308 -0
- package/dist/open-data-sync/streets.d.ts +2 -0
- package/dist/open-data-sync/streets.js +77 -0
- package/dist/smd-to-csv/json-to-csv.d.ts +1 -0
- package/dist/smd-to-csv/json-to-csv.js +153 -0
- package/dist/smd-to-csv/ordinance-to-csv-rules.d.ts +1 -0
- package/dist/smd-to-csv/ordinance-to-csv-rules.js +31 -0
- package/dist/smd-to-csv/txt-to-json.d.ts +1 -0
- package/dist/smd-to-csv/txt-to-json.js +102 -0
- package/dist/street-markdown/municipality.d.ts +5 -0
- package/dist/street-markdown/municipality.js +44 -0
- package/dist/street-markdown/roman-numerals.d.ts +1 -0
- package/dist/street-markdown/roman-numerals.js +67 -0
- package/dist/street-markdown/smd-line-parser.d.ts +4 -0
- package/dist/street-markdown/smd-line-parser.js +29 -0
- package/dist/street-markdown/smd-parser.d.ts +21 -0
- package/dist/street-markdown/smd-parser.js +343 -0
- package/dist/street-markdown/smd.d.ts +9 -0
- package/dist/street-markdown/smd.js +182 -0
- package/dist/street-markdown/token-definition.d.ts +23 -0
- package/dist/street-markdown/token-definition.js +154 -0
- package/dist/street-markdown/types.d.ts +83 -0
- package/dist/street-markdown/types.js +18 -0
- package/dist/utils/addressBuilder.d.ts +8 -0
- package/dist/utils/addressBuilder.js +101 -0
- package/dist/utils/atom.d.ts +2 -0
- package/dist/utils/atom.js +65 -0
- package/dist/utils/helpers.d.ts +34 -0
- package/dist/utils/helpers.js +157 -0
- 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
|
+
[](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 @@
|
|
|
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 @@
|
|
|
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
|
+
};
|
package/dist/db/db.d.ts
ADDED
|
@@ -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;
|