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.16
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   70692 use strict;
  1         13  
  1         30  
74 1     1   5 use warnings;
  1         2  
  1         27  
75              
76 1     1   670 use Template;
  1         23268  
  1         1463  
77              
78             our $VERSION = '0.16';
79              
80             sub new {
81 18     18 1 9361 my ( $class,%opts ) = @_;
82              
83 18 50       71 if ( !defined($opts{geocoder} ) ) {
84 18         624 require Geo::Coder::Google;
85 18         254 Geo::Coder::Google->import();
86              
87             $opts{'geocoder'} = Geo::Coder::Google->new(
88             apidriver => 3,
89 18 100       82 ( $opts{'api_key'} ? ( key => $opts{'api_key'}, sensor => 'false' ) : () ),
90             );
91             }
92              
93 18         65977 $opts{'points'} = [];
94 18         37 $opts{'poly_lines'} = [];
95              
96 18         171 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       118 return [ reverse @$point_text ] if ref( $point_text ) eq "ARRAY";
104              
105 2 50       9 if ( my @loc = $self->{geocoder}->geocode( location => $point_text ) ) {
106 2 50       23 if ( my $location = $loc[0] ) {
107              
108 2 50       7 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     18 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         15 ];
118             }
119             }
120             }
121              
122             # Unknown
123 0         0 return 0;
124             }
125              
126             sub _find_center {
127 19     19   58 my ( $self ) = @_;
128              
129             # Null case
130 19 100       32 return unless @{$self->{points}};
  19         56  
131              
132 12         26 my ( $total_lat,$total_lng,$total_abs_lng );
133              
134 12         21 foreach ( @{$self->{points}} ) {
  12         28  
135 16         23 my ( $lat,$lng ) = @{ $_->{point} };
  16         33  
136 16 50       46 $total_lat += defined $lat ? $lat : 0;
137 16 50       39 $total_lng += defined $lng ? $lng : 0;
138 16 50       40 $total_abs_lng += abs( defined $lng ? $lng : 0 );
139             }
140              
141             # Latitude is easy, just an average
142 12         30 my $center_lat = $total_lat/@{$self->{points}};
  12         40  
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         20 my $avg_lng = $total_lng/@{$self->{points}};
  12         20  
148 12         16 my $avg_abs_lng = $total_abs_lng/@{$self->{points}};
  12         21  
149              
150 12 100       77 return [ $center_lat,$avg_lng ] # All points are on the
151             if abs( $avg_lng ) == $avg_abs_lng; # same hemasphere
152              
153 2 100       7 if ( $avg_abs_lng > 90 ) { # Closer to the IDL
154 1 50 33     9 if ( $avg_lng < 0 && abs( $avg_lng ) <= 90) {
    0          
155 1         2 $avg_lng += 180;
156             } elsif ( abs( $avg_lng ) <= 90 ) {
157 0         0 $avg_lng -= 180;
158             }
159             }
160              
161 2         10 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 582 my ( $self,$point_text ) = @_;
177              
178 1   50     5 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 663 sub dragging { $_[0]->{dragging} = $_[1]; }
212 1     1 1 630 sub info_window { $_[0]->{info_window} = $_[1]; }
213 1     1 1 7 sub map_id { $_[0]->{id} = $_[1]; }
214 1     1 1 1213 sub zoom { $_[0]->{zoom} = $_[1]; }
215 1     1 0 586 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 3231 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     47 }->{ $type } || return 0;
234              
235 6         26 $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 180 my ( $self,%opts ) = @_;
251              
252             my $point = $self->_text_to_point($opts{point})
253 16   50     41 || return 0;
254              
255 16         84 push( @{$self->{points}}, {
256             point => $point,
257             html => $opts{html},
258             format => !$opts{noformat}
259 16         30 } );
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 1281 my ( $self,%opts ) = @_;
273              
274 5         9 my @points = map { $self->_text_to_point($_) } @{$opts{points}};
  10         24  
  5         18  
275 5 50       13 return 0 if grep { !$_ } @points;
  10         32  
