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