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 check-kind-type check-kind-type-seq 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  (defmacro check-vertex
052    "If `o` is not a vertex, throw an `IllegalArgumentException` with an
053    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
054    from the calling function."
055    [o]
056    `(check-kind-type ~o vertex? :vertex))
057  
058  (defmacro check-vertices
059    "If `o` is not a sequence of vertices, throw an `IllegalArgumentException` with an
060    appropriate message; otherwise, returns `o`. Macro, so exception is thrown
061    from the calling function."
062    [o]
063    `(check-kind-type-seq ~o vertex? :vertex))
064  
065  (defn vertex=
066    "True if vertices `v1`, `v2` represent the same vertex."
067    [v1 v2]
068    (check-vertex v1)
069    (check-vertex v2)
070    (every?
071      #(=ish (% v1) (% v2))
072      [:x :y :z]))
073  
074  (defn vertex*
075    "Return a vertex like `v1`, but with each of its coordinates multiplied
076    by the equivalent vertex in `v2`. It is an error, and an exception will
077    be thrown, if either `v1` or `v2` is not a vertex."
078    [v1 v2]
079    (let [f (fn [v1 v2 coord]
080              (* (or (coord v1) 0)
081                 ;; one here is deliberate!
082                 (or (coord v2) 1)))]
083      (assoc v1 :x (f (check-vertex v1) (check-vertex v2) :x)
084        :y (f v1 v2 :y)
085        :z (f v1 v2 :z))))
086  
087  (defn vertex
088    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
089    with those values, plus a unique `:walkmap.id/id` value, and `:kind` set to `:vertex`.
090    It's not necessary to use this function to create a vertex, but the `:walkmap.id/id`
091    must be present and must be unique."
092    ([x y]
093     (let [v {:x x :y y :kind :vertex}]
094       (assoc v :walkmap.id/id (vertex-key v))))
095    ([x y z]
096     (let [v {:x x :y y :z z :kind :vertex}]
097       (assoc v :walkmap.id/id (vertex-key v)))))
098  
099  (defn canonicalise
100    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
101    upgrade it to something we will recognise as a vertex."
102    [o]
103    (if
104      (and
105        (map? o)
106        (number? (:x o))
107        (number? (:y o))
108        (or (nil? (:z o)) (number? (:z o))))
109      (assoc o :kind :vertex :walkmap.id/id (vertex-key o))
110      (throw
111        (IllegalArgumentException.
112          (truncate
113            (str "Not a proto-vertex: must have numeric `:x` and `:y`: "
114                 (or o "nil"))
115            80)))))
116  
117  (def ensure3d
118    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
119    return a vertex like `o` but having this `dflt` value as the value of its
120    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified.
121  
122    If `o` is not a vertex, throws an exception."
123    (memoize
124      (fn
125        ([o]
126         (ensure3d o 0.0))
127        ([o dflt]
128         (if (:z (check-vertex o))
129           o
130           (assoc o :z dflt))))))
131  
132  (def ensure2d
133    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
134    (memoize
135      (fn [o]
136        (assoc (check-vertex o) :z 0.0))))
137  
138  (defn within-box?
139    "True if `target` is within the box defined by `minv` and `maxv`. All
140    arguments must be vertices; additionally, both `minv` and `maxv` must
141    have `:z` coordinates."
142    [target minv maxv]
143    (check-vertices [target minv maxv])
144    (every?
145      true?
146      (map
147        #(if (% target)
148           (<= (% minv) (% target) (% maxv))
149           true)
150        [:x :y :z])))