File Coverage

blib/lib/HTML/GoogleMaps.pm
Criterion Covered Total %
statement 124 162 76.5
branch 36 68 52.9
condition 21 37 56.7
subroutine 15 18 83.3
pod 13 14 92.8
total 209 299 69.9


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             HTML::GoogleMaps - a simple wrapper around the Google Maps API
4              
5             =head1 SYNOPSIS
6              
7             use HTML::GoogleMaps
8              
9             $map = HTML::GoogleMaps->new(key => $map_key);
10             $map->center("1810 Melrose St, Madison, WI");
11             $map->add_marker(point => "1210 W Dayton St, Madison, WI");
12             $map->add_marker(point => [ 51, 0 ] ); # Greenwich
13            
14             my ($head, $map_div) = $map->onload_render;
15              
16             =head1 NOTE
17              
18             This version is not API compatable with HTML::GoogleMaps versions 1
19             and 2. The render method now returns three values instead of two.
20              
21             =head1 DESCRIPTION
22              
23             HTML::GoogleMaps provides a simple wrapper around the Google Maps
24             API. It allows you to easily create maps with markers, polylines and
25             information windows. Thanks to Geo::Coder::Google you can now look
26             up locations around the world without having to install a local database.
27              
28             =head1 CONSTRUCTOR
29              
30             =over 4
31              
32             =item $map = HTML::GoogleMaps->new(key => $map_key);
33              
34             Creates a new HTML::GoogleMaps object. Takes a hash of options. The
35             only required option is I, which is your Google Maps API key.
36             You can get a key at http://maps.google.com/apis/maps/signup.html .
37             Other valid options are:
38              
39             =over 4
40              
41             =item height => height (in pixels or using your own unit)
42              
43             =item width => width (in pixels or using your own unit)
44              
45             =back
46              
47             =back
48              
49             =head1 METHODS
50              
51             =over 4
52              
53             =item $map->center($point)
54              
55             Center the map at a given point.
56              
57             =item $map->v2_zoom($level)
58              
59             Set the new zoom level (0 is corsest)
60              
61             =item $map->controls($control1, $control2)
62              
63             Enable the given controls. Valid controls are: B,
64             B, B and B.
65              
66             =item $map->dragging($enable)
67              
68             Enable or disable dragging.
69              
70             =item $map->info_window($enable)
71              
72             Enable or disable info windows.
73              
74             =item $map->map_type($type)
75              
76             Set the map type. Either B, B or B. The
77             v1 API B or B still work, but may be dropped
78             in a future version.
79              
80             =item $map->map_id($id)
81              
82             Set the id of the map div
83              
84             =item $map->add_icon(name => $icon_name,
85             image => $image_url,
86             shadow => $shadow_url,
87             icon_size => [ $width, $height ],
88             shadow_size => [ $width, $height ],
89             icon_anchor => [ $x, $y ],
90             info_window_anchor => [ $x, $y ]);
91              
92             Adds a new icon, which can later be used by add_marker. All args
93             are required except for info_window_anchor.
94              
95             =item $map->add_marker(point => $point, html => $info_window_html)
96              
97             Add a marker to the map at the given point. A point can be a unique
98             place name, like an address, or a pair of coordinates passed in as
99             an arrayref: [ longituded, latitude ].
100              
101             If B is specified,
102             add a popup info window as well. B can be used to switch to
103             either a user defined icon (via the name) or a standard google letter
104             icon (A-J).
105              
106             Any data given for B is placed inside a 350px by 200px div to
107             make it fit nicely into the Google popup. To turn this behavior off
108             just pass B => 1 as well.
109              
110             =item $map->add_polyline(points => [ $point1, $point2 ])
111              
112             Add a polyline that connects the list of points. Other options
113             include B (any valid HTML color), B (line width in
114             pixels) and B (between 0 and 1).
115              
116             =item $map->render
117              
118             B
119             better javascript.>
120              
121             Renders the map and returns a three element list. The first element
122             needs to be placed in the head section of your HTML document. The
123             second in the body where you want the map to appear. The third (the
124             Javascript that controls the map) needs to be placed in the body,
125             but outside any div or table that the map lies inside of.
126              
127             =item $map->onload_render
128              
129             Renders the map and returns a two element list. The first element
130             needs to be placed in the head section of your HTML document. The
131             second in the body where you want the map to appear. You will also
132             need to add a call to html_googlemaps_initialize() in your page's
133             onload handler. The easiest way to do this is adding it to the body
134             tag:
135              
136            
137              
138             =back
139              
140             =head1 SEE ALSO
141              
142             L
143             L
144              
145             =head1 AUTHORS
146              
147             Nate Mueller
148              
149             =cut
150              
151             package HTML::GoogleMaps;
152              
153 1     1   29301 use strict;
  1         3  
  1         35  
