#3: Good progress towards getting SVG reading going, but not there yet.
This commit is contained in:
parent
1ab35dbe7d
commit
9892af65e3
|
@ -5,6 +5,7 @@
|
||||||
:output-path "docs/codox"
|
:output-path "docs/codox"
|
||||||
:source-uri "https://github.com/simon-brooke/walkmap/blob/master/{filepath}#L{line}"}
|
:source-uri "https://github.com/simon-brooke/walkmap/blob/master/{filepath}#L{line}"}
|
||||||
:dependencies [[org.clojure/clojure "1.8.0"]
|
:dependencies [[org.clojure/clojure "1.8.0"]
|
||||||
|
[org.clojure/data.zip "1.0.0"]
|
||||||
[org.clojure/math.numeric-tower "0.0.4"]
|
[org.clojure/math.numeric-tower "0.0.4"]
|
||||||
[org.clojure/math.combinatorics "0.1.6"]
|
[org.clojure/math.combinatorics "0.1.6"]
|
||||||
[com.taoensso/timbre "4.10.0"]
|
[com.taoensso/timbre "4.10.0"]
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
"Essentially the specification for things we shall consider to be path.
|
"Essentially the specification for things we shall consider to be path.
|
||||||
**Note that** for these purposes `path` means any continuous linear
|
**Note that** for these purposes `path` means any continuous linear
|
||||||
feature, where such features specifically include watercourses."
|
feature, where such features specifically include watercourses."
|
||||||
(:require [walkmap.edge :as e]
|
(:require [clojure.string :as s]
|
||||||
|
[walkmap.edge :as e]
|
||||||
[walkmap.polygon :refer [polygon?]]
|
[walkmap.polygon :refer [polygon?]]
|
||||||
[walkmap.vertex :refer [vertex?]]))
|
[walkmap.vertex :refer [vertex?]]))
|
||||||
|
|
||||||
|
@ -26,7 +27,10 @@
|
||||||
(if
|
(if
|
||||||
(every? vertex? vertices)
|
(every? vertex? vertices)
|
||||||
{:vertices vertices :id (keyword (gensym "path")) :kind :path}
|
{:vertices vertices :id (keyword (gensym "path")) :kind :path}
|
||||||
(throw (IllegalArgumentException. "Each item on path must be a vertex."))))
|
(throw (IllegalArgumentException.
|
||||||
|
(str
|
||||||
|
"Each item on path must be a vertex: "
|
||||||
|
(s/join " " (map #(or (:kind %) (type %) "nil") vertices)))))))
|
||||||
|
|
||||||
(defn polygon->path
|
(defn polygon->path
|
||||||
"If `o` is a polygon, return an equivalent path. What's different about
|
"If `o` is a polygon, return an equivalent path. What's different about
|
||||||
|
|
95
src/walkmap/read_svg.clj
Normal file
95
src/walkmap/read_svg.clj
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
(ns walkmap.read-svg
|
||||||
|
"Utility functions for scalable vector graphics (SVG) into walkmap
|
||||||
|
structures."
|
||||||
|
(:require [clojure.data.zip :as dz]
|
||||||
|
[clojure.data.zip.xml :as zx]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clojure.string :as s]
|
||||||
|
[clojure.xml :as x]
|
||||||
|
[clojure.zip :as z]
|
||||||
|
[taoensso.timbre :as l :refer [info error spy]]
|
||||||
|
[walkmap.path :refer [path]]
|
||||||
|
;; [walkmap.polygon :refer [polygon]]
|
||||||
|
[walkmap.tag :refer [tag]]
|
||||||
|
[walkmap.vertex :refer [vertex vertex?]]))
|
||||||
|
|
||||||
|
(defn upper-case?
|
||||||
|
[s]
|
||||||
|
(every? #(Character/isUpperCase %) s))
|
||||||
|
|
||||||
|
(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'})))
|
||||||
|
|
||||||
|
(defn command-string->vertices
|
||||||
|
[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
|
||||||
|
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
|
||||||
|
(conj result (:vertex m))
|
||||||
|
(:x m)
|
||||||
|
(:y m)))))))
|
||||||
|
|
||||||
|
(defn path-elt->path
|
||||||
|
|
||||||
|
[elt]
|
||||||
|
(tag
|
||||||
|
(path (command-string->vertices (-> elt :attrs :d)))
|
||||||
|
(when (-> elt :attrs :class)
|
||||||
|
(map keyword (s/split (-> elt :attrs :class) #" ")))))
|
||||||
|
|
||||||
|
(defn progeny
|
||||||
|
"Return all the nodes in the XML structure below this `elt` which match
|
||||||
|
this `predicate`."
|
||||||
|
;; the name `descendants` is bound in `clojure.core` for something quite
|
||||||
|
;; different, and I chose not to rebind it.
|
||||||
|
[elt predicate]
|
||||||
|
(if
|
||||||
|
(apply predicate (list elt))
|
||||||
|
(list elt)
|
||||||
|
(reduce
|
||||||
|
concat
|
||||||
|
(remove
|
||||||
|
empty?
|
||||||
|
(map
|
||||||
|
#(progeny % predicate)
|
||||||
|
(:content elt))))))
|
||||||
|
|
||||||
|
(defn read-svg
|
||||||
|
;; I tried to get this working with all the clever zip stuff in
|
||||||
|
;; `clojure.zip`, `clojure.data.zip`, and so on. It would probably have
|
||||||
|
;; been more elegant, but it kept crashing out of heap space on even
|
||||||
|
;; quite small XML files. So I've implemented my own solution.
|
||||||
|
([file-name]
|
||||||
|
(read-svg file-name nil))
|
||||||
|
([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")))
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
[walkmap.edge :as e]
|
[walkmap.edge :as e]
|
||||||
[walkmap.polygon :refer [polygon?]]
|
[walkmap.polygon :refer [polygon?]]
|
||||||
[walkmap.tag :refer [tag]]
|
[walkmap.tag :refer [tag]]
|
||||||
|
[walkmap.utils :as u]
|
||||||
[walkmap.vertex :as v])
|
[walkmap.vertex :as v])
|
||||||
(:import org.clojars.smee.binary.core.BinaryIO
|
(:import org.clojars.smee.binary.core.BinaryIO
|
||||||
java.io.DataInput))
|
java.io.DataInput))
|
||||||
|
@ -84,7 +85,7 @@
|
||||||
(when-not
|
(when-not
|
||||||
(keyword? map-kind)
|
(keyword? map-kind)
|
||||||
(throw (IllegalArgumentException.
|
(throw (IllegalArgumentException.
|
||||||
(subs (str "Must be a keyword: " (or map-kind "nil")) 0 80))))
|
(u/truncate (str "Must be a keyword: " (or map-kind "nil")) 80))))
|
||||||
(cond
|
(cond
|
||||||
(and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o)
|
(and (coll? o) (not (map? o))) (map #(canonicalise % map-kind) o)
|
||||||
;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?`
|
;; if it has :facets it's an STL structure, but it doesn't yet conform to `stl?`
|
||||||
|
@ -120,7 +121,7 @@
|
||||||
(when-not
|
(when-not
|
||||||
(keyword? map-kind)
|
(keyword? map-kind)
|
||||||
(throw (IllegalArgumentException.
|
(throw (IllegalArgumentException.
|
||||||
(subs (str "Must be a keyword: " (or map-kind "nil")) 0 80))))
|
(u/truncate (str "Must be a keyword: " (or map-kind "nil")) 80))))
|
||||||
(let [in (io/input-stream filename)]
|
(let [in (io/input-stream filename)]
|
||||||
(canonicalise (b/decode binary-stl in) map-kind))))
|
(canonicalise (b/decode binary-stl in) map-kind))))
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
;; stage we need to build a map.
|
;; stage we need to build a map.
|
||||||
(assoc vi (:id v) (assoc current (:id o) (:id v))))
|
(assoc vi (:id v) (assoc current (:id o) (:id v))))
|
||||||
(throw (IllegalArgumentException. "Not a vertex: " v)))
|
(throw (IllegalArgumentException. "Not a vertex: " v)))
|
||||||
(throw (IllegalArgumentException. (subs (str "No `:id` value: " o) 0 80))))
|
(throw (IllegalArgumentException. (u/truncate (str "No `:id` value: " o) 80))))
|
||||||
;; it shouldn't actually be an error to try to index a vertex, but it
|
;; it shouldn't actually be an error to try to index a vertex, but it
|
||||||
;; also isn't useful to do so, so I'd be inclined to ignore it.
|
;; also isn't useful to do so, so I'd be inclined to ignore it.
|
||||||
(:vertex-index s)))
|
(:vertex-index s)))
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
"Utility functions for writing stereolithography (STL) files (and possibly,
|
"Utility functions for writing stereolithography (STL) files (and possibly,
|
||||||
later, other geometry files of interest to us) as scalable vector graphics
|
later, other geometry files of interest to us) as scalable vector graphics
|
||||||
(SVG)."
|
(SVG)."
|
||||||
(:require [clojure.string :as s]
|
(:require [clojure.java.io :as io]
|
||||||
|
[clojure.string :as s]
|
||||||
|
[clojure.xml :as x]
|
||||||
[dali.io :as neatly-folded-clock]
|
[dali.io :as neatly-folded-clock]
|
||||||
[hiccup.core :refer [html]]
|
[hiccup.core :refer [html]]
|
||||||
[taoensso.timbre :as l :refer [info error spy]]
|
[taoensso.timbre :as l :refer [info error spy]]
|
||||||
|
@ -106,3 +108,9 @@
|
||||||
:hiccup (spit out-filename (html s))
|
:hiccup (spit out-filename (html s))
|
||||||
(throw (Exception. "Unexpected renderer value: " *preferred-svg-render*)))
|
(throw (Exception. "Unexpected renderer value: " *preferred-svg-render*)))
|
||||||
s)))
|
s)))
|
||||||
|
|
||||||
|
|
||||||
|
(defn read-svg
|
||||||
|
([file-name]
|
||||||
|
(read-svg file-name nil))
|
||||||
|
([file-name map-kind]
|
||||||
|
|
|
@ -30,17 +30,21 @@
|
||||||
thrown) if
|
thrown) if
|
||||||
|
|
||||||
1. `object` is not a map;
|
1. `object` is not a map;
|
||||||
2. any of `tags` is not a keyword."
|
2. any of `tags` is not a keyword or sequence of keywords.
|
||||||
|
|
||||||
|
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]
|
[object & tags]
|
||||||
|
(let [tags' (flatten tags)]
|
||||||
(if
|
(if
|
||||||
(map? object)
|
(map? object)
|
||||||
(if
|
(if
|
||||||
(every? keyword? tags)
|
(every? keyword? tags')
|
||||||
(assoc object ::tags (union (set tags) (::tags object)))
|
(assoc object ::tags (union (set tags') (::tags object)))
|
||||||
(throw (IllegalArgumentException.
|
(throw (IllegalArgumentException.
|
||||||
(str "Must be keyword(s): " (map type tags)))))
|
(str "Must be keyword(s): " (map type tags')))))
|
||||||
(throw (IllegalArgumentException.
|
(throw (IllegalArgumentException.
|
||||||
(str "Must be a map: " (type object))))))
|
(str "Must be a map: " (type object)))))))
|
||||||
|
|
||||||
(defmacro tags
|
(defmacro tags
|
||||||
"Return the tags of this object, if any."
|
"Return the tags of this object, if any."
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
(ns walkmap.utils
|
(ns walkmap.utils
|
||||||
"Miscellaneous utility functions."
|
"Miscellaneous utility functions."
|
||||||
(:require [clojure.math.numeric-tower :as m]
|
(:require [clojure.math.numeric-tower :as m]))
|
||||||
[walkmap.path :as p]
|
|
||||||
[walkmap.polygon :as q]
|
|
||||||
[walkmap.vertex :as v]))
|
|
||||||
|
|
||||||
(defn deep-merge
|
(defn deep-merge
|
||||||
"Recursively merges maps. If vals are not maps, the last value wins."
|
"Recursively merges maps. If vals are not maps, the last value wins."
|
||||||
|
@ -13,11 +10,20 @@
|
||||||
(apply merge-with deep-merge vals)
|
(apply merge-with deep-merge vals)
|
||||||
(last vals)))
|
(last vals)))
|
||||||
|
|
||||||
(defn vertices
|
;; (defn vertices
|
||||||
"If `o` is an object with vertices, return those vertices, else nil."
|
;; "If `o` is an object with vertices, return those vertices, else nil."
|
||||||
[o]
|
;; [o]
|
||||||
(cond
|
;; (cond
|
||||||
(v/vertex? o) (list o)
|
;; (v/vertex? o) (list o)
|
||||||
(q/polygon? o) (:vertices o)
|
;; (q/polygon? o) (:vertices o)
|
||||||
(p/path? o) (:vertices o)))
|
;; (p/path? o) (:vertices o)))
|
||||||
|
|
||||||
|
(defn truncate
|
||||||
|
"If string `s` is more than `n` characters long, return the first `n`
|
||||||
|
characters; otherwise, return `s`."
|
||||||
|
[s n]
|
||||||
|
(if (and (string? s) (number? n) (> (count s) n))
|
||||||
|
(subs s 0 n)
|
||||||
|
s))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
two vertices, create an edge from them and use `walkmap.edge/length`."
|
two vertices, create an edge from them and use `walkmap.edge/length`."
|
||||||
(:require [clojure.math.numeric-tower :as m]
|
(:require [clojure.math.numeric-tower :as m]
|
||||||
[clojure.string :as s]
|
[clojure.string :as s]
|
||||||
[walkmap.geometry :refer [=ish]]))
|
[walkmap.geometry :refer [=ish]]
|
||||||
|
[walkmap.utils :refer [truncate]]))
|
||||||
|
|
||||||
(defn vertex-key
|
(defn vertex-key
|
||||||
"Making sure we get the same key everytime we key a vertex with the same
|
"Making sure we get the same key everytime we key a vertex with the same
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
(str "vert_" (:x o) "_" (:y o))
|
(str "vert_" (:x o) "_" (:y o))
|
||||||
:else
|
:else
|
||||||
(throw (IllegalArgumentException.
|
(throw (IllegalArgumentException.
|
||||||
(subs (str "Not a vertex: " (or o "nil")) 0 80))))
|
(truncate (str "Not a vertex: " (or o "nil")) 80))))
|
||||||
"."
|
"."
|
||||||
"-")))
|
"-")))
|
||||||
|
|
||||||
|
@ -79,10 +80,10 @@
|
||||||
(assoc o :kind :vertex :id (vertex-key o))
|
(assoc o :kind :vertex :id (vertex-key o))
|
||||||
(throw
|
(throw
|
||||||
(IllegalArgumentException.
|
(IllegalArgumentException.
|
||||||
(subs
|
(truncate
|
||||||
(str "Not a proto-vertex: must have numeric `:x` and `:y`: "
|
(str "Not a proto-vertex: must have numeric `:x` and `:y`: "
|
||||||
(or o "nil"))
|
(or o "nil"))
|
||||||
0 80)))))
|
80)))))
|
||||||
|
|
||||||
(def ensure3d
|
(def ensure3d
|
||||||
"Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
|
"Given a vertex `o`, if `o` has a `:z` value, just return `o`; otherwise
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
(cond
|
(cond
|
||||||
(not (vertex? o)) (throw
|
(not (vertex? o)) (throw
|
||||||
(IllegalArgumentException.
|
(IllegalArgumentException.
|
||||||
(subs (str "Not a vertex: " (or o "nil")) 0 80)))
|
(truncate (str "Not a vertex: " (or o "nil")) 80)))
|
||||||
(:z o) o
|
(:z o) o
|
||||||
:else (assoc o :z dflt))))))
|
:else (assoc o :z dflt))))))
|
||||||
|
|
||||||
|
@ -111,4 +112,4 @@
|
||||||
(assoc o :z 0.0)
|
(assoc o :z 0.0)
|
||||||
(throw
|
(throw
|
||||||
(IllegalArgumentException.
|
(IllegalArgumentException.
|
||||||
(subs (str "Not a vertex: " (or o "nil")) 0 80)))))))
|
(truncate (str "Not a vertex: " (or o "nil")) 80)))))))
|
||||||
|
|
|
@ -47,5 +47,8 @@
|
||||||
(is (thrown? IllegalArgumentException (tagged? {} :foo "bar" :ban))
|
(is (thrown? IllegalArgumentException (tagged? {} :foo "bar" :ban))
|
||||||
"An exception should be thrown if any of `tags` is not a keyword: `tagged?`.")
|
"An exception should be thrown if any of `tags` is not a keyword: `tagged?`.")
|
||||||
(is (thrown? IllegalArgumentException (untag {} :foo "bar" :ban))
|
(is (thrown? IllegalArgumentException (untag {} :foo "bar" :ban))
|
||||||
"An exception should be thrown if any of `tags` is not a keywordp: `untag`.")))
|
"An exception should be thrown if any of `tags` is not a keywordp: `untag`.")
|
||||||
|
(let [o (tag {} :foo '(:bar :ban) :froboz)]
|
||||||
|
(is (tagged? o :ban :bar :foo :froboz)
|
||||||
|
"It's now allowed to include lists of tags in the arg list for `tag`."))))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue