-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse_kml_to_csv.js
More file actions
116 lines (98 loc) · 3.06 KB
/
parse_kml_to_csv.js
File metadata and controls
116 lines (98 loc) · 3.06 KB
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
const fs = require('fs')
const inputPath = 'mission_control_2.kml'
const outputPath = 'zone_polygons_2.csv'
const xml = fs.readFileSync(inputPath, 'utf8')
const tagRegex = /<\/?([A-Za-z0-9:_-]+)([^>]*)>|([^<]+)/g
const elementStack = []
const folderStack = []
let currentPlacemark = null
const rows = []
function getCurrentZone() {
for (let i = folderStack.length - 1; i >= 0; i--) {
const name = (folderStack[i].name || '').trim()
if (/^Zone\s+\d+$/i.test(name)) {
return name.replace(/\s+/g, ' ').trim()
}
}
return null
}
let match
while ((match = tagRegex.exec(xml)) !== null) {
const [, tagNameRaw, , textRaw] = match
if (tagNameRaw) {
const full = match[0]
const isClose = full.startsWith('</')
const isSelfClose = full.endsWith('/>')
const tagName = tagNameRaw.includes(':')
? tagNameRaw.split(':')[1]
: tagNameRaw
if (!isClose) {
elementStack.push(tagName)
if (tagName === 'Folder') {
folderStack.push({ name: null })
} else if (tagName === 'Placemark') {
currentPlacemark = { name: null, hasPolygon: false }
} else if (tagName === 'Polygon' && currentPlacemark) {
currentPlacemark.hasPolygon = true
}
if (isSelfClose) {
elementStack.pop()
}
} else {
if (tagName === 'Placemark' && currentPlacemark) {
if (currentPlacemark.hasPolygon && currentPlacemark.name) {
const zone = getCurrentZone()
if (zone) {
rows.push({
zone,
polygon: currentPlacemark.name.trim().replace(/\s+/g, ' '),
})
}
}
currentPlacemark = null
} else if (tagName === 'Folder') {
folderStack.pop()
}
for (let i = elementStack.length - 1; i >= 0; i--) {
const t = elementStack.pop()
if (t === tagName) break
}
}
} else if (textRaw) {
const text = textRaw.trim()
if (!text) continue
const currentTag = elementStack[elementStack.length - 1]
const parentTag = elementStack[elementStack.length - 2]
if (currentTag === 'name') {
if (parentTag === 'Folder' && folderStack.length) {
folderStack[folderStack.length - 1].name = text
} else if (parentTag === 'Placemark' && currentPlacemark) {
currentPlacemark.name = text
}
}
}
}
const unique = new Map()
for (const row of rows) {
unique.set(`${row.zone}||${row.polygon}`, row)
}
const data = Array.from(unique.values())
data.sort((a, b) => {
const aZone = Number((a.zone.match(/\d+/) || ['0'])[0])
const bZone = Number((b.zone.match(/\d+/) || ['0'])[0])
if (aZone !== bZone) return aZone - bZone
return a.polygon.localeCompare(b.polygon, undefined, {
numeric: true,
sensitivity: 'base',
})
})
function csvEscape(value) {
return `"${String(value).replace(/"/g, '""')}"`
}
const csv =
[
'Zone,Polygon',
...data.map((row) => `${csvEscape(row.zone)},${csvEscape(row.polygon)}`),
].join('\n') + '\n'
fs.writeFileSync(outputPath, csv, 'utf8')
console.log(`Wrote ${outputPath} with ${data.length} rows.`)