276              
277 5         68 push( @{$self->{poly_lines}}, {
278             points => \@points,
279             color => $opts{color} || "\#0000ff",
280             weight => $opts{weight} || 5,
281 5   100     9 opacity => $opts{opacity} || .5 }
      100        
      100        
282             );
283             }
284              
285             sub _js_template {
286              
287 17     17   32268 my $template =<<"EndOfTemplate";
288              
289             function html_googlemaps_initialize() {
290              
291             [% # Github issue 22 %]
292             [% IF center %]
293             myCenterLatLng = new google.maps.LatLng({lat: [% center.0 %], lng: [% center.1 %]});
294             [% END %]
295              
296             // key map controls
297             var map = new google.maps.Map(document.getElementById('[% id %]'), {
298             mapTypeId: google.maps.MapTypeId.[% type %],
299             [% IF center %]center: myCenterLatLng,[% END %]
300             scrollwheel: false,
301             zoom: [% zoom %],
302             draggable: [% dragging ? 'true' : 'false' %]
303             });
304              
305             [% FOREACH point IN points %]
306              
307             // marker
308             myMarker[% loop.count %]LatLng = new google.maps.LatLng({lat: [% point.point.0 %], lng: [% point.point.1 %]});
309             var marker[% loop.count %] = new google.maps.Marker({
310             map: map,
311             position: myMarker[% loop.count %]LatLng,
312             });
313              
314             // marker infoWindow
315             [% IF info_window AND point.html %]
316             var contentString[% loop.count %] = '[% point.html %]';
317             var infowindow[% loop.count %] = new google.maps.InfoWindow({
318             content: contentString[% loop.count %]
319             });
320              
321             marker[% loop.count %].addListener('click', function() {
322             infowindow[% loop.count %].open(map, marker[% loop.count %]);
323             });
324             [% END %]
325              
326             [% END -%]
327              
328             [% FOREACH route IN poly_lines %]
329              
330             // polylines
331             var route[% loop.count %]Coordinates = [
332             [% FOREACH point IN route.points %]{lat: [% point.0 %], lng: [% point.1 %]}[% loop.last ? '' : ',' %]
333             [% END %]
334             ];
335              
336             var route[% loop.count %] = new google.maps.Polyline({
337             path: route[% loop.count %]Coordinates,
338             geodesic: true,
339             strokeColor: '[% route.color %]',
340             strokeOpacity: [% route.opacity %],
341             strokeWeight: [% route.weight %]
342             });
343              
344             route[% loop.count %].setMap(map);
345             [% END %]
346             }
347             EndOfTemplate
348             }
349              
350             =item $map->onload_render
351              
352             Renders the map and returns a two element list. The first element
353             needs to be placed in the head section of your HTML document. The
354             second in the body where you want the map to appear. You will also
355             need to add a call to html_googlemaps_initialize() in your page's
356             onload handler. The easiest way to do this is adding it to the body
357             tag:
358              
359            
360              
361             =back
362              
363             =cut
364              
365             sub onload_render {
366 17     17 1 98 my ( $self ) = @_;
367              
368             # Add in all the defaults
369 17   100     70 $self->{id} ||= 'map';
370 17   100     57 $self->{height} ||= '400px';
371 17   100     50 $self->{width} ||= '600px';
372 17   100     55 $self->{type} ||= "NORMAL";
373 17   100     53 $self->{zoom} ||= 13;
374 17   100     76 $self->{center} ||= $self->_find_center;
375 17 100       40 $self->{dragging} = 1 unless defined $self->{dragging};
376 17 100       40 $self->{info_window} = 1 unless defined $self->{info_window};
377              
378 17 100       91 $self->{width} .= 'px' if $self->{width} =~ m/^\d+$/;
379 17 100       54 $self->{height} .= 'px' if $self->{height} =~ m/^\d+$/;
380              
381 17         38 my $header = ''
383             ;
384              
385             my $key = $self->{api_key}
386 17 100       44 ? "?key=@{[ $self->{api_key} ]}" : "";
  3         12  
387              
388 17         74 $header =~ s/__KEY__/$key/;
389              
390             my $map = sprintf(
391             '
',
392 17         110 @{$self}{qw/ id width height / },
393             exists($self->{'z_index'})
394 17 100       36 ? '; z-index: ' . $self->{'z_index'} : ''
395             );
396              
397 17         24 my $out;
398 17         73 Template->new->process( \$self->_js_template,$self,\$out );
399              
400 17         524048 $header .= "";
401              
402 17         899 return ( $header,$map );
403             }
404              
405             =head1 SEE ALSO
406              
407             L
408              
409             =head1 LICENSE
410              
411             This library is free software; you can redistribute it and/or modify it under
412             the same terms as Perl itself. If you would like to contribute documentation,
413             features, bug fixes, or anything else then please raise an issue / pull request:
414              
415             https://github.com/Humanstate/html-googlemaps-v3
416              
417             =cut
418              
419             =head1 AUTHORS
420              
421             Nate Mueller - Original Author
422              
423             Lee Johnson - Maintainer of this fork
424              
425             Nigel Horne - Contributor of several patches
426              
427             =cut
428              
429             1;
430              
431             # vim: ts=4:sw=4:et