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