001  (ns walkmap.vertex
002    "Essentially the specification for things we shall consider to be vertices.
003  
004    Note that there's no `distance` function here; to find the distance between
005    two vertices, create an edge from them and use `walkmap.edge/length`."
006    (:require [clojure.math.numeric-tower :as m]
007              [clojure.string :as s]
008              [taoensso.timbre :as l]
009              [walkmap.utils :refer [=ish kind-type truncate]]))
010  
011  (defn vertex-key
012    "Making sure we get the same key everytime we key a vertex with the same
013    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
014    `:z`; it is an error and an exception will be thrown if `o` does not
015    conform to this specification.
016  
017    **Note:** these keys can be quite long. No apology is made: it is required
018    that the same key can *never* refer to two different locations in space."
019    [o]
020    (keyword
021      (s/replace
022        (cond
023          (and (:x o) (:y o) (:z o))
024          (str "vert_" (:x o) "_" (:y o) "_" (:z o))
025          (and (:x o) (:y o))
026          (str "vert_" (:x o) "_" (:y o))
027          :else
028          (throw (IllegalArgumentException.
029                   (truncate (str "Not a vertex: " (or o "nil")) 80))))
030        "."
031        "-")))
032  
033  (defn vertex?
034    "True if `o` satisfies the conditions for a vertex. That is, essentially,
035    that it must rerpresent a two- or three- dimensional vector. A vertex is
036    shall be a map having at least the keys `:x` and `:y`, where the value of
037    those keys is a number. If the key `:z` is also present, its value must also
038    be a number.
039  
040    The name  `vector?` was not used as that would clash with a function of that
041    name in `clojure.core` whose semantics are entirely different."
042    [o]
043    (and
044      (map? o)
045      (:walkmap.id/id o)
046      (number? (:x o))
047      (number? (:y o))
048      (or (nil? (:z o)) (number? (:z o)))
049      (or (nil? (:kind o)) (= (:kind o) :vertex))))
050  
051  (defn vertex=
052    "True if vertices `v1`, `v2` represent the same vertex."
053    [v1 v2]
054    (every?
055      #(=ish (% v1) (% v2))
056      [:x :y :z]))
057  
058  (defn vertex*
059    "Return a vertex like `v1`, but with each of its coordinates multiplied
060    by the equivalent vertex in `v2`."
061    [v1 v2]
062    (if
063      (and (vertex? v1) (vertex? v2))
064      (let [f (fn [v1 v2 coord]
065                (* (or (coord v1) 0)
066                   ;; one here is deliberate!
067                   (or (coord v2) 1)))]
068        (assoc v1 :x (f v1 v2 :x)
069          :y (f v1 v2 :y)
070          :z (f v1 v2 :z)))
071      (do (l/warn
072            (s/join
073              " "
074              ["in `vertex-multiply`, both must be vectors. v1:" v1 "v2:" v2]))
075        v1)))
076  
077  (defn vertex
078    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
079    with those values, plus a unique `:walkmap.id/id` value, and `:kind` set to `:vertex`.
080    It's not necessary to use this function to create a vertex, but the `:walkmap.id/id`
081    must be present and must be unique."
082    ([x y]
083     (let [v {:x x :y y :kind :vertex}]
084       (assoc v :walkmap.id/id (vertex-key v))))
085    ([x y z]
086     (let [v (assoc (vertex x y) :z z)]
087       (assoc v :walkmap.id/id (vertex-key v)))))
088  
089  (defn canonicalise
090    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
091    upgrade it to something we will recognise as a vertex."
092    [o]
093    (if
094      (and
095        (map? o)
096        (number? (:x o))
097        (number? (:y o))
098        (or (nil? (:z o)) (number? (:z o))))
099      (assoc o :kind :vertex :walkmap.id/id (vertex-key o))
100      (throw
101        (IllegalArgumentException.
102          (truncate
103            (str "Not a proto-vertex: must have numeric `:x` and `:y`: "
104                 (or o "nil"))
105            80)))))
106  
107  (def ensure3d
108    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
109    return a vertex like `o` but having thie `dflt` value as the value of its
110    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified.
111  
112    If `o` is not a vertex, throws an exception."
113    (memoize
114      (fn
115        ([o]
116         (ensure3d o 0.0))
117        ([o dflt]
118         (cond
119           (not (vertex? o)) (throw
120                               (IllegalArgumentException.
121                                 (truncate (str "Not a vertex: " (or o "nil")) 80)))
122           (:z o) o
123           :else (assoc o :z dflt))))))
124  
125  (def ensure2d
126    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
127    (memoize
128      (fn [o]
129        (if
130          (vertex? o)
131          (assoc o :z 0.0)
132          (throw
133            (IllegalArgumentException.
134              (truncate (str "Not a vertex: " (or o "nil")) 80)))))))
135  
136  (defn within-box?
137    "True if `target` is within the box defined by `minv` and `maxv`. All
138    arguments must be vertices; additionally, both `minv` and `maxv` must
139    have `:z` coordinates."
140    [target minv maxv]
141    (when-not (and (vertex? target) (vertex? minv) (vertex? maxv))
142      (throw (IllegalArgumentException.
143               (s/join " " ["Arguments to `within-box?` must be vertices:"
144                            (map kind-type [target minv maxv])]))))
145    (every?
146      (map
147        #(< (% minv) (or (% target) 0) (% maxv))
148        [:x :y :z])))