get_weather_opendata 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. #!/bin/bash
  2. usage() {
  3. [ -n "$*" ] && echo "$*"
  4. cat <<EOF
  5. Get XML weather data from FMI's Open data service ($urlbase).
  6. A location ID from geonames is required.
  7. Dependencies: ${deps[@]}
  8. Usage: $me [options] location_id
  9. Options:
  10. -l str language (default: $lang), possible values: fin,eng
  11. -t int Timespan in hours. Default: $timespan
  12. -t int Offset of first forecast, in hours. Can be negative.
  13. -q str Type of forecast, get a list with ./wfs_describeStoredQueries
  14. Default: $query
  15. -p str Forecast parameters. Get a list of possible values with "-P"
  16. Options -P, -U and -R will exit without parsing the forecast data.
  17. -P Only get all possible forecast parameters for query & location.
  18. -U Only print URL.
  19. -R Only output raw XML received.
  20. -f str Parse local file. Invalidates other options. Mostly for development.
  21. EOF
  22. exit 1
  23. }
  24. deps=( curl date xmlstarlet )
  25. timespan=3
  26. offset=0
  27. file=""
  28. lang=eng
  29. # type of forecast, get a list with ./wfs_describeStoredQueries
  30. query="fmi::forecast::harmonie::surface::point::simple"
  31. urlbase="https://opendata.fmi.fi"
  32. wfs_req="wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id"
  33. # List parameters only
  34. P=0
  35. # spit out URL only
  36. urlonly=0
  37. # spit out raw data only
  38. raw=0
  39. # Separator, used in labels file
  40. sep=" %% "
  41. iconbase_ws3="icons/opendata-resources/symbols/" # Path where icons for weathersymbol3 are stored
  42. iconbase_ss="icons/smartsymbol/v31/p/" # Path where icons for smartsymbol are stored
  43. iconext=".svg"
  44. ### PARAMETERS ARE RETURNED IN THE SAME ORDER AS REQUESTED
  45. # sane choices
  46. parameters="totalcloudcover,cloudsymbol,temperature,humidity,dewpoint,fogsymbol,visibility,precipitation1h,precipitationamount,pressure,windspeedms,maximumwind,windgust,winddirection,weathersymbol3"
  47. # saner choices: remove useless because unknown symbols
  48. #~ parameters="totalcloudcover,temperature,humidity,dewpoint,visibility,precipitation1h,precipitationamount,pressure,windspeedms,maximumwind,windgust,winddirection,weathersymbol3"
  49. #~ parameters="Temperature,Precipitation1h,PrecipitationAmount,WindDirection,WindSpeedMS,MaximumWind,WindGust,Humidity,TotalCloudCover,LowCloudCover,MediumCloudCover,HighCloudCover,Pressure,smartsymbol,pop"
  50. while getopts "l:f:t:o:q:p:PURh" opt; do
  51. case $opt in
  52. l) [[ "$OPTARG" =~ ^(fin|eng)$ ]] && lang="$OPTARG" || usage "Invalid language: $OPTARG"
  53. ;;
  54. f) [ -r "$OPTARG" ] && file="$OPTARG" || usage "cannot read $OPTARG"
  55. ;;
  56. t) [[ "$OPTARG" =~ [1-9][0-9]* ]] && (( OPTARG <= 36 )) || usage "Option -${opt}: invalid number $OPTARG"
  57. timespan="$OPTARG"
  58. ;;
  59. o) [[ "$OPTARG" =~ [1-9][0-9]* ]] && (( OPTARG <= 36 )) || usage "Option -${opt}: invalid number $OPTARG"
  60. timespan="$OPTARG"
  61. ;;
  62. q) query="$OPTARG"
  63. ;;
  64. p) parameters="$OPTARG"
  65. ;;
  66. P) P=1
  67. timespan=1
  68. ;;
  69. U) urlonly=1
  70. ;;
  71. R) raw=1
  72. ;;
  73. *) usage
  74. ;;
  75. esac
  76. done
  77. shift $((OPTIND-1))
  78. [[ "$0" == *'/'* ]] && { cd "${0%/*}" || exit 1; }
  79. dir="files/${0##*/}_data"
  80. mkdir -p "$dir" || exit 1
  81. symbol="files/symbol_text_language" # descriptions for smartsymbol, replace "language" with $lang
  82. # File contains ObservableProperty labels in either english or finnish.
  83. # from https://opendata.fmi.fi/meta?observableProperty=forecast&language=$lang
  84. labels="$dir/labels_$lang"
  85. # Won't be downloaded if it exists.
  86. if ! [ -r "$labels" ]; then
  87. URL="$urlbase/meta?observableProperty=forecast&language=$lang"
  88. echo "# Retrieved from $URL" >"$labels"
  89. curl -s "$URL" | \
  90. xmlstarlet sel -t -m "/_:CompositeObservableProperty/_:component/_:ObservableProperty" -v "_:basePhenomenon" -o " (" -v "normalize-space(_:label)" -o ")$sep" -v "@gml:id" -o "$sep" -m "_:uom" -v "@uom" -n >>"$labels"
  91. # Each line contains: Localised Name %% ParameterName %% Unit of measurement
  92. fi
  93. # Read labels file into array (discard first line, it's a comment)
  94. mapfile -s 1 -t labels <"$labels"
  95. if [ -n "$file" ]; then
  96. weather="$(<"$file")"
  97. else
  98. [ -n "$geoid" ] && geoid="$1" || geoid=658225 # Helsinki
  99. [[ "$geoid" =~ [0-9]+ ]] || usage "Parameter \"$geoid\" is not a numerical geoid. Use ./get_location_id to get one."
  100. starttime="$(date -u '+%FT%R:%SZ' --date="$offset hours")" # starting at now +/- offset
  101. endtime="$(date -u '+%FT%R:%SZ' --date="$((timespan + offset)) hours")" # assuming we want a $timespan hours forecast
  102. timestep=60 # minutes
  103. URL="$urlbase/$wfs_req=${query}&geoid=${geoid}&starttime=${starttime}&endtime=${endtime}&timestep=${timestep}"
  104. if [[ "$P" == 1 ]]; then
  105. [[ "$urlonly" == 1 ]] && echo "URL: $URL"
  106. parameters="$(curl -s "$URL" | xmlstarlet sel -t -m "//wfs:member/BsWfs:BsWfsElement" -v "BsWfs:ParameterName" -o ',')"
  107. parameters="${parameters%,}" # remove the last trailing comma
  108. echo "Parameters: ${parameters,,}" # make it all lowercase
  109. exit
  110. fi
  111. URL="${URL}&parameters=${parameters,,}" # parameters should be all lowercase
  112. [[ "$urlonly" == 1 ]] && echo "URL: $URL"
  113. [[ "$raw" == 1 ]] && curl "$URL"
  114. { [[ "$urlonly" == 1 ]] || [[ "$raw" == 1 ]]; } && exit
  115. weather="$(curl -s "$URL")"
  116. fi
  117. # Parse query response XML, read into array:
  118. # 0: Time
  119. # 1: ParameterName
  120. # 2: ParameterValue etc...
  121. mapfile -t array <<<"$(xmlstarlet sel -t -m "/wfs:FeatureCollection/wfs:member/BsWfs:BsWfsElement" -v "BsWfs:Time" -n -v "BsWfs:ParameterName" -n -v "BsWfs:ParameterValue" -n <<<"$weather")"
  122. # Forecast, simple text output
  123. oldtime=''
  124. for ((i=0;i<${#array[@]};i+=3)); do
  125. if [[ "$oldtime" != "${array[i]}" ]]; then
  126. # Only here is UTC ISO 8601 time converted to local time
  127. date '+%n%H:%Mh%n¯¯¯¯¯¯' --date="${array[i]}"
  128. oldtime="${array[i]}"
  129. fi
  130. # Replace weathersymbol3 with path to corresponding image
  131. [[ "${array[i+1]}" == weathersymbol3 ]] && echo "$iconbase_ws3${array[i+2]%.*}$iconext" && continue
  132. # Replace smartsymbol with path to corresponding image
  133. [[ "${array[i+1]}" == smartsymbol ]] && echo "$iconbase_ss${array[i+2]%.*}$iconext => $(grep -w ^${array[i+2]%.*} "${symbol/language/${lang:0:2}}" |cut -d: -f2)" && continue
  134. # Replace ParameterName with localised label
  135. for ((j=0,found=0;j<${#labels[@]};j++)); do
  136. [[ "${labels[j]}" == *"$sep${array[i+1]}$sep"* ]] && echo "${labels[j]%%$sep*} ${array[i+2]} ${labels[j]##*$sep}" && found=1 && break
  137. done
  138. [[ "$found" == 0 ]] && echo "${array[i+1]}: ${array[i+2]}"
  139. done