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)))))))