diff --git a/lib/Geo/Hash.pm b/lib/Geo/Hash.pm index 12a2fe0..58ecbb8 100644 --- a/lib/Geo/Hash.pm +++ b/lib/Geo/Hash.pm @@ -2,6 +2,7 @@ package Geo::Hash; use warnings; use strict; +use POSIX qw/ceil/; use Carp; =head1 NAME @@ -16,6 +17,10 @@ This document describes Geo::Hash version 0.03 our $VERSION = '0.03'; +use constant LOG2_OF_10 => log(10) / log(2); +use constant LOG2_OF_180 => log(180) / log(2); +use constant LOG2_OF_360 => log(360) / log(2); + =head1 SYNOPSIS use Geo::Hash; @@ -58,14 +63,25 @@ sub _mid { 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 { +sub _num_of_decimal_places($) { my $n = shift; return 0 unless $n =~ s/.*\.//; - return _d2b( length $n ); + return length $n; +} + +sub _length_for_bits($$) { + my ( $bits, $is_lat ) = @_; + my $q = int( $bits / 5 ); + my $r = $bits % 5; + if ( $r == 0 ) { + return $q * 2; + } + elsif ( $r <= ( $is_lat ? 2 : 3 ) ) { + return $q * 2 + 1; + } + else { + return $q * 2 + 2; + } } =head2 C<< precision >> @@ -79,9 +95,11 @@ lat, lon pair. 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 ); + my $lat_bit = ceil( _num_of_decimal_places( $lat ) * LOG2_OF_10 + LOG2_OF_180 ); + my $lon_bit = ceil( _num_of_decimal_places( $lon ) * LOG2_OF_10 + LOG2_OF_360 ); + my $lat_len = _length_for_bits( $lat_bit, 1 ); + my $lot_len = _length_for_bits( $lon_bit, 0 ); + return $lat_len > $lot_len ? $lat_len : $lot_len; } =head2 C<< encode >> diff --git a/t/precision.t b/t/precision.t new file mode 100644 index 0000000..15cac8c --- /dev/null +++ b/t/precision.t @@ -0,0 +1,17 @@ +use strict; +use warnings; +use Test::More; +use Geo::Hash; + +my $gh = Geo::Hash->new; +is $gh->precision('45', '-120'), 4; +is $gh->precision('45.0', '-120'), 5; +is $gh->precision('45', '-120.0'), 5; +is $gh->precision('45.00', '-120'), 6; +is $gh->precision('45', '-120.00'), 7; +is $gh->precision('45.000', '-120'), 8; +is $gh->precision('45', '-120.000'), 8; +is $gh->precision('45.00000', '-120'), 10; +is $gh->precision('45', '-120.00000'), 11; + +done_testing;