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