GD-Map-Mercator
view release on metacpan or search on metacpan
lib/GD/Map/Mercator.pm view on Meta::CPAN
our %regions = (
'africa-bdy' => [ -30.658889, -17.075556, 41.594722,53.089722],
'africa-cil' => [ -54.462778, -25.360556, 65.650278,77.588889],
'africa-riv' => [ -34.400278, -16.708611, 42.038056,55.2525],
'asia-bdy' => [ -9.126944, 19.001389, 54.476667,141.008889],
'asia-cil' => [ -54.753889, -190.351944, 81.851944,180.0],
'asia-riv' => [ -46.569722, -179.988056, 74.412222,180.0],
'europe-bdy' => [ 36.151389, -8.751389, 70.088889,31.586667],
'europe-cil' => [ 34.808889, -31.29, 71.165000,31.250278],
'europe-riv' => [ 36.566111, -21.791944, 69.999722,29.468889],
'namer-bdy' => [ 41.675556, -141.003056, 69.645556,-66.901111],
'namer-cil' => [ 24.538333, -168.132778, 83.623611,-12.155],
'namer-pby' => [ 29.706944, -139.047778, 68.900556,-57.1],
'namer-riv' => [ 25.768333, -166.053056, 74.032222,-54.470833],
'samer-bdy' => [ -55.556389, -117.1225, 32.718333,-51.682778],
'samer-cil' => [ -85.470278, -179.987778, 33.003333,179.976111],
'samer-riv' => [ -52.733333, -117.126111, 32.718333,-34.917222],
);
our %datasets = (
africa => [ 'bdy', 'cil', 'riv' ],
asia => [ 'bdy', 'cil', 'riv' ],
europe => [ 'bdy', 'cil', 'riv' ],
namer => [ 'bdy', 'cil', 'riv', 'pby' ],
samer => [ 'bdy', 'cil', 'riv' ],
);
our %colors = (
white => [255,255,255],
lgray => [191,191,191],
gray => [127,127,127],
dgray => [63,63,63],
black => [0,0,0],
lblue => [0,0,255],
blue => [0,0,191],
dblue => [0,0,127],
gold => [255,215,0],
lyellow => [255,255,0],
yellow => [191,191,0],
dyellow => [127,127,0],
lgreen => [0,255,0],
green => [0,191,0],
dgreen => [0,127,0],
lred => [255,0,0],
red => [191,0,0],
dred => [127,0,0],
lpurple => [255,0,255],
purple => [191,0,191],
dpurple => [127,0,127],
lorange => [255,183,0],
orange => [255,127,0],
pink => [255,183,193],
dpink => [255,105,180],
marine => [127,127,255],
cyan => [0,255,255],
lbrown => [210,180,140],
dbrown => [165,42,42],
transparent => [1,1,1]
);
my %valid_fmt = qw(png newFromPng gif newFromGif jpg newFromJpeg jpeg newFromJpeg);
=pod
=begin classdoc
@constructor
Create an instance of GD::Map::Mercator. Either creates a
new basemap image from the specified minimum/maximum latitude/longitude
values, or loads an existing basemap of the given name. Applies
the <a href='http://en.wikipedia.org/wiki/Mercator_projection'>Mercator Projection</a>
to datapoints collected from the
<a href='http://www.evl.uic.edu/pape/data/WDB/'>CIA World Databank II</a> dataset
to render a map image.
<p>
The map region to be rendered is specified by providing a set of
minimum/maximum latitude/longitude values (in degrees) defining the
bounding box of the region to be mapped. If the bounding box coordinates
are not specified, this object attempts to load a pre-existing
map image and data. When a map is rendered, an additional file
of configuration data is saved containing the bounding box coordinates
in both latitude/longitude and Mercator distances (in meters).
<p>
Once the base map has been created or loaded, the application may
use this object to
<ul>
<li>access the <cpan>GD</cpan> object to directly manipulate it,
<li>translate latitude/longitude coordinates to pixel coordinates
within the image
<li>convert pixel coordinates back to latitude/longitude coordinates
<li>rescale the image and its associated configuration data
<li>extract sub-images from the map to create new map images
</ul>
<p>
<b>NOTES:</b>
<ol>
<li>The Mercator projection is subject to severe dimensional
distortions near the poles. Use of map coordinates above 70 degrees or
below -70 degrees latitude is discouraged.
<li>Latitiude values are specified in degrees between
+90 and -90, where negative values are in the southern hemisphere;
longitude values are in degrees between +180 and -180, with negative values
in the western hemisphere.
<li>This package uses the "Mercated" binary datasets generated
by <cpan>wdb2merc</cpan>. These datasets must be generated before
using this package.
</ol>
@optional basemap_path directory path for basemap image and datafile. If a
new map is generated, its image and config files will ba saved in this
path; if using an existing map, its image and config files are loaded
from this path. Note that, if creating a new map, this parameter is optional.
@optional basemap_name filename of basemap image and data; may contain
the image format suffix ('.png', '.gif', '.jpg', '.jpeg'); if not
suffixed, defaults to '.png'. The config data for the map is stored
lib/GD/Map/Mercator.pm view on Meta::CPAN
my ($x, $y) = ($seg->[$i++], $seg->[$i++]);
$polyline->addPt($x, $y),
$ptcnt++,
($lx, $ly) = ($x, $y),
next
if defined $x;
#
# if an undef marker, draw current line
#
$img->polyline($polyline, $fg)
if ($ptcnt > 2);
$polyline = GD::Polyline->new();
($lx, $ly) = (undef, undef);
$ptcnt = 0;
next;
}
#
# draw any remaining line
#
$img->polyline($polyline, $fg)
if ($ptcnt > 1);
return 1;
}
sub _load_basemap {
my $self = shift;
my $pat = $valid_fmt{$self->{imgfmt}};
my $conf = "$self->{basemap_loc}.conf";
my $imgfile = "$self->{basemap_loc}.$self->{imgfmt}";
die "Unsupported image format $self->{imgfmt}; check your GD configuration."
unless GD::Image->can($pat);
die "$conf not found."
unless (-s $conf);
die "$imgfile not found."
unless (-s $imgfile);
die "Cannot open $conf: $!"
unless open F, $conf;
my $data = <F>;
close F;
chomp $data;
my @mapdata = split /,/, $data;
die "Invalid map data file"
unless (scalar @mapdata == 10);
#
# only install the lat/long and pixel coords, skip the mercator distances
#
$self->{mercator} = GD::Map::Mercator::Projector->new(
@mapdata[0..3], @mapdata[8..9], $self->{verbose});
my $fd;
die "Cannot open $imgfile: $!"
unless open $fd, $imgfile;
$self->{image} =
($self->{imgfmt} eq 'gif') ? GD::Image->newFromGif($fd)
: ($self->{imgfmt} eq 'png') ? GD::Image->newFromPng($fd)
: GD::Image->newFromJpeg($fd);
close $fd;
return $self;
}
1;
package GD::Map::Mercator::Projector;
=pod
=begin hidden
Translates latitude, longitude datapoints to pixel coordinates
using Mercator Projection.
(Yes, I know Mercator is bad. Really bad. Criminal, even.
But its nice for rasterization. And its good enough for Google, so
its good enough for me.)
To convert to Mercator, we first computes
scales based on min/max lat/long pts.
Longitude is linear, except for some fiddling to deal with
crossing boundry from positive to negative.
Latitude requires some trig:
y = log(tan(lat) + sec(lat)) = log(sin(lat)/cos(lat) + 1/cos(lat))
= log((sin(lat) + 1)/cos(lat));
(all in radians, of course)
(We should really use UTM...)
=end hidden
=cut
use POSIX;
use Geo::Mercator;
use strict;
use warnings;
sub new {
my ($class, $minlat, $minlong, $maxlat, $maxlong, $width, $height, $verbose) = @_;
die "Bad min/max longitude"
if ($minlong < -180) || ($minlong > 180) ||
($maxlong < -180) || ($maxlong > 180) ||
(($maxlong < $minlong) &&
((($maxlong < 0) && ($minlong < 0)) ||
(($maxlong > 0) && ($minlong > 0))));
die "Bad min/max latitude"
if ($minlat > $maxlat) || ($minlat < -90) || ($minlat > 90) ||
($maxlat < -90) || ($maxlat > 90);
( run in 2.175 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )