-
Notifications
You must be signed in to change notification settings - Fork 0
/
KatasterParser.kt
161 lines (150 loc) · 6.35 KB
/
KatasterParser.kt
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
import org.locationtech.proj4j.CRSFactory
import org.locationtech.proj4j.CoordinateTransformFactory
import org.locationtech.proj4j.ProjCoordinate
import java.io.InputStream
import javax.xml.stream.XMLInputFactory
import javax.xml.stream.events.XMLEvent
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.PI
private data class KatasterTree(
val tags: Map<String, String>,
val isHPA: Boolean
)
fun parseKataster(inputStream: InputStream): List<OsmNode> {
val reader = XMLInputFactory.newDefaultFactory().createXMLStreamReader(inputStream)
val trees = ArrayList<KatasterTree>()
var tree: MutableMap<String, String>? = null
val elements = ArrayList<String>()
val crsFactory = CRSFactory()
var publicationTimeStamp: String? = null
var isHPA = false
val transform = CoordinateTransformFactory().createTransform(
crsFactory.createFromName("epsg:25832"),
crsFactory.createFromName("epsg:4326")
)
while (reader.hasNext()) {
when (reader.next()) {
XMLEvent.START_ELEMENT -> {
val name = reader.name.localPart
when (name) {
"strassenbaumkataster" -> isHPA = false
"strassenbaumkataster_hpa" -> isHPA = true
"FeatureCollection" -> publicationTimeStamp = reader.attributes["timeStamp"]!!
"featureMember" -> tree = HashMap()
// check if points are in expected coordinate system
"Point" -> check(reader.attributes["srsName"] == "EPSG:25832")
}
elements.add(name)
}
XMLEvent.END_ELEMENT -> {
val name = reader.name.localPart
if (name == "featureMember") {
trees.add(KatasterTree(tree!!, isHPA))
tree = null
}
elements.removeLast()
}
XMLEvent.CHARACTERS -> {
val value = reader.text.trim()
val key = elements.last()
if (tree == null) continue
// transform epsg:25832 to epsg:4326 coordinates
if (key == "pos") {
val posStr = value.split(' ', ignoreCase = false, limit = 2)
val x = posStr[0].toDouble()
val y = posStr[1].toDouble()
val result = ProjCoordinate()
transform.transform(ProjCoordinate(x, y), result)
tree["lat"] = result.y.toString()
tree["lon"] = result.x.toString()
} else {
tree[key] = value
}
}
}
}
return trees.mapIndexed { index, t ->
transformKatasterToOsm(t.tags, -(index + 1L), t.isHPA, publicationTimeStamp!!)
}
}
private fun transformKatasterToOsm(
tags: Map<String, String>,
id: Long,
isHPA: Boolean,
publicationTimeStamp: String
): OsmNode {
val osmTags = HashMap<String, String>()
osmTags["natural"] = "tree"
osmTags.putAll(tags.mapNotNull { (k, v) ->
when (k) {
"baumid" -> (if (isHPA) "ref:hpa" else "ref:bukea") to v
"pflanzjahr" -> {
// einige Bäume im Quelldatensatz haben Pflanzjahr = 0
val year = v.toIntOrNull()?.takeIf { it != 0 }
if (year != null) "start_date" to v else null
}
"kronendurchmesser" -> {
// einige Bäume im Quelldatensatz haben kronendurchmesser = 0
val diameter = v.toIntOrNull()?.takeIf { it != 0 }
if (diameter != null) "diameter_crown" to v else null
}
"stammumfang" -> {
// einige Bäume im Quelldatensatz haben stammumfang = 0
val circumference = v.toDoubleOrNull()?.takeIf { it != 0.0 }
if (circumference != null) "circumference" to (circumference / 100).format(2) else null
}
"stand_bearbeitung" -> "check_date" to v
"gattung_latein" -> "genus" to v
"gattung_deutsch" -> "genus:de" to v
"art_latein" -> "species" to v
"art_deutsch" -> "species:de" to v
"sorte_latein" -> "taxon" to v
else -> null
}
})
// wenn species gleicher Wert wie genus, entfernen:
// und auch wenn explizit geschrieben steht, dass die Art unbekannt ist
if (osmTags["species"] == osmTags["genus"] || osmTags["species"]?.endsWith(" spec.") == true) {
osmTags.remove("species")
}
if (osmTags["species:de"] == osmTags["genus:de"] || osmTags["species:de"]?.endsWith(" Art unbekannt") == true) {
osmTags.remove("species:de")
}
// wenn Art "f." (="Form") enthält, stattdessen in "taxon" tun
if (osmTags["species"]?.contains("f.") == true) {
osmTags["taxon"] = osmTags["species"]!!
osmTags.remove("species")
}
// wenn taxon gleicher Wert wie Species oder genus, entfernen
if (osmTags["taxon"] == osmTags["species"] || osmTags["taxon"] == osmTags["genus"]) {
osmTags.remove("taxon")
}
// taxon:cultivar aus taxon extrahieren
val taxon = osmTags["taxon"]
val taxonCultivarRegex = Regex(".*'(.+)'")
if (taxon != null) {
val match = taxonCultivarRegex.matchEntire(taxon)
if (match != null) {
osmTags["taxon:cultivar"] = match.groupValues[1]
// einige Kultivare sind malformed, z.B. ''New Horizon'' oder 'Autumn Blaze''
.replace("'","")
osmTags.remove("taxon")
}
}
// SEHR implausible Daten entfernen
val trunkCircumference = osmTags["circumference"]?.toDouble()
// ein Stammumfang von <10cm bei Neupflanzung ist zwar nicht unplausibel bei neu gepflanzten Bäumen, allerdings
// ändert sich das dann so schnell, dass diese Daten bei Veröffentlichung schon wieder veraltet sind (und normaler-
// weise werden als Straßenbäume bereits größere Bäume aus der Baumschule gepflanzt)
if (trunkCircumference != null && (trunkCircumference < 0.1 || trunkCircumference > 7.0)) {
osmTags.remove("circumference")
}
return OsmNode(
id = id,
version = 1,
timestamp = publicationTimeStamp,
position = LatLon(tags.getValue("lat").toDouble(), tags.getValue("lon").toDouble()),
tags = osmTags
)
}