121 lines
3.6 KiB
Perl
121 lines
3.6 KiB
Perl
|
|
#!/usr/bin/env perl
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
use warnings;
|
||
|
|
|
||
|
|
use HTTP::Tiny;
|
||
|
|
use POSIX qw(strftime);
|
||
|
|
use Text::ParseWords qw(parse_line);
|
||
|
|
use URI::Escape qw(uri_escape);
|
||
|
|
|
||
|
|
my ($lat, $lon, $outfile, $iso_date) = @ARGV;
|
||
|
|
|
||
|
|
die usage() unless defined $lat && defined $lon;
|
||
|
|
|
||
|
|
$outfile ||= "declination.txt";
|
||
|
|
$iso_date ||= strftime("%Y-%m-%d", gmtime());
|
||
|
|
|
||
|
|
die "Latitude must be numeric\n" unless $lat =~ /\A-?\d+(?:\.\d+)?\z/;
|
||
|
|
die "Longitude must be numeric\n" unless $lon =~ /\A-?\d+(?:\.\d+)?\z/;
|
||
|
|
die "Latitude out of range\n" unless $lat >= -90 && $lat <= 90;
|
||
|
|
die "Longitude out of range\n" unless $lon >= -180 && $lon <= 180;
|
||
|
|
die "Date must be YYYY-MM-DD\n" unless $iso_date =~ /\A(\d{4})-(\d{2})-(\d{2})\z/;
|
||
|
|
|
||
|
|
my ($year, $month, $day) = ($1, $2, $3);
|
||
|
|
|
||
|
|
my $base_url = "https://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination";
|
||
|
|
my %query = (
|
||
|
|
lat1 => $lat,
|
||
|
|
lon1 => $lon,
|
||
|
|
model => "WMM",
|
||
|
|
startYear => $year,
|
||
|
|
startMonth => $month + 0,
|
||
|
|
startDay => $day + 0,
|
||
|
|
resultFormat => "csv",
|
||
|
|
);
|
||
|
|
|
||
|
|
my $url = $base_url . "?" . join("&",
|
||
|
|
map { uri_escape($_) . "=" . uri_escape($query{$_}) } sort keys %query
|
||
|
|
);
|
||
|
|
|
||
|
|
my $http = HTTP::Tiny->new(
|
||
|
|
agent => "microReticulum-ex22-declination-fetch/1.0",
|
||
|
|
timeout => 30,
|
||
|
|
verify_SSL => 1,
|
||
|
|
);
|
||
|
|
|
||
|
|
my $res = $http->get($url);
|
||
|
|
die "HTTP request failed: $res->{status} $res->{reason}\n" unless $res->{success};
|
||
|
|
|
||
|
|
my $body = $res->{content};
|
||
|
|
my $declination = extract_declination_from_csv($body);
|
||
|
|
$declination = extract_declination_from_text($body) unless defined $declination;
|
||
|
|
|
||
|
|
die "Unable to parse declination from NOAA response\n" unless defined $declination;
|
||
|
|
|
||
|
|
my $procured = format_procured_date($year, $month, $day);
|
||
|
|
open my $fh, ">", $outfile or die "Cannot write $outfile: $!\n";
|
||
|
|
print {$fh} "# procured: $procured\n";
|
||
|
|
print {$fh} "# for coordinates: $lat, $lon\n";
|
||
|
|
print {$fh} "# source: NOAA/NCEI geomagnetic declination calculator\n";
|
||
|
|
print {$fh} "# date: $iso_date\n";
|
||
|
|
print {$fh} "# sign convention: east positive, west negative\n";
|
||
|
|
printf {$fh} "declination_deg=%.6f\n", $declination;
|
||
|
|
close $fh or die "Cannot close $outfile: $!\n";
|
||
|
|
|
||
|
|
print "Wrote $outfile\n";
|
||
|
|
printf "declination_deg=%.6f\n", $declination;
|
||
|
|
|
||
|
|
sub extract_declination_from_csv {
|
||
|
|
my ($text) = @_;
|
||
|
|
my @lines = grep { /\S/ } split /\r?\n/, $text;
|
||
|
|
return undef unless @lines >= 2;
|
||
|
|
|
||
|
|
my @header = parse_line(",", 0, $lines[0] // "");
|
||
|
|
my $decl_idx;
|
||
|
|
for my $i (0 .. $#header) {
|
||
|
|
next unless defined $header[$i];
|
||
|
|
if ($header[$i] =~ /\Adeclination\z/i) {
|
||
|
|
$decl_idx = $i;
|
||
|
|
last;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return undef unless defined $decl_idx;
|
||
|
|
|
||
|
|
for my $line (@lines[1 .. $#lines]) {
|
||
|
|
my @fields = parse_line(",", 0, $line);
|
||
|
|
next unless defined $fields[$decl_idx];
|
||
|
|
my $value = $fields[$decl_idx];
|
||
|
|
$value =~ s/^\s+|\s+$//g;
|
||
|
|
$value =~ s/[^\d+.\-]//g;
|
||
|
|
return $value + 0 if $value =~ /\A[+-]?\d+(?:\.\d+)?\z/;
|
||
|
|
}
|
||
|
|
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub extract_declination_from_text {
|
||
|
|
my ($text) = @_;
|
||
|
|
return $1 + 0 if $text =~ /declination[^-+0-9]*([+-]?\d+(?:\.\d+)?)/i;
|
||
|
|
return undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub format_procured_date {
|
||
|
|
my ($y, $m, $d) = @_;
|
||
|
|
my @month_names = qw(
|
||
|
|
January February March April May June
|
||
|
|
July August September October November December
|
||
|
|
);
|
||
|
|
my $name = $month_names[$m - 1] // die "Invalid month\n";
|
||
|
|
return sprintf("%s %d, %04d", $name, $d, $y);
|
||
|
|
}
|
||
|
|
|
||
|
|
sub usage {
|
||
|
|
return <<"USAGE";
|
||
|
|
Usage:
|
||
|
|
fetch_declination.pl <latitude> <longitude> [outfile] [YYYY-MM-DD]
|
||
|
|
|
||
|
|
Example:
|
||
|
|
fetch_declination.pl 44.93642012667761 -123.02203699545396 declination.txt 2026-04-16
|
||
|
|
USAGE
|
||
|
|
}
|