NAME
Geo::Distance - Calculate Distances and Closest Locations
SYNOPSIS
use Geo::Distance;
my $geo = new Geo::Distance;
$geo->reg_unit('foobar',390);
my $dist1 = $geo->distance( unit=>'foobar', lon1=>$lon1, lat1=>$lat1, lon2=>$lon2, lat2=>$lat2 );
my $dist2 = $geo->distance_calc('light_second',$lon1,$lat1,$lon2,$lat2);
my $dist3 = $geo->distance_calc('centimeter',$ary_ref1,$ary_ref2);
my $locations = $geo->find_closest(
lon=>$lon, lat=>$lat,
distance=>$dist, unit=>$unt,
dbh=>$dbh, table=>$tbl, field=>$fld
);
or
use Geo::Distance qw{ :all };
reg_unit('dinosaur_step',44560);
my $dist1 = distance( unit=>'meter', lon1=>$lon1, lat1=>$lat1, lon2=>$lon2, lat2=>$lat2 );
my $dist2 = distance_calc('dinosaur_step',$lon1,$lat1,$lon2,$lat2);
my $dist3 = distance_calc('foot',$ary_ref1,$ary_ref2);
my $locations = find_closest(
lon=>$lon, lat=>$lat,
distance=>$dist, unit=>$unt,
dbh=>$dbh, table=>$tbl, field=>$fld
);
DESCRIPTION
This perl library aims to provide as many tools to make it as simple as possible to calculate distances between geographic points, and anything that can be derived from that. Currently there is support for finding the closest locations within a specified distance, to find the closest number of points to a specified point, and to do basic point-to-point distance calculations.
The latest version of the Geo::Distance module can be found here:
http://www.bluefeet.net/perl/modules/geo-distance/
and of course at,
http://www.cpan.org/ (or your closest CPAN mirror)
Soon to be available will be some free to download and use data sets for use with find_closest().
More Accurate!
This new version of Geo::Distance has an experimental, but potentially much more accurate method of calculating distances. Instead of relying on the very inaccurate "great circle distance" (GCD) calculations Geo::Distance may be enabled to use the Haversine formula (hsin). Thanks to Dean Scott for submitting the initial code to make this happen! By default Geo::Distance will still use the old GCD formula, so to enable the hsin formula do:
In OO mode:
$geo->formula('hsin'); # Switches to useing Haversine formula.
$geo->formula('gcd'); # Switches to useing the great circle distance formula. (default)
In non-OO mode:
formula('hsin');
formula('gcd'); # (default)
Oh, and the only reason hsin isn't the default formula is because I haven't thuroughly tested it, but at the same time want the rest of you coders to have access to the latest and greatest. Once I've had the chance to test it and received any feedback from you users I'll make hsin the default.
Next Version: OO Only
Unless anyone really needs the non-OO interface, I would like to just scrap it and have only the OO interface. It will be a month or two until the next version of Geo::Distance, so you have time to contact me (scroll down to AUTHOR) before I change anything.
OO VS. POLLUTION
There are two styles of programming available. The first of which being an elegant object oriented interface and the second being a namespace polluting function interface. Personally, I like to pollute my namespace, but an object oriented interface does come in handy at times.
Both the object oriented interface and the function interface have the same functions, just a different context under which to call them with.
At the moment there are several ways to import functions. The most common being the :all export tag. This will export the distance(), distance_calc(), and find_closest() functions. You may also export the functions individually, as follows:
# Export just distance().
use Geo::Distance qw( distance );
# Export just find_closest().
use Geo::Distance qw( find_closest );
# Export all functions.
use Geo::Distance qw( distance distance_calc find_closest );
# or
use Geo::Distance qw( :all );
PROPERTIES
UNITS
All functions accept a unit type to do the computations of distance with. There are several of the most common unit types pre-defined.
kilometer
meter
centimeter
mile
yard
foot
inches
light_second
If you want to use a different unit type than is available use the reg_unit() function.
LATITUDE AND LONGITUDE
When a function needs a lon and lat they must always be in decimal format. If you have a different coordinate system and need to convert head on over to your local CPAN and do a look up for any Geo::Coordinates::* modules.
# DMS to Decimal
my $decimal = $degrees + ($minutes/60) + ($seconds/3600);
# Precision Six Integer to Decimal
my $decimal = $integer * .000001;
METHODS
Currently there are three methods supplied by this module, each with function oriented and object oriented interface. Whatever the interface, these methods take the same arguments and return the same results.
DISTANCE
Takes a name value pairs in a hash style. All arguments will be validated. Returns the distance between the two locations in the unit type passed to it.
my $dist1 = geo_distance( unit=>'meter', lon1=>$lon1, lat1=>$lat1, lon2=>$lon2, lat2=>$lat2 );
my $dist2 = $geo->distance( unit=>'mile', lon1=>$lon1, lat1=>$lat1, lon2=>$lon2, lat2=>$lat2 );
DISTANCE_CALC
This method is used internally by distance() and find_closest() to do the actual distance calculation. The benefit of using distance_calc() above distance() is that it is faster, it does not verify the input, and does not accept a hash style argument list.
This method takes arguments in two forms, as five arguments or as three. The five arguments are unit, lon1, lat1, lon2, and lat2. See the distance method for a description of these arguments. The three arguments are unit, array_ref1, and array_ref2. The array_refs are array references each with two entries, the first being lon, and the second being lat.
my $dist1 = $geo->distance_calc('light_second',$lon1,$lat1,$lon2,$lat2);
my $dist2 = distance_calc('inch',$lon1,$lat1,$lon2,$lat2);
my $dist3 = $geo->distance_calc('centimeter',$ary_ref1,$ary_ref2);
my $dist4 = distance_calc('foot',$ary_ref1,$ary_ref2);
FIND_CLOSEST
This method finds the closest locations within a certain distance and returns an array reference of arrays of locations, and optionally an array reference of distances, depending on if you called with a scalar context or array context.
my $locations = $geo->find_closest(...);
my ($locations,$distances) = $geo->find_closest(...);
The locations to search can be provided as either a database connection or as an array ref.
DATABASE SEARCH
This method uses some very simplistic calculations to SQL select out of the $dbh. This means that the SQL should work fine on almost any database (only tested on MySQL so far) and this also means that it is very fast. Once this sub set of locations has been retrieved then more precise calculations are made to narrow down the result set. Remember, though, that the farther out your distance is, and the more locations in the table, the slower your searches will be.
When searching a database you must also provide a table name to search and at least one field to return. The table that you want to search in must have lon and lat fields, both being of the type float.
# Database connection example.
my $dbh = DBI->connect(...);
# Find all zip codes within 50 miles of the county Wilbarger, TX, US.
my($lon,$lat) = $dbh->selectrow_array(
'SELECT lon,lat FROM counties WHERE fips=48487'
);
my($zips,$distances) = geo_find_closest(
lon => $lon,
lat => $lat,
distance => 50,
unit => 'mile',
dbh => $dbh,
table => 'zipcodes',
field => 'id,state'
);
# Internally an SQL select like this is created:
# SELECT lon,lat,id,state FROM counties WHERE ...
# Print out each Zip.
for(my $i=0; $i<@$zips; $i++){
# @$zips = [lon,lat,id,state]
my $zip = $$zips[$i];
print "The Zip $$zip[2], $$zip[3], ($$zip[0] x $$zip[1]) was $$distances[$i] miles away from $lon x $lat.\n";
}
ARRAY REFERENCE SEARCH
You may also pass an array reference as the data to search. While not regarded as an effecient method of finding closest locations, it is still useful at times especially for testing.
my $locations_db = load_zips();
$locations = find_closest(
array=>$locations_db,
lon=>$lon,
lat=>$lat,
unit=>'mile',
distance=>'50'
);
COUNT ARGUMENT
find_closest() has an optional 'count' field that provides the ability to only retrieve a certain number of locations.
# Retrieve the 5 closest locations.
$locations = find_closest(count=>5, ...);
Coincidentally, if you request a zero count (count=>0), you will get all locations as if you didn't specify a count except they will be ordered by distance.
# Sort locations by their distance.
($locations,$distances) = find_closest(count=>0, ...);
REG_UNIT
This method is used to create custom unit types. There are two ways of calling it, depending on if you are defining the unit from scratch, or if you are basing it off of an existing unit (such as saying inches = feet / 12 ). When defining a unit from scratch you pass the name and rho (radius of the earth in that unit) value.
So, if you wanted to do your calculations in human adult steps you would have to have an average human adult walk around the earth at the equator (ignore the fact that if you did this you would first have to make bridges that span the oceans around the earth). So, assuming we did this and we came up with 43,200 steps to make it all the way around the equator of the earth, you'd do something like the following.
# Create adult_step unit.
$geo->reg_unit('adult_step',43200);
Now, if you also wanted to do distances in baby steps you might think "well, now I gotta get a baby to walk around the earth". But, you don't have to! If you do some research you'll find (no research was actually conducted) that there are, on average, 4.7 baby steps in each adult step.
# Create baby_step unit based off adult_step unit.
$geo->reg_unit('baby_step',4.7,'adult_step');
And if we were doing this in reverse and already had the baby step unit but not the adult step...
# Create adult_step unit based off baby_step unit.
$geo->reg_unit('adult_step',1/4.7,'baby_step');
TODO
Need a more accurate way of calculating the distance that does not rely upon Math::Trig. Will probably be math intensive, so Math::Trig should still remain the default for the sake of speed.
Berkely DB would be a nice alternative to DBI and Array find_closest() searching.
Array searching by find_closest() needs to do a first pass using the more simplistic outer radius calculation, like it does with a database connection.
A second pass should be done in find_closest before distance calculations are made that does an inner radius simplistic calculation.
BUGS
None known right now, but by the time you read this, who knows? Check http://www.bluefeet.net/perl/modules/geo-distance/ for the latest bugs and enhancements.
NOTES
Geo::Distance is currently in its alpha stage. The interface may, and probably will change, features will be added and removed without notice, and good things will hopefully happen before the bad. So, until this module reaches beta stage, don't be too peeved if something doesn't work when you upgrade.
Note: The below does not apply if you are useing the hsin formula.
This module relies on Math::Trig (great_circle_distance) for most of its computations. Math::Trig is a core Perl module. Be aware that Math::Trig states:
"The answers may be off by few percentages because of the irregular (slightly aspherical) form of the Earth."
also
"The formula used for grear circle distances is also somewhat unreliable for small distances (for locations separated less than about five degrees) because it uses arc cosine which is rather ill-conditioned for values close to zero."
CHEERS
Thanks!
Dean Scott
Michael R. Meuser
Jack D.
Bryce Nesbitt
AUTHOR
Copyright (C) 2003-2004 Aran Clary Deltac (CPAN: BLUEFEET)
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Address bug reports and comments to: <aran@bluefeet.net>. When sending bug reports, please provide the version of Geo::Distance, the version of Perl, and the name and version of the operating system you are using. Patches are welcome if you are brave!
SEE ALSO
Math::Trig - Inverse and hyperbolic trigonemetric Functions.