File Coverage

blib/lib/HTML/GoogleMaps/V3.pm
Criterion Covered Total %
statement 94 98 95.9
branch 32 44 72.7
condition 24 31 77.4
subroutine 19 19 100.0
pod 10 13 76.9
total 179 205 87.3


line stmt bran cond sub pod time code
1             package HTML::GoogleMaps::V3;
2              
3             =head1 NAME
4              
5             HTML::GoogleMaps::V3 - a simple wrapper around the Google Maps API
6              
7             =for html
8             Build Status
9             Coverage Status
10              
11             =head1 VERSION
12              
13             0.14
14              
15             =head1 SYNOPSIS
16              
17             use HTML::GoogleMaps::V3
18              
19             $map = HTML::GoogleMaps::V3->new;
20             $map->center("1810 Melrose St, Madison, WI");
21             $map->add_marker(point => "1210 W Dayton St, Madison, WI");
22             $map->add_marker(point => [ 51, 0 ] ); # Greenwich
23              
24             my ($head, $map_div) = $map->onload_render;
25              
26             =head1 NOTE
27              
28             This module is forked from L and updated to use V3 of
29             the API. Note that the module isn't quite a drop in replacement, although
30             it should be trivial to update your code to use it.
31              
32             Note that V3 of the API does not require an API key, however you can pass
33             one and it will be used (useful for analytics).
34              
35             Also note that this library only implements a subset of the functionality
36             available in the maps API, if you want more then raise an issue or create
37             a pull request.
38              
39             =head1 DESCRIPTION
40              
41             HTML::GoogleMaps::V3 provides a simple wrapper around the Google Maps
42             API. It allows you to easily create maps with markers, polylines and
43             information windows. Thanks to Geo::Coder::Google you can now look
44             up locations around the world without having to install a local database.
45              
46             =head1 CONSTRUCTOR
47              
48             =over 4
49              
50             =item $map = HTML::GoogleMaps::V3->new;
51              
52             Creates a new HTML::GoogleMaps::V3 object. Takes a hash of options.
53             Valid options are:
54              
55             =over 4
56              
57             =item api_key => key (your Google Maps API key)
58              
59             =item height => height (in pixels or using your own unit)
60              
61             =item width => width (in pixels or using your own unit)
62              
63             =item z_index => place on z-axis (e.g. -1 to ensure scrolling works)
64              
65             =item geocoder => an object such as Geo::Coder::Google
66              
67             =back
68              
69             =back
70              
71             =cut
72              
73 1     1   69125 use strict;
  1         13  
  1         31  
74 1     1   5 use warnings;
  1         2  
  1         31  
75              
76 1     1   539 use Template;
  1         22028  
  1         1383  
