text-to-map 1.1.8 → 1.1.9

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 CHANGED
@@ -1,21 +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.
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 CHANGED
@@ -1,20 +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)
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)
@@ -1,4 +1,4 @@
1
- import { AddressPoint, FullStreetNumber, RangeSpec, SeriesType, SmdError, SmdLine, WholeMunicipalitySmdLine } from "../street-markdown/types";
1
+ import { AddressPoint, FullStreetNumber, RangeSpec, SeriesType, SmdError, SmdLine } from "../street-markdown/types";
2
2
  import { Founder, Municipality } from "./types";
3
3
  export declare const Column: {
4
4
  admCode: number;
@@ -33,7 +33,27 @@ export declare const checkStreetExists: (streetName: string, founder: Founder) =
33
33
  exists: boolean;
34
34
  errors: SmdError[];
35
35
  }>;
36
- export declare const findAddressPoints: (smdLine: SmdLine | WholeMunicipalitySmdLine, municipality: Municipality) => Promise<AddressPoint[]>;
36
+ export declare enum FindAddressPointsType {
37
+ SmdLine = 0,
38
+ MunicipalityPart = 1,
39
+ WholeMunicipality = 2,
40
+ WholeMunicipalityNoStreetName = 3
41
+ }
42
+ export type FindAddressPointsParams = {
43
+ type: FindAddressPointsType.SmdLine;
44
+ smdLine: SmdLine;
45
+ municipality: Municipality;
46
+ } | {
47
+ type: FindAddressPointsType.MunicipalityPart;
48
+ municipalityPartCode: number;
49
+ } | {
50
+ type: FindAddressPointsType.WholeMunicipality;
51
+ municipality: Municipality;
52
+ } | {
53
+ type: FindAddressPointsType.WholeMunicipalityNoStreetName;
54
+ municipality: Municipality;
55
+ };
56
+ export declare const findAddressPoints: (params: FindAddressPointsParams) => Promise<AddressPoint[]>;
37
57
  export declare const isInRange: (number: number | null, letter: string | null, range: RangeSpec) => boolean;
38
58
  export declare const fitsType: (number: number | null, type: SeriesType) => boolean;
39
59
  export declare const equalsFullStreetNumber: (fullStreetNumber: FullStreetNumber, addressPoint: AddressPoint) => boolean;
@@ -8,7 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { AddressPointType, createSingleLineAddress } from "czech-address";
11
- import { SeriesType, isNegativeSeriesSpec, isRange, isSeriesSpecArray, isWholeMunicipalitySmdLine, } from "../street-markdown/types";
11
+ import { SeriesType, isNegativeSeriesSpec, isRange, isSeriesSpecArray, } from "../street-markdown/types";
12
12
  import { findClosestString } from "../utils/helpers";
13
13
  import jtsk2wgs84 from "../utils/jtsk2wgs84";
14
14
  import { extractKeyValuesPairs, generate2DPlaceholders, getKnexDb, insertMultipleRows, isSqlite, nonEmptyOrNull, } from "./db";
@@ -94,7 +94,8 @@ export const insertDistricts = (buffer) => __awaiter(void 0, void 0, void 0, fun
94
94
  export const insertMunicipalityParts = (buffer) => __awaiter(void 0, void 0, void 0, function* () {
95
95
  return yield insertMultipleRows(extractKeyValuesPairs(buffer, Column.municipalityPartCode, [
96
96
  Column.municipalityPartName,
97
- ]), "municipality_part", ["code", "name"]);
97
+ Column.cityCode,
98
+ ]), "municipality_part", ["code", "name", "city_code"]);
98
99
  });
