001 (ns walkmap.path
002 "Essentially the specification for things we shall consider to be path.
003 **Note that** for these purposes `path` means any continuous linear
004 feature, where such features specifically include watercourses."
005 (:require [walkmap.edge :as e]
006 [walkmap.polygon :refer [polygon?]]
007 [walkmap.vertex :refer [vertex?]]))
008
009 (defn path?
010 "True if `o` satisfies the conditions for a path. A path shall be a map
011 having the key `:nodes`, whose value shall be a sequence of vertices as
012 defined in `walkmap.vertex`."
013 [o]
014 (let
015 [v (:nodes o)]
016 (and
017 (seq? v)
018 (> (count v) 2)
019 (every? vertex? v)
020 (:id o)
021 (or (nil? (:kind o)) (= (:kind o) :path)))))
022
023 (defn path
024 "Return a path constructed from these `vertices`."
025 [& vertices]
026 (if
027 (every? vertex? vertices)
028 {:nodes vertices :id (keyword (gensym "path")) :kind :path}
029 (throw (IllegalArgumentException. "Each item on path must be a vertex."))))
030
031 (defn polygon->path
032 "If `o` is a polygon, return an equivalent path. What's different about
033 a path is that in polygons there is an implicit edge between the first
034 vertex and the last. In paths, there isn't, so we need to add that
035 edge explicitly.
036
037 If `o` is not a polygon, will throw an exception."
038 [o]
039 (if
040 (polygon? o)
041 (assoc (dissoc o :vertices) :kind :path :nodes (concat (:vertices o) (list (first (:vertices o)))))
042 (throw (IllegalArgumentException. "Not a polygon!"))))
043
044 (defn path->edges
045 "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of
046 edges representing that path, polygon or sequence.
047
048 Throws `IllegalArgumentException` if `o` is not a path, a polygon, or
049 sequence of vertices."
050 [o]
051 (cond
052 (seq? o)
053 (when
054 (and
055 (vertex? (first o))
056 (vertex? (first (rest o))))
057 (cons
058 (e/edge (first o) (rest o))
059 (path->edges (rest o))))
060 (path? o)
061 (path->edges (:nodes o))
062 :else
063 (throw (IllegalArgumentException.
064 "Not a path or sequence of vertices!"))))
065
066 (defn length
067 "Return the length of this path, in metres. **Note that**
068 1. This is not the same as the distance from the start to the end of the
069 path, which, except for absolutely straight paths, will be shorter;
070 2. It is not even quite the same as the length of the path *as rendered*,
071 since paths will generally be rendered as spline curves."
072 [path]
073 (if
074 (path? path)
075 (reduce + (map e/length (path->edges path)))
076 (throw (IllegalArgumentException. "Not a path!"))))