get_weather 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. #!/bin/bash
  2. usage() {
  3. cat <<EOF
  4. Get weather data from FMI's mobile data service.
  5. Last non-option argument:
  6. Most options can also be shell-sourced from $conf
  7. Usage: ./$me [options] [location_id]
  8. Dependencies: ${deps[@]}
  9. Options:
  10. -L int numerical location ID (e.g. from geonames.org; you can use the provided
  11. get_location_id script). If no location ID is given Helsinki is used.
  12. Default: geoid=$geoid
  13. -l str language, possible values: en,fi,sv
  14. Default: lang=$lang
  15. -i str comma-separated list of indices (positive whole numbers) to get
  16. forecasts for. The default is to get them for every hour for the first
  17. two hours, then every other hour twice, then every three hours. No index
  18. must be larger than $max_index.
  19. Default: indices=$indices
  20. -f str Formatted output templates specifier.
  21. Will use header."string", forecast."string" and footer."string"
  22. templates from storage folder (see below, -s option).
  23. Currently "lua" is the only already present set of templates
  24. Default: format='' (terminal output)
  25. -o str Comma-separated list of options to pass to formatting section, to
  26. override one or several defaults.
  27. Example: "offset=50,img_size_fc=32,padding_h=10,font=droid\ sans\ mono"
  28. width - - - - - (px) overall width of output, padding excluded (you
  29. will still need to adjust your conky.config manually -
  30. default: $width)
  31. padding_h - - - (px) horizontal padding (default: $padding_h)
  32. offset - - - - - (px) initial offset for forecasts - or: height of the
  33. header (default: $offset). This value is incremented
  34. with each forecast element.
  35. img_size_fc - - (px) width and height of forecast image (default: $img_size_fc)
  36. img_size_wind - (px) width and height of wind image (default: $img_size_wind)
  37. forecast_height (px) height of each forecast element
  38. (default: same as img_size_fc)
  39. tempcol_plus - - (hex) color for temperature >= 0 (default: $tempcol)
  40. tempcol_minus - (hex) color for temperature < 0 (default: $tempcol_minus)
  41. textcol - - - - (hex) color for other text (default: $textcol)
  42. linecol - - - - (hex) color for line separator and time
  43. (default: $linecol)
  44. popcol70 - - - - (hex) color for PoP (chance of precipitation)
  45. when percentage is >=70 (default: $popcol70)
  46. popbg30 - - - - (hex) background color for PoP when percentage is >=30
  47. (default: $popbg30)
  48. popbg70 - - - - (hex) background color for PoP when percentage is >=70
  49. (default: $popbg70)
  50. font - - - - - - (str) font to use for all text (default: $font)
  51. Default: options=''
  52. -g Include wind gusts in forecast
  53. (comes from a different source: $urlbase_gusts)
  54. Default: gusts=$gusts
  55. -p Convert images to PNG instead of using native SVG
  56. Default: usepng=$usepng
  57. -r Remove cached weather data first
  58. EOF
  59. exit 0
  60. }
  61. ex_err() {
  62. [ -n "$opt" ] && printf '%s' "-${opt}: "
  63. [ -n "$*" ] && printf '%s\n' "$*"
  64. printf 'Try %s -h\n%b' "$0"
  65. exit 1
  66. }
  67. dl_smartsymbol() {
  68. # Check if smartsymbol SVG exists, otherwise download it, then check again
  69. # $1 full path to desired output
  70. [ -r "$1" ] && return 0
  71. curl --output "$1" --user-agent "$useragent" "$urlbase_img_fc/${1##*/}" >&2
  72. [ -r "$1" ] || usage "Oof. Could not download required image $1 from $urlbase_img_fc/${1##*/}"
  73. }
  74. dl_interval() {
  75. # $1: file to download to
  76. # $2: URL
  77. # will download a file if its age starts with the previous full hour
  78. # meaning: the threshold to re-download is not a full hour, but the start of a new hour
  79. if ! [ -r "$1" ]; then
  80. echo "Downloading for the first time: $2" >&2
  81. curl --user-agent "$useragent" -o "$1" "$2" >&2 || usage "Something went wrong. Bailing."
  82. elif check_hour "$1"; then
  83. echo "Updating $1 - downloading: $2" >&2
  84. curl --user-agent "$useragent" -o "$1" "$2" >&2 || usage "Something went wrong. Bailing."
  85. else
  86. echo "No new hour has started & file $1 exists." >&2
  87. fi
  88. }
  89. get_ss_text() {
  90. # get forecast text according to smartsymbol, from symbol_$lang file
  91. # $1: smartsymbol number
  92. while read line; do
  93. [[ "$line" == "$1"* ]] && RETVAL="${line#*:}" && break
  94. done <"$symbol_text$lang"
  95. }
  96. make_wind_svg() {
  97. # replaces strings in SVG data string with calculated values
  98. wind_svg="${wind_svg_base/@wind_deg@/$((wind_deg-180))}"
  99. wind_svg="${wind_svg//@circle_color@/$circle_color}"
  100. wind_svg="${wind_svg//@arrow_color@/$arrow_color}"
  101. }
  102. check_hour() {
  103. # $1: file to check
  104. # just return whether the current hour is at least 1 more than the file's age
  105. # because forecasts are updated on the full hour
  106. (( "$HOUR" > "$(date '+%y%m%d%H' --date "$(stat -c '%y' "$1")")" ))
  107. }
  108. reset="\e[0;0m" # reset terminal to normal colours
  109. me="${0##*/}"
  110. HOUR="$(date '+%y%m%d%H')" # the current hour, to decide whether re-downloading is required
  111. urlbase="https://widget.weatherproof.fi/android/androidwidget.php"
  112. urlbase_gusts="https://opendata.fmi.fi"
  113. urlbase_img_fc="https://cdn.fmi.fi/symbol-images/smartsymbol/v31/p"
  114. RETVAL='' # global variable to store functions' return values
  115. deps=( "curl:" "required," "jshon:" "required," "stat:" "required," "date:" "required," "sed:" "for conky formatted output," "rsvg-convert:" "to convert SVGs to PNGs" )
  116. max_index=220 # That many forecasts are contained in the json
  117. # files, storage, static and changing data
  118. files="${0%/*}/files"
  119. storage="$files/${me}.d"
  120. conf="$storage/conf"
  121. mkdir -p "$storage"
  122. symbol_text="$files/symbol_text_" # 2-letter language code will be appended
  123. geoid=658225 # Helsinki
  124. lang=en
  125. gusts=0
  126. indices="0,1,3,6,10,15,21,28,36,45,55"
  127. format=''
  128. usepng=0
  129. ### options (see help text for -o option) ###
  130. options=''
  131. ### what options haven't been set by users will be set here
  132. width=330
  133. padding_h=10
  134. offset=30
  135. img_size_fc=64
  136. img_size_wind=30
  137. tempcol_plus="ee0000"
  138. tempcol_minus="1e90ff"
  139. textcol="303193"
  140. linecol="888888"
  141. font="Roboto Condensed"
  142. popcol70="ffffff"
  143. popbg30="A1C8E6"
  144. popbg70="3A66E3"
  145. # wind_svg_base must contain the invalid string "@wind_deg@" which will be replaced with the actual rotation, also
  146. # @arrow_color@ and @circle_color@
  147. #~ wind_svg_base='<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><g fill="none" fill-rule="evenodd" transform="rotate(@wind_deg@ 15 15)"><circle cx="15" cy="15" r="10" fill="@circle_color@" stroke="@arrow_color@" stroke-width="2"/><path fill="@arrow_color@" fill-rule="nonzero" d="M14.999 5L20 12 16 12 16 25 14 25 14 11.999 10 11.999z"/></g></svg>'
  148. # without stroke
  149. wind_svg_base='<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><g fill="none" fill-rule="evenodd" transform="rotate(@wind_deg@ 15 15)"><circle cx="15" cy="15" r="13" fill="@circle_color@" /><path fill="@arrow_color@" fill-rule="nonzero" d="M14.999 5L20 12 16 12 16 25 14 25 14 11.999 10 11.999z"/></g></svg>'
  150. # Mobile User Agent
  151. # curl "https://www.whatismybrowser.com/guides/the-latest-user-agent/android" | xmllint --html --nonet --xpath "//table//li//text()" - 2>/dev/null | head -1
  152. useragent="Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.54 Mobile Safari/537.36"
  153. printf '%b' "\e[33m" >&2 # change color to yellow until optionparsing & data aquisition is done
  154. # dependency check
  155. for ((i=0;i<${#deps[@]};i+=2)); do
  156. type -f ${deps[i]%:} >/dev/null 2>&1 || { [[ "${deps[i+1]%,}" == "required" ]] && usage "Missing dependency: ${deps[i]%:}" || echo "Missing optional dependency: ${deps[i]%:} (${deps[i+1]%,})" >&2; }
  157. done
  158. [ -r "$conf" ] && source "$conf"
  159. while getopts "L:l:i:f:o:gprh" opt; do
  160. case $opt in
  161. L) [[ "$OPTARG" =~ [0-9]+ ]] || ex_err "Invalid location ID: $OPTARG"
  162. geoid="$OPTARG"
  163. ;;
  164. l) [[ "$OPTARG" =~ ^(fi|en|sv)$ ]] || ex_err "Invalid language: $OPTARG"
  165. lang="$OPTARG"
  166. ;;
  167. i) { [[ "${OPTARG//,/}" =~ ^[0-9]+$ ]] && [[ "$OPTARG" =~ ^[0-9].*[0-9]$ ]]; } || ex_err "-${opt}: invalid option $OPTARG"
  168. indices="$OPTARG"
  169. ;;
  170. f) [[ "$OPTARG" =~ ^[0-9a-zA-Z]+$ ]] || ex_err "invalid option $OPTARG"
  171. format="$OPTARG"
  172. ;;
  173. o) options="$OPTARG"
  174. ;;
  175. g) gusts=1
  176. ;;
  177. p) usepng=1
  178. ;;
  179. r) rm -f "$storage/"*.{json,xml}
  180. ;;
  181. h) usage
  182. ;;
  183. *) unset opt; ex_err
  184. ;;
  185. esac
  186. done
  187. max="${indices##*,}"
  188. file_forecast="$storage/forecast-$geoid.json"
  189. file_gusts="$storage/gusts-$geoid.xml"
  190. if [ -n "$options" ]; then
  191. # convert -o "options" string to actual variables
  192. oldifs="$IFS"; IFS=, options=( $options ); IFS="$oldifs"
  193. for ((i=0;i<${#options[@]};i++));do
  194. var="${options[i]%%=*}"
  195. val="${options[i]##*=}"
  196. declare "$var"="$val" || {
  197. opt=o ex_err" Could not set variable \"$var\" to value \"$val\"."
  198. }
  199. done
  200. fi
  201. # variables that need to be set after parsing options
  202. forecast_height="${forecast_height-"$img_size_fc"}"
  203. # declare this array even if it isn't used in the end
  204. declare -A wg # windgusts indexed by local timestamps
  205. ###########################
  206. ####### DATA AQUISITION ###
  207. ### Load gusts XML and extract values ###
  208. if [[ "$gusts" == 1 ]]; then
  209. # type of forecast, get a list with ./wfs_describeStoredQueries
  210. #Harmonie Point Weather Forecast as multipointcoverage (fmi::forecast::harmonie::surface::point::multipointcoverage)
  211. #Harmonie Point Weather Forecast as simple features (fmi::forecast::harmonie::surface::point::simple)
  212. #Harmonie Point Weather Forecast as time value pairs (fmi::forecast::harmonie::surface::point::timevaluepair)
  213. query="fmi::forecast::harmonie::surface::point::simple"
  214. wfs_req="wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id"
  215. starttime="$(date -u '+%FT%R:%SZ')" # now, in UTC
  216. endtime="$(date -u '+%FT%R:%SZ' --date="$((max + 1)) hours")" # maximum results. Have to indcrease by 1 becasue forecasts are always on the full hour.
  217. timestep=60 # always 60 minutes, because that's what the mobile service offers
  218. URL="$urlbase_gusts/$wfs_req=${query}&geoid=${geoid}&starttime=${starttime}&endtime=${endtime}&timestep=${timestep}&parameters=windgust"
  219. #~ curl -s -o "$file_xml" "$URL"
  220. #~ curl -s -o "$file_forecast" --user-agent "$useragent" "${urlbase}?l=${lang}&locations=${geoid}"
  221. #~ exit
  222. dl_interval "$file_gusts" "$URL"
  223. xml="$(<"$file_gusts")"
  224. while read line; do
  225. # avoid dependency for XML parsing, quick'n'dirty line by line instead:
  226. [[ "$line" == *'<BsWfs:Time>'* ]] && {
  227. index="${line#*>}"
  228. index="${index%%<*}"
  229. index="$(date '+%Y%m%dT%H%M%S' --date "$index")"
  230. continue
  231. }
  232. [[ "$line" == *'<BsWfs:ParameterValue>'* ]] && {
  233. line="${line#*>}"
  234. line="${line%%<*}"
  235. { [[ "${line#*.}" == "0"* ]] || (( ${line#*.} < 50 )); } && line=${line%.*} || line=$(( ${line%.*} + 1 ))
  236. wg[$index]="$line"
  237. }
  238. done <<<"$xml"
  239. fi
  240. ### Load weather forecast JSON ###
  241. URL="${urlbase}?l=${lang}&locations=${geoid}"
  242. dl_interval "$file_forecast" "$URL"
  243. json="$(<"$file_forecast")"
  244. ### Associative array to house all forecasts' key/value pairs
  245. declare -A output
  246. # This we need only once, the name of the place, and its region:
  247. name="$(jshon -e forecasts -e 0 -e forecast -e 0 -e name -u -p -e region -u <<<"$json")"
  248. region="${name##*$'\n'}"
  249. name="${name%%$'\n'*}"
  250. # This we need for every hourly forecast index:
  251. keys=(localtime Temperature FeelsLike SmartSymbol PoP WindSpeedMS WindDirection WindCompass8 Precipitation1h)
  252. units=("" "°C" "°C" "" "%" "m/s" "°" "" "mm") # applied later
  253. for ((i=0;i<${#keys[@]};i++)); do
  254. j=0
  255. while read line; do
  256. output[${keys[i]} $((j++))]="$line"
  257. done <<<"$(jshon -Q -e forecasts -e 0 -e forecast -a -e "${keys[i]}" -u <<<"$json")"
  258. done
  259. printf '%b' "$reset" >&2 # reset colors
  260. ################################################################################
  261. ####### DATA OUTPUT & FORMATTING #############
  262. indices=( ${indices//,/ } ) # transform comma-separated list to array
  263. declare -A weekdays=(
  264. [en1]=Mon [en2]=Tue [en3]=Wed [en4]=Thu [en5]=Fri [en6]=Sat [en7]=Sun
  265. [en1long]=Monday [en2long]=Tuesday [en3long]=Wednesday [en4long]=Thursday [en5long]=Friday [en6long]=Saturday [en7long]=Sunday
  266. [fi1]=ma [fi2]=ti [fi3]=ke [fi4]=to [fi5]=pe [fi6]=la [fi7]=su
  267. [fi1long]=maanantai [fi2long]=tiistai [fi3long]=keskiviikko [fi4long]=torstai [fi5long]=perjantai [fi6long]=lauantai [fi7long]=sunnuntai
  268. [sv1]=mån [sv2]=tis [sv3]=ons [sv4]=tor [sv5]=fre [sv6]=lör [sv7]=sön
  269. [sv1long]=måndag [sv2long]=tisdag [sv3long]=onsdag [sv4long]=torsdag [sv5long]=fredag [sv6long]=lördag [sv7long]=söndag
  270. )
  271. long="" # set to "long" to use long names
  272. upper=0 # set to 0 to use days as they are, without capitalizing them
  273. if [ -z "$format" ]; then
  274. ### Simple CLI output ###
  275. sep="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯"
  276. ### HEADING ###
  277. printf '%s, %s\n%s\n' "$name" "$region" "$sep"
  278. ### FORECAST ###
  279. for i in ${indices[@]}; do
  280. for ((j=0;j<${#keys[@]};j++)); do
  281. [[ "${keys[j],,}" == smartsymbol ]] && {
  282. # get weather icon description from symbol_ file
  283. get_ss_text "${output[${keys[j]} $i]}"
  284. echo "${keys[j]}: $RETVAL"
  285. continue
  286. }
  287. echo "${keys[j]}: ${output[${keys[j]} $i]}${units[j]}"
  288. done
  289. [[ "$gusts" == 1 ]] && echo "WindGusts: ${wg[${output[localtime $i]}]}"
  290. echo "$sep"
  291. done
  292. else
  293. ### Output with images etc. (conky) ###
  294. ### check for or create different subfolders for SVG and PNG images
  295. smartsymbols_png="$storage/smartsymbol_png_$img_size_fc"
  296. windsymbols_png="$storage/windsymbol_png_$img_size_wind"
  297. smartsymbols_svg="$storage/smartsymbol_svg"
  298. windsymbols_svg="$storage/windsymbol_svg"
  299. mkdir -p "$smartsymbols_svg" "$windsymbols_svg" || ex_err "Could not find or create subfolders in \"$storage\""
  300. [[ "$usepng" == 1 ]] && { mkdir -p "$smartsymbols_png" "$windsymbols_png" || ex_err "Could not find or create subfolders in \"$storage\""; }
  301. template_header="$files/header.$format"
  302. template_forecast="$files/forecast.$format"
  303. template_footer="$files/footer.$format"
  304. # /^[ \t]*#/d; /^[ \t]*--/d = remove comments
  305. sed_script_base="/^[ \t]*--/d; /^[ \t]*#/d
  306. s|%textcol%|$textcol|g
  307. s|%linecol%|$linecol|g
  308. s|%padding_h%|$padding_h|g
  309. s|%font%|$font|g
  310. s|%img_size_fc%|$img_size_fc|g
  311. s|%img_size_wind%|$img_size_wind|g
  312. s|%width%|$width|g
  313. s|%forecast_height%|$forecast_height|g"
  314. ### HEADER ###
  315. [ -r "$template_header" ] && sed "$sed_script_base
  316. s|%offset%|$offset|g
  317. s|%name%|$name|
  318. s|%region%|$region|" "$template_header"
  319. ### FORECASTS ###
  320. [ -r "$template_forecast" ] && { # all of this only happens if there's a template file to iterate this over
  321. oldday="${output[localtime ${indices[0]}]:4:4}"
  322. for i in ${indices[@]}; do
  323. time="${output[localtime $i]:9:2}:${output[localtime $i]:11:2}"
  324. day="${output[localtime $i]:4:4}"
  325. # if a new day begins, show it as a weekday, not a numerical date.
  326. if [[ "$day" != "$oldday" ]]; then
  327. oldday="$day"
  328. day="$(date +%u --date="${output[localtime $i]:0:8}")"
  329. day="${weekdays[$lang$day$long]}"
  330. [[ "$upper" == 1 ]] && day="${day^}"
  331. else
  332. day=""
  333. fi
  334. # smartsymbol (an integer)
  335. ss="${output[SmartSymbol $i]}"
  336. # get forecast text corresponding to smartsymbol
  337. get_ss_text "$ss"
  338. ss_text="$RETVAL"
  339. # adjust temperature color depending on temperature plus or minus
  340. (( ${output[Temperature $i]} < 0 )) && tempcol="$tempcol_minus" || tempcol="$tempcol_plus"
  341. ### now prepare images & colors ###
  342. # rotate wind svg (data)
  343. wind_deg="${output[WindDirection $i]}"
  344. wind_speed="${output[WindSpeedMS $i]}"
  345. # rounding up/down to end on 0, so that we have only 36 directions instead of 360
  346. (( ${wind_deg: -1} >= 5 )) && wind_deg="$(( ${wind_deg%?} + 1 ))0" || wind_deg="${wind_deg%?}0"
  347. # colorize SVG acccording to wind strength
  348. if (( wind_speed >= 21 )); then
  349. circle_color="#000000"; arrow_color="#ffffff"; level="level-04"
  350. elif (( wind_speed >= 14 )); then
  351. circle_color="#303193"; arrow_color="#ffffff"; level="level-03"
  352. elif (( wind_speed >= 8 )); then
  353. circle_color="#3A66E3"; arrow_color="#ffffff"; level="level-02"
  354. elif (( wind_speed >= 1 )); then
  355. circle_color="#E7F0FA"; arrow_color="#303193"; level="level-01"
  356. else
  357. circle_color="#E7F0FA"; arrow_color="#E7F0FA"; level="level-00"
  358. fi
  359. # colors for PoP background box & text
  360. #~ pop="${output[PoP $i]}"
  361. #~ if (( pop >= 70 )); then
  362. #~ popbg="$popbg70"
  363. #~ popcol="$popcol70"
  364. #~ elif (( pop >= 30 )); then
  365. #~ popbg="$popbg30"
  366. #~ popcol="$textcol"
  367. #~ else
  368. #~ popbg="x"
  369. #~ popcol="$textcol"
  370. #~ fi
  371. # colors for PoP text (no background box)
  372. pop="${output[PoP $i]}"
  373. if (( pop >= 70 )); then
  374. popbg="x"
  375. popcol="$textcol"
  376. elif (( pop >= 30 )); then
  377. popbg="x"
  378. popcol="$popbg70"
  379. else
  380. popbg="x"
  381. popcol="$popbg30"
  382. fi
  383. si_svg="$smartsymbols_svg/$ss.svg"
  384. if [[ "$usepng" == 1 ]]; then
  385. # convert things to PNG
  386. wind_img="$windsymbols_png/deg${wind_deg}-$level.png"
  387. [ -r "$wind_img" ] || {
  388. make_wind_svg
  389. rsvg-convert -o "$wind_img" --page-width="$img_size_wind" --page-height="$img_size_wind" --top=$(((img_size-30)/2)) --left=$(((img_size-30)/2)) <<<"$wind_svg"
  390. }
  391. smart_img="$smartsymbols_png/$ss.png"
  392. [ -r "$smart_img" ] || {
  393. dl_smartsymbol "$si_svg"
  394. rsvg-convert -o "$smart_img" --width="$img_size_fc" --height="$img_size_fc" "$si_svg"
  395. }
  396. else
  397. # use SVGs
  398. dl_smartsymbol "$si_svg"
  399. smart_img="$si_svg"
  400. wind_img="$windsymbols_svg/deg${wind_deg}-$level.svg"
  401. [ -r "$wind_img" ] || {
  402. make_wind_svg
  403. echo "$wind_svg" > "$wind_img"
  404. }
  405. fi
  406. # If gust is the same as windspped, it is not required
  407. gust="${wg[${output[localtime $i]}]}"
  408. [[ "$gust" == "$wind_speed" ]] || wind_speed="$wind_speed-$gust"
  409. ### now translate $format templates
  410. sed "$sed_script_base
  411. s|%localtime%|$time|
  412. s|%day%|$day|
  413. s|%Temperature%|${output[Temperature $i]}${units[1]}|
  414. s|%FeelsLike%|${output[FeelsLike $i]}${units[2]}|
  415. s|%PoP%|$pop${units[4]}|
  416. s|%Precipitation1h%|${output[Precipitation1h $i]}|
  417. s|%WindSpeedMS%|$wind_speed|
  418. s|%WindSpeedUnit%|${units[5]}|
  419. s|%WindCompass8%|${output[WindCompass8 $i]}|
  420. s|%wind_img%|$wind_img|; s|%offset%|$offset|g
  421. s|%smart_img%|$smart_img|
  422. s|%tempcol%|$tempcol|g
  423. s|%popbg%|$popbg|g
  424. s|%popcol%|$popcol|g
  425. s|%ss_text%|$ss_text|; s|%offset%|$offset|g" "$template_forecast"
  426. ### increase offset
  427. offset=$(( offset + forecast_height ))
  428. done
  429. } # END [ -r "$template_forecast" ] &&
  430. ### FOOTER ###
  431. [ -r "$template_footer" ] && sed "$sed_script_base; s|%offset%|$offset|g; s|%offset_file%|$storage/offset|g" "$template_footer"
  432. fi