update OC.DE GPX URI
[useful-scripts/mirkarte.git] / gpx.sh
1 #!/bin/mksh
2 #-
3 # Copyright © 2007, 2008, 2012, 2013, 2014, 2019
4 #       mirabilos <m@mirbsd.org>
5 #
6 # Provided that these terms and disclaimer and all copyright notices
7 # are retained or reproduced in an accompanying document, permission
8 # is granted to deal in this work without restriction, including un‐
9 # limited rights to use, publicly perform, distribute, sell, modify,
10 # merge, give away, or sublicence.
11 #
12 # This work is provided “AS IS” and WITHOUT WARRANTY of any kind, to
13 # the utmost extent permitted by applicable law, neither express nor
14 # implied; without malicious intent or gross negligence. In no event
15 # may a licensor, author or contributor be held liable for indirect,
16 # direct, other damage, loss, or other issues arising in any way out
17 # of dealing in the work, even if advised of the possibility of such
18 # damage or existence of a defect, except proven that it results out
19 # of said person’s immediate fault when using the work as intended.
20 #-
21 # Provide our own GPX, or filter or regenerate those we get from the
22 # waypoint listing site. Does not support all sites yet…
23 #
24 # TODO:
25 # – CacheBox unknown types “waypoint|virtual stage”, “waypoint|physical stage”
26
27 unset LANGUAGE; export LC_ALL=C
28 unset HTTP_PROXY
29
30 # magic from MirOS: src/kern/c/mirtime.c,v 1.3 2011/11/20 23:40:10 tg Exp $
31
32 # struct tm members and (POSIX) time functions
33 typeset -ir tm_sec=0            # seconds [0-59]
34 typeset -ir tm_min=1            # minutes [0-59]
35 typeset -ir tm_hour=2           # hours [0-23]
36 typeset -ir tm_mday=3           # day of month [1-31]
37 typeset -ir tm_mon=4            # month of year - 1 [0-11]
38 typeset -ir tm_year=5           # year - 1900
39 typeset -ir tm_wday=6           # day of week [0 = sunday]      input:ignored
40 typeset -ir tm_yday=7           # day of year [0-365]           input:ignored
41 typeset -ir tm_isdst=8          # summer time act.? [0/1] (0)   input:ignored
42 typeset -ir tm_gmtoff=9         # seconds offset from UTC (0)
43 typeset -ir tm_zone=10          # abbrev. of timezone ("UTC")   input:ignored
44
45 set -A mirtime_months -- Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
46 set -A mirtime_wdays -- Sun Mon Tue Wed Thu Fri Sat
47 readonly mirtime_months[*] mirtime_wdays[*]
48
49 # $ timet2mjd posix_timet
50 # ⇒ mjd sec
51 function timet2mjd {
52         local -i10 mjd=$1 sec
53
54         (( sec = mjd % 86400 ))
55         (( mjd = (mjd / 86400) + 40587 ))
56         while (( sec < 0 )); do
57                 (( --mjd ))
58                 (( sec += 86400 ))
59         done
60
61         print -- $mjd $sec
62 }
63
64 # $ mjd2timet mjd sec
65 # ⇒ posix_timet
66 function mjd2timet {
67         local -i10 t=$1 sec=$2
68
69         (( t = (t - 40587) * 86400 + sec ))
70         print -- $t
71 }
72
73 # $ mjd_explode mjd sec
74 # ⇒ tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
75 #   tm_wday tm_yday "0" "0" "UTC"
76 function mjd_explode {
77         local tm
78         set -A tm
79         local -i10 sec=$2 day yday mon year=$1
80
81         while (( sec < 0 )); do
82                 (( --year ))
83                 (( sec += 86400 ))
84         done
85         while (( sec >= 86400 )); do
86                 (( ++year ))
87                 (( sec -= 86400 ))
88         done
89
90         (( day = year % 146097 + 678881 ))
91         (( year = 4 * ((year / 146097) + (day / 146097)) ))
92         (( day %= 146097 ))
93         (( tm[tm_wday] = (day + 3) % 7 ))
94         if (( day == 146096 )); then
95                 (( year += 3 ))
96                 (( day = 36524 ))
97         else
98                 (( year += day / 36524 ))
99                 (( day %= 36524 ))
100         fi
101         (( year = 4 * ((year * 25) + (day / 1461)) ))
102         (( day %= 1461 ))
103         (( yday = (day < 306) ? 1 : 0 ))
104         if (( day == 1460 )); then
105                 (( year += 3 ))
106                 (( day = 365 ))
107         else
108                 (( year += day / 365 ))
109                 (( day %= 365 ))
110         fi
111         (( yday += day ))
112         (( day *= 10 ))
113         (( mon = (day + 5) / 306 ))
114         (( day = ((day + 5) % 306) / 10 ))
115         if (( mon >= 10 )); then
116                 (( mon -= 10 ))
117                 (( yday -= 306 ))
118                 (( ++year ))
119         else
120                 (( mon += 2 ))
121                 (( yday += 59 ))
122         fi
123         (( tm[tm_sec] = sec % 60 ))
124         (( sec /= 60 ))
125         (( tm[tm_min] = sec % 60 ))
126         (( tm[tm_hour] = sec / 60 ))
127         (( tm[tm_mday] = day + 1 ))
128         (( tm[tm_mon] = mon ))
129         (( tm[tm_year] = (year < 1 ? year - 1 : year) - 1900 ))
130         (( tm[tm_yday] = yday ))
131         (( tm[tm_isdst] = 0 ))
132         (( tm[tm_gmtoff] = 0 ))
133         tm[tm_zone]=UTC
134
135         print -r -- "${tm[@]}"
136 }
137
138 # $ mjd_implode tm_sec tm_min tm_hour tm_mday tm_mon tm_year \
139 #   ignored ignored ignored tm_gmtoff [ignored]
140 # ⇒ mjd sec
141 function mjd_implode {
142         local tm
143         set -A tm -- "$@"
144         local -i10 day x y sec
145
146         (( sec = tm[tm_sec] + 60 * tm[tm_min] + 3600 * tm[tm_hour] - \
147             tm[tm_gmtoff] ))
148         (( (day = tm[tm_year] + 1900) < 0 )) && (( ++day ))
149         (( y = day % 400 ))
150         (( day = (day / 400) * 146097 - 678882 + tm[tm_mday] ))
151         while (( sec < 0 )); do
152                 (( --day ))
153                 (( sec += 86400 ))
154         done
155         while (( sec >= 86400 )); do
156                 (( ++day ))
157                 (( sec -= 86400 ))
158         done
159         (( x = tm[tm_mon] ))
160         while (( x < 0 )); do
161                 (( --y ))
162                 (( x += 12 ))
163         done
164         (( y += x / 12 ))
165         (( x %= 12 ))
166         if (( x < 2 )); then
167                 (( x += 10 ))
168                 (( --y ))
169         else
170                 (( x -= 2 ))
171         fi
172         (( day += (306 * x + 5) / 10 ))
173         while (( y < 0 )); do
174                 (( day -= 146097 ))
175                 (( y += 400 ))
176         done
177         (( day += 146097 * (y / 400) ))
178         (( y %= 400 ))
179         (( day += 365 * (y % 4) ))
180         (( y /= 4 ))
181         (( day += 1461 * (y % 25) + 36524 * (y / 25) ))
182
183         print -- $day $sec
184 }
185
186 # end magic from mirtime.c
187
188 # escape XHTML characters (three mandatory XML ones plus double quotes,
189 # the latter in an XML safe fashion numerically though)
190 function xhtml_fesc {
191         REPLY=${1//'&'/'&amp;'}
192         REPLY=${REPLY//'<'/'&lt;'}
193         REPLY=${REPLY//'>'/'&gt;'}
194         REPLY=${REPLY//'"'/'&#34;'}
195 }
196
197 function decmin2min {
198         local x=${1#.}0000000
199         typeset -i10 -Z9 y
200
201         x=${x::7}
202         (( y = x * 60 ))
203
204         REPLY=${y::2}.${y:2}
205 }
206
207 function decmin2txt {
208         local graticule=$1 decimal=$2 plus=$3 minus=$4 places=$5 x
209         typeset -i10 -Z$places n
210
211         if [[ $graticule = -* ]]; then
212                 REPLY=$minus
213         else
214                 REPLY=$plus
215         fi
216         n=${graticule#-}
217         REPLY+=" ${n}° "
218
219         x=${|decmin2min $decimal;}
220         typeset -i10 -Z2 n=${x%.*}
221         x=${x#*.}
222         typeset -i10 -Z4 m=${x::4}
223         if (( ${x:4:1} >= 5 )); then
224                 if (( ++m > 9999 )); then
225                         (( ++n ))
226                         m=0
227                 fi
228         fi
229         REPLY+=$n.$m
230 }
231
232 function arbusage {
233         print -ru2 "E: arbitrary waypoint $1"
234         print -ru2 "I: gpx.sh -WP1234 lat lon owner url title description"
235         exit 1
236 }
237
238 function chklatlon {
239         local minus plus vmax mins
240         local -i10 val
241         local -u arg=${2//+([\'\"]|â\80\99\80²|°|°)}
242         arg=${arg##+([   ])}
243         arg=${arg%%+([   ])}
244
245         case $1 {
246         (lat)
247                 minus=S
248                 plus=N
249                 vmax=90
250                 ;;
251         (lon)
252                 minus=W
253                 plus=E
254                 vmax=180
255                 ;;
256         (*)
257                 print internal error
258                 return 1
259                 ;;
260         }
261         if [[ $arg = ${minus}*([         ])+([0-9])?(.*([0-9])) ]]; then
262                 arg=-${arg##$minus*([    ])}
263         elif [[ $arg = ${plus}*([        ])+([0-9])?(.*([0-9])) ]]; then
264                 arg=${arg##$plus*([      ])}
265         elif [[ $arg = +([0-9])?(.*([0-9]))*([   ])$minus ]]; then
266                 arg=-${arg%%*([  ])$minus}
267         elif [[ $arg = +([0-9])?(.*([0-9]))*([   ])$plus ]]; then
268                 arg=${arg%%*([   ])$plus}
269         elif [[ $arg = ${minus}*([       ]).+([0-9]) ]]; then
270                 arg=-0${arg##$minus*([   ])}
271         elif [[ $arg = ${plus}*([        ]).+([0-9]) ]]; then
272                 arg=0${arg##$plus*([     ])}
273         elif [[ $arg = .+([0-9])*([      ])$minus ]]; then
274                 arg=-0${arg%%*([         ])$minus}
275         elif [[ $arg = .+([0-9])*([      ])$plus ]]; then
276                 arg=0${arg%%*([  ])$plus}
277         elif [[ $arg = ${minus}*([       ])+([0-9])+([   ])@(+([0-9])?(.*([0-9]))|.+([0-9])) ]]; then
278                 arg=${arg##$minus*([     ])}
279                 val=10#${arg%%[  ]*}
280                 arg=${arg##*+([  ])}
281                 arg=-$(dc -e "20k $arg 60/ ${val}+ps.")
282         elif [[ $arg = ${plus}*([        ])+([0-9])+([   ])@(+([0-9])?(.*([0-9]))|.+([0-9])) ]]; then
283                 arg=${arg##$plus*([      ])}
284                 val=10#${arg%%[  ]*}
285                 arg=${arg##*+([  ])}
286                 arg=$(dc -e "20k $arg 60/ ${val}+ps.")
287         elif [[ $arg = +([0-9])+([       ])@(+([0-9])?(.*([0-9]))|.+([0-9]))*([  ])$minus ]]; then
288                 arg=${arg%%*([   ])$minus}
289                 val=10#${arg%%[  ]*}
290                 arg=${arg##*+([  ])}
291                 arg=-$(dc -e "20k $arg 60/ ${val}+ps.")
292         elif [[ $arg = +([0-9])+([       ])@(+([0-9])?(.*([0-9]))|.+([0-9]))*([  ])$plus ]]; then
293                 arg=${arg%%*([   ])$plus}
294                 val=10#${arg%%[  ]*}
295                 arg=${arg##*+([  ])}
296                 arg=$(dc -e "20k $arg 60/ ${val}+ps.")
297         fi
298         [[ $arg = '+'* ]] && arg=${arg#+}
299         [[ $arg = ?(-)+([0-9]) ]] && arg+=.
300         [[ $arg = ?(-).+([0-9]) ]] && arg=${arg/./0.}
301         case $arg {
302         (+([0-9]).*([0-9]))
303                 arg=${arg#+}
304                 val=10#${arg%.*}
305                 mins=${arg#*.}
306                 mins=${mins%%*(0)}
307                 if (( val < vmax )); then
308                         :
309                 elif (( val == vmax )) && [[ -z $mins ]]; then
310                         :
311                 else
312                         print -r "value $arg out of range"
313                         return 1
314                 fi
315                 arg=$val.${mins:-0}
316                 ;;
317         (-+([0-9]).*([0-9]))
318                 arg=${arg#-}
319                 val=10#${arg%.*}
320                 mins=${arg#*.}
321                 mins=${mins%%*(0)}
322                 if (( val < vmax )); then
323                         :
324                 elif (( val == vmax )) && [[ -z $mins ]]; then
325                         :
326                 else
327                         print -r "value $arg out of range"
328                         return 1
329                 fi
330                 arg=-$val.${mins:-0}
331                 ;;
332         (*)
333                 print -r "value $arg unrecognised"
334                 return 1
335                 ;;
336         }
337         print -r -- $arg
338 }
339
340 typeset -i10 -Z4 dY
341 typeset -i10 -Z2 dM dD
342
343 xff="${HTTP_X_FORWARDED_FOR:+$HTTP_X_FORWARDED_FOR, }$REMOTE_ADDR"
344 set -A fetch -- ftp -H "X-Forwarded-For: $xff" -H "User-Agent: MirKarte/0.2 (Beta; +https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=useful-scripts/mirkarte.git using MirBSD ftp)" -o -
345 whence -p wget >/dev/null 2>&1 && \
346     set -A fetch -- wget -e robots=off --header "X-Forwarded-For: $xff" -U "MirKarte/0.2 (Beta; +https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=useful-scripts/mirkarte.git using GNU wget)" -qO- -T3
347 function gnumd5 {
348         md5sum | sed 's/ .*$//'
349 }
350 md=gnumd5
351 whence -p md5 >/dev/null 2>&1 && md=md5
352
353 wp=$1
354 wptype=
355 case $wp {
356 (-*)
357         wptype=arbitrary ;;
358 (OC*)
359         wptype=oc ;;
360 (GD*)
361         wptype=gd ;;
362 (VX*)
363         wptype=vx ;;
364 (2[0-9][0-9][0-9]-@(0[1-9]|1[0-2])-[0-3][0-9]_?(-)+([0-9])_?(-)+([0-9]))
365         wptype=geohash ;;
366 (2[0-9][0-9][0-9]-@(0[1-9]|1[0-2])-[0-3][0-9]_[Gg][Ll][Oo][Bb][Aa][Ll])
367         wptype=globalhash ;;
368 }
369
370 [[ -n $wptype ]] || exit 1
371
372 # redirections
373 if [[ $wptype = oc ]]; then
374         i=$("${fetch[@]}" "https://www.opencaching.de/search.php?searchbywp=1&showresult=1&output=GPX&f_inactive=0&f_ignored=0&wp=$wp" \
375             2>/dev/null)
376         [[ $i = *'<wpt'* ]] || exit 1
377         print -r -- "$i"
378         exit 0
379 fi
380
381 now=$(date -u +'%Y-%m-%dT%H:%M:%SZ')                            # current time
382
383 case $wptype {
384 (arbitrary)
385         [[ -n $4 && -n $5 && -n $6 && -n $7 ]] || arbusage 'syntax error'
386         wp_src='for an arbitrary waypoint'                      # data source
387         lat=$(chklatlon lat "$2") || arbusage "$lat"            # position N/S
388         lon=$(chklatlon lon "$3") || arbusage "$lon"            # position E/W
389         wptime=$now                                             # date placed
390         wpname=${wp#-}                                          # WP code full
391         if (( ${#wpname} > 8 )); then
392                 typeset -Uui16 -Z11 hex="0x${wpname@#} & 0x7FFFFFFF"
393                 wpcode=${hex#16#}                               # WP code 8byte
394         else
395                 wpcode=$wpname                                  # WP code 8byte
396         fi
397         wpdesc=$6                                               # title text
398         wpurlt=$5                                               # link target
399         wpurln=$6                                               # link text
400         wpownr=$4                                               # owner text
401         wp_dif=2                                                # D rating
402         wp_ter=2                                                # T rating
403         wpsdsc="Arbitrary waypoint <tt>${|xhtml_fesc "$wpname";}</tt>"  # short html
404         wpldsc=$7                                               # long html
405         wphint=''                                               # hint text
406         ;;
407 (gd)
408         wp_src='from geodashing.gpsgames.org data'              # data source
409         set -eo pipefail
410         "${fetch[@]}" "http://geodashing.gpsgames.org/cgi-bin/dp.pl?dp=$wp" | \
411             sed -n '/^.*maps\.pl.*[&;?]lat=\([0-9.-]*\)&a*m*p*;*lon=\([0-9.-]*\)".*$/s//\1 \2/p' |&
412         read -p lat lon                                         # position
413         [[ -n $lat && -n $lon ]]
414         set +e
415         lattxt=${|decmin2txt ${lat%.*} .${lat#*.} N S 2;}
416         lontxt=${|decmin2txt ${lon%.*} .${lon#*.} E W 3;}
417         wptime=${now%%+([0-9])T*}01T00:00:00Z                   # date placed
418         wpname=$wp                                              # WP code full
419         wpcode=${wp//-}                                         # WP code 8byte
420         wpdesc="$wp Dashpoint"                                  # title text
421         wpurlt="http://geodashing.gpsgames.org/cgi-bin/dp.pl?dp=$wp"    # link target
422         wpurln="$wp"                                            # link text
423         wpownr="gpsgames.org"                                   # owner text
424         wp_dif=2                                                # D rating
425         wp_ter=2                                                # T rating
426         wpsdsc="Dashpoint ${wpname//_/ }"                       # short html
427         wpldsc="GeoDashing at $lattxt $lontxt"                  # long html
428         wphint=''                                               # hint text
429         ;;
430 (vx)
431         wp_src='from geovexilla.gpsgames.org data'              # data source
432         T=$(mktemp /tmp/gpx.XXXXXXXXXX) || exit 1
433         "${fetch[@]}" >"$T" "http://geovexilla.gpsgames.org/cgi-bin/vx.pl?listwaypointlogs=yes&wp=$wp" || { rm -f "$T"; exit 1; }
434         sed -n '/^.*maps\.pl.*[&;?]lat=\([0-9.-]*\)&a*m*p*;*lon=\([0-9.-]*\)".*$/s//\1 \2/p' <"$T" |&
435         read -p lat lon || lat=                                 # position
436         [[ -n $lat && -n $lon ]] || { rm -f "$T"; exit 1; }
437         flag=$(sed -n '/^.*src=".images.flags.*title="\([^"]*\)".*$/s//\1/p' <"$T")
438         lattxt=${|decmin2txt ${lat%.*} .${lat#*.} N S 2;}
439         lontxt=${|decmin2txt ${lon%.*} .${lon#*.} E W 3;}
440         wptime=$(sed -n '/Expires:/s/^.* \(2[0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]\) \([0-2][0-9]:[0-5][0-9]:[0-6][0-9]\) UTC\( .*\)\{0,1\}$/\1T\2Z/p' "$T")
441         [[ -n $wptime ]] || wptime=$now                         # date placed
442         wpname=$wp                                              # WP code full
443         wpcode=${wp//-}                                         # WP code 8byte
444         wpdesc="$wp${flag:+ $flag} flag"                        # title text
445         wpurlt="http://geovexilla.gpsgames.org/cgi-bin/vx.pl?listwaypointlogs=yes&wp=$wp"       # link target
446         wpurln="$wp"                                            # link text
447         wpownr="gpsgames.org"                                   # owner text
448         wp_dif=2                                                # D rating
449         wp_ter=2                                                # T rating
450         wpsdsc="Flag ${wpname//_/ }"                            # short html
451         wpldsc="GeoVexilla at $lattxt $lontxt$(fgrep \
452             -e Near: -e Expires: -e Flag: "$T" | sed \
453             -e 's!src="/images!src="http://geovexilla.gpsgames.org/images!g' \
454             -e 's!</TD>!!g')"                                   # long html
455         wphint=''                                               # hint text
456         rm -f "$T"
457         ;;
458 (geohash)
459         # split into day and graticule
460         IFS=$' \t\n_'
461         set -A w -- $wp
462         IFS=$' \t\n-'
463         set -A d -- ${w[0]}
464         IFS=$' \t\n'
465         dY=${d[0]}
466         dM=${d[1]}
467         dD=${d[2]}
468         lat=${w[1]}
469         lon=${w[2]}
470
471         # get MJD for that day
472         set -A t -- $(mjd_implode 0 0 0 $((dD)) $((dM - 1)) $((dY - 1900)))
473         mjd=${t[0]}
474         # 30W rule
475         (( t[0] -= ((lon > -30 && mjd >= 54613) ? 1 : 0) ))
476         # get DJIA day
477         set -A t -- $(mjd_explode ${t[0]} 0)
478         (( dY = t[tm_year] + 1900 ))
479         (( dM = t[tm_mon] + 1 ))
480         (( dD = t[tm_mday] ))
481         # get DJIA
482         i=$("${fetch[@]}" http://carabiner.peeron.com/xkcd/map/data/$dY/$dM/$dD \
483             2>/dev/null)
484         [[ -n $i ]] || exit 1
485         # get hash day
486         set -A t -- $(mjd_explode $mjd 0)
487         (( dY = t[tm_year] + 1900 ))
488         (( dM = t[tm_mon] + 1 ))
489         (( dD = t[tm_mday] ))
490         # get that day’s hash
491         set -A latlon -- $(print -nr -- "$dY-$dM-$dD-$i" | $md | \
492             sed -e 'y/abcdef/ABCDEF/' -e 's/.\{16\}/.&p/g' | \
493             dc -e 16i -)
494         # format the common notation
495         lattxt=${|decmin2txt $lat ${latlon[0]} N S 2;}
496         lontxt=${|decmin2txt $lon ${latlon[1]} E W 3;}
497         # get that graticule’s coordinates
498         lat+=${latlon[0]}                                       # position N/S
499         lon+=${latlon[1]}                                       # position E/W
500         # fill out data and metadata
501         wp_src='using DJIA from carabiner.peeron.com'           # data source
502         wptime=$dY-$dM-${dD}T00:00:00Z                          # date placed
503         wpname=$dY-$dM-${dD}_${lat%.*}_${lon%.*}                # WP code full
504         typeset -Uui16 -Z11 hex="0x${wpname@#} & 0x7FFFFFFF"
505         wpcode=${hex#16#}                                       # WP code 8byte
506         wpdesc="${wpname//_/ } GeoHash"                         # title text
507         wpurlt="http://wiki.xkcd.com/geohashing/$wpname"        # link target
508         wpurln="Meetup ${wpname//_/ }"                          # link text
509         wpownr="The Internet"                                   # owner text
510         wp_dif=2                                                # D rating
511         wp_ter=2                                                # T rating
512         wpsdsc="GeoHash ${wpname//_/ }"                         # short html
513         wpldsc="Regular Geo Hash on $lattxt $lontxt"            # long html
514         wphint=''                                               # hint text
515         ;;
516 (globalhash)
517         # split into day and graticule
518         IFS=$' \t\n_'
519         set -A w -- $wp
520         IFS=$' \t\n-'
521         set -A d -- ${w[0]}
522         IFS=$' \t\n'
523         dY=${d[0]}
524         dM=${d[1]}
525         dD=${d[2]}
526
527         # get MJD for that day
528         set -A t -- $(mjd_implode 0 0 0 $((dD)) $((dM - 1)) $((dY - 1900)))
529         mjd=${t[0]}
530         # 30W rule
531         (( --t[0] ))
532         # get DJIA day
533         set -A t -- $(mjd_explode ${t[0]} 0)
534         (( dY = t[tm_year] + 1900 ))
535         (( dM = t[tm_mon] + 1 ))
536         (( dD = t[tm_mday] ))
537         # get DJIA
538         i=$("${fetch[@]}" http://carabiner.peeron.com/xkcd/map/data/$dY/$dM/$dD \
539             2>/dev/null)
540         [[ -n $i ]] || exit 1
541         # get hash day
542         set -A t -- $(mjd_explode $mjd 0)
543         (( dY = t[tm_year] + 1900 ))
544         (( dM = t[tm_mon] + 1 ))
545         (( dD = t[tm_mday] ))
546         # get that day’s hash
547         set -A latlon -- $(print -nr -- "$dY-$dM-$dD-$i" | $md | \
548             sed -e 'y/abcdef/ABCDEF/' -e 's/.\{16\}/.&p/g' | \
549             dc -e 16i -)
550         x=$(dc -e "${latlon[0]} 180* 90-ps."); lat=${x%.*}; latlon[0]=.${x#*.}
551         x=$(dc -e "${latlon[1]} 360* 180-ps."); lon=${x%.*}; latlon[1]=.${x#*.}
552         # format the common notation
553         lattxt=${|decmin2txt $lat ${latlon[0]} N S 2;}
554         lontxt=${|decmin2txt $lon ${latlon[1]} E W 3;}
555         # get that graticule’s coordinates
556         lat+=${latlon[0]}                                       # position N/S
557         lon+=${latlon[1]}                                       # position E/W
558         # fill out data and metadata
559         wp_src='using DJIA from carabiner.peeron.com'           # data source
560         wptime=$dY-$dM-${dD}T00:00:00Z                          # date placed
561         wpname=$dY-$dM-${dD}_global                             # WP code full
562         typeset -Uui16 -Z11 hex="0x${wpname@#} & 0x7FFFFFFF"
563         wpcode=${hex#16#}                                       # WP code 8byte
564         wpdesc="${wpname//_/ } GeoHash"                         # title text
565         wpurlt="http://wiki.xkcd.com/geohashing/$wpname"        # link target
566         wpurln="Meetup ${wpname//_/ }"                          # link text
567         wpownr="The Internet"                                   # owner text
568         wp_dif=2                                                # D rating
569         wp_ter=2                                                # T rating
570         wpsdsc="GeoHash ${wpname//_/ }"                         # short html
571         wpldsc="Global Geo Hash on $lattxt $lontxt"             # long html
572         wphint=''                                               # hint text
573         ;;
574 (*)
575         exit 1
576         ;;
577 }
578
579 typeset -u wpcode
580
581 sed $'s/$/\r/' <<EOF
582 <?xml version="1.0" encoding="utf-8"?>
583 <gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1.0" creator="MirKarte by mirabilos - https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=useful-scripts/mirkarte.git" xsi:schemaLocation="http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd http://www.groundspeak.com/cache/1/0/1 http://www.groundspeak.com/cache/1/0/1/cache.xsd" xmlns="http://www.topografix.com/GPX/1/0">
584   <name>Waypoint Listing Generated by MirKarte</name>
585   <desc>This is an individual waypoint generated ${|xhtml_fesc "$wp_src";} by MirKarte</desc>
586   <author>mirabilos</author>
587   <email>miros-discuss@mirbsd.org</email>
588   <url>https://evolvis.org/plugins/scmgit/cgi-bin/gitweb.cgi?p=useful-scripts/mirkarte.git</url>
589   <urlname>MirKarte (beta)</urlname>
590   <time>${|xhtml_fesc "$now";}</time>
591   <keywords>cache, geocache, dashpoint, geovexilla, xkcd, geohashing, waypoint, shutterspot, terracaching, opencaching</keywords>
592   <bounds minlat="${|xhtml_fesc "$lat";}" minlon="${|xhtml_fesc "$lon";}" maxlat="${|xhtml_fesc "$lat";}" maxlon="${|xhtml_fesc "$lon";}" />
593   <wpt lat="${|xhtml_fesc "$lat";}" lon="${|xhtml_fesc "$lon";}">
594     <time>${|xhtml_fesc "$wptime";}</time>
595     <name>${|xhtml_fesc "$wpcode";}</name>
596     <desc>${|xhtml_fesc "$wpdesc";}</desc>
597     <url>${|xhtml_fesc "$wpurlt";}</url>
598     <urlname>${|xhtml_fesc "$wpurln";}</urlname>
599     <sym>Geocache</sym>
600     <type>Geocache|Virtual Cache</type>
601     <groundspeak:cache id="notfromgs_${wpname@#}" available="False" archived="False" xmlns:groundspeak="http://www.groundspeak.com/cache/1/0/1">
602       <groundspeak:name>${|xhtml_fesc "$wpname";}</groundspeak:name>
603       <groundspeak:placed_by>${|xhtml_fesc "$wpownr";}</groundspeak:placed_by>
604       <groundspeak:owner id="notfromgspr">${|xhtml_fesc "$wpownr";}</groundspeak:owner>
605       <groundspeak:type>Virtual Cache</groundspeak:type>
606       <groundspeak:container>Virtual</groundspeak:container>
607       <groundspeak:attributes>
608         <groundspeak:attribute id="67" inc="1">GeoTour</groundspeak:attribute>
609       </groundspeak:attributes>
610       <groundspeak:difficulty>${|xhtml_fesc "$wp_dif";}</groundspeak:difficulty>
611       <groundspeak:terrain>${|xhtml_fesc "$wp_ter";}</groundspeak:terrain>
612       <groundspeak:country>Terra</groundspeak:country>
613       <groundspeak:state>unknown</groundspeak:state>
614       <groundspeak:short_description html="True">${|xhtml_fesc "$wpsdsc";}</groundspeak:short_description>
615       <groundspeak:long_description html="True">${|xhtml_fesc "$wpldsc";}</groundspeak:long_description>
616       <groundspeak:encoded_hints>${|xhtml_fesc "$wphint";}</groundspeak:encoded_hints>
617       <groundspeak:logs>
618       </groundspeak:logs>
619       <groundspeak:travelbugs />
620     </groundspeak:cache>
621   </wpt>
622 </gpx>
623 EOF
624 exit 0