# walkmap/routing.clj

001 (ns walkmap.routing
002 "Finding optimal routes to traverse a map."
003 (:require [clojure.math.numeric-tower :as m :only [expt]]
004 [clojure.set :refer [intersection]]
005 [walkmap.edge :as e]
006 [walkmap.path :as p]
007 [walkmap.polygon :as q]
008 [walkmap.superstructure :as s]
009 [walkmap.tag :as t]
010 [walkmap.utils :as u]
011 [walkmap.vertex :as v]))
012
013 ;; Breadth first search is a good algorithm for terrain in which all steps have
014 ;; equal, but in our world (like the real world), they don't.
015
016 ;; Reading list:
017 ;;
018 ;; https://en.wikipedia.org/wiki/A*_search_algorithm
019 ;; https://www.redblobgames.com/pathfinding/a-star/introduction.html
020 ;; https://faculty.nps.edu/ncrowe/opmpaper2.htm
021 ;;
022 ;; See https://simon-brooke.github.io/the-great-game/codox/Pathmaking.html
023
024 (def ^:dynamic *gradient-exponent*
025 "The exponent to be applied to `(inc (:z (unit-vector from to)))`
026 of a path segment to calculate the gradient-related part of the
027 cost of traversal. Dynamic, because we will want to tune this."
028 2)
029
030 (def ^:dynamic *traversals-exponent*
031 "The (expected to be negative) exponent to be applied to the number
032 of traversals of a path to compute the road bonus. Paths more travelled by
033 should have larger bonuses, but not dramatically so - so the increase in
034 bonus needs to scale significantly less than linearly with the number
035 of traversals. Dynamic, because we will want to tune this."
036 -2)
037
038 (defn traversable?
039 "True if this object can be considered as part of the walkmap."
040 [object]
041 (and
042 (or
043 (and
044 (q/polygon? object)
045 (:centre object))
046 (p/path? object))
047 (not (t/tagged? object :no-traversal))))
048
049 (declare traversal-cost)
050
051 (defn vertices-traversal-cost
052 [vertices s]
053 (reduce
054 +
055 (map
056 #(traversal-cost %1 %2 s)
057 (v/check-vertices vertices)
058 (rest vertices))))
059
060 (defn path-traversal-cost
061 [path s]
062 (vertices-traversal-cost (:vertices (p/check-path path)) s))
063
064 (defn barriers-crossed
065 "Search superstructure `s` and return a sequence of barriers, if any, which
066 obstruct traversal from vertex `from` to vertex `to`."
067 [from to s]
068 ;; TODO: implement
069 '())
070
071 (defn crossing-penalty
072 "TODO: should return the cost of crossing this `barrier`, initially mainly
073 a watercourse, on the axis from vertex `from` to vertex `to`. in the context
074 of superstructure `s`. If there's a bridge, ferry or other crossing mechanism
075 in `s` at the intersection of the vertex and the barrier, then the penalty
076 should be substantially less than it would otherwise be."
077 [barrier from to s]
078 ;; TODO: implement
079 0)
080
081 (defn gradient-cost
082 "Compute the per-unit-distance cost of traversing this `edge`."
083 [edge]
084 (let [g (:z (e/unit-vector edge))]
085 (if (pos? g)
086 (m/expt (inc g) *gradient-exponent*)
087 1)))
088
089 ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 0)))
090 ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 2 0)))
091 ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 1)))
092 ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 2 1)))
093 ;; (gradient-cost (e/edge (v/vertex 0 0 0) (v/vertex 0 1 0.0001)))
094
095 (defn best-road
096 "Find the best traversable path which links the vertices `from` and `to`
097 in this superstructure `s`, or nil if there are none."
098 [from to s]
099 (let [f (fn [v] (set (s/touching v p/path? s)))]
100 (first
101 (sort-by
102 ;;; I... chose the path more travelled by.
103 #(or (:traversals %) 0)
104 (filter traversable? (intersection (f from) (f to)))))))
105
106 (defn road-bonus
107 "Calculate the road bonus of the edge represented by the vertices `from`,
108 `to`, in the context of the superstructure `s`. Obviously there only is
109 such a bonus if there actually is an existing thoroughfare to use. Road
110 bonuses scale with some fractional exponent of the number of traversals
111 which have been made of the road segment in question."
112 [from to s]
113 (let [best (best-road from to s)]
114 (when (:traversals best)
115 (m/expt (:traversals best) *traversals-exponent*))))
116
117 (defn traversal-cost
118 "Return the traversal cost of the edge represented by the vertices `from`,
119 `to`, in the context of the superstructure `s`. It is legitimate to pass
120 `nil` as the `to` argument, in which case the result will be zero, in order
121 to allow `reduce` to be used to compute total path costs."
122 [from to s]
123 (if (nil? to)
124 0
125 (let [edge (e/edge from to)
126 distance (e/length edge)]
127 (/
128 (+
129 (* distance
130 (gradient-cost edge))
131 (reduce +
132 (map
133 #(crossing-penalty [% from to s])
134 (barriers-crossed from to s))))
135 (or (road-bonus from to s) 1)))))
136
137 ;; (def p '({:x 1.40625, :y 0, :kind :vertex, :walkmap.id/id :vert_1-40625_0}
138 ;; {:x 1.40625, :y -10.703125, :kind :vertex, :walkmap.id/id :vert_1-40625_-10-703125}
139 ;; {:x 7.578125, :y -10.703125, :kind :vertex, :walkmap.id/id :vert_7-578125_-10-703125}
140 ;; {:x 7.578125, :y 0, :kind :vertex, :walkmap.id/id :vert_7-578125_0}
141 ;; {:x 2.171875, :y -0.765625, :kind :vertex, :walkmap.id/id :vert_2-171875_-0-765625}
142 ;; {:x 6.8125, :y -0.765625, :kind :vertex, :walkmap.id/id :vert_6-8125_-0-765625}))
143 ;; (v/check-vertices p)
144 ;; (def p' (p/path p))
145
146 ;; (traversal-cost (first p) (nth p 1) {})
147 ;; (vertices-traversal-cost p {})
148 ;; (path-traversal-cost (p/path p))
149
150 (defn extend-frontier
151 "Return a sequence like `frontier` with all of these `candidates` which are
152 not already members either of `frontier` or of `rejects` appended."
153 ([frontier candidates]
154 (extend-frontier frontier candidates nil))
155 ([frontier candidates rejects]
156 (if
157 (empty? frontier)
158 candidates
159 (let [fs (set (concat frontier rejects))]
160 (concat frontier (remove fs candidates))))))
161
162 ;; (extend-frontier '(1 2 3 4 5) '(7 3 6 2 9 8) '(6 8))
163 ;; (extend-frontier '(1 2 3 4 5) '(7 3 6 2 9 8))
164 ;; (extend-frontier '(1 2 3 4 5) '())
165 ;; (extend-frontier '(1 2 3 4 5) nil)
166 ;; (extend-frontier nil '(1 2 3 4 5))
167
168 (defn route
169 ;; NOT YET GOOD ENOUGH! Simple breadth first, and although it will
170 ;; reach the goal
171 ([from to s search-radius]
172 (loop [f from
173 t to
174 frontier (extend-frontier
175 nil
176 (s/neighbour-ids
177 (s/nearest s from :centre search-radius)
178 traversable?
179 s))
180 visited nil
181 track nil]
182 (let [here (s/retrieve (first frontier) s)]
183 (cond
184 (< (e/length (e/edge (:centre here)) to) search-radius)
185 ;; close enough
186 (apply p/path (cons (:centre here) track))
187 (empty? (rest frontier))
188 ;; failed
189 nil
190 :else
191 (recur
192 f
193 t
194 (extend-frontier
195 (rest frontier)
196 (s/neighbour-ids here traversable? s)
197 visited)
198 (cons here visited)
199 ;; this is going to be wrong, and I need to think about how to fix.
200 (cons here track)))))))
201