001 (ns walkmap.superstructure
002 "single indexing structure for walkmap objects"
003 (:require [clojure.walk :refer [postwalk]]
004 [taoensso.timbre :as l]
005 [walkmap.path :as p]
006 [walkmap.polygon :as q]
007 [walkmap.utils :as u]
008 [walkmap.vertex :as v]))
009
010 ;; TODO: Think about reification/dereification. How can we cull a polygon, if
011 ;; some vertices still index it? I *think* that what's needed is that when
012 ;; we store something in the superstructure, we replace all its vertices (and
013 ;; other dependent structures, if any with their ids - as well as, obviously,
014 ;; adding/merging those vertices/dependent structures into the superstructure
015 ;; as first class objects in themselves. That means, for each identified thing,
016 ;; the superstructure only contains one copy of it.
017 ;;
018 ;; The question then is, when we want to do things with those objects, do we
019 ;; exteract a copy with its dependent structures fixed back up (reification),
020 ;; or do we indirect through the superstructure every time we want to access
021 ;; them? In a sense, the copy in the superstructure is the 'one true copy',
022 ;; but it may become very difficult then to have one true copy of the
023 ;; superstructure - unless we replace the superstructure altogether with a
024 ;; database, which may be the Right Thing To Do.
025
026 (def vertex-index ::vertex-index)
027
028 (defn vertices
029 "If `o` is an object with vertices, return those vertices, else nil."
030 [o]
031 (cond
032 (v/vertex? o) (list o)
033 (q/polygon? o) (:vertices o)
034 (p/path? o) (:vertices o)))
035
036 (defn index-vertex
037 "Return a superstructure like `s` in which object `o` is indexed by vertex
038 `v`. It is an error (and an exception may be thrown) if
039
040 1. `s` is not a map;
041 2. `o` is not a map;
042 3. `o` does not have a value for the key `:walkmap.id/id`;
043 4. `v` is not a vertex."
044 [s o v]
045 (if-not (v/vertex? o)
046 (if (:walkmap.id/id o)
047 (if (v/vertex? v)
048 (let [vi (or (::vertex-index s) {})
049 current (or (vi (:walkmap.id/id v)) {})]
050 ;; deep-merge doesn't merge sets, only maps; so at this
051 ;; stage we need to build a map.
052 (assoc vi (:walkmap.id/id v) (assoc current (:walkmap.id/id o) (:walkmap.id/id v))))
053 (throw (IllegalArgumentException. "Not a vertex: " v)))
054 (throw (IllegalArgumentException. (u/truncate (str "No `:walkmap.id/id` value: " o) 80))))
055 ;; it shouldn't actually be an error to try to index a vertex, but it
056 ;; also isn't useful to do so, so I'd be inclined to ignore it.
057 (::vertex-index s)))
058
059 (defn index-vertices
060 "Return a superstructure like `s` in which object `o` is indexed by its
061 vertices. It is an error (and an exception may be thrown) if
062
063 1. `s` is not a map;
064 2. `o` is not a map;
065 3. `o` does not have a value for the key `:walkmap.id/id`."
066 [s o]
067 (u/deep-merge
068 s
069 {::vertex-index
070 (reduce
071 u/deep-merge
072 {}
073 (map
074 #(index-vertex s o %)
075 (:vertices o)))}))
076
077 (defn in-retrieve
078 "Internal guts of `retrieve`, q.v. `x` can be anything; `s` must be a
079 walkmap superstructure. TODO: recursive, quite likely to blow the fragile
080 Clojure stack. Probably better to do this with `walk`, but I don't yet
081 understand that."
082 [x s]
083 (cond
084 ;; if it's a keyword identifying something in s, retrieve that something.
085 (keyword? x) (if (s x)
086 (in-retrieve (s x) s)
087 x)
088 ;; if it's a map, for every key which is not `:walkmap.id/id`, recurse.
089 (map? x) (let [v (reduce
090 (fn [m k]
091 (assoc m k (in-retrieve (x k) s)))
092 {}
093 (keys (dissoc x :walkmap.id/id)))
094 id (:walkmap.id/id x)]
095 ;; if it has an id, bind it to that id in the returned value.
096 (if id
097 (assoc
098 v
099 :walkmap.id/id
100 (:walkmap.id/id x))
101 v))
102 (set? x) x ;; TODO: should I search in sets for objects when storing?
103 (coll? x) (map #(in-retrieve % s) x)
104 :else x))
105
106 (defn retrieve
107 "Retrieve the canonical representation of the object with this `id` from the
108 superstructure `s`."
109 [id s]
110 (in-retrieve (id s) s))
111
112 (defn in-store-find-objects
113 "Return an id -> object map of every object within `o`. Internal to
114 `in-store`, q.v. Use at your own peril."
115 ([o]
116 (in-store-find-objects o {}))
117 ([o s]
118 (l/debug "Finding objects in:" o)
119 (cond
120 (set? o) s ;; TODO: should I search in sets for objects when storing?
121 (map? o) (if (:walkmap.id/id o)
122 (assoc
123 (in-store-find-objects (vals o) s)
124 (:walkmap.id/id o)
125 o)
126 (in-store-find-objects (vals o) s))
127 (coll? o) (reduce merge s (map #(in-store-find-objects % s) o))
128 :else s)))
129
130 (defn in-store-replace-with-keys
131 "Return a copy of `o` in which each reified walkmap object within `o` has
132 been replaced with the `:walkmap.id/id` of that object. Internal to
133 `in-store`, q.v. Use at your own peril."
134 [o]
135 (assoc
136 (postwalk #(or (:walkmap.id/id %) %) (dissoc o :walkmap.id/id))
137 :walkmap.id/id
138 (:walkmap.id/id o)))
139
140 ;; (in-store-replace-with-keys (p/path (v/vertex 0 0 0) (v/vertex 0 1 2) (v/vertex 3 3 3)))
141 ;; (in-store-find-objects (p/path (v/vertex 0 0 0) (v/vertex 0 1 2) (v/vertex 3 3 3)))
142
143 (defn store
144 "Return a superstructure like `s` with object `o` added. If only one
145 argument is supplied it will be assumed to represent `o` and a new
146 superstructure will be returned.
147
148 It is an error (and an exception may be thrown) if
149
150 1. `s` is not a map;
151 2. `o` is not a recognisable walkmap object"
152 ([o]
153 (store o {}))
154 ([o s]
155 (when-not (:walkmap.id/id o)
156 (throw
157 (IllegalArgumentException.
158 (str "Not a walkmap object: no value for `:walkmap.id/id`: "
159 (u/kind-type o)))))
160 (when-not (map? s)
161 (throw
162 (IllegalArgumentException.
163 (str "Superstructure must be a map: " (u/kind-type s)))))
164 (assoc
165 (u/deep-merge s (in-store-find-objects o) (index-vertices s o))
166 (:walkmap.id/id o)
167 (in-store-replace-with-keys o))))
168
169 (defn search-vertices
170 "Search superstructure `s` for vertices within the box defined by vertices
171 `minv` and `maxv`. Every coordinate in `minv` must have a lower value than
172 the equivalent coordinate in `maxv`. If `d2?` is supplied and not false,
173 search only in the x,y projection."
174 ([s minv maxv]
175 (search-vertices s minv maxv false))
176 ([s minv maxv d2?]
177 (let [minv' (if d2? (assoc minv :z Double/NEGATIVE_INFINITY) minv)
178 maxv' (if d2? (assoc maxv :z Double/POSITIVE_INFINITY) maxv)]
179 (filter
180 #(v/within-box? % minv maxv)
181 (filter #(= (:kind %) :vertex) (vals s))))))
182
183