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