1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
package Geo::Hash;
use warnings;
use strict;
use Carp;
=head1 NAME
Geo::Hash - Encode / decode geohash.org locations.
=head1 VERSION
This document describes Geo::Hash version 0.02
=cut
our $VERSION = '0.02';
=head1 SYNOPSIS
use Geo::Hash;
my $gh = Geo::Hash->new;
my $hash = $gh->encode( $lat, $lon );
my ( $lat, $lon ) = $gh->decode( $hash );
=head1 DESCRIPTION
Geohash is a latitude/longitude geocode system invented by Gustavo
Niemeyer when writing the web service at geohash.org, and put into the
public domain.
This module encodes and decodes geohash locations.
See L<http://en.wikipedia.org/wiki/Geohash> and L<http://geohash.org>
for more information.
=head1 INTERFACE
=head2 C<< new >>
Create a new Geo::Hash object.
my $gh = Geo::Hash->new;
=cut
sub new { bless {}, shift }
my @ENC = qw(
0 1 2 3 4 5 6 7 8 9 b c d e f g h j k m n p q r s t u v w x y z
);
my %DEC = map { $ENC[$_] => $_ } 0 .. $#ENC;
sub _mid {
my ( $ar, $wh ) = @_;
return ( $ar->[$wh][0] + $ar->[$wh][1] ) / 2;
}
# The number of bits necessary to represent the specified number of
# decimal digits
sub _d2b { int( shift() * 3.32192809488736 + 1 ) }
sub _bits_for_number {
my $n = shift;
return 0 unless $n =~ s/.*\.//;
return _d2b( length $n );
}
=head2 C<< precision >>
Infer a suitable precision (number of character in hash) for a given
lat, lon pair.
my $prec = $gh->precision( $lat, $lon );
=cut
sub precision {
my ( $self, $lat, $lon ) = @_;
my $lab = _bits_for_number( $lat ) + 8;
my $lob = _bits_for_number( $lon ) + 9;
return int( ( ( $lab > $lob ? $lab : $lob ) + 1 ) / 2.5 );
}
=head2 C<< encode >>
Encode a lat, long pair into a geohash.
my $hash = $gh->encode( $lat, $lon );
You may optionally supply the length of the desired geohash:
# Very precise
my $hash = $gh->encode( $lat, $lon, 10 );
If the precision argument is omitted C<precision> will be used to
provide a default.
=cut
sub encode {
croak "encode needs two or three arguments"
unless @_ >= 3 && @_ <= 4;
my ( $self, @pos ) = splice @_, 0, 3;
my $prec = shift || $self->precision( @pos );
my $int = [ [ 90, -90 ], [ 180, -180 ] ];
my $flip = 1;
my @enc = ();
while ( @enc < $prec ) {
my $bits = 0;
for ( 0 .. 4 ) {
my $mid = _mid( $int, $flip );
my $bit = $pos[$flip] >= $mid ? 1 : 0;
$bits = ( ( $bits << 1 ) | $bit );
$int->[$flip][$bit] = $mid;
$flip ^= 1;
}
push @enc, $ENC[$bits];
}
return join '', @enc;
}
=head2 C<< decode_to_interval >>
Like C<decode> but instead of returning a pair of coordinates returns
the interval for each coordinate. This gives some indication of how
precisely the original hash specified the location.
The return value is a pair of array refs. Each referred to array
contains the upper and lower bounds for each coordinate.
my ( $lat_range, $lon_range ) = $gh->decode_to_interval( $hash );
# $lat_range and $lon_range are references to two element arrays
=cut
sub decode_to_interval {
croak "Needs one argument"
unless @_ == 2;
my ( $self, $hash ) = @_;
my $int = [ [ 90, -90 ], [ 180, -180 ] ];
my $flip = 1;
for my $ch ( split //, $hash ) {
if ( defined( my $bits = $DEC{$ch} ) ) {
for ( 0 .. 4 ) {
$int->[$flip][ ( $bits & 16 ) >> 4 ]
= _mid( $int, $flip );
$flip ^= 1;
$bits <<= 1;
}
}
else {
croak "Bad character '$ch' in hash '$hash'";
}
}
return @$int;
}
=head2 C<< decode >>
Decode a geohash into a lat, long pair.
my ( $lat, $lon ) = $gh->decode( $hash );
=cut
sub decode {
my @int = shift->decode_to_interval( @_ );
return map { _mid( \@int, $_ ) } 0 .. 1;
}
1;
__END__
=head1 CONFIGURATION AND ENVIRONMENT
Geo::Hash requires no configuration files or environment variables.
=head1 DEPENDENCIES
None.
=head1 INCOMPATIBILITIES
None reported.
=head1 BUGS AND LIMITATIONS
No bugs have been reported.
Please report any bugs or feature requests to
C<bug-geo-hash@rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org>.
=head1 AUTHOR
Andy Armstrong C<< <andy@hexten.net> >>
L<http://geohash.org/gcwrdtsvrfgr>
=head1 LICENCE AND COPYRIGHT
Copyright (c) 2008, Andy Armstrong C<< <andy@hexten.net> >>.
This module is free software; you can redistribute it and/or
modify it under the same terms as Perl itself. See L<perlartistic>.
|