99
100
  export const insertPragueDistricts = (buffer) => __awaiter(void 0, void 0, void 0, function* () {
100
101
  return yield insertMultipleRows(extractKeyValuesPairs(buffer, Column.pragueDistrictCode, [
@@ -167,7 +168,6 @@ export const checkStreetExists = (streetName, founder) => __awaiter(void 0, void
167
168
  // `Street '${streetName}' has wrong case, correct case: '${match}'.`
168
169
  // );
169
170
  }
170
- exists = true;
171
171
  }
172
172
  else {
173
173
  const closest = findClosestString(streetName, allStreets);
@@ -183,14 +183,29 @@ const getAllStreets = (cityCode) => __awaiter(void 0, void 0, void 0, function*
183
183
  const knex = getKnexDb();
184
184
  return yield knex.pluck("name").from("street").where("city_code", cityCode);
185
185
  });
186
- export const findAddressPoints = (smdLine, municipality) => __awaiter(void 0, void 0, void 0, function* () {
186
+ export var FindAddressPointsType;
187
+ (function (FindAddressPointsType) {
188
+ FindAddressPointsType[FindAddressPointsType["SmdLine"] = 0] = "SmdLine";
189
+ FindAddressPointsType[FindAddressPointsType["MunicipalityPart"] = 1] = "MunicipalityPart";
190
+ FindAddressPointsType[FindAddressPointsType["WholeMunicipality"] = 2] = "WholeMunicipality";
191
+ FindAddressPointsType[FindAddressPointsType["WholeMunicipalityNoStreetName"] = 3] = "WholeMunicipalityNoStreetName";
192
+ })(FindAddressPointsType || (FindAddressPointsType = {}));
193
+ export const findAddressPoints = (params) => __awaiter(void 0, void 0, void 0, function* () {
187
194
  const knex = getKnexDb();
188
- const params = isWholeMunicipalitySmdLine(smdLine)
189
- ? [municipality.code]
190
- : [smdLine.street, municipality.code];
191
- const streetJoinCondition = isWholeMunicipalitySmdLine(smdLine)
192
- ? "LEFT JOIN street s ON a.street_code = s.code"
193
- : `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`;
195
+ const queryParams = params.type === FindAddressPointsType.SmdLine
196
+ ? [params.smdLine.street, params.municipality.code]
197
+ : params.type === FindAddressPointsType.MunicipalityPart
198
+ ? [params.municipalityPartCode]
199
+ : [params.municipality.code];
200
+ const streetJoinCondition = params.type === FindAddressPointsType.SmdLine
201
+ ? `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`
202
+ : "LEFT JOIN street s ON a.street_code = s.code";
203
+ const whereCondition = params.type === FindAddressPointsType.MunicipalityPart
204
+ ? "a.municipality_part_code = ?"
205
+ : getMunicipalityWhere("a", params.municipality) +
206
+ (params.type === FindAddressPointsType.WholeMunicipalityNoStreetName
207
+ ? " AND a.street_code IS NULL"
208
+ : "");
194
209
  const queryResult = yield knex.raw(`SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
195
210
  a.orientational_number, a.orientational_number_letter, c.name AS city_name,
196
211
  d.name AS district_name, m.name AS municipality_part_name, p.name AS prague_district_name,
@@ -202,13 +217,13 @@ export const findAddressPoints = (smdLine, municipality) => __awaiter(void 0, vo
202
217
  LEFT JOIN city_district d ON a.city_district_code = d.code
203
218
  LEFT JOIN municipality_part m ON a.municipality_part_code = m.code
204
219
  LEFT JOIN prague_district p ON a.prague_district_code = p.code
205
- WHERE ${getMunicipalityWhere("a", municipality)}`, params);
220
+ WHERE ${whereCondition}`, queryParams);
206
221
  const filteredAddressPoints = queryResult.map(rowToAddressPoint);
207
- if (isWholeMunicipalitySmdLine(smdLine)) {
222
+ if (params.type !== FindAddressPointsType.SmdLine) {
208
223
  return filteredAddressPoints;
209
224
  }
210
225
  const result = [];
211
- const numberSpec = smdLine.numberSpec;
226
+ const numberSpec = params.smdLine.numberSpec;
212
227
  if (isSeriesSpecArray(numberSpec)) {
213
228
  numberSpec.forEach((seriesSpec) => {
214
229
  result.push(...filterAddressPointsByRanges(filteredAddressPoints, seriesSpec));
@@ -1,4 +1,4 @@
1
- import { DbMunicipalityResult, SmdError } from "../street-markdown/types";
1
+ import { DbMunicipalityResult, MunicipalityPartResult, SmdError } from "../street-markdown/types";
2
2
  import { Founder, MunicipalityType } from "./types";
3
3
  export declare const insertFounders: (founders: Founder[]) => Promise<number>;
4
4
  export declare const findFounder: (nameWithHashtag: string) => Promise<{
@@ -6,4 +6,5 @@ export declare const findFounder: (nameWithHashtag: string) => Promise<{
6
6
  errors: SmdError[];
7
7
  }>;
8
8
  export declare const getFounderCityCode: (founder: Founder) => Promise<number>;
9
- export declare const findMunicipalityByNameAndType: (name: string, type: MunicipalityType) => Promise<DbMunicipalityResult>;
9
+ export declare const findMunicipalityPartByName: (name: string, founder: Founder) => Promise<MunicipalityPartResult>;
10
+ export declare const findMunicipalityByNameAndType: (name: string, type: MunicipalityType, founder: Founder) => Promise<DbMunicipalityResult>;
@@ -144,18 +144,16 @@ const fixFounderProblems = (founder, municipalityCode, differingSchools, extract
144
144
  return municipalities[0].code;
145
145
  }
146
146
  else {
147
- // get code and position of the closest city
148
- let lowestDistance = Number.MAX_SAFE_INTEGER;
149
- let municipalityCode = null;
150
- for (const municipality of municipalities) {
151
- let municipalityDistance = distance([municipality.lat, municipality.lng], [schoolPosition.wgs84_latitude, schoolPosition.wgs84_longitude]);
152
- if (municipalityDistance < lowestDistance) {
153
- lowestDistance = municipalityDistance;
154
- municipalityCode = municipality.code;
155
- }
156
- }
157
147
  console.log("using the closest municipality matching the extracted name");
158
- return municipalityCode;
148
+ return getClosestCode({
149
+ code: 1,
150
+ lat: schoolPosition.wgs84_latitude,
151
+ lng: schoolPosition.wgs84_longitude,
152
+ }, municipalities.map((municipality) => ({
153
+ code: municipality.code,
154
+ lat: municipality.lat,
155
+ lng: municipality.lng,
156
+ })));
159
157
  }
160
158
  }
161
159
  else {
@@ -174,6 +172,18 @@ const fixFounderProblems = (founder, municipalityCode, differingSchools, extract
174
172
  }
175
173
  }
176
174
  });
175
+ const getClosestCode = (from, toList) => __awaiter(void 0, void 0, void 0, function* () {
176
+ let lowestDistance = Number.MAX_SAFE_INTEGER;
177
+ let code = null;
178
+ for (const place of toList) {
179
+ let municipalityDistance = distance([place.lat, place.lng], [from.lat, from.lng]);
180
+ if (municipalityDistance < lowestDistance) {
181
+ lowestDistance = municipalityDistance;
182
+ code = place.code;
183
+ }
184
+ }
185
+ return code;
186
+ });
177
187
  const getCityCodeByDistrictCode = (districtCode) => __awaiter(void 0, void 0, void 0, function* () {
178
188
  var _d;
179
189
  const result = yield getKnexDb()
@@ -318,14 +328,67 @@ const getAllFounderNames = () => __awaiter(void 0, void 0, void 0, function* ()
318
328
  municipalityName: String(row.city_name ? row.city_name : row.city_district_name),
319
329
  }));
320
330
  });
321
- export const findMunicipalityByNameAndType = (name, type) => __awaiter(void 0, void 0, void 0, function* () {
331
+ export const findMunicipalityPartByName = (name, founder) => __awaiter(void 0, void 0, void 0, function* () {
322
332
  const errors = [];
333
+ const cityCode = yield getFounderCityCode(founder);
323
334
  const result = yield getKnexDb()
324
335
  .first("code")
325
- .from(type === MunicipalityType.City ? "city" : "city_district")
326
- .where("name", name);
336
+ .from("municipality_part")
337
+ .where({ name, city_code: cityCode });
327
338
  if (result) {
328
- return { municipality: { code: result.code, type }, errors };
339
+ return { municipalityPartCode: result.code, errors };
340
+ }
341
+ else {
342
+ const allNames = yield getAllMunicipalityPartNames(cityCode);
343
+ const namesList = allNames.map((row) => row.name);
344
+ const bestMatch = findClosestString(name, namesList);
345
+ errors.push({
346
+ message: `Obec ani městská část '${name}' neexistuje, mysleli jste '${bestMatch}'?`,
347
+ startOffset: 0,
348
+ endOffset: 0,
349
+ });
350
+ return {
351
+ municipalityPartCode: null,
352
+ errors,
353
+ };
354
+ }
355
+ });
356
+ export const findMunicipalityByNameAndType = (name, type, founder) => __awaiter(void 0, void 0, void 0, function* () {
357
+ const errors = [];
358
+ const result = type === MunicipalityType.City
359
+ ? yield getKnexDb().select("code").from("city").where("name", name)
360
+ : yield getKnexDb()
361
+ .select("code")
362
+ .from("city_district")
363
+ .where("name", name)
364
+ .andWhere("city_code", yield getFounderCityCode(founder));
365
+ if (result.length > 0) {
366
+ if (result.length > 1) {
367
+ const cityCode = yield getFounderCityCode(founder);
368
+ const positions = yield getKnexDb()
369
+ .select("city_code", "wgs84_latitude", "wgs84_longitude")
370
+ .from("address_point")
371
+ .groupBy("city_code")
372
+ .whereIn("city_code", result.map((row) => row.code));
373
+ const founderPosition = yield getKnexDb()
374
+ .first("wgs84_latitude", "wgs84_longitude")
375
+ .from("address_point")
376
+ .groupBy("city_code")
377
+ .where("city_code", cityCode);
378
+ const closestCode = yield getClosestCode({
379
+ code: cityCode,
380
+ lat: founderPosition.wgs84_latitude,
381
+ lng: founderPosition.wgs84_longitude,
382
+ }, positions.map((row) => ({
383
+ code: row.city_code,
384
+ lat: row.wgs84_latitude,
385
+ lng: row.wgs84_longitude,
386
+ })));
387
+ return { municipality: { code: closestCode, type }, errors };
388
+ }
389
+ else {
390
+ return { municipality: { code: result[0].code, type }, errors };
391
+ }
329
392
  }
330
393
  else {
331
394
  const allNames = yield getAllMunicipalityNames(type);
@@ -351,3 +414,12 @@ const getAllMunicipalityNames = (type) => __awaiter(void 0, void 0, void 0, func
351
414
  code: row.code,
352
415
  }));
353
416
  });
417
+ const getAllMunicipalityPartNames = (cityCode) => __awaiter(void 0, void 0, void 0, function* () {
418
+ return (yield getKnexDb()
419
+ .select("name", "code")
420
+ .from("municipality_part")
421
+ .where("city_code", cityCode)).map((row) => ({
422
+ name: row.name,
423
+ code: row.code,
424
+ }));
425
+ });
@@ -28,10 +28,6 @@ export function up(knex) {
28
28
  table_6.text("key").primary().notNullable();
29
29
  table_6.text("value").notNullable();
30
30
  });
31
- yield knex.schema.createTable("municipality_part", function (table_7) {
32
- table_7.integer("code").primary().notNullable();
33
- table_7.text("name").notNullable();
34
- });
35
31
  yield knex.schema.createTable("object_type", function (table_8) {
36
32
  table_8.integer("id").primary().notNullable();
37
33
  table_8.text("name").notNullable();
@@ -88,6 +84,15 @@ export function up(knex) {
88
84
  .notNullable();
89
85
  table_2.text("name").notNullable();
90
86
  });
87
+ yield knex.schema.createTable("municipality_part", function (table_7) {
88
+ table_7.integer("code").primary().notNullable();
89
+ table_7
90
+ .integer("city_code")
91
+ .references("code")
92
+ .inTable("city")
93
+ .notNullable();
94
+ table_7.text("name").notNullable();
95
+ });
91
96
  yield knex.schema.createTable("street", function (table_15) {
92
97
  table_15.integer("code").primary().notNullable();
93
98
  table_15
@@ -32,6 +32,11 @@ export interface Municipality {
32
32
  code: number;
33
33
  }
34
34
  export type MunicipalityWithPosition = Municipality & Position;
35
+ export interface PlaceWithPosition {
36
+ code: number;
37
+ lat: number;
38
+ lng: number;
39
+ }
35
40
  export declare const founderToMunicipality: (founder: Founder) => Municipality;
36
41
  export interface DbfStreet {
37
42
  KOD: string;
@@ -1,5 +1,9 @@
1
- import { DbMunicipalityResult } from "./types";
1
+ import { Founder } from "../db/types";
2
+ import { DbMunicipalityResult, MunicipalityPartResult } from "./types";
3
+ export declare const isMunicipalityPart: (line: string) => boolean;
4
+ export declare const getMunicipalityPart: (line: string, founder: Founder) => Promise<MunicipalityPartResult>;
2
5
  export declare const isMunicipalitySwitch: (line: string) => boolean;
3
- export declare const getSwitchMunicipality: (line: string) => Promise<DbMunicipalityResult>;
6
+ export declare const getSwitchMunicipality: (line: string, founder: Founder) => Promise<DbMunicipalityResult>;
4
7
  export declare const isWholeMunicipality: (line: string) => boolean;
5
- export declare const getWholeMunicipality: (line: string) => Promise<DbMunicipalityResult>;
8
+ export declare const isNoStreetNameLine: (line: string) => boolean;
9
+ export declare const getWholeMunicipality: (line: string, founder: Founder) => Promise<DbMunicipalityResult>;
@@ -7,16 +7,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { findMunicipalityByNameAndType } from "../db/founders";
10
+ import { findMunicipalityByNameAndType, findMunicipalityPartByName, } from "../db/founders";
11
11
  import { MunicipalityType } from "../db/types";
12
+ const municipalityPartPattern = /^část (?<type>obce|města) (?<name>.+)$/;
12
13
  const municipalitySwitchStartPattern = /^navíc ulice /;
13
14
  const municipalitySwitchPattern = /^navíc ulice (z )?(?<type>městské části|městského obvodu|obce|města) (?<name>.+):$/;
14
15
  const wholeMunicipalityStartPattern = /^území /;
15
16
  const wholeMunicipalityPattern = /^území (?<type>městské části|městského obvodu|obce|města) (?<name>.+)$/;
17
+ const noStreetNamePattern = /^všechny ostatní budovy bez názvu ulice\s*$/;
18
+ export const isMunicipalityPart = (line) => {
19
+ return municipalityPartPattern.test(line);
20
+ };
21
+ export const getMunicipalityPart = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
22
+ const match = municipalityPartPattern.exec(line);
23
+ if (match === null) {
24
+ return {
25
+ errors: [
26
+ {
27
+ message: "Neplatný zápis pro přidání části obce. Správný zápis je: části <obce/města> <název části obce>. Např.: 'části obce Kladno' (název části obce musí být v 1. pádě).",
28
+ startOffset: 0,
29
+ endOffset: line.length + 1,
30
+ },
31
+ ],
32
+ municipalityPartCode: null,
33
+ };
34
+ }
35
+ const { name } = match.groups;
36
+ return yield getMunicipalityPartResult(cleanName(name), line, founder);
37
+ });
16
38
  export const isMunicipalitySwitch = (line) => {
17
39
  return municipalitySwitchStartPattern.test(line);
18
40
  };
19
- export const getSwitchMunicipality = (line) => __awaiter(void 0, void 0, void 0, function* () {
41
+ export const getSwitchMunicipality = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
20
42
  const match = municipalitySwitchPattern.exec(line);
21
43
  if (match === null) {
22
44
  return {
@@ -31,12 +53,15 @@ export const getSwitchMunicipality = (line) => __awaiter(void 0, void 0, void 0,
31
53
  };
32
54
  }
33
55
  const { type, name } = match.groups;
34
- return yield getMunicipalityResult(type, name, line);
56
+ return yield getMunicipalityResult(type, cleanName(name), line, founder);
35
57
  });
36
58
  export const isWholeMunicipality = (line) => {
37
59
  return wholeMunicipalityStartPattern.test(line);
38
60
  };
39
- export const getWholeMunicipality = (line) => __awaiter(void 0, void 0, void 0, function* () {
61
+ export const isNoStreetNameLine = (line) => {
62
+ return noStreetNamePattern.test(line);
63
+ };
64
+ export const getWholeMunicipality = (line, founder) => __awaiter(void 0, void 0, void 0, function* () {
40
65
  const match = wholeMunicipalityPattern.exec(line);
41
66
  if (match === null) {
42
67
  return {
@@ -51,11 +76,11 @@ export const getWholeMunicipality = (line) => __awaiter(void 0, void 0, void 0,
51
76
  };
52
77
  }
53
78
  const { type, name } = match.groups;
54
- return yield getMunicipalityResult(type, name, line);
79
+ return yield getMunicipalityResult(type, cleanName(name), line, founder);
55
80
  });
56
- const getMunicipalityResult = (type, name, line) => __awaiter(void 0, void 0, void 0, function* () {
81
+ const getMunicipalityResult = (type, name, line, founder) => __awaiter(void 0, void 0, void 0, function* () {
57
82
  const typeValue = getMunicipalityType(type);
58
- const { municipality, errors } = yield findMunicipalityByNameAndType(name, typeValue);
83
+ const { municipality, errors } = yield findMunicipalityByNameAndType(name, typeValue, founder);
59
84
  const startOffset = line.indexOf(name);
60
85
  const endOffset = startOffset + name.length + 1;
61
86
  return {
@@ -64,6 +89,16 @@ const getMunicipalityResult = (type, name, line) => __awaiter(void 0, void 0, vo
64
89
  endOffset }))),
65
90
  };
66
91
  });
92
+ const getMunicipalityPartResult = (name, line, founder) => __awaiter(void 0, void 0, void 0, function* () {
93
+ const { municipalityPartCode, errors } = yield findMunicipalityPartByName(name, founder);
94
+ const startOffset = line.indexOf(name);
95
+ const endOffset = startOffset + name.length + 1;
96
+ return {
97
+ municipalityPartCode,
98
+ errors: errors.map((error) => (Object.assign(Object.assign({}, error), { startOffset,
99
+ endOffset }))),
100
+ };
101
+ });
67
102
  const getMunicipalityType = (type) => {
68
103
  switch (type) {
69
104
  case "městské části":
@@ -74,3 +109,6 @@ const getMunicipalityType = (type) => {
74
109
  return MunicipalityType.City;
75
110
  }
76
111
  };
112
+ const cleanName = (name) => {
113
+ return name.trim();
114
+ };
@@ -7,17 +7,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { checkStreetExists, findAddressPoints, getAddressPointById, } from "../db/address-points";
10
+ import { FindAddressPointsType, checkStreetExists, findAddressPoints, getAddressPointById, } from "../db/address-points";
11
11
  import { disconnectKnex } from "../db/db";
12
12
  import { findFounder } from "../db/founders";
13
13
  import { findSchool } from "../db/schools";
14
14
  import { founderToMunicipality } from "../db/types";
15
- import { getSwitchMunicipality, getWholeMunicipality, isMunicipalitySwitch, isWholeMunicipality, } from "./municipality";
15
+ import { getMunicipalityPart, getSwitchMunicipality, getWholeMunicipality, isMunicipalityPart, isMunicipalitySwitch, isNoStreetNameLine, isWholeMunicipality, } from "./municipality";
16
16
  import { parseLine } from "./smd-line-parser";
17
17
  import { isAddressPoint, } from "./types";
18
18
  export const parseOrdinanceToAddressPoints = (lines, initialState = {}, onError = () => { }, onWarning = () => { }, onProcessedLine = () => { }) => __awaiter(void 0, void 0, void 0, function* () {
19
19
  try {
20
- const state = Object.assign({ currentMunicipality: null, currentFilterMunicipality: null, currentSchool: null, municipalities: [] }, initialState);
20
+ const state = Object.assign({ currentMunicipality: null, currentFilterMunicipality: null, currentSchool: null, noStreetNameSchoolIzo: null, municipalities: [] }, initialState);
21
21
  let lineNumber = 1;
22
22
  for (const rawLine of lines) {
23
23
  const line = cleanLine(rawLine);
@@ -38,6 +38,9 @@ export const parseOrdinanceToAddressPoints = (lines, initialState = {}, onError
38
38
  state.currentMunicipality.schools.push(mapSchoolForExport(state.currentSchool));
39
39
  }
40
40
  if (state.currentMunicipality != null) {
41
+ if (state.noStreetNameSchoolIzo) {
42
+ yield addNoStreetNameToSchool(state);
43
+ }
41
44
  state.municipalities.push(convertMunicipality(state.currentMunicipality));
42
45
  }
43
46
  return state.municipalities;
@@ -79,25 +82,37 @@ const processOneLine = (params) => __awaiter(void 0, void 0, void 0, function* (
79
82
  yield processSchoolLine(params);
80
83
  return;
81
84
  }
82
- if (isMunicipalitySwitch(line)) {
85
+ if (isMunicipalityPart(line)) {
86
+ yield processMunicipalityPartLine(params);
87
+ }
88
+ else if (isMunicipalitySwitch(line)) {
83
89
  processMunicipalitySwitchLine(params);
84
90
  }
85
91
  else if (isWholeMunicipality(line)) {
86
92
  yield processWholeMunicipalityLine(params);
87
93
  }
94
+ else if (isNoStreetNameLine(line)) {
95
+ state.noStreetNameSchoolIzo = state.currentSchool.izo;
96
+ }
88
97
  else {
89
98
  yield processAddressPointLine(params);
90
99
  }
91
100
  });
92
- const processMunicipalityLine = ({ line, state, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
101
+ const processMunicipalityLine = ({ line, lineNumber, state, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
93
102
  if (state.currentSchool !== null) {
94
103
  state.currentMunicipality.schools.push(state.currentSchool);
95
104
  state.currentSchool = null;
96
105
  }
97
106
  if (state.currentMunicipality !== null) {
107
+ if (state.noStreetNameSchoolIzo) {
108
+ yield addNoStreetNameToSchool(state);
109
+ }
98
110
  state.municipalities.push(convertMunicipality(state.currentMunicipality));
99
111
  }
100
112
  const { municipality, errors } = yield getNewMunicipality(line);
113
+ if (errors.length > 0) {
114
+ onError({ lineNumber, line, errors });
115
+ }
101
116
  state.currentMunicipality = municipality;
102
117
  state.currentFilterMunicipality = founderToMunicipality(state.currentMunicipality.founder);
103
118
  state.currentSchool = null;
@@ -122,8 +137,21 @@ const processSchoolLine = ({ line, lineNumber, state, onError, }) => __awaiter(v
122
137
  state.currentSchool = yield getNewSchool(line, state.currentMunicipality.founder, lineNumber, onError);
123
138
  state.currentFilterMunicipality = founderToMunicipality(state.currentMunicipality.founder);
124
139
  });
140
+ const processMunicipalityPartLine = ({ line, state, lineNumber, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
141
+ const { municipalityPartCode, errors } = yield getMunicipalityPart(line, state.currentMunicipality.founder);
142
+ if (errors.length > 0) {
143
+ onError({ lineNumber, line, errors });
144
+ }
145
+ else {
146
+ const addressPoints = yield findAddressPoints({
147
+ type: FindAddressPointsType.MunicipalityPart,
148
+ municipalityPartCode,
149
+ });
150
+ state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
151
+ }
152
+ });
125
153
  const processMunicipalitySwitchLine = ({ line, state, lineNumber, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
126
- const { municipality, errors } = yield getSwitchMunicipality(line);
154
+ const { municipality, errors } = yield getSwitchMunicipality(line, state.currentMunicipality.founder);
127
155
  if (errors.length > 0) {
128
156
  onError({ lineNumber, line, errors });
129
157
  }
@@ -132,12 +160,15 @@ const processMunicipalitySwitchLine = ({ line, state, lineNumber, onError, }) =>
132
160
  }
133
161
  });
134
162
  const processWholeMunicipalityLine = ({ line, state, lineNumber, onError, }) => __awaiter(void 0, void 0, void 0, function* () {
135
- const { municipality, errors } = yield getWholeMunicipality(line);
163
+ const { municipality, errors } = yield getWholeMunicipality(line, state.currentMunicipality.founder);
136
164
  if (errors.length > 0) {
137
165
  onError({ lineNumber, line, errors });
138
166
  }
139
167
  else {
140
- const addressPoints = yield findAddressPoints({ wholeMunicipality: true, street: "", numberSpec: [] }, municipality);
168
+ const addressPoints = yield findAddressPoints({
169
+ type: FindAddressPointsType.WholeMunicipality,
170
+ municipality,
171
+ });
141
172
  state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
142
173
  }
143
174
  });
@@ -153,12 +184,33 @@ const processAddressPointLine = ({ line, state, lineNumber, onError, onWarning,
153
184
  onWarning({ lineNumber, line, errors });
154
185
  }
155
186
  if (exists) {
156
- let addressPoints = yield findAddressPoints(smdLine, state.currentFilterMunicipality);
187
+ let addressPoints = yield findAddressPoints({
188
+ type: FindAddressPointsType.SmdLine,
189
+ smdLine,
190
+ municipality: state.currentFilterMunicipality,
191
+ });
157
192
  state.currentSchool.addresses.push(...filterOutSchoolAddressPoint(addressPoints, state.currentSchool).map(mapAddressPointForExport));
158
193
  }
159
194
  }
160
195
  }
161
196
  });
197
+ const addNoStreetNameToSchool = (state) => __awaiter(void 0, void 0, void 0, function* () {
198
+ const addressPoints = state.currentMunicipality.schools.flatMap((school) => school.addresses);
199
+ // get all address points without street name for current municipality
200
+ const pointsNoStreetName = yield findAddressPoints({
201
+ type: FindAddressPointsType.WholeMunicipalityNoStreetName,
202
+ municipality: {
203
+ code: state.currentMunicipality.founder.cityOrDistrictCode,
204
+ type: state.currentMunicipality.founder.municipalityType,
205
+ },
206
+ });
207
+ // filter out address points already present
208
+ const remainingPoints = pointsNoStreetName.filter((point) => !addressPoints.some((ap) => ap.address === point.address));
209
+ // find the right school and add the remaining address points
210
+ const schoolIndex = state.currentMunicipality.schools.findIndex((school) => school.izo === state.noStreetNameSchoolIzo);
211
+ state.currentMunicipality.schools[schoolIndex].addresses.push(...remainingPoints);
212
+ state.noStreetNameSchoolIzo = null;
213
+ });
162
214
  export const getNewMunicipality = (name) => __awaiter(void 0, void 0, void 0, function* () {
163
215
  const { founder, errors } = yield findFounder(name);
164
216
  return {
@@ -8,12 +8,6 @@ export interface SmdLine {
8
8
  street: string;
9
9
  numberSpec: SeriesSpec[] | NegativeSeriesSpec;
10
10
  }
11
- export interface WholeMunicipalitySmdLine extends SmdLine {
12
- wholeMunicipality: true;
13
- street: string;
14
- numberSpec: SeriesSpec[] | NegativeSeriesSpec;
15
- }
16
- export declare const isWholeMunicipalitySmdLine: (something: SmdLine | WholeMunicipalitySmdLine) => something is WholeMunicipalitySmdLine;
17
11
  export interface SeriesSpec {
18
12
  type: SeriesType;
19
13
  ranges: (RangeSpec | FullStreetNumber)[];
@@ -69,6 +63,10 @@ export interface DbMunicipalityResult {
69
63
  municipality: DbMunicipality;
70
64
  errors: SmdError[];
71
65
  }
66
+ export interface MunicipalityPartResult {
67
+ municipalityPartCode: number;
68
+ errors: SmdError[];
69
+ }
72
70
  export interface MunicipalityWithFounder extends Municipality {
73
71
  founder: Founder | null;
74
72
  }
@@ -76,6 +74,7 @@ export interface SmdState {
76
74
  currentMunicipality: MunicipalityWithFounder;
77
75
  currentFilterMunicipality: DbMunicipality;
78
76
  currentSchool: School;
77
+ noStreetNameSchoolIzo: string;
79
78
  municipalities: Municipality[];
80
79
  }
81
80
  export interface ProcessLineCallbackParams {
@@ -1,4 +1,3 @@
1
- export const isWholeMunicipalitySmdLine = (something) => something.hasOwnProperty("wholeMunicipality");
2
1
  export const isSeriesSpecArray = (something) => Array.isArray(something);
3
2
  export const isNegativeSeriesSpec = (something) => something.hasOwnProperty("negative");
4
3
  export const isFullStreetNumber = (something) => something.hasOwnProperty("orientationNumber");
package/package.json CHANGED
@@ -1,87 +1,87 @@
1
- {
2
- "type": "module",
3
- "dependencies": {
4
- "@turf/distance": "^6.5.0",
5
- "adm-zip": "^0.5.9",
6
- "better-sqlite3": "^8.0.1",
7
- "chevrotain": "^10.4.2",
8
- "csv-parse": "^5.3.3",
9
- "czech-address": "^1.0.1",
10
- "dotenv": "^16.3.1",
11
- "feedparser": "^2.2.10",
12
- "iconv-lite": "^0.6.3",
13
- "knex": "^2.4.2",
14
- "node-fetch": "^3.3.0",
15
- "parsedbf": "^1.1.1",
16
- "pg": "^8.11.1",
17
- "sax": "^1.2.4",
18
- "typescript": "^4.9.4"
19
- },
20
- "devDependencies": {
21
- "@chevrotain/types": "^10.5.0",
22
- "@jest/globals": "^29.5.0",
23
- "@types/adm-zip": "^0.5.0",
24
- "@types/better-sqlite3": "^7.6.3",
25
- "@types/feedparser": "^2.2.5",
26
- "@types/node": "^18.11.17",
27
- "copyfiles": "^2.4.1",
28
- "jest": "^29.5.0",
29
- "npm-run-all": "^1.7.0",
30
- "rimraf": "^5.0.1",
31
- "ts-jest": "^29.1.1",
32
- "ts-node": "^10.9.1"
33
- },
34
- "scripts": {
35
- "sync-all": "ts-node --experimental-specifier-resolution=node --esm src/bin/sync-all.ts",
36
- "address-points": "ts-node --experimental-specifier-resolution=node --esm src/bin/address-points.ts",
37
- "regions": "ts-node --experimental-specifier-resolution=node --esm src/bin/regions.ts",
38
- "schools": "ts-node --experimental-specifier-resolution=node --esm src/bin/schools.ts",
39
- "streets": "ts-node --experimental-specifier-resolution=node --esm src/bin/streets.ts",
40
- "diagrams": "ts-node --experimental-specifier-resolution=node --esm src/bin/generate-syntax-diagrams.ts",
41
- "clear-all": "ts-node --experimental-specifier-resolution=node --esm src/bin/clear-all.ts",
42
- "init-db": "ts-node --experimental-specifier-resolution=node --esm src/bin/init-db.ts",
43
- "parse-ordinance": "ts-node --experimental-specifier-resolution=node --esm src/bin/parse-ordinance.ts",
44
- "knex": "ts-node --experimental-specifier-resolution=node --esm src/bin/knex.ts",
45
- "test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest-esm.config.mjs --no-cache",
46
- "ordinance-to-csv-rules": "node src/smd-to-csv/ordinance-to-csv-rules.js",
47
- "clean": "rimraf dist",
48
- "copyfiles": "copyfiles -u 1 src/address_points_init.db dist",
49
- "build": "npm-run-all clean -p copyfiles build:*",
50
- "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist -p tsconfig.declarations.json",
51
- "build:js": "tsc --outDir dist",
52
- "prepare": "npm run build"
53
- },
54
- "name": "text-to-map",
55
- "description": "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.",
56
- "version": "1.1.8",
57
- "main": "dist/index.js",
58
- "types": "dist/index.d.ts",
59
- "files": [
60
- "dist/**/*",
61
- "LICENSE",
62
- "README.md"
63
- ],
64
- "directories": {
65
- "example": "examples",
66
- "test": "tests"
67
- },
68
- "repository": {
69
- "type": "git",
70
- "url": "git+https://github.com/maral/text-to-map.git"
71
- },
72
- "keywords": [
73
- "streets",
74
- "catchment areas",
75
- "schools",
76
- "map",
77
- "geodata",
78
- "address points",
79
- "polygons"
80
- ],
81
- "author": "Marek Lisý",
82
- "license": "MIT",
83
- "bugs": {
84
- "url": "https://github.com/maral/text-to-map/issues"
85
- },
86
- "homepage": "https://github.com/maral/text-to-map#readme"
87
- }
1
+ {
2
+ "type": "module",
3
+ "dependencies": {
4
+ "@turf/distance": "^6.5.0",
5
+ "adm-zip": "^0.5.9",
6
+ "better-sqlite3": "^8.0.1",
7
+ "chevrotain": "^10.4.2",
8
+ "csv-parse": "^5.3.3",
9
+ "czech-address": "^1.0.1",
10
+ "dotenv": "^16.3.1",
11
+ "feedparser": "^2.2.10",
12
+ "iconv-lite": "^0.6.3",
13
+ "knex": "^2.4.2",
14
+ "node-fetch": "^3.3.0",
15
+ "parsedbf": "^1.1.1",
16
+ "pg": "^8.11.1",
17
+ "sax": "^1.2.4",
18
+ "typescript": "^4.9.4"
19
+ },
20
+ "devDependencies": {
21
+ "@chevrotain/types": "^10.5.0",
22
+ "@jest/globals": "^29.5.0",
23
+ "@types/adm-zip": "^0.5.0",
24
+ "@types/better-sqlite3": "^7.6.3",
25
+ "@types/feedparser": "^2.2.5",
26
+ "@types/node": "^18.11.17",
27
+ "copyfiles": "^2.4.1",
28
+ "jest": "^29.5.0",
29
+ "npm-run-all": "^1.7.0",
30
+ "rimraf": "^5.0.1",
31
+ "ts-jest": "^29.1.1",
32
+ "ts-node": "^10.9.1"
33
+ },
34
+ "scripts": {
35
+ "sync-all": "ts-node --experimental-specifier-resolution=node --esm src/bin/sync-all.ts",
36
+ "address-points": "ts-node --experimental-specifier-resolution=node --esm src/bin/address-points.ts",
37
+ "regions": "ts-node --experimental-specifier-resolution=node --esm src/bin/regions.ts",
38
+ "schools": "ts-node --experimental-specifier-resolution=node --esm src/bin/schools.ts",
39
+ "streets": "ts-node --experimental-specifier-resolution=node --esm src/bin/streets.ts",
40
+ "diagrams": "ts-node --experimental-specifier-resolution=node --esm src/bin/generate-syntax-diagrams.ts",
41
+ "clear-all": "ts-node --experimental-specifier-resolution=node --esm src/bin/clear-all.ts",
42
+ "init-db": "ts-node --experimental-specifier-resolution=node --esm src/bin/init-db.ts",
43
+ "parse-ordinance": "ts-node --experimental-specifier-resolution=node --esm src/bin/parse-ordinance.ts",
44
+ "knex": "ts-node --experimental-specifier-resolution=node --esm src/bin/knex.ts",
45
+ "test": "node --experimental-vm-modules --no-warnings node_modules/jest/bin/jest.js -c=jest-esm.config.mjs --no-cache",
46
+ "ordinance-to-csv-rules": "node src/smd-to-csv/ordinance-to-csv-rules.js",
47
+ "clean": "rimraf dist",
48
+ "copyfiles": "copyfiles -u 1 src/address_points_init.db dist",
49
+ "build": "npm-run-all clean -p copyfiles build:*",
50
+ "build:types": "tsc --declaration --emitDeclarationOnly --outDir dist -p tsconfig.declarations.json",
51
+ "build:js": "tsc --outDir dist",
52
+ "prepare": "npm run build"
53
+ },
54
+ "name": "text-to-map",
55
+ "description": "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.",
56
+ "version": "1.1.9",
57
+ "main": "dist/index.js",
58
+ "types": "dist/index.d.ts",
59
+ "files": [
60
+ "dist/**/*",
61
+ "LICENSE",
62
+ "README.md"
63
+ ],
64
+ "directories": {
65
+ "example": "examples",
66
+ "test": "tests"
67
+ },
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "git+https://github.com/maral/text-to-map.git"
71
+ },
72
+ "keywords": [
73
+ "streets",
74
+ "catchment areas",
75
+ "schools",
76
+ "map",
77
+ "geodata",
78
+ "address points",
79
+ "polygons"
80
+ ],
81
+ "author": "Marek Lisý",
82
+ "license": "MIT",
83
+ "bugs": {
84
+ "url": "https://github.com/maral/text-to-map/issues"
85
+ },
86
+ "homepage": "https://github.com/maral/text-to-map#readme"
87
+ }