File Coverage

blib/lib/Geohash.pm
Criterion Covered Total %
statement 142 149 95.3
branch 22 32 68.7
condition 7 9 77.7
subroutine 28 28 100.0
pod 4 5 80.0
total 203 223 91.0


line stmt bran cond sub pod time code
1             package Geohash;
2 9     9   211703 use strict;
  9         25  
  9         366  
3 9     9   48 use warnings;
  9         18  
  9         487  
4             our $VERSION = '0.04';
5              
6 9     9   48 use Exporter 'import';
  9         23  
  9         2262  
7             our @EXPORT_OK = qw( ADJ_TOP ADJ_RIGHT ADJ_LEFT ADJ_BOTTOM );
8             our %EXPORT_TAGS = (adjacent => \@EXPORT_OK);
9              
10             BEGIN {
11 9     9   37 my @classes = qw( Geo::Hash::XS Geohash::backendPP );
12 9 50       62 if (my $backend = $ENV{PERL_GEOHASH_BACKEND}) {
13 0 0       0 if ($backend eq 'Geo::Hash') {
    0          
14 0         0 @classes = qw( Geohash::backendPP );
15             } elsif ($backend eq '+Geo::Hash') {
16 0         0 @classes = qw( Geo::Hash );
17             } else {
18 0         0 @classes = ( $backend );
19             }
20             }
21              
22 9         21 local $@;
23 9         24 my $class;
24 9         22 for (@classes) {
25 18         30 $class = $_;
26 18 100       59 last if $class eq 'Geohash::backendPP';
27 9     9   641 eval "use $class";## no critic
  9         4469  
  0         0  
  0         0  
28 9 50       63 last unless $@;
29             }
30 9 50       35 unless ($class eq 'Geohash::backendPP') {
31 0 0       0 die $@ if $@;
32             }
33              
34 11     11 0 875 sub backend_class { $class }
35              
36 9     9   52 no strict 'refs';
  9         16  
  9         1557  
37 9     3   54 *ADJ_RIGHT = sub { &{"$class\::ADJ_RIGHT"} };
  3         4  
  3         17  
38 9     3   33 *ADJ_LEFT = sub { &{"$class\::ADJ_LEFT"} };
  3         4  
  3         16  
39 9     3   30 *ADJ_TOP = sub { &{"$class\::ADJ_TOP"} };
  3         3  
  3         15  
40 9     3   1207 *ADJ_BOTTOM = sub { &{"$class\::ADJ_BOTTOM"} };
  3         5  
  3         18  
41             }
42              
43             sub new {
44 8     8 1 125 my($class) = @_;
45 8         36 my $backend = $class->backend_class->new;
46 8         73 bless {
47             backend => $backend,
48             }, $class;
49             }
50              
51              
52             for my $method (qw/ encode decode decode_to_interval adjacent neighbors precision /) {
53             my $code = sub {
54 58     58   22554 my $self = shift;
55 58         220 $self->{backend}->$method(@_);
56             };
57 9     9   112 no strict 'refs';
  9         14  
  9         5136  
58             *{$method} = $code;
59             }
60              
61              
62             my @ENC = qw(
63             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
64             );
65             my %ENC_MAP = map { $_ => 1 } @ENC;
66              
67             sub _merge_strip_last_char {
68 223     223   628 my($self, $geohash) = @_;
69 223         182 my @results;
70              
71 223 100 50     510 if (length($geohash || '') < 2) {
72 2         6 return ($geohash);
73             }
74              
75 221         1006 my($parent_geohash, $last_char) = $geohash =~ /^(.+)(.)$/;
76 221 100       449 if ($last_char eq $ENC[0]) {
    100          
77 7         22 $self->{cache}{$parent_geohash}{$last_char}++;
78             } elsif ($last_char eq $ENC[-1]) {
79 7         25 $self->{cache}{$parent_geohash}{$last_char}++;
80              
81 7 100       10 if (scalar(keys %{ $self->{cache}{$parent_geohash} }) == scalar(@ENC)) {
  7         25  
82 6         13 push @results, $self->_merge_strip_last_char($parent_geohash);
83             } else {
84 1         2 push @results, map { "$parent_geohash$_" } keys %{ $self->{cache}{$parent_geohash} };
  21         34  
  1         7  
85             }
86              
87 7         40 delete $self->{cache}{$parent_geohash};
88             } else {
89 207 100 66     761 if ($self->{cache}{$parent_geohash} && $ENC_MAP{$last_char}) {
90 199         341 $self->{cache}{$parent_geohash}{$last_char}++;
91             } else {
92 8         10 push @results, $geohash;
93             }
94             }
95              
96 221         380 return @results;
97             }
98              
99             sub merge {
100 3     3 1 3714 my $self = shift;
101 3         61 my @geohashes = sort @_;
102              
103 3         12 $self->{cache} = +{};
104 3         5 my @results;
105 3         5 for my $geohash (@geohashes) {
106 217         351 push @results, $self->_merge_strip_last_char($geohash);
107             }
108 3         7 delete $self->{cache};
109              
110 3         30 sort @results;
111             }
112              
113             sub split {
114 1     1 1 5 my($self, $geohash) = @_;
115 1         2 map { "$geohash$_" } @ENC;
  32         56  
116             }
117              
118             sub validate {
119 6     6 1 12 my($self, $geohash) = @_;
120 6 100       41 $geohash && $geohash =~ /^[0123456789bcdefghjkmnpqrstuvwxyz]+$/;
121             }
122              
123              
124             {
125             package Geohash::backendPP;
126 9     9   52 use strict;
  9         15  
  9         399  
127 9     9   42 use warnings;
  9         19  
  9         323  
128 9     9   7793 use parent 'Geo::Hash';
  9         2795  
  9         59  
129 9     9   17687 use Carp;
  9         21  
  9         578  
130              
131             # https://github.com/yappo/Geo--Hash/tree/feature-geo_hash_xs
132 9     9   52 use constant ADJ_RIGHT => 0;
  9         22  
  9         836  
133 9     9   47 use constant ADJ_LEFT => 1;
  9         15  
  9         423  
134 9     9   43 use constant ADJ_TOP => 2;
  9         15  
  9         374  
135 9     9   46 use constant ADJ_BOTTOM => 3;
  9         14  
  9         5881  
136              
137             my @NEIGHBORS = (
138             [ "bc01fg45238967deuvhjyznpkmstqrwx", "p0r21436x8zb9dcf5h7kjnmqesgutwvy" ],
139             [ "238967debc01fg45kmstqrwxuvhjyznp", "14365h7k9dcfesgujnmqp0r2twvyx8zb" ],
140             [ "p0r21436x8zb9dcf5h7kjnmqesgutwvy", "bc01fg45238967deuvhjyznpkmstqrwx" ],
141             [ "14365h7k9dcfesgujnmqp0r2twvyx8zb", "238967debc01fg45kmstqrwxuvhjyznp" ]
142             );
143              
144             my @BORDERS = (
145             [ "bcfguvyz", "prxz" ],
146             [ "0145hjnp", "028b" ],
147             [ "prxz", "bcfguvyz" ],
148             [ "028b", "0145hjnp" ]
149             );
150              
151             sub adjacent {
152 83     83   97 my ( $self, $hash, $where ) = @_;
153 83         82 my $hash_len = length $hash;
154              
155 83 50       135 croak "PANIC: hash too short!"
156             unless $hash_len >= 1;
157              
158 83         78 my $base;
159             my $last_char;
160 83         85 my $type = $hash_len % 2;
161              
162 83 100       110 if ( $hash_len == 1 ) {
163 4         5 $base = '';
164 4         5 $last_char = $hash;
165             }
166             else {
167 79         298 ( $base, $last_char ) = $hash =~ /^(.+)(.)$/;
168 79 100       541 if ($BORDERS[$where][$type] =~ /$last_char/) {
169 13         29 my $tmp = $self->adjacent($base, $where);
170 13         24 substr($base, 0, length($tmp)) = $tmp;
171             }
172             }
173 83         262 return $base . $ENC[ index($NEIGHBORS[$where][$type], $last_char) ];
174             }
175              
176             sub neighbors {
177 4     4   9 my ( $self, $hash, $around, $offset ) = @_;
178 4   100     14 $around ||= 1;
179 4   100     11 $offset ||= 0;
180              
181 4         7 my $last_hash = $hash;
182 4         4 my $i = 1;
183 4         11 while ( $offset-- > 0 ) {
184 1         3 my $top = $self->adjacent( $last_hash, ADJ_TOP );
185 1         3 my $left = $self->adjacent( $top, ADJ_LEFT );
186 1         1 $last_hash = $left;
187 1         13 $i++;
188             }
189              
190 4         5 my @list;
191 4         8 while ( $around-- > 0 ) {
192 5         7 my $max = 2 * $i - 1;
193 5         10 $last_hash = $self->adjacent( $last_hash, ADJ_TOP );
194 5         6 push @list, $last_hash;
195              
196 5         12 for ( 0..( $max - 1 ) ) {
197 9         13 $last_hash = $self->adjacent( $last_hash, ADJ_RIGHT );
198 9         18 push @list, $last_hash;
199             }
200              
201 5         8 for ( 0..$max ) {
202 14         22 $last_hash = $self->adjacent( $last_hash, ADJ_BOTTOM );
203 14         25 push @list, $last_hash;
204             }
205              
206 5         7 for ( 0..$max ) {
207 14         24 $last_hash = $self->adjacent( $last_hash, ADJ_LEFT );
208 14         22 push @list, $last_hash;
209             }
210              
211 5         8 for ( 0..$max ) {
212 14         24 $last_hash = $self->adjacent( $last_hash, ADJ_TOP );
213 14         26 push @list, $last_hash;
214             }
215 5         11 $i++;
216             }
217              
218 4         24 return @list;
219             }
220             }
221              
222             1;
223             __END__