#!/bin/perl -w # Script to decode (very partially) a land or ship synop # # Note that certain groups (LA, LO, ICE) will be null if we're a land synop # Note that a null is returned if some serious error occurs. # # WMC: early/97 - original # WMC: 30/04/97 - add conversion of m/s to kts if required # WMC: 29/07/97 - realise that DEWT can be coded as 29rel: fix code to throw this case away # WMC: 20/11/97 - return null in wind group bit if message null # # This script comes with no warranty of its fitness for any purpose. # In fact, I guarantee that there are bugs in it somewhere. # Use it non-commercially as you will; but don't come back and complain about problems. # # - W. M. Connolley July 1997. require "guess_mon_year.pl"; sub decode_synop { my(@GROUPS)=split(' ',$_[0]); my($TYPE,$NAME,$YEAR,$MON,$DAY,$HOUR,$LA,$Q,$LO,$T,$TOTAL_CLOUD,$DIR,$SPD,$IW,%IWs,$ICE,$DEWT,$PSTA); my($PTND,$PTND_code,$PTND_value,$PPN,$PPN_code,$PPN_value); my($H,$Ir,$Ix,$CLOUD_H,$VV); my($WW,$W1,$W2,$WEATHER); my($NH,$CL,$CM,$CH,$CLOUD); my($V,@VALUES); # Get type - AAXX or BBXX. Return null if not one of the two. $TYPE=shift @GROUPS; if ($TYPE !~ /^(AAXX|BBXX)$/) { if ($DEBUG > 1) { warn "Returing null since wrong type" }; return '' }; # If this is a land group, exchange the first two groups if ($TYPE eq "AAXX") {@GROUPS[0,1]=@GROUPS[1,0]}; # Get identifier $NAME=shift @GROUPS; if ($DEBUG > 1) { warn "Id is $NAME" }; # Now, check that the rest of the message (up to 333/444/555/ICE) consists of 5 figure groups # Groups that aren't 5 figures get deleted. for ($I=0; $I<$#GROUPS; $I++) { last if ($GROUPS[$I] =~ /333|444|555|ICE/); if (length($GROUPS[$I]) != 5) { splice(@GROUPS,$I,1) } }; # Get date (day and hour) $DAY=substr($GROUPS[0],0,2); $IW=substr($GROUPS[0],4,1); $HOUR=substr(shift @GROUPS,2,2); %IWs=(0, "m/s -> kts (estimated)", 1, "m/s -> kts (anemometer)", 3, "kts (estimated)", 4, "kts (anemometer)"); $IW=$IWs{$IW}; # Now guess the year and month, based on the current date. ($MON,$YEAR)=guess_mon_year($DAY); # Only get lat, lon if a ship if ($TYPE eq "BBXX") { # Get latitude. Should be headed by "99" - return null if not $LA=shift @GROUPS; if (substr($LA,0,2) ne '99') { if ($DEBUG > 1) { warn "Returning null: LA group not headed by 99 but by ".substr($LA,0,2) }; return '' }; $LA=substr($LA,2,3)/10.; # Get longitude. First char is quadrant: 5 for SW, 3 for SE $Q=substr($GROUPS[0],0,1); $LO=substr(shift @GROUPS,1,4)/10.; if ($Q == 3 or $Q == 5) {$LA=-$LA}; if ($Q == 5 or $Q == 7) {$LO=-$LO}; }; # iivVV # # Get i_r i_x h VV group # ($Ir,$Ix,$CLOUD_H,$VV) = (shift(@GROUPS) =~ /(.)(.)(.)(..)/ ); # Ir (ppn presence) # Ir == 3 means ppn omitted later *and is zero* # Ir == something else mean ppn not available/reported later if ($Ir eq "3") { $PPN_value=0 }; # Ix (station type/weather) if ($Ir =~ /(1|2|3)/ ) { $ST_TYPE="Manned" } elsif ($Ir =~ /(4|5|6|7)/ ) { $ST_TYPE="AWS" } else { $ST_TYPE="Unknown ($Ir)" }; # h (cloud base height) (code table 1600) if ($CLOUD_H =~ /\d/) { $CLOUD_H = (0,50,100,200,300,600,1000,1500,2000,2500)[$CLOUD_H] } else { undef $CLOUD_H }; if ($VV !~ /\d{2}/) { undef $VV }; # Nddff # # Wind group. Note conversion to kts if in m/s based on IW (30/4/97) # $GROUPS[0]=&check($GROUPS[0]); if ($GROUPS[0]) { $TOTAL_CLOUD=substr($GROUPS[0],0,1); $DIR=10*substr($GROUPS[0],1,2); if ($DIR == 990) {undef $DIR}; if ($DIR > 360) {$DIR.=" (really???)"}; $SPD=substr(shift @GROUPS,3,2); if ($SPD !~ /\d{2}/) {undef $SPD}; if ($IW =~ /m\/s/) {$SPD=int($SPD*10/0.5148+0.5)/10.} } else { # # Note - if the wind group is null, we may as well return nothing at this point # if ($DEBUG > 1) { warn "Returning null: wind group null" }; return "" }; # 1sTTT # # Get temperature (checking leading digit is a 1) # ($GROUP_ID,$S,$T)=(shift(@GROUPS) =~ /(.)(.)(...)/); if ($GROUP_ID eq "1") { if ($T =~ /\d{3}/ and $S =~ /0|1/) { $T=$T/10.; if ($S) { $T=-$T } } else { undef $T } } else { unshift(@GROUPS,$GROUP_ID.$S.$T); undef $T }; # 2sTTT # # Get dew temperature (checking leading digit is a 2. If it isn't, return it to the list) # If S (sign) is not 0 or 1 (it could be 9, for relative humidity) then throw DEWT away. # ($GROUP_ID,$S,$DEWT)=(shift(@GROUPS) =~ /(.)(.)(...)/); if ($GROUP_ID eq "2") { if ($DEWT =~ /\d{3}/ and $S =~ /0|1/) { $DEWT=$DEWT/10.; if ($S) { $DEWT=-$DEWT } } else { undef $DEWT } } else { unshift(@GROUPS,$GROUP_ID.$S.$DEWT); undef $DEWT }; # 3PPPP # # Get station pressure group (checking leading digit is a 3. If it isn't, return it to the list) # $PSTA=shift @GROUPS; if (substr($PSTA,0,1) eq "3") { $PSTA=substr($PSTA,1,4)/10.; if ($PSTA<400) { $PSTA+=1000 } } else { unshift(@GROUPS,$PSTA); undef $PSTA }; # 4PPPP # # Get MSLP pressure group (4) (checking leading digit is a 4. If it isn't, return it to the list) # $PRED=shift @GROUPS; if (substr($PRED,0,1) eq "4") { $PRED=substr($PRED,1,4); if ($PRED !~ /\d{4}/) { undef $PRED } else { $PRED/=10.; if ($PRED<400) { $PRED+=1000 } } } else { unshift(@GROUPS,$PRED); undef $PRED }; # 5aPPP # # Get P tendency group (5) # $PTND=shift @GROUPS; if (substr($PTND,0,1) eq "5") { $PTND_code=substr($PTND,1,1)." (code table 200)"; $PTND_value=substr($PTND,2,3); if ($PTND_value !~ /\//) {$PTND_value/=10.} else { undef $PTND_value }; } else { unshift(@GROUPS,$PTND); undef $PTND }; # 6RRRt # # Get ppn group (6) # $PPN=shift @GROUPS; if (substr($PPN,0,1) eq "6") { $PPN_code=substr($PPN,4,1)." (code table 3590)"; $PPN_value=substr($PPN,1,3); if ($PPN_value !~ /\//) { if ($PPN_value == 990) { $PPN_value="trace" } elsif ($PPN_value > 990) { $PPN_value=($PPN_value-990)/10. } } else { undef $PPN_value }; } else { unshift(@GROUPS,$PPN); undef $PPN }; # 7wwWW # # Get weather type groups # # Ix codes for whether these groups are supposed to be present or not. # However, since we've got no-where to complain to there's not a lot # of point checking. ($GROUP_ID,$WEATHER) = (shift(@GROUPS) =~ /(.)(....)/ ); if ($GROUP_ID eq "7") { ($WW,$W1,$W2) = ($WEATHER =~ /(..)(.)(.)/ ); if ($W1!~/\d/) { undef $W1 }; if ($W2!~/\d/) { undef $W2 }; if ($WW!~/\d\d/) { undef $WW }; } else { unshift(@GROUPS,$GROUP_ID.$WEATHER); undef $WEATHER }; # 8NCCC # # Cloud group - 8NhClCmCh # ($GROUP_ID,$CLOUD) = (shift(@GROUPS) =~ /(.)(....)/ ); if ($GROUP_ID eq "8") { ($NH,$CL,$CM,$CH) = ($CLOUD =~ /(.)(.)(.)(.)/ ); } else { unshift(@GROUPS,$GROUP_ID.$CLOUD); undef $CLOUD }; # End - see if we can find an ICE group (we'll fail if this is a land one! (and indeed for most ships)) while ($GROUP=shift @GROUPS) { if ($GROUP eq "ICE") { $ICE=join(" ",@GROUPS); undef @GROUPS; # Lets try to decode the ICE group a bit (if it doesn't look like plain text) if (length($ICE) == 5 and $ICE !~ /[^\d\/]/) { local @Ices = ( $ICE=~/(.)(.)(.).(.)/ ); for (@Ices) { s#/#10# }; $Ices[0]='Concentration: '.('No ice in sight','in open lead or fast ice','uniform; < 3/10','uniform; 4/10 to 6/10','uniform 7/10 to 8/10','uniform 9/10+','varied; low','varied','varied; high','varied; very high','unable to report')[$Ices[0]]; $Ices[1]='Thickness: '.('new ice','nilas < 10cm','young 10-30cm','mostly new','mostly thin 1st-year','1st-year 30-70cm','mostly 1st-year 70-120cm','1st-year medium & thick','mostly baby oil','mostly old','unable to report')[$Ices[1]]; $GB='growlers/bergy bits'; $Ices[2]='Type: '.('None','1-5 bergs','6-10 bergs','11-20 bergs',"<=10 $GB","1-5 bergs + $GB","6-10 bergs + $GB","11-20 bergs + $GB","20+ bergs + $GB - nav. hazard",'unable to report')[$Ices[2]]; $Ices[3]='Situation/Trend: '.('open water; ice in sight','easy penetration; improving','easy penetration','easy penetration; worsening','hard penetration; improving','hard penetration','hard penetration; ice forming; worsening','hard penetration; ice pressure; worsening','hard penetration; heavy ice pressure; worsening','beset; worsening','unable to report')[$Ices[3]]; $ICE=join(" ",@Ices); }; }; }; # return @VALUES=($NAME,$YEAR,$MON,$DAY,$HOUR,$LA,$LO,$T,$DIR,$SPD,$IW,$TOTAL_CLOUD,$ICE,$DEWT,$PSTA,$PRED,$PTND_code,$PTND_value,$PPN_code,$PPN_value,$ST_TYPE,$WW,$W1,$W2,$NH,$CL,$CM,$CH,$CLOUD_H,$VV); foreach $V (@VALUES) { if ( ($V=~/^\/+$/) or (!$V and $V !~ /0/) ) { $V="null" } }; return @VALUES }; # Check that a group has 5 characters, all digits or "/"'s # IF the group is null, let that pass sub check { local($_)=@_; return $_ if (!$_); if ((length($_) != 5) or /[^\d\/]/) { if ($DEBUG) { print "Invalid group: expected 5 digits or \"\/\"'s: $_" }; $_="/////"; if ($DEBUG) { print " changed to: $_\n" }; }; return $_; }; 1;