1

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.

Edit 3

World

I have worked out an overly complicated solution for my question. The geogebra.xml needs some postprocessing to get rid of trailing commas and adding a header and a footer.

Even more ambitiously: I now want to read directly from a shapefile.

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

(defun remove-junk (string)
  (string-right-trim ")" (string-right-trim " " (string-right-trim "^M" (string-right-trim ")" (string-left-trim "(" (string-left-trim " " string)))))))

(defun split (delimiter-string string)
  "Arthur Lemmens idea extended to splitting on substrings, better version due to pjb from #clschool"
  (loop with len = (length string)
        with dlen = (length delimiter-string)
        with left = 0 
        while (< left len)
        collect (let ((right (or (search delimiter-string string :start2 left)
                                 len)))
                  (prog1 (subseq string left right)
                    (setf left (+ right dlen))))))

(defparameter *isocodes* '("AQ" "AF" "AL" "DZ" "AS" "AD" "AO" "AI" "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"))

(with-open-file (in "c:/Users/nmajo/Dropbox/World_Countries_(Generalized)/world3.txt" :direction :input)
  (with-open-file (out "c:/Users/nmajo/Dropbox/World_Countries_(Generalized)/geogebra.xml" :direction :output)
    (let ((sexpr (loop for text = (read-line in nil)
                       while text collect
                                  (loop for string in (mapcar #'remove-junk (split "))," (subseq text (length "MULTIPOLYGON "))))
                                        collect
                                        (loop for substring in (split-sequence:split-sequence #\, string)
                                          collect `(,(extract-lon (string-left-trim " " substring)) ,(extract-lat (string-left-trim " " substring))))))))
      (loop for iso in sexpr
            for i from 0
            do (format out "<expression label=~s exp=\"{" (nth i *isocodes*))
               (loop for polygon in iso
                     do (format out "PolyLine[")
                        (loop for coordinates in polygon
                              do (eval `(format ,out "(5; ~f°; ~f°)," ,@coordinates)))
                        (format out "],"))
               (format out "}\" />
<element type=\"list\" label=~s>
    <show object=\"true\" label=\"true\" ev=\"4\"/>
    <objColor r=\"0\" g=\"0\" b=\"0\" alpha=\"0.0\"/>
    <layer val=\"0\"/>
    <labelMode val=\"0\"/>
    <lineStyle thickness=\"1\" type=\"0\" typeHidden=\"1\"/>
    <pointSize val=\"5\"/>
    <pointStyle val=\"0\"/>
    <angleStyle val=\"0\"/>
</element>" (nth i *isocodes*))))))
  • Can you add the complete and exact content of `no.txt` file? If it's long, you can add a link to that file or send me that in PM. – Martin Půda Sep 15 '22 at 20:20
  • Have you also got a sample Geogebra file for a simple example? Does it literally have the little "degree" circle after the angles? Are the angles the same sense as in lat-long on Earth (ie +90 is at the N pole, and negative Y coords are West etc)? – Spacedman Sep 15 '22 at 21:02
  • @Spacedman: The degree circle is important otherwise geogebra thinks it’s radians and it *is* lon and lat. I only ever did make the main Polyline((5;10°;64°),…,(5;10°;64°)) of the list starting and ending in the same point corresponding to the first outline shown in the image and missing all the rest; the islands. It’s getting late here, so I’ll do more tomorrow morning. – Jan-Magnus Økland Sep 15 '22 at 21:20

1 Answers1

1

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.

AlbusMPiroglu
  • 645
  • 3
  • 13