File Coverage

blib/lib/GeoHash.pm
Criterion Covered Total %
statement 97 151 64.2
branch 17 30 56.6
condition 3 9 33.3
subroutine 24 29 82.7
pod 4 5 80.0
total 145 224 64.7


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