File Coverage

blib/lib/Geo/Hash/Grid.pm
Criterion Covered Total %
statement 73 73 100.0
branch 10 16 62.5
condition 9 21 42.8
subroutine 13 13 100.0
pod 4 5 80.0
total 109 128 85.1


line stmt bran cond sub pod time code
1             package Geo::Hash::Grid;
2              
3 1     1   15435 use Moo;
  1         11008  
  1         6  
4 1     1   1833 use namespace::clean;
  1         9115  
  1         4  
5              
6             extends 'Geo::Hash::XS';
7              
8 1     1   173 use strict;
  1         7  
  1         29  
9 1     1   5 use warnings;
  1         1  
  1         39  
10              
11 1     1   4 use Carp qw( croak );
  1         1  
  1         41  
12              
13 1     1   470 use Geo::Hash::XS qw( ADJ_RIGHT ADJ_TOP );
  1         417  
  1         53  
14 1     1   5 use Scalar::Util qw( looks_like_number );
  1         1  
  1         640  
15              
16              
17             =head1 NAME
18              
19             Geo::Hash::Grid - Make a grid based off of GeoHashes
20              
21             =head1 VERSION
22              
23             Version 0.08
24              
25             =cut
26              
27             our $VERSION = '0.08';
28              
29              
30             =head1 SYNOPSIS
31              
32             Sometimes you need a simple grid to cover a geographic area. This is a pretty
33             easy way to get one that covers a geographic box and with a little spillover.
34              
35             This module inherits from Geo::Hash::XS and subclasses all it's methods.
36              
37             use Geo::Hash::Grid;
38            
39             my $grid = Geo::Hash::Grid->new(
40             sw_lat => $south_west_latitude,
41             sw_lon => $south_west_longidude,
42             ne_lat => $north_east_latitude,
43             ne_lon => $north_east_longitude,
44             precision => 8,
45             );
46            
47             my $hash_count = $grid->count;
48            
49             my $geohash_list = $grid->hashes;
50            
51             my $origin_list = $grid->origins;
52            
53              
54             =head1 METHODS
55              
56             =head2 new
57              
58             Create the geohash grid that fits in a bounding box and specify the grid size.
59              
60             =over 4
61              
62             =item * sw_lat => $decimal_degrees
63              
64             Latitude of the southwest corner of bounding box
65              
66             =item * sw_lon => $decimal_degrees
67              
68             Longitude of the southwest corner of bounding box
69              
70             =item * ne_lat => $decimal_degrees
71              
72             Latitude of the northeast corner of bounding box
73              
74             =item * ne_lon => $decimal_degrees
75              
76             Longitude of the northeast corner of bounding box
77              
78             =item * precision => $integer
79              
80             Geohash precision
81              
82             =back
83              
84             =cut
85              
86             has 'sw_lat' => ( is => 'ro', required => 1 );
87             has 'sw_lon' => ( is => 'ro', required => 1 );
88             has 'ne_lat' => ( is => 'ro', required => 1 );
89             has 'ne_lon' => ( is => 'ro', required => 1 );
90             has 'precision' => ( is => 'ro', required => 1 );
91              
92             sub BUILD {
93              
94 1     1 0 2429 my $self = shift;
95            
96            
97 1 50 33     11 croak "sw_lat attribute missing or malformed" if not defined $self->sw_lat or not looks_like_number $self->sw_lat;
98 1 50 33     8 croak "sw_lon attribute missing or malformed" if not defined $self->sw_lon or not looks_like_number $self->sw_lon;
99 1 50 33     9 croak "ne_lat attribute missing or malformed" if not defined $self->ne_lat or not looks_like_number $self->ne_lat;
100 1 50 33     7 croak "ne_lon attribute missing or malformed" if not defined $self->ne_lon or not looks_like_number $self->ne_lon;
101 1 50 33     10 croak "precision attribute missing or malformed" if not defined $self->precision or not $self->precision =~ m/^\d+$/;
102            
103 1         3 my $gh = Geo::Hash::XS->new();
104            
105            
106             # place to store the hashes
107 1         3 my @coverage;
108            
109            
110             # convert the southwest corner into the start hash
111 1         6 my $current_hash = $gh->encode( $self->sw_lat, $self->sw_lon, $self->precision );
112            
113            
114             # let's find our start lat/lon
115 1         9 my ( $current_lat, $current_lon ) = $gh->decode( $current_hash );
116            
117 1         1 my $row_start_hash = $current_hash;
118            
119             # while both our current lat/longs are still within the bounding box
120 1         1 my $over_bounds_detected = 0;
121 1   66     1 do {
      66        
122            
123             # if our longitude hasn't over run our bounding box, then we need the next to the right
124             # if our longitude has run over the bounding box, then we need the next one on the top
125 36         21 my $next_hash;
126 36 100       50 if ( $current_lon <= $self->ne_lon ) {
127            
128 32         64 $next_hash = $gh->adjacent( $current_hash, ADJ_RIGHT );
129            
130 32         35 push @coverage, $current_hash;
131            
132             } else {
133            
134 4         4 $current_hash = $row_start_hash;
135 4         13 $next_hash = $gh->adjacent( $current_hash, ADJ_TOP );
136 4         5 $row_start_hash = $next_hash;
137            
138             # check to see if the next row is out of bounds
139 4         6 my ( $lat ) = $gh->decode( $row_start_hash );
140            
141 4 100       9 $over_bounds_detected = 1 if $lat > $self->ne_lat;
142            
143             }
144            
145             # get ready to evaluate the next hash
146 36         32 $current_hash = $next_hash;
147 36         170 ( $current_lat, $current_lon ) = $gh->decode( $current_hash );
148            
149             } while ( ( $current_lat <= $self->ne_lat or $current_lon <= $self->ne_lon ) and not $over_bounds_detected );
150            
151             # store data
152 1         27 $self->{'coverage'} = [ @coverage ];
153            
154             }
155              
156             =head2 count
157              
158             Get count of GeoHash's in bounding box
159              
160             =cut
161              
162             sub count {
163 1     1 1 7 my $self = shift;
164 1         1 return scalar @{$self->{'coverage'}};
  1         5  
165             }
166              
167             =head2 hashes
168              
169             Get array reference of GeoHash's in bounding box
170              
171             =cut
172              
173             sub hashes {
174 1     1 1 2 my $self = shift;
175 1         9 return $self->{'coverage'};
176             }
177              
178             =head2 origins
179              
180             Get list of hash references of GeoHash lat/long origins in bounding box
181              
182             =cut
183              
184             sub origins {
185 1     1 1 2 my $self = shift;
186            
187 1         4 my $gh = Geo::Hash::XS->new();
188            
189 1 50       8 if ( not defined $self->{'origins'} ) {
190 1         2 my $origins;
191 1         1 foreach my $hash ( @{$self->{'coverage'}} ) {
  1         5  
192 32         83 my ( $lat, $lon ) = $gh->decode( $hash );
193 32         86 push @$origins, {
194             lat => $lat,
195             lon => $lon,
196             };
197             }
198 1         18 $self->{'origins'} = $origins;
199             }
200            
201 1         4 return $self->{'origins'};
202            
203             }
204              
205             =head2 bboxes
206              
207             Get a list of bounding boxes for each hash in the grid
208              
209             =cut
210              
211             sub bboxes {
212 1     1 1 5311 my $self = shift;
213            
214 1         1 my @bboxes;
215 1         1 foreach my $hash ( @{$self->{'coverage'}} ) {
  1         3  
216            
217 32         37 my $bbox = $self->_get_bbox( $hash );
218 32         37 push @bboxes, $bbox;
219            
220             }
221            
222 1         5 return [ @bboxes ];
223              
224             }
225              
226              
227             sub _get_bbox {
228 32     32   26 my $self = shift;
229 32         24 my $hash = shift;
230            
231 32         49 my $gh = Geo::Hash::XS->new();
232              
233 32         99 my ( $lat_range, $lon_range ) = $gh->decode_to_interval( $hash );
234            
235             return {
236 32         109 sw => {
237             lat => $lat_range->[1],
238             lon => $lon_range->[1],
239             },
240             ne => {
241             lat => $lat_range->[0],
242             lon => $lon_range->[0],
243             },
244             };
245            
246             }
247              
248             =head1 AUTHOR
249              
250             Adam Wohld,
251              
252             =head1 BUGS
253              
254             Please report any bugs or feature requests to C, or through
255             the web interface at L. I will be notified, and then you'll
256             automatically be notified of progress on your bug as I make changes.
257              
258              
259              
260              
261             =head1 SUPPORT
262              
263             You can find documentation for this module with the perldoc command.
264              
265             perldoc Geo::Hash::Grid
266              
267              
268             You can also look for information at:
269              
270             =over 4
271              
272             =item * RT: CPAN's request tracker (report bugs here)
273              
274             L
275              
276             =item * AnnoCPAN: Annotated CPAN documentation
277              
278             L
279              
280             =item * CPAN Ratings
281              
282             L
283              
284             =item * Search CPAN
285              
286             L
287              
288             =back
289              
290              
291             =head1 ACKNOWLEDGEMENTS
292              
293              
294             =head1 LICENSE AND COPYRIGHT
295              
296             Copyright 2015 Adam Wohld.
297              
298             This program is free software; you can redistribute it and/or modify it
299             under the terms of the the Artistic License (2.0). You may obtain a
300             copy of the full license at:
301              
302             L
303              
304             Any use, modification, and distribution of the Standard or Modified
305             Versions is governed by this Artistic License. By using, modifying or
306             distributing the Package, you accept this license. Do not use, modify,
307             or distribute the Package, if you do not accept this license.
308              
309             If your Modified Version has been derived from a Modified Version made
310             by someone other than you, you are nevertheless required to ensure that
311             your Modified Version complies with the requirements of this license.
312              
313             This license does not grant you the right to use any trademark, service
314             mark, tradename, or logo of the Copyright Holder.
315              
316             This license includes the non-exclusive, worldwide, free-of-charge
317             patent license to make, have made, use, offer to sell, sell, import and
318             otherwise transfer the Package with respect to any patent claims
319             licensable by the Copyright Holder that are necessarily infringed by the
320             Package. If you institute patent litigation (including a cross-claim or
321             counterclaim) against any party alleging that the Package constitutes
322             direct or contributory patent infringement, then this Artistic License
323             to you shall terminate on the date that such litigation is filed.
324              
325             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
326             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
327             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
328             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
329             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
330             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
331             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
332             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
333              
334              
335             =cut
336              
337             1;