#!/usr/local/bin/perl # PERL program to correct name changes in tropical storms. Uses # the WMO header numbers, the dates, and the positions to sort things out. # # 1996-04-08 TRM Make sure each storm name has only one associated # depression. # Copyright (C) 1996 Thomas R. Metcalf # # This software is provided "as is" and is subject to change without # notice. No warranty of any kind is made with regard to this software, # including, but not limited to, the implied warranties of # merchantability and fitness for a particular purpose. The author shall # not be liable for any errors or for direct, indirect, special, # incidental or consequential damages in connection with the furnishing, # performance, or use of this software: use it at your own risk. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free # Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # $LastFile = "/data1/WWW/Tropical/Bin/LastNameChange"; $GifArchive = "/data1/WWW/Tropical/GifArchive"; $DataDir = "/data1/WWW/Tropical/Data"; $debug = 0; $oneweek = 0.0191257; $oneday = 0.00274; $onehour = 0.000114; $tconstraint = $oneday; $piover2 = atan2(1,0); $pi = $piover2*2; $dtor = $piover2/90; #setpriority(0,0,getpriority(0,0)+4); # nice the process ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime; if ($year>=94) {$year+=1900;} else {$year+=2000;} $now = $year + ($yday+1 + ($hour/24.0) + ($min/1440.))/366.0; if (open(LASTFILE,"$LastFile")) { while() { chop; s/ / /g; @line = split(" "); if (($now-$line[2]) < ($oneweek*4)) { $changes{$line[0]} = $line[1]; $cdate{$line[1]} = $line[2]; } } close LASTFILE; } @namedstorms = keys(%cdate); @depressions = keys(%changes); open(LASTFILE,">$LastFile"); while (<>) { # Read input file into an array and grab the headers/names s/\n//; # Get rid of carriage return. while (s/\s\s/ /g) {}; # Remove multiple white spaces. $#sp = 0; # Delete the old sp variable. @sp = split; # wmo is an associative array. The key is the WMO header and the values # is the best name of the storm. # wmo* holds the date/position of the _first_ incidence of a name. # wmo*2 holds the date/position of the _last_ incidence of a name. if ($sp[22] ne "" && $sp[22] ne "???" && $sp[20] ne "FOR" && $sp[22] =~ /^WT/) { if ($wmo{$sp[22]} ne $sp[9]) { if ($wmo{$sp[22]} ne "") { if (!&ISDEPRESSION($sp[9])) { $wmo{$sp[22]} = $sp[9]; $wmodate{$sp[22]} = $sp[21]; $wmolatitude{$sp[22]} = $sp[5]; $wmolongitude{$sp[22]} = $sp[7]; if ($sp[6] eq "S") {$wmolatitude{$sp[22]} = -$wmolatitude{$sp[22]};} if ($sp[8] eq "W") {$wmolongitude{$sp[22]} = -$wmolongitude{$sp[22]};} $wmodate2{$sp[22]} = $sp[21]; # Get most recent date $wmolatitude2{$sp[22]} = $sp[5]; $wmolongitude2{$sp[22]} = $sp[7]; if ($sp[6] eq "S") {$wmolatitude2{$sp[22]} = -$wmolatitude2{$sp[22]};} if ($sp[8] eq "W") {$wmolongitude2{$sp[22]} = -$wmolongitude2{$sp[22]};} } } else { $wmo{$sp[22]} = $sp[9]; $wmodate{$sp[22]} = $sp[21]; $wmolatitude{$sp[22]} = $sp[5]; $wmolongitude{$sp[22]} = $sp[7]; if ($sp[6] eq "S") {$wmolatitude{$sp[22]} = -$wmolatitude{$sp[22]};} if ($sp[8] eq "W") {$wmolongitude{$sp[22]} = -$wmolongitude{$sp[22]};} $wmodate2{$sp[22]} = $sp[21]; # Get most recent date $wmolatitude2{$sp[22]} = $sp[5]; $wmolongitude2{$sp[22]} = $sp[7]; if ($sp[6] eq "S") {$wmolatitude2{$sp[22]} = -$wmolatitude2{$sp[22]};} if ($sp[8] eq "W") {$wmolongitude2{$sp[22]} = -$wmolongitude2{$sp[22]};} } } else { $wmodate2{$sp[22]} = $sp[21]; # Get most recent date $wmolatitude2{$sp[22]} = $sp[5]; $wmolongitude2{$sp[22]} = $sp[7]; if ($sp[6] eq "S") {$wmolatitude2{$sp[22]} = -$wmolatitude2{$sp[22]};} if ($sp[8] eq "W") {$wmolongitude2{$sp[22]} = -$wmolongitude2{$sp[22]};} } } $input[$.-1] = $_; } if ($debug) { foreach $key (keys(%wmo)) { print STDERR $key," ",$wmo{$key},"\n"; } } # Loop through the input looking for name changes. # If the depression is before the named storm, use wmo*. If the depression # is after the named storm, use wmo*2. for ($i=0;$i<=$#input;++$i) { $#sp = 0; # Delete the old sp variable. @sp = split(/\s/,$input[$i]); @sp1 = split(/\s/,$input[$i+1]); # Look for identical entries (except for name). Assumes input sorted by time. #if ($sp[9] ne $sp1[9] &&$sp[0] eq $sp1[0] && $sp[1] eq $sp1[1] && $sp[2] eq $sp1[2] && # $sp[3] eq $sp1[3] && $sp[5] eq $sp1[5] && $sp[6] eq $sp1[6] && # $sp[7] eq $sp1[7] && $sp[8] eq $sp1[8] && $sp[10] eq $sp1[10] && # $sp[12] eq $sp1[12] && $sp[14] eq $sp1[14] && $sp[16] eq $sp1[16] && # $sp[17] eq $sp1[17] && $sp[19] eq $sp1[19] && $sp[20] eq $sp1[20] && # $sp[21] eq $sp1[21] && $sp[22] eq $sp1[22]) { # if (&ISDEPRESSION($sp[9]) && !&ISDEPRESSION($sp1[9])) { # $changes{$sp[9]}=$sp1[9]; # } elsif (!&ISDEPRESSION($sp[9]) && &ISDEPRESSION($sp1[9])) { # $changes{$sp1[9]}=$sp[9]; # } #} # Look for reasonable name changes. Check WMO header, time, position, speed $wmoname = $wmo{$sp[22]}; $advname = $sp[9]; if ($sp[22] ne "" && $sp[22] ne "???" && $sp[22] =~ /^WT/) { if ($advname ne $wmoname && $sp[20] ne "FOR" && &ISDEPRESSION($advname) && !&ISDEPRESSION($wmoname) && grep(/^$wmoname$/,@namedstorms) == 0 && grep(/^$advname$/,@depressions) == 0) { if ($debug) { $stone = $sp[9]; $sttwo = $wmo{$sp[22]}; print STDERR "Checking $stone $sttwo\n"; } $timegap = $sp[21] - $wmodate{$sp[22]}; # time difference from first name $timegap2 = $sp[21] - $wmodate2{$sp[22]}; # time difference from last name # compute Great Circle distance between observations $latitude = $sp[5]; $longitude = $sp[7]; if ($sp[6] eq "S") {$latitude = -$latitude;} if ($sp[8] eq "W") {$longitude = -$longitude;} $distance = &GCDISTANCE($latitude,$longitude,$wmolatitude{$sp[22]},$wmolongitude{$sp[22]}); $distance2 = &GCDISTANCE($latitude,$longitude,$wmolatitude2{$sp[22]},$wmolongitude2{$sp[22]}); $speed = 0; $speed2 = 0; if ($timegap != 0) {$speed = $distance/($timegap/$onehour);} if ($timegap2 != 0) {$speed2 = $distance2/($timegap2/$onehour);} if ($speed < 0) {$speed = -$speed;} if ($speed2 < 0) {$speed2 = -$speed2;} if($timegap <= $onehour && $timegap2 <= $onehour) { if ($timegap>-$tconstraint && $distance <= 500 && $speed <= 50) { $changes{$sp[9]}=$wmo{$sp[22]}; } } elsif ($timegap >= -$onehour && $timegap2 >= -$onehour) { if ($timegap2<$tconstraint && $distance2 <= 500 && $speed2 <= 50) { $changes{$sp[9]}=$wmo{$sp[22]}; } } elsif ($timegap > $onehour && $timegap2 < -$onehour) { if (($distance <= 500 && $speed <= 50) || ($distance2 <= 500 && $speed2 <= 50)) { $changes{$sp[9]}=$wmo{$sp[22]}; } } } } } if ($debug) { foreach $key (keys(%changes)) { print STDERR $key," --> ",$changes{$key},"\n"; } } # Substitute corrected names and print for ($i=0;$i<=$#input;++$i) { foreach $key (keys(%changes)) { $metakey = $key; $metakey =~ s/(\W)/\\\1/g; # make any metacharacters literal if ($input[$i] =~ /$metakey/i) {$cdate{$changes{$key}}=$now;} $input[$i] =~ s/$metakey/$changes{$key}/i; } print $input[$i],"\n"; } foreach $key (keys(%changes)) { if ($cdate{$changes{$key}} eq "") {$cdate{$changes{$key}}=$now;} print LASTFILE $key," ",$changes{$key}," ",$cdate{$changes{$key}},"\n"; } close LASTFILE; foreach $dep (keys(%changes)) { $storm = $changes{$dep}; $changed = $GifArchive."/$dep.gif"; $changedto = $GifArchive."/$storm.gif"; if (-e $changed) {rename($changed,$changedto);} $changed = $DataDir."/$dep"; $changedto = $DataDir."/$storm"; if (-e $changed) {rename($changed,$changedto);} $changed = $DataDir."/$dep.html"; $changedto = $DataDir."/storm.html"; if (-e $changed) {rename($changed,$changedto);} $changed = $DataDir."/$dep.forecast"; $changedto = $DataDir."/$storm.forecast"; if (-e $changed) {rename($changed,$changedto);} } ################################## sub ISDEPRESSION { # Check if a name looks like a tropical depression name local($newname) = @_; local($junk) = 0; local($HyphenNumNew) = 0; $HyphenNumNew = split(/\-/,$newname); # How many hyphens in the new name? if ($HyphenNumNew > 2 || $newname =~ /[^a-z0-9 -]/i || $newname =~ /^\d+[A-Z]\-/i || $newname =~ /^\d+\-/i || $newname =~ /^one\-/i || $newname =~ /^two\-/i || $newname =~ /^three\-/i || $newname =~ /^four\-/i || $newname =~ /^five\-/i || $newname =~ /^six\-/i || $newname =~ /^seven\-/i || $newname =~ /^eight\-/i || $newname =~ /^nine\-/i || $newname =~ /^ten\-/i || $newname =~ /^eleven\-/i || $newname =~ /^twelve\-/i || $newname =~ /^thirteen\-/i || $newname =~ /^fourteen\-/i || $newname =~ /^fifteen\-/i || $newname =~ /^sixteen\-/i || $newname =~ /^seventeen\-/i || $newname =~ /^eighteen\-/i || $newname =~ /^nineteen\-/i || $newname =~ /^twenty\-/i || # is there a dash after twenty etc.? $newname =~ /^thirty\-/i || $newname =~ /^fourty\-/i || $newname =~ /^forty\-/i || $newname =~ /^fifty\-/i || $newname =~ /^sixty\-/i || $newname =~ /^seventy\-/i || $newname =~ /^eighty\-/i || $newname =~ /^ninety\-/i) {$junk=1;} else {$junk=0;} $junk; } sub ACOS { # Compute the arc cosine # Input: Cosine (forced to -1 <= cosine <= 1) # Output: Angle in radians (0 0) {0;} else {$pi;} } else { $piover2 - atan2($cosine/(sqrt($argument)),1); } } sub GCDISTANCE { # Compute Great Circle distance between positions # Input: lat1, lon1, lat2, lon, all in decimal degrees # Output: Distance in Nautical Miles local($latitude1,$longitude1,$latitude2,$longitude2) = @_; &ACOS(sin($latitude1*$dtor)*sin($latitude2*$dtor) + cos($latitude1*$dtor)*cos($latitude2*$dtor) * cos(($longitude1-$longitude2)*$dtor))*60/$dtor; }