From a shapefile of world countries to geogebra Polylines on a sphere

Viewed 72

Norway

From an ESRI shapefile of world countries I have created, in R,

library(sf)
world <- st_read("World_Countries__Generalized_.shp", stringsAsFactors=FALSE)
cs<-c("af", "al", "dz", "as", "ad", "ao", "ai",  "aq", "ag", "ar", "am", "aw", "au", "at", "az", "pt", "bs", "bh",  "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "bq", "ba",  "bw", "bv", "br", "io", "vg", "bn", "bg", "bf", "bi", "cv", "kh",  "cm", "ca", "es", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co",  "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cw", "cy", "cz",  "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "sz",  "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm",  "ge", "de", "gh", "gi", "tf", "gr", "gl", "gd", "gp", "gu", "gt",  "gg", "gn", "gw", "gy", "ht", "hm", "hn", "hu", "is", "in", "id",  "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "tf",  "kz", "ke", "ki", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly",  "li", "lt", "lu", "mg", "pt", "mw", "my", "mv", "ml", "mt", "mh",  "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms",  "ma", "mz", "mm", "na", "nr", "np", "nl", "nc", "nz", "ni", "ne",  "ng", "nu", "nf", "kp", "mk", "mp", "no", "om", "pk", "pw", "ps",  "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re",  "ro", "ru", "rw", "bq", "bl", "bq", "sh", "kn", "lc", "mf", "pm",  "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sx",  "sk", "si", "sb", "so", "za", "gs", "kr", "ss", "es", "lk", "sd",  "sr", "sj", "se", "ch", "sy", "tj", "tz", "th", "tl", "tg", "tk",  "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb",  "us", "um", "uy", "vi", "uz", "vu", "va", "ve", "vn", "wf", "ye", "zm", "zw")
for(i in 1:length(cs)){
  dput(world$geometry[[i]],file=paste0(cs[i],".R"))
}
no<-source("no.R")$value
cat(print(no),file = "no.txt")

files isocode.txt (i.e. no.txt for Norway) containing strings

"MULTIPOLYGON (((X1 Y1, X2 Y2, ...), ((XX1 YY1, ...) ))"

and I want to turn them into

"no={Polyline((radius; X1°; X2°), ...),Polyline((radius; XX1°;YY1°), ...)}"

for use in geogebra to draw on the sphere x^2+y^2+z^2=radius^2. Say with radius=5.

My work: for proof of concept I used some horribly slow emacs lisp keyboard macros.

I don't really know common lisp, but I heard it is good for these kinds of problems.

Edit

no.txt

no2ggb.txt

world.ggb

Edit 2

I've gotten a bit more ambitious and want to write to the geogebra.xml in the world.ggb directly (it's just a .zip of some files; this being one of them). For each line of world2.txt it should have an entry of the form

<command name="PolyLine">
    <input [my output goes here]\>
    <output a0="aq[the iso code for antarctica]"/>
</command>
<element type="polyline3d" label="aq">
    <lineStyle thickness="1" type="0" typeHidden="1" opacity="178"/>
    <show object="true" label="false" ev="4"/>
    <objColor r="0" g="0" b="0" alpha="0.0"/>
    <layer val="0"/>
    <labelMode val="0"/>
</element>

where world2.txt just keeps lines of these MULTIPOLYGONs.

There a few problems with my implementation, it only handles the first polygon in the list, and it feels a bit hacky. Also the list of names (iso codes) in the order of the lines in the file should go in there.

(ql:quickload "split-sequence")
(ql:quickload "parse-number")

(defun extract-lon (str)
  (let ((m (search " " str)))    
    (parse-number:parse-real-number (subseq str 0 m))))

(defun extract-lat (str)
  (let ((m (search " " str))
    (l (length str)))    
    (parse-number:parse-real-number (subseq str (1+ m) l))))

(with-open-file (in "c:/Users/nmajo/Downloads/World_Countries_(Generalized)/world2.txt" :direction :input)
  (loop for text = (read-line in nil) while text do
    (loop for string in (split-sequence:split-sequence #\, (if (search "))," text) (subseq text (+ (search "(((" text) 3) (search "))," text)) (subseq text (+ (search "(((" text) 3) (search ")))" text))))
      while string
      for a from 0
      do (format t "a~d=\"(5; ~f°; ~f°)\" " a (extract-lon (string-left-trim " " string)) (extract-lat (string-left-trim " " string))))))
1 Answers

Re: "but I heard it is good for these kinds of problems", yes, common-lisp is good for these, and any other kind of problems.

Your question reminded me the video example of @Rainer Joswig on domain specific languages: https://vimeo.com/77280671

In the video, he shows how to create some framework around converting a shape of data. If you follow his code, you can change the place where he uses (subseq line start (1+ end)) to parse a data from exact locations to perhaps a regexp that'll suit your need, something like below (but the regexp is currently wrong, this is a tip to hopefully get you started):

(setf pattern "MULTIPOLYGON \\(\\(\\(($x $y, )\\)\\)\\)")
(setf text "MULTIPOLYGON (((X1 Y1, X2 Y2, ...), ((XX1 YY1, ...) ))")

or please have a look at common-lisp cookbook file section to read the data from file to your text (hint I remember something like uiop:read-file for helper)

then load up the portable regex library by edi weitz and find matches, then use the matches to put in your objects with something like:

(ql:quickload "cl-ppcre")
(ppcre:create-scanner pattern)
(setf matches (ppcre:all-matches-as-strings scanner text))
(loop for match in matches
      (add-match-to-structure match))

The add-match-to-structure is a function name I made up to perhaps give an idea on how to proceed.

When you parse the source file into the data structure / objects that you'll define, then you're in a good position to save those data into the shape you want, with perhaps another (loop) construct.

I am just giving some ideas here, as none of the code is complete or working, but I'm hoping it'll guide you in some direction.

Related