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