Can now adequately read paths from SVG

Not yet reading curvature, which will probably one day be necessary.
This commit is contained in:
Simon Brooke 2020-05-29 11:23:24 +01:00
parent 9892af65e3
commit f2c39f9017
No known key found for this signature in database
GPG key ID: A7A4F18D1D4DF987
4 changed files with 57 additions and 44 deletions

View file

@ -5,6 +5,7 @@
(:require [clojure.string :as s]
[walkmap.edge :as e]
[walkmap.polygon :refer [polygon?]]
[walkmap.utils :refer [kind-type]]
[walkmap.vertex :refer [vertex?]]))
(defn path?
@ -30,7 +31,7 @@
(throw (IllegalArgumentException.
(str
"Each item on path must be a vertex: "
(s/join " " (map #(or (:kind %) (type %) "nil") vertices)))))))
(s/join " " (map kind-type vertices)))))))
(defn polygon->path
"If `o` is a polygon, return an equivalent path. What's different about

View file

@ -7,10 +7,11 @@
[clojure.string :as s]
[clojure.xml :as x]
[clojure.zip :as z]
[taoensso.timbre :as l :refer [info error spy]]
[taoensso.timbre :as l]
[walkmap.path :refer [path]]
;; [walkmap.polygon :refer [polygon]]
[walkmap.tag :refer [tag]]
[walkmap.utils :refer [kind-type truncate]]
[walkmap.vertex :refer [vertex vertex?]]))
(defn upper-case?
@ -19,38 +20,53 @@
(defn match->vertex
[match-vector x y]
(let [command (nth match-vector 1)
xcoord (read-string (nth match-vector 2))
ycoord (read-string (nth match-vector 3))
;; upper case command letters mean the coordinates that follow are
;; absolute; lower case, relative.
x' (if (upper-case? command) xcoord (+ x xcoord))
y' (if (upper-case? command) ycoord (+ y ycoord))]
(case (s/lower-case command)
("m" "l") {:vertex (vertex x' y') :x x' :y y'})))
(when-not (empty? match-vector)
(let [command (nth match-vector 1)
xcoord (read-string (nth match-vector 2))
ycoord (read-string (nth match-vector 3))
;; upper case command letters mean the coordinates that follow are
;; absolute; lower case, relative.
x' (if (upper-case? command) xcoord (+ x xcoord))
y' (if (upper-case? command) ycoord (+ y ycoord))]
(case (s/lower-case command)
("m" "l") {:vertex (vertex x' y') :x x' :y y'}
nil))))
(defn command-string->vertices
"Return the destination of each successive line (`l`, `L`) and move (`m`, `M`)
command in this string `s`, expected to be an SVG path command string."
[s]
(let [matcher (re-matcher #"([a-zA-Z]) +([-+]?[0-9]*\.?[0-9]+) +([-+]?[0-9]*\.?[0-9]+) +" s)]
(loop [match (re-find matcher) ;loop starts with 2 set arguments
(let [cmd-matcher ;; matches a 'command' in the string: a letter followed by
;;spaces and numbers
(re-matcher #"[a-zA-Z][^a-zA-Z]*" s)
seg-pattern ;; matches a command which initiates a move of the current
;; position.
#"([a-zA-Z]) +([-+]?[0-9]*\.?[0-9]+) +([-+]?[0-9]*\.?[0-9]+) +"]
(loop [match (re-find cmd-matcher)
result []
x 0
y 0]
(if-not match
(filter vertex? result)
(let [m (match->vertex match x y)]
(recur (re-find matcher) ;loop with 2 new arguments
(let [m (match->vertex (re-find seg-pattern match) x y)]
(recur (re-find cmd-matcher) ;loop with 2 new arguments
(conj result (:vertex m))
(:x m)
(:y m)))))))
(or (:x m) x)
(or (:y m) y)))))))
(defn path-elt->path
"Given the SVG path element `elt`, return a walkmap path structure
representing the line (`l`, `L`) and move (`m`, `M`) commands in
that path."
[elt]
(tag
(path (command-string->vertices (-> elt :attrs :d)))
(when (-> elt :attrs :class)
(map keyword (s/split (-> elt :attrs :class) #" ")))))
(if (= (-> elt :tag) :path)
(let [vs (command-string->vertices (-> elt :attrs :d))
p (when-not (empty? vs) (apply path vs))]
(if (and p (-> elt :attrs :class))
(tag p (map keyword (s/split (-> elt :attrs :class) #" ")))
p))
(throw (IllegalArgumentException.
(str "Must be an SVG `path` element: " elt)))))
(defn progeny
"Return all the nodes in the XML structure below this `elt` which match
@ -79,17 +95,4 @@
([file-name map-kind]
(let [xml (x/parse (io/file file-name))
paths (progeny xml #(= (:tag %) :path))]
(map path-elt->path paths))))
(read-svg "resources/iom/manual_roads.svg")
;; (def xx (z/xml-zip (x/parse (io/file "resources/iom/manual_roads.svg"))))
;; (type xx)
;; (first xx)
;; (zx/xml-> xx :svg :g :path)
;; (def xxx (x/parse (io/file "resources/iom/manual_roads.svg")))
(remove nil? (map path-elt->path paths)))))

View file

@ -2,7 +2,9 @@
"Code for tagging, untagging, and finding tags on objects. Note the use of
the namespaced keyword, `:walkmap.tag/tags`, denoted in this file `::tags`.
This is in an attempt to avoid name clashes with other uses of this key."
(:require [clojure.set :refer [difference union]]))
(:require [clojure.set :refer [difference union]]
[taoensso.timbre :as l]
[walkmap.utils :refer [kind-type]]))
(defn tagged?
"True if this `object` is tagged with each of these `tags`. It is an error
@ -20,9 +22,9 @@
(set? ot)
(every? ot tags)))
(throw (IllegalArgumentException.
(str "Must be keyword(s): " (map type tags)))))
(str "Must be keyword(s): " (map kind-type tags)))))
(throw (IllegalArgumentException.
(str "Must be a map: " (type object))))))
(str "Must be a map: " (kind-type object))))))
(defn tag
"Return an object like this `object` but with these `tags` added to its tags,
@ -35,6 +37,7 @@
It's legal to include sequences of keywords in `tags`, so that users can do
useful things like `(tag obj (map keyword some-strings))`."
[object & tags]
(l/debug "Tagging" (or (:kind object) (type object) nil) "with" tags)
(let [tags' (flatten tags)]
(if
(map? object)
@ -42,9 +45,9 @@
(every? keyword? tags')
(assoc object ::tags (union (set tags') (::tags object)))
(throw (IllegalArgumentException.
(str "Must be keyword(s): " (map type tags')))))
(str "Must be keyword(s): " (map kind-type tags')))))
(throw (IllegalArgumentException.
(str "Must be a map: " (type object)))))))
(str "Must be a map: " (kind-type object)))))))
(defmacro tags
"Return the tags of this object, if any."
@ -64,6 +67,6 @@
(every? keyword? tags)
(assoc object ::tags (difference (::tags object) (set tags)))
(throw (IllegalArgumentException.
(str "Must be keywords: " (map type tags)))))
(str "Must be keywords: " (map kind-type tags)))))
(throw (IllegalArgumentException.
(str "Must be a map: " (type object))))))
(str "Must be a map: " (kind-type object))))))

View file

@ -26,4 +26,10 @@
(subs s 0 n)
s))
(defn kind-type
"Identify the type of an `object`, e.g. for logging. If it has a `:kind` key,
it's one of ours, and that's what we want. Otherwise, we want its type; but
the type of `nil` is `nil`, which doesn't get printed when assembling error
,essages, so return \"nil\"."
[object]
(or (:kind object) (type object) "nil"))