123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- #!/bin/bash
- usage() {
- [ -n "$*" ] && echo "$*"
- cat <<EOF
- Get XML weather data from FMI's Open data service ($urlbase).
- A location ID from geonames is required.
- Dependencies: ${deps[@]}
- Usage: $me [options] location_id
- Options:
- -l str language (default: $lang), possible values: fin,eng
- -t int Timespan in hours. Default: $timespan
- -t int Offset of first forecast, in hours. Can be negative.
- -q str Type of forecast, get a list with ./wfs_describeStoredQueries
- Default: $query
- -p str Forecast parameters. Get a list of possible values with "-P"
- Options -P, -U and -R will exit without parsing the forecast data.
- -P Only get all possible forecast parameters for query & location.
- -U Only print URL.
- -R Only output raw XML received.
- -f str Parse local file. Invalidates other options. Mostly for development.
- EOF
- exit 1
- }
- deps=( curl date xmlstarlet )
- timespan=3
- offset=0
- file=""
- lang=eng
- # type of forecast, get a list with ./wfs_describeStoredQueries
- query="fmi::forecast::harmonie::surface::point::simple"
- urlbase="https://opendata.fmi.fi"
- wfs_req="wfs?service=WFS&version=2.0.0&request=getFeature&storedquery_id"
- # List parameters only
- P=0
- # spit out URL only
- urlonly=0
- # spit out raw data only
- raw=0
- # Separator, used in labels file
- sep=" %% "
- iconbase_ws3="icons/opendata-resources/symbols/" # Path where icons for weathersymbol3 are stored
- iconbase_ss="icons/smartsymbol/v31/p/" # Path where icons for smartsymbol are stored
- iconext=".svg"
- ### PARAMETERS ARE RETURNED IN THE SAME ORDER AS REQUESTED
- # sane choices
- parameters="totalcloudcover,cloudsymbol,temperature,humidity,dewpoint,fogsymbol,visibility,precipitation1h,precipitationamount,pressure,windspeedms,maximumwind,windgust,winddirection,weathersymbol3"
- # saner choices: remove useless because unknown symbols
- #~ parameters="totalcloudcover,temperature,humidity,dewpoint,visibility,precipitation1h,precipitationamount,pressure,windspeedms,maximumwind,windgust,winddirection,weathersymbol3"
- #~ parameters="Temperature,Precipitation1h,PrecipitationAmount,WindDirection,WindSpeedMS,MaximumWind,WindGust,Humidity,TotalCloudCover,LowCloudCover,MediumCloudCover,HighCloudCover,Pressure,smartsymbol,pop"
- while getopts "l:f:t:o:q:p:PURh" opt; do
- case $opt in
- l) [[ "$OPTARG" =~ ^(fin|eng)$ ]] && lang="$OPTARG" || usage "Invalid language: $OPTARG"
- ;;
- f) [ -r "$OPTARG" ] && file="$OPTARG" || usage "cannot read $OPTARG"
- ;;
- t) [[ "$OPTARG" =~ [1-9][0-9]* ]] && (( OPTARG <= 36 )) || usage "Option -${opt}: invalid number $OPTARG"
- timespan="$OPTARG"
- ;;
- o) [[ "$OPTARG" =~ [1-9][0-9]* ]] && (( OPTARG <= 36 )) || usage "Option -${opt}: invalid number $OPTARG"
- timespan="$OPTARG"
- ;;
- q) query="$OPTARG"
- ;;
- p) parameters="$OPTARG"
- ;;
- P) P=1
- timespan=1
- ;;
- U) urlonly=1
- ;;
- R) raw=1
- ;;
- *) usage
- ;;
- esac
- done
- shift $((OPTIND-1))
- [[ "$0" == *'/'* ]] && { cd "${0%/*}" || exit 1; }
- dir="files/${0##*/}_data"
- mkdir -p "$dir" || exit 1
- symbol="files/symbol_text_language" # descriptions for smartsymbol, replace "language" with $lang
- # File contains ObservableProperty labels in either english or finnish.
- # from https://opendata.fmi.fi/meta?observableProperty=forecast&language=$lang
- labels="$dir/labels_$lang"
- # Won't be downloaded if it exists.
- if ! [ -r "$labels" ]; then
- URL="$urlbase/meta?observableProperty=forecast&language=$lang"
- echo "# Retrieved from $URL" >"$labels"
- curl -s "$URL" | \
- 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"
- # Each line contains: Localised Name %% ParameterName %% Unit of measurement
- fi
- # Read labels file into array (discard first line, it's a comment)
- mapfile -s 1 -t labels <"$labels"
- if [ -n "$file" ]; then
- weather="$(<"$file")"
- else
- [ -n "$geoid" ] && geoid="$1" || geoid=658225 # Helsinki
- [[ "$geoid" =~ [0-9]+ ]] || usage "Parameter \"$geoid\" is not a numerical geoid. Use ./get_location_id to get one."
- starttime="$(date -u '+%FT%R:%SZ' --date="$offset hours")" # starting at now +/- offset
- endtime="$(date -u '+%FT%R:%SZ' --date="$((timespan + offset)) hours")" # assuming we want a $timespan hours forecast
- timestep=60 # minutes
- URL="$urlbase/$wfs_req=${query}&geoid=${geoid}&starttime=${starttime}&endtime=${endtime}×tep=${timestep}"
- if [[ "$P" == 1 ]]; then
- [[ "$urlonly" == 1 ]] && echo "URL: $URL"
- parameters="$(curl -s "$URL" | xmlstarlet sel -t -m "//wfs:member/BsWfs:BsWfsElement" -v "BsWfs:ParameterName" -o ',')"
- parameters="${parameters%,}" # remove the last trailing comma
- echo "Parameters: ${parameters,,}" # make it all lowercase
- exit
- fi
- URL="${URL}¶meters=${parameters,,}" # parameters should be all lowercase
- [[ "$urlonly" == 1 ]] && echo "URL: $URL"
- [[ "$raw" == 1 ]] && curl "$URL"
- { [[ "$urlonly" == 1 ]] || [[ "$raw" == 1 ]]; } && exit
- weather="$(curl -s "$URL")"
- fi
- # Parse query response XML, read into array:
- # 0: Time
- # 1: ParameterName
- # 2: ParameterValue etc...
- 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")"
- # Forecast, simple text output
- oldtime=''
- for ((i=0;i<${#array[@]};i+=3)); do
- if [[ "$oldtime" != "${array[i]}" ]]; then
- # Only here is UTC ISO 8601 time converted to local time
- date '+%n%H:%Mh%n¯¯¯¯¯¯' --date="${array[i]}"
- oldtime="${array[i]}"
- fi
- # Replace weathersymbol3 with path to corresponding image
- [[ "${array[i+1]}" == weathersymbol3 ]] && echo "$iconbase_ws3${array[i+2]%.*}$iconext" && continue
- # Replace smartsymbol with path to corresponding image
- [[ "${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
- # Replace ParameterName with localised label
- for ((j=0,found=0;j<${#labels[@]};j++)); do
- [[ "${labels[j]}" == *"$sep${array[i+1]}$sep"* ]] && echo "${labels[j]%%$sep*} ${array[i+2]} ${labels[j]##*$sep}" && found=1 && break
- done
- [[ "$found" == 0 ]] && echo "${array[i+1]}: ${array[i+2]}"
- done
|