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 `:vertices`, whose value shall be a sequence of vertices as
012 defined in `walkmap.vertex`."
013 [o]
014 (let
015 [v (:vertices 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 {:vertices 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 :vertices (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 ;; TODO: think about: when constructing an edge from a path, should the
059 ;; constructed edge be tagged with the tags of the path?
060 (e/edge (first o) (rest o))
061 (path->edges (rest o))))
062 (path? o)
063 (path->edges (:vertices o))
064 :else
065 (throw (IllegalArgumentException.
066 "Not a path or sequence of vertices!"))))
067
068 (defn length
069 "Return the length of this path, in metres. **Note that**
070 1. This is not the same as the distance from the start to the end of the
071 path, which, except for absolutely straight paths, will be shorter;
072 2. It is not even quite the same as the length of the path *as rendered*,
073 since paths will generally be rendered as spline curves."
074 [path]
075 (if
076 (path? path)
077 (reduce + (map e/length (path->edges path)))
078 (throw (IllegalArgumentException. "Not a path!"))))