This repository has been archived by the owner on Jan 17, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
osm.clj
139 lines (122 loc) · 5.51 KB
/
osm.clj
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
(ns hiposfer.kamal.router.io.osm
(:require [clojure.data.xml :as xml]
[hiposfer.kamal.router.algorithms.core :as record]))
;;TODO: include routing attributes for penalties
;; bridge=yes Also true/1/viaduct
;; tunnel=yes Also true/1
;; surface=paved No Speed Penalty. Also cobblestone/asphalt/concrete
;; surface=unpaved Speed Penalty. Also dirt/grass/mud/earth/sand
;; surface=* Speed Penalty (all other values)
;; access=private
;; name=* Street Name (Official). Also official_name / int_name / name:en / nat_name
;; name_1=* Street Name (Alternate). Also reg_name / loc_name / old_name
;; ref=* Route network and number, but information from parent Route Relations has priority,
;; see below. Also int_ref=* / nat_ref=* / reg_ref=* / loc_ref=* / old_ref=*
(def way-attrs #{"name"})
;; <node id="298884269" lat="54.0901746" lon="12.2482632" user="SvenHRO"
;; uid="46882" visible="true" version="1" changeset="676636"
;; timestamp="2008-09-21T21:37:45Z"/>)
(defn- point-entry
"takes an OSM node and returns a [id Node-instance]"
[element]
{:node/id (Long/parseLong (:id (:attrs element)))
:node/location (record/->Location (Double/parseDouble (:lon (:attrs element)))
(Double/parseDouble (:lat (:attrs element))))})
; <way id="26659127" user="Masch" uid="55988" visible="true" version="5" changeset="4142606"
;timestamp="2010-03-16T11:47:08Z">
; <nd ref="292403538"/>
; <nd ref="298884289"/>
; ...
; <nd ref="261728686"/>
; <tag k="highway" v="unclassified"/>
; <tag k="name" v="Pastower Straße"/>
; </way>
(defn- ways-entry
"parse a OSM xml-way into a [way-id {attrs}] representing the same way"
[element] ;; returns '(arc1 arc2 ...)
(let [attrs (eduction (filter #(= :tag (:tag %)))
(filter #(contains? way-attrs (:k (:attrs %))))
(map #(vector (keyword "way" (:k (:attrs %)))
(:v (:attrs %))))
(:content element))
nodes (eduction (filter #(= :nd (:tag %)))
(map (comp :ref :attrs))
(map #(Long/parseLong %))
(:content element))]
(into {:way/id (Long/parseLong (:id (:attrs element)))
:way/nodes nodes}
attrs)))
(defn- join-ways
"merges ways that are linked to each other by their head or their tail.
This is an optimization HACK.
We artificially merge ways because OSM might decide to break a road into
reusable pieces (ways) which makes the graph representation very redundant.
This way we reduce both the amount of way entries in Datascript and the
amount of nodes"
([group] (join-ways (rest group) (first group) nil))
([ways current result]
(let [fncurrent (first (:way/nodes current))
lncurrent (last (:way/nodes current))
[point match] (some (fn [way]
(cond
(= fncurrent (last (:way/nodes way)))
[:start way]
(= lncurrent (first (:way/nodes way)))
[:end way]))
ways)]
(cond
(empty? ways)
(conj result current)
(some? point)
(recur (remove #(= % match) ways)
(if (= :start point)
(update match :way/nodes concat (rest (:way/nodes current)))
(update current :way/nodes concat (rest (:way/nodes match))))
result)
:else (recur (rest ways) (first ways) (conj result current))))))
(defn- trim-ways
"returns the ways sequence including only the first/last and intersection
nodes i.e. the connected nodes in a graph structure.
Uses the :way/nodes of each way and counts which nodes appear more than once"
[ways]
(let [groups (group-by :way/name ways)
point-count (frequencies (mapcat :way/nodes ways))]
(for [[way-name group] groups
:let [group-ways (if (empty? way-name) group
(join-ways group))]
way group-ways]
(assoc way :way/nodes
(filter #(or (= % (first (:way/nodes way)))
(>= (point-count %) 2)
(= % (last (:way/nodes way))))
(:way/nodes way))))))
(defn- entries
"returns a [id node], {id way} or nil otherwise"
[xml-entry]
(case (:tag xml-entry)
:node (point-entry xml-entry)
:way (ways-entry xml-entry)
nil))
;; We assume that preprocessing the files was already performed and that only
;; the useful data is part of the OSM file. See README
(defn transaction!
"read an OSM file and transforms it into a sequence of datascript transactions"
[raw-data] ;; read all elements into memory
(let [nodes&ways (keep entries (:content (xml/parse raw-data)))
;; separate ways from nodes
ways (trim-ways (filter :way/id nodes&ways))
;; post-processing nodes
ids (into #{} (mapcat :way/nodes) ways)
nodes (filter #(contains? ids (:node/id %)) nodes&ways)]
(concat nodes ways)))
;; https://www.wikiwand.com/en/Preferred_walking_speed
(def walking-speed 1.4);; m/s
;; LEARNINGS ----- resources/osm/saarland.osm
;; way count
;; - without filtering for highways 169241
;; - only highways 72342
;; - only pedestrian highways 61986
;; node count
;; - without filtering for highways 1119289
;; - only highways 470230
;; - only connecting highways 73614