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 [clojure.string :as s]
006 [walkmap.edge :as e]
007 [walkmap.polygon :refer [polygon?]]
008 [walkmap.utils :refer [kind-type]]
009 [walkmap.vertex :refer [vertex?]]))
010
011 (defn path?
012 "True if `o` satisfies the conditions for a path. A path shall be a map
013 having the key `:vertices`, whose value shall be a sequence of vertices as
014 defined in `walkmap.vertex`."
015 [o]
016 (let
017 [v (:vertices o)]
018 (and
019 (seq? v)
020 (> (count v) 2)
021 (every? vertex? v)
022 (:walkmap.id/id o)
023 (or (nil? (:kind o)) (= (:kind o) :path)))))
024
025 (defn path
026 "Return a path constructed from these `vertices`."
027 [& vertices]
028 (when-not (every? vertex? vertices)
029 (throw (IllegalArgumentException.
030 (str
031 "Each item on path must be a vertex: "
032 (s/join " " (map kind-type (remove vertex? vertices)))))))
033 {:vertices vertices :walkmap.id/id (keyword (gensym "path")) :kind :path})
034
035 (defn polygon->path
036 "If `o` is a polygon, return an equivalent path. What's different about
037 a path is that in polygons there is an implicit edge between the first
038 vertex and the last. In paths, there isn't, so we need to add that
039 edge explicitly.
040
041 If `o` is not a polygon, will throw an exception."
042 [o]
043 (when-not (polygon? o)
044 (throw (IllegalArgumentException. (str "Not a polygon: " (kind-type o)))))
045 (assoc (dissoc o :vertices)
046 :kind :path
047 ;; `concat` rather than `conj` because order matters.
048 :vertices (concat (:vertices o) (list (first (:vertices o))))))
049
050 (defn path->edges
051 "if `o` is a path, a polygon, or a sequence of vertices, return a sequence of
052 edges representing that path, polygon or sequence.
053
054 Throws `IllegalArgumentException` if `o` is not a path, a polygon, or
055 sequence of vertices."
056 [o]
057 (cond
058 (seq? o)
059 (when
060 (and
061 (vertex? (first o))
062 (vertex? (first (rest o))))
063 (cons
064 ;; TODO: think about: when constructing an edge from a path, should the
065 ;; constructed edge be tagged with the tags of the path?
066 (e/edge (first o) (rest o))
067 (path->edges (rest o))))
068 (path? o)
069 (path->edges (:vertices o))
070 :else
071 (throw (IllegalArgumentException.
072 "Not a path or sequence of vertices!"))))
073
074 (defn length
075 "Return the length of this path, in metres. **Note that**
076 1. This is not the same as the distance from the start to the end of the
077 path, which, except for absolutely straight paths, will be shorter;
078 2. It is not even quite the same as the length of the path *as rendered*,
079 since paths will generally be rendered as spline curves."
080 [path]
081 (if
082 (path? path)
083 (reduce + (map e/length (path->edges path)))
084 (throw (IllegalArgumentException. "Not a path!"))))