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 )