This repository has been archived by the owner on Jan 27, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconvert.js
181 lines (148 loc) · 5.69 KB
/
convert.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import assert from 'node:assert/strict';
import { readFile, writeFile } from 'node:fs/promises';
import { XMLParser } from 'fast-xml-parser';
import { createObjectCsvWriter } from 'csv-writer';
import GeoJson from 'geojson';
const kmlFileName = new URL('./data/hydranten_ewk.kml', import.meta.url);
const rawCsvFileName = new URL('./data/raw-data.csv', import.meta.url);
const osmCsvFileName = new URL('./data/osm-data.csv', import.meta.url);
const osmGeoJsonFileName = new URL('./data/osm-data.geojson', import.meta.url);
const kmlContent = await readFile(kmlFileName, 'utf-8');
const xmlParser = new XMLParser({
ignoreAttributes: false,
attributeNamePrefix: '$',
parseTagValue: false, // don't try to parse numbers/booleans, keep everything as strings
});
const document = xmlParser.parse(kmlContent).kml.Document;
const schema = /** @type {any[]} */ (document.Schema.SimpleField);
const features = /** @type {any[]} */ (document.Folder.Placemark);
const hydrantTypeMapping = {
Unterflurhydrant: 'underground',
Überflurhydrant: 'pillar',
};
function getCoordinates(feature) {
assert(feature.Point, 'Hydrant has no point');
assert(!Array.isArray(feature.Point), 'Multiple points found in a single hydrant');
assert(feature.Point.coordinates, 'Hydrant point has no coordinates');
const coordinatesRegexp = /^(\d+\.\d+),(\d+\.\d+)$/;
assert.match(feature.Point.coordinates, coordinatesRegexp, 'Hydrant point coordinates invalid');
const [, longitude, latitude] = coordinatesRegexp.exec(feature.Point.coordinates);
return { latitude, longitude };
}
function getAttributes(feature) {
const simpleData = feature.ExtendedData?.SchemaData?.SimpleData;
assert(simpleData, 'Hydrant has no attributes');
for (const { $name: attributeName } of simpleData) {
assert(attributeName, 'Hydrant attribute name is undefined');
assert(
schema.some((simpleField) => simpleField.$name === attributeName),
`Hydrant has unknown attribute '${attributeName}'`,
);
}
const attributes = Object.fromEntries(
simpleData.map((attribute) => [attribute.$name, attribute['#text']]),
);
assert.equal(attributes.STATUS, 'in Betrieb', 'Hydrant is not in use');
return attributes;
}
function getOsmHydrantType(hydrant) {
assert(hydrant.BAUART in hydrantTypeMapping, `Unknown hydrant type '${hydrant.BAUART}'`);
return hydrantTypeMapping[hydrant.BAUART];
}
function getOsmStartDate(hydrant) {
if (!hydrant.EINBAUJAHR) {
return undefined;
}
const startDateRegexp = /^(\d{4})\/(\d{2})\/(\d{2})$/;
assert.match(hydrant.EINBAUJAHR, startDateRegexp, 'Hydrant start date invalid');
const [, year, month, date] = startDateRegexp.exec(hydrant.EINBAUJAHR);
// ignore month and date because they are mostly January 1st, which seems implausible
return year;
}
function getOsmDiameter(hydrant) {
const diameters = [
hydrant.NENNWEITE1,
hydrant.NENNWEITE2,
hydrant.NENNWEITE3,
hydrant.NENNWEITE4,
hydrant.NENNWEITE5,
hydrant.NENNWEITE6,
hydrant.NENNWEITE7,
]
.map((x) => Number.parseFloat(x))
.filter((x) => !Number.isNaN(x))
.filter((x) => x > 0);
if (diameters.length === 0) {
return undefined;
}
assert(
diameters.some((x) => x % 5 === 0),
'None of the diameters is cleanly divisible by 5',
);
const roundedTo5Diameters = diameters.map((x) => Math.round(x / 5) * 5);
if (roundedTo5Diameters.some((x) => x !== roundedTo5Diameters[0])) {
// not all diameters are equal after rounding to nearest 5, so let's discard them
return undefined;
}
assert(roundedTo5Diameters[0] <= 200, `Diameter ${roundedTo5Diameters[0]} is bigger than 200`);
return String(roundedTo5Diameters[0]);
}
function getOsmRef(hydrant) {
assert(hydrant.XTRID, 'Hydrant has no XTRID');
assert(hydrant.ID, 'Hydrant has no ID');
assert(hydrant.LOGISCHE_I, 'Hydrant has no LOGISCHE_I');
assert.equal(hydrant.XTRID, hydrant.ID, 'Hydrant has different IDs');
assert.equal(`ID-${hydrant.ID}`, hydrant.LOGISCHE_I, 'Hydrant has different IDs');
return hydrant.XTRID;
}
function getOsmOperator(hydrant) {
assert.equal(hydrant.NUMMERIERU, 'EWK Kirchzarten');
return hydrant.NUMMERIERU;
}
const hydrants = features.map((feature) => ({
...getCoordinates(feature),
...getAttributes(feature),
}));
const osmHydrants = hydrants.map((hydrant) => ({
latitude: hydrant.latitude,
longitude: hydrant.longitude,
emergency: 'fire_hydrant',
operator: getOsmOperator(hydrant),
'ref:ewk': getOsmRef(hydrant),
'fire_hydrant:type': getOsmHydrantType(hydrant),
'fire_hydrant:diameter': getOsmDiameter(hydrant),
start_date: getOsmStartDate(hydrant),
}));
// console.log(schema);
// console.log(hydrants);
async function writeRawCsv() {
const csvWriter = createObjectCsvWriter({
path: rawCsvFileName.pathname,
header: [
{ id: 'latitude', title: 'latitude' },
{ id: 'longitude', title: 'longitude' },
...schema.map((field) => ({
id: field.$name,
title: `${field.$name} (${field.$type})`,
})),
],
});
await csvWriter.writeRecords(hydrants);
console.log(`${rawCsvFileName.pathname} written.`);
}
async function writeOsmCsv() {
const csvWriter = createObjectCsvWriter({
path: osmCsvFileName.pathname,
header: Object.keys(osmHydrants[0]).map((id) => ({ id, title: id })),
});
await csvWriter.writeRecords(osmHydrants);
console.log(`${osmCsvFileName.pathname} written.`);
}
async function writeOsmGeoJson() {
const geoJsonContent = GeoJson.parse(osmHydrants, { Point: ['latitude', 'longitude'] });
await writeFile(osmGeoJsonFileName, JSON.stringify(geoJsonContent, undefined, 2), 'utf-8');
console.log(`${osmGeoJsonFileName.pathname} written.`);
}
await writeRawCsv();
await writeOsmCsv();
await writeOsmGeoJson();