154 1     1   898 use Geo::Coder::Google;
  1         205  
  1         2236  
155              
156             our $VERSION = 10;
157              
158             sub new {
159 15     15 1 8291 my ($class, %opts) = @_;
160              
161 15 50       58 die "Need a map key? Go to http://www.google.com/apis/maps/signup.html\n"
162             unless $opts{key};
163              
164 15 50       45 if ($opts{db}) {
165 0         0 require Geo::Coder::US;
166 0         0 Geo::Coder::US->set_db($opts{db});
167             }
168            
169             bless {
170 15         96 %opts,
171             points => [],
172             poly_lines => [],
173             geocoder => Geo::Coder::Google->new(apikey => $opts{key}),
174             }, $class;
175             }
176              
177             sub _text_to_point {
178 21     21   36 my ($this, $point_text) = @_;
179              
180             # IE, already a long/lat pair
181 21 100       99 return [reverse @$point_text] if ref($point_text) eq "ARRAY";
182              
183             # US street address
184 2 50       10 if ($this->{db}) {
185 0         0 my ($point) = Geo::Coder::US->geocode($point_text);
186 0 0       0 if ($point->{lat}) {
187 0         0 return [$point->{lat}, $point->{long}];
188             }
189             } else {
190 2         12 my $location = $this->{geocoder}->geocode(location => $point_text);
191             return [
192 2         294044 $location->{Point}{coordinates}[1],
193             $location->{Point}{coordinates}[0],
194             ];
195             }
196            
197             # Unknown
198 0         0 return 0;
199             }
200              
201             sub _find_center {
202 16     16   48 my ($this) = @_;
203              
204             # Null case
205 16 100       26 return unless @{$this->{points}};
  16         59  
206              
207 11         14 my $total_lat;
208             my $total_long;
209 0         0 my $total_abs_long;
210 11         21 foreach my $point (@{$this->{points}}) {
  11         30  
211 15         317 $total_lat += $point->{point}[0];
212 15         221 $total_long += $point->{point}[1];
213 15         172 $total_abs_long += abs($point->{point}[1]);
214             }
215            
216             # Latitude is easy, just an average
217 11         19 my $center_lat = $total_lat/@{$this->{points}};
  11         30  
218            
219             # Longitude, on the other hand, is trickier. If points are
220             # clustered around the international date line a raw average
221             # would produce a center around longitude 0 instead of -180.
222 11         14 my $avg_long = $total_long/@{$this->{points}};
  11         22  
223 11         14 my $avg_abs_long = $total_abs_long/@{$this->{points}};
  11         17  
224 11 100       88 return [ $center_lat, $avg_long ] # All points are on the
225             if abs($avg_long) == $avg_abs_long; # same hemasphere
226              
227 2 100       6 if ($avg_abs_long > 90) { # Closer to the IDL
228 1 50 33     22 if ($avg_long < 0 && abs($avg_long) <= 90) {
    0          
229 1         3 $avg_long += 180;
230             } elsif (abs($avg_long) <= 90) {
231 0         0 $avg_long -= 180;
232             }
233             }
234              
235 2         13 return [$center_lat, $avg_long];
236             }
237              
238             sub center {
239 1     1 1 354 my ($this, $point_text) = @_;
240              
241 1         4 my $point = $this->_text_to_point($point_text);
242 1 50       5 return 0 unless $point;
243            
244 1         3 $this->{center} = $point;
245 1         3 return 1;
246             }
247              
248             sub zoom {
249 1     1 0 954 my ($this, $zoom_level) = @_;
250              
251 1         4 $this->{zoom} = 17-$zoom_level;
252             }
253              
254             sub v2_zoom {
255 1     1 1 424 my ($this, $zoom_level) = @_;
256              
257 1         2 $this->{zoom} = $zoom_level;
258             }
259              
260             sub controls {
261 0     0 1 0 my ($this, @controls) = @_;
262              
263 0         0 my %valid_controls = map { $_ => 1 } qw(large_map_control
  0         0  
264             small_map_control
265             small_zoom_control
266             map_type_control);
267 0 0       0 return 0 if grep { !$valid_controls{$_} } @controls;
  0         0  
268              
269 0         0 $this->{controls} = [ @controls ];
270             }
271              
272             sub dragging {
273 2     2 1 2329 my ($this, $dragging) = @_;
274              
275 2         8 $this->{dragging} = $dragging;
276             }
277              
278             sub info_window {
279 0     0 1 0 my ($this, $info) = @_;
280              
281 0         0 $this->{info_window} = $info;
282             }
283              
284             sub map_type {
285 5     5 1 3030 my ($this, $type) = @_;
286              
287 5         23 my %valid_types = (map_type => 'G_NORMAL_MAP',
288             satellite_type => 'G_SATELLITE_MAP',
289             normal => 'G_NORMAL_MAP',
290             satellite => 'G_SATELLITE_MAP',
291             hybrid => 'G_HYBRID_MAP');
292 5 50       19 return 0 unless $valid_types{$type};
293              
294 5         20 $this->{type} = $valid_types{$type};
295             }
296              
297             sub map_id {
298 1     1 1 405 my ($this, $id) = @_;
299 1         4 $this->{id} = $id;
300             }
301              
302             sub add_marker {
303 16     16 1 169611 my ($this, %opts) = @_;
304            
305 16 0 33     62 return 0 if $opts{icon} && $opts{icon} !~ /^[A-J]$/
      33        
306             && !$this->{icon_hash}{$opts{icon}};
307              
308 16         42 my $point = $this->_text_to_point($opts{point});
309 16 50       44 return 0 unless $point;
310              
311 16         34 push @{$this->{points}}, { point => $point,
  16         120  
312             icon => $opts{icon},
313             html => $opts{html},
314             format => !$opts{noformat} };
315             }
316              
317             sub add_icon {
318 0     0 1 0 my ($this, %opts) = @_;
319              
320 0 0 0     0 return 0 unless $opts{image} && $opts{shadow} && $opts{name};
      0        
321            
322 0         0 $this->{icon_hash}{$opts{name}} = 1;
323 0         0 push @{$this->{icons}}, \%opts;
  0         0  
324             }
325              
326             sub add_polyline {
327 2     2 1 21 my ($this, %opts) = @_;
328              
329 2         4 my @points = map { $this->_text_to_point($_) } @{$opts{points}};
  4         18  
  2         6  
330 2 50       6 return 0 if grep { !$_ } @points;
  4         15  
331              
332 2   50     3 push @{$this->{poly_lines}}, { points => \@points,
  2   50     41  
      50        
333             color => $opts{color} || "\#0000ff",
334             weight => $opts{weight} || 5,
335             opacity => $opts{opacity} || .5 };
336             }
337              
338             sub onload_render {
339 14     14 1 21 my ($this) = @_;
340              
341             # Add in all the defaults
342 14   100     74 $this->{id} ||= 'map';
343 14   100     60 $this->{height} ||= '400px';
344 14   100     56 $this->{width} ||= '600px';
345 14 100       53 $this->{dragging} = 1 unless defined $this->{dragging};
346 14 100       40 $this->{info_window} = 1 unless defined $this->{info_window};
347 14   100     49 $this->{type} ||= "G_NORMAL_MAP";
348 14   100     44 $this->{zoom} ||= 13;
349 14   100     61 $this->{center} ||= $this->_find_center;
350              
351 14 100       77 if ( $this->{width} =~ m/^\d+$/ ) {
352 1         3 $this->{width} .= 'px';
353             }
354 14 100       57 if ( $this->{height} =~ m/^\d+$/ ) {
355 1         2 $this->{height} .= 'px';
356             }
357              
358 14         59 my $header = sprintf(
359             '',
361             $this->{key},
362             );
363 14         55 my $map = sprintf(
364             '
',
365             $this->{id},
366             $this->{width},
367             $this->{height},
368             );
369              
370 14         50 $header .= <
371             ";
463              
464 14         68 return ($header, $map);
465             }
466              
467             sub render {
468 14     14 1 1474 my ($this) = @_;
469 14         40 my ($header, $map) = $this->onload_render;
470 14         75 ($header, my $text) = split(/\n/, $header, 2);
471 14         152 $text =~ s/(.*})/$1\n html_googlemaps_initialize();/s;
472              
473 14         95 return ($header, $map, $text);
474             }
475              
476             1;