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              [walkmap.geometry :refer [=ish]]))
009  
010  (defn vertex-key
011    "Making sure we get the same key everytime we key a vertex with the same
012    coordinates. `o` must have numeric values for `:x`, `:y`, and optionally
013    `:z`; it is an error and an exception will be thrown if `o` does not
014    conform to this specification.
015  
016    **Note:** these keys can be quite long. No apology is made: it is required
017    that the same key can *never* refer to two different locations in space."
018    [o]
019    (keyword
020      (s/replace
021        (cond
022          (and (:x o) (:y o) (:z o))
023          (str "vert_" (:x o) "_" (:y o) "_" (:z o))
024          (and (:x o) (:y o))
025          (str "vert_" (:x o) "_" (:y o))
026          :else
027          (throw (IllegalArgumentException.
028                   (subs (str "Not a vertex: " (or o "nil")) 0 80))))
029        "."
030        "-")))
031  
032  (defn vertex?
033    "True if `o` satisfies the conditions for a vertex. That is, essentially,
034    that it must rerpresent a two- or three- dimensional vector. A vertex is
035    shall be a map having at least the keys `:x` and `:y`, where the value of
036    those keys is a number. If the key `:z` is also present, its value must also
037    be a number.
038  
039    The name  `vector?` was not used as that would clash with a function of that
040    name in `clojure.core` whose semantics are entirely different."
041    [o]
042    (and
043      (map? o)
044      (:id o)
045      (number? (:x o))
046      (number? (:y o))
047      (or (nil? (:z o)) (number? (:z o)))
048      (or (nil? (:kind o)) (= (:kind o) :vertex))))
049  
050  (defn vertex=
051    "True if vertices `v1`, `v2` represent the same vertex."
052    [v1 v2]
053    (every?
054      #(=ish (% v1) (% v2))
055      [:x :y :z]))
056  
057  (defn vertex
058    "Make a vertex with this `x`, `y` and (if provided) `z` values. Returns a map
059    with those values, plus a unique `:id` value, and `:kind` set to `:vertex`.
060    It's not necessary to use this function to create a vertex, but the `:id`
061    must be present and must be unique."
062    ([x y]
063     (let [v {:x x :y y :kind :vertex}]
064       (assoc v :id (vertex-key v))))
065    ([x y z]
066     (let [v (assoc (vertex x y) :z z)]
067       (assoc v :id (vertex-key v)))))
068  
069  (defn canonicalise
070    "If `o` is a map with numeric values for `:x`, `:y` and optionally `:z`,
071    upgrade it to something we will recognise as a vertex."
072    [o]
073    (if
074      (and
075        (map? o)
076        (number? (:x o))
077        (number? (:y o))
078        (or (nil? (:z o)) (number? (:z o))))
079      (assoc o :kind :vertex :id (vertex-key o))
080      (throw
081        (IllegalArgumentException.
082          (subs
083            (str "Not a proto-vertex: must have numeric `:x` and `:y`: "
084                 (or o "nil"))
085            0 80)))))
086  
087  (def ensure3d
088    "Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
089    return a vertex like `o` but having thie `dflt` value as the value of its
090    `:z` key, or zero as the value of its `:z` key if `dflt` is not specified.
091  
092    If `o` is not a vertex, throws an exception."
093    (memoize
094      (fn
095        ([o]
096         (ensure3d o 0.0))
097        ([o dflt]
098         (cond
099           (not (vertex? o)) (throw
100                               (IllegalArgumentException.
101                                 (subs (str "Not a vertex: " (or o "nil")) 0 80)))
102           (:z o) o
103           :else (assoc o :z dflt))))))
104  
105  (def ensure2d
106    "If `o` is a vertex, set its `:z` value to zero; else throw an exception."
107    (memoize
108      (fn [o]
109        (if
110          (vertex? o)
111          (assoc o :z 0.0)
112          (throw
113            (IllegalArgumentException.
114              (subs (str "Not a vertex: " (or o "nil")) 0 80)))))))