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