77              
78             our $VERSION = '0.14';
79              
80             sub new {
81 17     17 1 10816 my ( $class,%opts ) = @_;
82              
83 17 50       72 if ( !defined($opts{geocoder} ) ) {
84 17         643 require Geo::Coder::Google;
85 17         279 Geo::Coder::Google->import();
86              
87             $opts{'geocoder'} = Geo::Coder::Google->new(
88             apidriver => 3,
89 17 100       97 ( $opts{'api_key'} ? ( key => $opts{'api_key'}, sensor => 'false' ) : () ),
90             );
91             }
92              
93 17         67462 $opts{'points'} = [];
94 17         42 $opts{'poly_lines'} = [];
95              
96 17         165 return bless \%opts, $class;
97             }
98              
99             sub _text_to_point {
100 27     27   51 my ( $self,$point_text ) = @_;
101              
102             # IE, already a long/lat pair
103 27 100       126 return [ reverse @$point_text ] if ref( $point_text ) eq "ARRAY";
104              
105 2 50       12 if ( my @loc = $self->{geocoder}->geocode( location => $point_text ) ) {
106 2 50       23 if ( my $location = $loc[0] ) {
107              
108 2 50       10 if ( ref( $location ) ne 'HASH' ) {
109 0         0 warn "$point_text didn't return a HASH ref as first element from ->geocode";
110 0         0 return 0;
111             }
112              
113 2 50 33     17 if(defined($location->{geometry}{location}{lat}) && defined($location->{geometry}{location}{lng})) {
114             return [
115             $location->{geometry}{location}{lat},
116             $location->{geometry}{location}{lng},
117 2         14 ];
118             }
119             }
120             }
121              
122             # Unknown
123 0         0 return 0;
124             }
125              
126             sub _find_center {
127 18     18   71 my ( $self ) = @_;
128              
129             # Null case
130 18 100       31 return unless @{$self->{points}};
  18         68  
131              
132 12         25 my ( $total_lat,$total_lng,$total_abs_lng );
133              
134 12         20 foreach ( @{$self->{points}} ) {
  12         35  
135 16         22 my ( $lat,$lng ) = @{ $_->{point} };
  16         37  
136 16 50       43 $total_lat += defined $lat ? $lat : 0;
137 16 50       38 $total_lng += defined $lng ? $lng : 0;
138 16 50       45 $total_abs_lng += abs( defined $lng ? $lng : 0 );
139             }
140              
141             # Latitude is easy, just an average
142 12         32 my $center_lat = $total_lat/@{$self->{points}};
  12         53  
143              
144             # Longitude, on the other hand, is trickier. If points are
145             # clustered around the international date line a raw average
146             # would produce a center around longitude 0 instead of -180.
147 12         22 my $avg_lng = $total_lng/@{$self->{points}};
  12         27  
148 12         18 my $avg_abs_lng = $total_abs_lng/@{$self->{points}};
  12         21  
149              
150 12 100       75 return [ $center_lat,$avg_lng ] # All points are on the
151             if abs( $avg_lng ) == $avg_abs_lng; # same hemasphere
152              
153 2 100       6 if ( $avg_abs_lng > 90 ) { # Closer to the IDL
154 1 50 33     13 if ( $avg_lng < 0 && abs( $avg_lng ) <= 90) {
    0          
155 1         3 $avg_lng += 180;
156             } elsif ( abs( $avg_lng ) <= 90 ) {
157 0         0 $avg_lng -= 180;
158             }
159             }
160              
161 2         13 return [ $center_lat,$avg_lng ];
162             }
163              
164             =head1 METHODS
165              
166             =over 4
167              
168             =item $map->center($point)
169              
170             Center the map at a given point. Returns 1 on success, 0 if
171             the point could not be found.
172              
173             =cut
174              
175             sub center {
176 1     1 1 545 my ( $self,$point_text ) = @_;
177              
178 1   50     7 my $point = $self->_text_to_point( $point_text )
179             || return 0;
180              
181 1         4 $self->{center} = $point;
182 1         3 return 1;
183             }
184              
185             =item $map->zoom($level)
186              
187             Set the new zoom level (0 is corsest)
188              
189             =cut
190              
191             =item $map->dragging($enable)
192              
193             Enable or disable dragging.
194              
195             =cut
196              
197             =item $map->info_window($enable)
198              
199             Enable or disable info windows.
200              
201             =cut
202              
203             =item $map->map_id($id)
204              
205             Set the id of the map div
206              
207             =cut
208              
209 1     1 0 11 sub add_icon { 1; }
210 1     1 0 6 sub controls { 1; }
211 2     2 1 829 sub dragging { $_[0]->{dragging} = $_[1]; }
212 1     1 1 846 sub info_window { $_[0]->{info_window} = $_[1]; }
213 1     1 1 8 sub map_id { $_[0]->{id} = $_[1]; }
214 1     1 1 900 sub zoom { $_[0]->{zoom} = $_[1]; }
215 1     1 0 572 sub v2_zoom { $_[0]->{zoom} = $_[1]; }
216              
217             =item $map->map_type($type)
218              
219             Set the map type. Either B, B, B, or B.
220              
221             =cut
222              
223             sub map_type {
224 6     6 1 4064 my ( $self,$type ) = @_;
225              
226             $type = {
227             normal => 'NORMAL',
228             map_type => 'NORMAL',
229             satellite_type => 'SATELLITE',
230             satellite => 'SATELLITE',
231             hybrid => 'HYBRID',
232             road => 'ROADMAP',
233 6   50     61 }->{ $type } || return 0;
234              
235 6         30 $self->{type} = $type;
236             }
237              
238             =item $map->add_marker(point => $point, html => $info_window_html)
239              
240             Add a marker to the map at the given point. A point can be a unique
241             place name, like an address, or a pair of coordinates passed in as
242             an arrayref: [ longitude, latitude ]. Will return 0 if the point
243             is not found and 1 on success.
244              
245             If B is specified, add a popup info window as well.
246              
247             =cut
248              
249             sub add_marker {
250 16     16 1 170 my ( $self,%opts ) = @_;
251              
252             my $point = $self->_text_to_point($opts{point})
253 16   50     59 || return 0;
254              
255 16         96 push( @{$self->{points}}, {
256             point => $point,
257             html => $opts{html},
258             format => !$opts{noformat}
259 16         33 } );
260             }
261              
262             =item $map->add_polyline(points => [ $point1, $point2 ])
263              
264             Add a polyline that connects the list of points. Other options
265             include B (any valid HTML color), B (line width in
266             pixels) and B (between 0 and 1). Will return 0 if the points
267             are not found and 1 on success.
268              
269             =cut
270              
271             sub add_polyline {
272 5     5 1 1379 my ( $self,%opts ) = @_;
273              
274 5         10 my @points = map { $self->_text_to_point($_) } @{$opts{points}};
  10         22  
  5         19  
275 5 50       14 return 0 if grep { !$_ } @points;
  10         31  
276              
277 5         77 push( @{$self->{poly_lines}}, {
278             points => \@points,
279             color => $opts{color} || "\#0000ff",
280             weight => $opts{weight} || 5,
281 5   100     8 opacity => $opts{opacity} || .5 }
      100        
      100        
282             );
283             }
284              
285             sub _js_template {
286              
287 16     16   32074 my $template =<<"EndOfTemplate";
288              
289             function html_googlemaps_initialize() {
290              
291             myCenterLatLng = new google.maps.LatLng({lat: [% center.0 %], lng: [% center.1 %]});
292              
293             // key map controls
294             var map = new google.maps.Map(document.getElementById('[% id %]'), {
295             mapTypeId: google.maps.MapTypeId.[% type %],
296             [% IF center %]center: myCenterLatLng,[% END %]
297             scrollwheel: false,
298             zoom: [% zoom %],
299             draggable: [% dragging ? 'true' : 'false' %]
300             });
301              
302             [% FOREACH point IN points %]
303              
304             // marker
305             myMarker[% loop.count %]LatLng = new google.maps.LatLng({lat: [% point.point.0 %], lng: [% point.point.1 %]});
306             var marker[% loop.count %] = new google.maps.Marker({
307             map: map,
308             position: myMarker[% loop.count %]LatLng,
309             });
310              
311             // marker infoWindow
312             [% IF info_window AND point.html %]
313             var contentString[% loop.count %] = '[% point.html %]';
314             var infowindow[% loop.count %] = new google.maps.InfoWindow({
315             content: contentString[% loop.count %]
316             });
317              
318             marker[% loop.count %].addListener('click', function() {
319             infowindow[% loop.count %].open(map, marker[% loop.count %]);
320             });
321             [% END %]
322              
323             [% END -%]
324              
325             [% FOREACH route IN poly_lines %]
326              
327             // polylines
328             var route[% loop.count %]Coordinates = [
329             [% FOREACH point IN route.points %]{lat: [% point.0 %], lng: [% point.1 %]}[% loop.last ? '' : ',' %]
330             [% END %]
331             ];
332              
333             var route[% loop.count %] = new google.maps.Polyline({
334             path: route[% loop.count %]Coordinates,
335             geodesic: true,
336             strokeColor: '[% route.color %]',
337             strokeOpacity: [% route.opacity %],
338             strokeWeight: [% route.weight %]
339             });
340              
341             route[% loop.count %].setMap(map);
342             [% END %]
343             }
344             EndOfTemplate
345             }
346              
347             =item $map->onload_render
348              
349             Renders the map and returns a two element list. The first element
350             needs to be placed in the head section of your HTML document. The
351             second in the body where you want the map to appear. You will also
352             need to add a call to html_googlemaps_initialize() in your page's
353             onload handler. The easiest way to do this is adding it to the body
354             tag:
355              
356            
357              
358             =back
359              
360             =cut
361              
362             sub onload_render {
363 16     16 1 111 my ( $self ) = @_;
364              
365             # Add in all the defaults
366 16   100     95 $self->{id} ||= 'map';
367 16   100     70 $self->{height} ||= '400px';
368 16   100     62 $self->{width} ||= '600px';
369 16   100     64 $self->{type} ||= "NORMAL";
370 16   100     73 $self->{zoom} ||= 13;
371 16   100     89 $self->{center} ||= $self->_find_center;
372 16 100       58 $self->{dragging} = 1 unless defined $self->{dragging};
373 16 100       53 $self->{info_window} = 1 unless defined $self->{info_window};
374              
375 16 100       115 $self->{width} .= 'px' if $self->{width} =~ m/^\d+$/;
376 16 100       65 $self->{height} .= 'px' if $self->{height} =~ m/^\d+$/;
377              
378 16         37 my $header = ''
380             ;
381              
382             my $key = $self->{api_key}
383 16 100       67 ? "?key=@{[ $self->{api_key} ]}" : "";
  3         14  
384              
385 16         97 $header =~ s/__KEY__/$key/;
386              
387             my $map = sprintf(
388             '
',
389 16         115 @{$self}{qw/ id width height / },
390             exists($self->{'z_index'})
391 16 100       57 ? '; z-index: ' . $self->{'z_index'} : ''
392             );
393              
394 16         32 my $out;
395 16         107 Template->new->process( \$self->_js_template,$self,\$out );
396              
397 16         483731 $header .= "";
398              
399 16         1071 return ( $header,$map );
400             }
401              
402             =head1 SEE ALSO
403              
404             L
405              
406             =head1 LICENSE
407              
408             This library is free software; you can redistribute it and/or modify it under
409             the same terms as Perl itself. If you would like to contribute documentation,
410             features, bug fixes, or anything else then please raise an issue / pull request:
411              
412             https://github.com/Humanstate/html-googlemaps-v3
413              
414             =cut
415              
416             =head1 AUTHORS
417              
418             Nate Mueller - Original Author
419              
420             Lee Johnson - Maintainer of this fork
421              
422             Nigel Horne - Contributor of several patches
423              
424             =cut
425              
426             1;
427              
428             # vim: ts=4:sw=4:et