| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
=head1 NAME |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Geo::Google - Perform geographical queries using Google Maps |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use strict; |
|
8
|
|
|
|
|
|
|
use Data::Dumper; |
|
9
|
|
|
|
|
|
|
use Geo::Google; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
#Allen's office |
|
12
|
|
|
|
|
|
|
my $gonda_addr = '695 Charles E Young Dr S, Los Angeles, Los Angeles, California 90024, United States'; |
|
13
|
|
|
|
|
|
|
#Stan's Donuts |
|
14
|
|
|
|
|
|
|
my $stans_addr = '10948 Weyburn Ave, Westwood, CA 90024'; |
|
15
|
|
|
|
|
|
|
#Roscoe's House of Chicken and Waffles |
|
16
|
|
|
|
|
|
|
my $roscoes_addr = "5006 W Pico Blvd, Los Angeles, CA 90019"; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
#Instantiate a new Geo::Google object. |
|
19
|
|
|
|
|
|
|
my $geo = Geo::Google->new(); |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
#Create Geo::Google::Location objects. These contain |
|
22
|
|
|
|
|
|
|
#latitude/longitude coordinates, along with a few other details |
|
23
|
|
|
|
|
|
|
#about the locus. |
|
24
|
|
|
|
|
|
|
my ( $gonda ) = $geo->location( address => $gonda_addr ); |
|
25
|
|
|
|
|
|
|
my ( $stans ) = $geo->location( address => $stans_addr ); |
|
26
|
|
|
|
|
|
|
my ( $roscoes ) = $geo->location( address => $roscoes_addr ); |
|
27
|
|
|
|
|
|
|
print $gonda->latitude, " / ", $gonda->longitude, "\n"; |
|
28
|
|
|
|
|
|
|
print $stans->latitude, " / ", $stans->longitude, "\n"; |
|
29
|
|
|
|
|
|
|
print $roscoes->latitude, " / ", $roscoes->longitude, "\n"; |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
#Create a Geo::Google::Path object from $gonda to $roscoes |
|
32
|
|
|
|
|
|
|
#by way of $stans. |
|
33
|
|
|
|
|
|
|
my ( $donut_path ) = $geo->path($gonda, $stans, $roscoes); |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
#A path contains a series of Geo::Google::Segment objects with |
|
36
|
|
|
|
|
|
|
#text labels representing turn-by-turn driving directions between |
|
37
|
|
|
|
|
|
|
#two or more locations. |
|
38
|
|
|
|
|
|
|
my @segments = $donut_path->segments(); |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
#This is the human-readable directions for the first leg of the |
|
41
|
|
|
|
|
|
|
#journey. |
|
42
|
|
|
|
|
|
|
print $segments[0]->text(),"\n"; |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
#Geo::Google::Segment objects contain a series of |
|
45
|
|
|
|
|
|
|
#Geo::Google::Location objects -- one for each time the segment |
|
46
|
|
|
|
|
|
|
#deviates from a straight line to the end of the segment. |
|
47
|
|
|
|
|
|
|
my @points = $segments[1]->points; |
|
48
|
|
|
|
|
|
|
print $points[0]->latitude, " / ", $points[0]->longitude, "\n"; |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
#Now how about some coffee nearby? |
|
51
|
|
|
|
|
|
|
my @coffee = $geo->near($stans,'coffee'); |
|
52
|
|
|
|
|
|
|
#Too many. How about some Coffee Bean & Tea Leaf? |
|
53
|
|
|
|
|
|
|
@coffee = grep { $_->title =~ /Coffee.*?Bean/i } @coffee; |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
#Still too many. Let's find the closest with a little trig and |
|
56
|
|
|
|
|
|
|
#a Schwartzian transform |
|
57
|
|
|
|
|
|
|
my ( $coffee ) = map { $_->[1] } |
|
58
|
|
|
|
|
|
|
sort { $a->[0] <=> $b->[0] } |
|
59
|
|
|
|
|
|
|
map { [ sqrt( |
|
60
|
|
|
|
|
|
|
($_->longitude - $stans->longitude)**2 |
|
61
|
|
|
|
|
|
|
+ |
|
62
|
|
|
|
|
|
|
($_->latitude - $stans->latitude)**2 |
|
63
|
|
|
|
|
|
|
), $_ ] } @coffee; |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# Export a location as XML for part of a Google Earth KML file |
|
66
|
|
|
|
|
|
|
my $strStansDonutsXML = $stans->toXML(); |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Export a location as JSON data to use with Google Maps |
|
69
|
|
|
|
|
|
|
my $strRoscoesJSON = $roscoes->toJSON(); |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
Geo::Google provides access to the map data used by the popular |
|
74
|
|
|
|
|
|
|
L web application. |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=head2 WHAT IS PROVIDED |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=over |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
=item Conversion of a street address to a 2D Cartesian point |
|
81
|
|
|
|
|
|
|
(latitude/longitude) |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=item Conversion of a pair of points to a multi-segmented path of |
|
84
|
|
|
|
|
|
|
driving directions between the two points. |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=item Querying Google's "Local Search" given a point and one or more |
|
87
|
|
|
|
|
|
|
query terms. |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=back |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=head2 WHAT IS NOT PROVIDED |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=over |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=item Documentation of the Google Maps map data XML format |
|
96
|
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
=item Documentation of the Google Maps web application API |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=item Functionality to create your own Google Maps web page. |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=back |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head1 AUTHOR |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
Allen Day Eallenday@ucla.eduE, Michael Trowbridge |
|
106
|
|
|
|
|
|
|
Emichael.a.trowbridge@gmail.comE |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Copyright (c) 2004-2007 Allen Day. All rights |
|
111
|
|
|
|
|
|
|
reserved. This program is free software; you can redistribute it |
|
112
|
|
|
|
|
|
|
and/or modify it under the same terms as Perl itself. |
|
113
|
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
=head1 BUGS / TODO |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Report documentation and software bugs to the author, or better yet, |
|
117
|
|
|
|
|
|
|
send a patch. Known bugs/issues: |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=over |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=item Lack of documentation. |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
=item JSON exporting is not exactly identical to the original Google |
|
124
|
|
|
|
|
|
|
JSON response. Some of the Google Maps-specific data is discarded |
|
125
|
|
|
|
|
|
|
during parsing, and the perl JSON module does not allow for bare keys |
|
126
|
|
|
|
|
|
|
while exporting to a JSON string. It should still be functionally |
|
127
|
|
|
|
|
|
|
interchangeable with a Google JSON reponse. |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=back |
|
130
|
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
http://maps.google.com |
|
134
|
|
|
|
|
|
|
http://www.google.com/apis/maps/ |
|
135
|
|
|
|
|
|
|
http://libgmail.sourceforge.net/googlemaps.html |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
=cut |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
package Geo::Google; |
|
140
|
1
|
|
|
1
|
|
55428
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
52
|
|
|
141
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
#this gets a javascript page containing map XML |
|
144
|
1
|
|
|
1
|
|
6
|
use constant LQ => 'http://maps.google.com/maps?output=js&v=1&q=%s'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
48
|
|
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
#this gets a javascript page containing map XML. special for "nearby" searches |
|
147
|
1
|
|
|
1
|
|
6
|
use constant NQ => 'http://maps.google.com/maps?output=js&v=1&near=%s&q=%s'; |
|
|
1
|
|
|
|
|
7
|
|
|
|
1
|
|
|
|
|
39
|
|
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
#used in polyline codec |
|
150
|
1
|
|
|
1
|
|
4
|
use constant END_OF_STREAM => 9999; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
38
|
|
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
#external libs |
|
153
|
1
|
|
|
1
|
|
1044
|
use Data::Dumper; |
|
|
1
|
|
|
|
|
33989
|
|
|
|
1
|
|
|
|
|
113
|
|
|
154
|
1
|
|
|
1
|
|
11
|
use Digest::MD5 qw( md5_hex ); |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
82
|
|
|
155
|
1
|
|
|
1
|
|
1081
|
use HTML::Entities; |
|
|
1
|
|
|
|
|
12974
|
|
|
|
1
|
|
|
|
|
245
|
|
|
156
|
1
|
|
|
1
|
|
13
|
use JSON; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
9
|
|
|
157
|
1
|
|
|
1
|
|
2348
|
use LWP::Simple; |
|
|
1
|
|
|
|
|
74415
|
|
|
|
1
|
|
|
|
|
10
|
|
|
158
|
1
|
|
|
1
|
|
451
|
use URI::Escape; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
64
|
|
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
#our libs |
|
161
|
1
|
|
|
1
|
|
618
|
use Geo::Google::Location; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
30
|
|
|
162
|
1
|
|
|
1
|
|
884
|
use Geo::Google::Path; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
27
|
|
|
163
|
1
|
|
|
1
|
|
493
|
use Geo::Google::Segment; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
4010
|
|
|
164
|
|
|
|
|
|
|
|
|
165
|
1
|
|
|
1
|
0
|
6
|
sub version { return $VERSION } |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=cut |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
=head2 new() |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
Usage : my $geo = Geo::Google->new(); |
|
174
|
|
|
|
|
|
|
Function : constructs and returns a new Geo::Google object |
|
175
|
|
|
|
|
|
|
Returns : a Geo::Google object |
|
176
|
|
|
|
|
|
|
Args : n/a |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=cut |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub new { |
|
181
|
1
|
|
|
1
|
1
|
18
|
return bless {}, __PACKAGE__; |
|
182
|
|
|
|
|
|
|
} |
|
183
|
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=head1 OBJECT METHODS |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=cut |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=head2 error() |
|
189
|
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
Usage : my $error = $geo->error(); |
|
191
|
|
|
|
|
|
|
Function : Fetch error messages produced by the Google Maps XML server. |
|
192
|
|
|
|
|
|
|
Errors can be produced for a number of reasons, e.g. inability |
|
193
|
|
|
|
|
|
|
of the server to resolve a street address to geographical |
|
194
|
|
|
|
|
|
|
coordinates. |
|
195
|
|
|
|
|
|
|
Returns : The most recent error string. Calling this method clears the |
|
196
|
|
|
|
|
|
|
last error. |
|
197
|
|
|
|
|
|
|
Args : n/a |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=cut |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
sub error { |
|
202
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $msg ) = @_; |
|
203
|
0
|
0
|
0
|
|
|
0
|
if ( !defined($msg) or ! $self->isa(__PACKAGE__) ) { |
|
204
|
0
|
|
|
|
|
0
|
my $error = $self->{error}; |
|
205
|
0
|
|
|
|
|
0
|
$self->{error} = undef; |
|
206
|
0
|
|
|
|
|
0
|
return $error; |
|
207
|
|
|
|
|
|
|
} |
|
208
|
|
|
|
|
|
|
else { |
|
209
|
0
|
|
|
|
|
0
|
$self->{error} = $msg; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
} |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=head2 location() |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
Usage : my $loc = $geo->location( address => $address ); |
|
216
|
|
|
|
|
|
|
Function : creates a new Geo::Google::Location object, given a |
|
217
|
|
|
|
|
|
|
street address. |
|
218
|
|
|
|
|
|
|
Returns : a Geo::Google::Location object, or undef on error |
|
219
|
|
|
|
|
|
|
Args : an anonymous hash: |
|
220
|
|
|
|
|
|
|
key required? value |
|
221
|
|
|
|
|
|
|
------- --------- ----- |
|
222
|
|
|
|
|
|
|
address yes address to search for |
|
223
|
|
|
|
|
|
|
id no unique identifier for the |
|
224
|
|
|
|
|
|
|
location. useful if producing |
|
225
|
|
|
|
|
|
|
XML. |
|
226
|
|
|
|
|
|
|
icon no image to be used to represent |
|
227
|
|
|
|
|
|
|
point in Google Maps web |
|
228
|
|
|
|
|
|
|
application |
|
229
|
|
|
|
|
|
|
infoStyle no unknown. css-related, perhaps? |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=cut |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub location { |
|
234
|
1
|
|
|
1
|
1
|
5
|
my ( $self, %arg ) = @_; |
|
235
|
1
|
|
|
|
|
3
|
my @result = (); |
|
236
|
|
|
|
|
|
|
|
|
237
|
1
|
50
|
0
|
|
|
7
|
my $address = $arg{'address'} or ($self->error("must provide an address to location()") and return undef); |
|
238
|
|
|
|
|
|
|
|
|
239
|
1
|
|
|
|
|
307
|
my $json = new JSON (skipinvalid => 1, barekey => 1, quotapos => 1, unmapping => 1 ); |
|
240
|
0
|
|
|
|
|
0
|
my $response_json = undef; |
|
241
|
|
|
|
|
|
|
# I'm using an an array here because I might need to parse several pages if Google suggests a different address |
|
242
|
0
|
|
|
|
|
0
|
my @pages = ( get( sprintf( LQ, uri_escape($address) ) ) ); |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# See if google returned no results |
|
245
|
0
|
0
|
|
|
|
0
|
if ( $pages[0] =~ /did\snot\smatch\sany\slocations/i ) { |
|
|
|
0
|
|
|
|
|
|
|
246
|
0
|
0
|
|
|
|
0
|
$self->error( "Google couldn't find any locations matching $address." ) and return undef; |
|
247
|
|
|
|
|
|
|
} |
|
248
|
|
|
|
|
|
|
# See if Google was unable to resolve the address, but suggested other addresses |
|
249
|
|
|
|
|
|
|
# To see this, run a query for 695 Charles E Young Dr S, Westwood, CA 90024 |
|
250
|
|
|
|
|
|
|
elsif ( $pages[0] =~ m#Did you mean:#is ) { |
|
251
|
|
|
|
|
|
|
# Extract the queries from all the http get queries for alterate addresses |
|
252
|
|
|
|
|
|
|
# \u003cdiv class=\"ref\"\u003e\u003ca href=\"/maps?v=1\u0026amp;q=695+Charles+E+Young+Drive+East,+Los+Angeles,+Los+Angeles,+California+90024,+United+States\u0026amp;ie=UTF8\u0026amp;hl=en\u0026amp;oi=georefine\u0026amp;ct=clnk\u0026amp;cd=2\" onclick=\"return loadUrl(this.href)\"\u003e |
|
253
|
|
|
|
|
|
|
# We need it to fit the LQ query 'http://maps.google.com/maps?output=js&v=1&q=%s' |
|
254
|
0
|
|
|
|
|
0
|
my @queries = $pages[0] =~ m#\\u003cdiv class=\\"ref\\"\\u003e\\u003ca href=\\"/maps\?v=1\\u0026amp;q=(.+?)\\u0026amp;#gsi; |
|
255
|
|
|
|
|
|
|
# clear the $pages array so we can fill it with the pages from the @urls |
|
256
|
0
|
|
|
|
|
0
|
@pages = (); |
|
257
|
0
|
|
|
|
|
0
|
foreach my $suggested_query (@queries) { |
|
258
|
0
|
|
|
|
|
0
|
push( @pages, get( sprintf( LQ, $suggested_query ) ) ); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
# Verify that we actually retrieved pages to parse |
|
262
|
0
|
0
|
|
|
|
0
|
if ( scalar(@pages) > 0 ) { |
|
263
|
0
|
|
|
|
|
0
|
foreach my $page (@pages) { |
|
264
|
|
|
|
|
|
|
# attempt to locate the JSON formatted data block |
|
265
|
0
|
0
|
|
|
|
0
|
if ($page =~ m#loadVPage\((.+), "\w+"\);}//]]>#is) { $response_json = $json->jsonToObj($1); } |
|
|
0
|
|
|
|
|
0
|
|
|
266
|
|
|
|
|
|
|
else { |
|
267
|
0
|
0
|
|
|
|
0
|
$self->error( "Unable to locate the JSON format data in google's response.") and return undef; |
|
268
|
|
|
|
|
|
|
} |
|
269
|
0
|
0
|
|
|
|
0
|
if ( scalar(@{$response_json->{"overlays"}->{"markers"}}) > 0 ) { |
|
|
0
|
|
|
|
|
0
|
|
|
270
|
0
|
|
|
|
|
0
|
foreach my $marker (@{$response_json->{"overlays"}->{"markers"}}) { |
|
|
0
|
|
|
|
|
0
|
|
|
271
|
0
|
|
|
|
|
0
|
my $loc = $self->_obj2location($marker, %arg); |
|
272
|
0
|
|
|
|
|
0
|
push @result, $loc; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
|
|
|
|
|
|
} |
|
275
|
|
|
|
|
|
|
else { |
|
276
|
0
|
0
|
|
|
|
0
|
$self->error("Found the JSON Data block and was able to parse it, but it had no location markers " |
|
277
|
|
|
|
|
|
|
. "in it. Maybe Google changed their JSON data structure?.") and return undef; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
else { |
|
282
|
0
|
0
|
|
|
|
0
|
$self->error("Google couldn't resolve the address $address but suggested alternate addresses. " |
|
283
|
|
|
|
|
|
|
. "I attempted to download them but failed.") and return undef; |
|
284
|
|
|
|
|
|
|
} |
|
285
|
0
|
|
|
|
|
0
|
return @result; |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head2 near() |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
Usage : my @near = $geo->near( $loc, $phrase ); |
|
291
|
|
|
|
|
|
|
Function : searches Google Local for records matching the |
|
292
|
|
|
|
|
|
|
phrase provided, with the constraint that they are |
|
293
|
|
|
|
|
|
|
physically nearby the Geo::Google::Location object |
|
294
|
|
|
|
|
|
|
provided. search phrase is passed verbatim to Google. |
|
295
|
|
|
|
|
|
|
Returns : a list of Geo::Google::Location objects |
|
296
|
|
|
|
|
|
|
Args : 1. A Geo::Google::Location object |
|
297
|
|
|
|
|
|
|
2. A search phrase. |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=cut |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
sub near { |
|
302
|
0
|
|
|
0
|
1
|
0
|
my ( $self, $where, $query ) = @_; |
|
303
|
0
|
|
|
|
|
0
|
my $page = get( sprintf( NQ, join(',', $where->lines ), $query ) ); |
|
304
|
|
|
|
|
|
|
|
|
305
|
0
|
|
|
|
|
0
|
my $json = new JSON (skipinvalid => 1, barekey => 1, |
|
306
|
|
|
|
|
|
|
quotapos => 1, unmapping => 1 ); |
|
307
|
0
|
|
|
|
|
0
|
my $response_json = undef; |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# See if google returned no results |
|
310
|
0
|
0
|
|
|
|
0
|
if ( $page =~ /did\snot\smatch\sany\slocations/i ) { |
|
|
|
0
|
|
|
|
|
|
|
311
|
0
|
0
|
|
|
|
0
|
$self->error( "Google couldn't find a $query near " . $where->title) and return undef; |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
# attempt to locate the JSON formatted data block |
|
314
|
|
|
|
|
|
|
elsif ($page =~ m#loadVPage\((.+), "\w+"\);}//]]>#is) { |
|
315
|
0
|
|
|
|
|
0
|
my $strJSON = $1; |
|
316
|
0
|
|
|
|
|
0
|
$response_json = $json->jsonToObj($strJSON); |
|
317
|
|
|
|
|
|
|
} |
|
318
|
|
|
|
|
|
|
else { |
|
319
|
0
|
0
|
|
|
|
0
|
$self->error( "Unable to locate the JSON format data in Google's response.") and return undef; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
|
|
322
|
0
|
0
|
|
|
|
0
|
if ( scalar(@{$response_json->{"overlays"}->{"markers"}}) > 0 ) { |
|
|
0
|
|
|
|
|
0
|
|
|
323
|
0
|
|
|
|
|
0
|
my @result = (); |
|
324
|
0
|
|
|
|
|
0
|
foreach my $marker (@{$response_json->{"overlays"}->{"markers"}}) { |
|
|
0
|
|
|
|
|
0
|
|
|
325
|
0
|
|
|
|
|
0
|
my $loc = $self->_obj2location($marker); |
|
326
|
0
|
|
|
|
|
0
|
push @result, $loc; |
|
327
|
|
|
|
|
|
|
} |
|
328
|
0
|
|
|
|
|
0
|
return @result; |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
else { |
|
331
|
0
|
0
|
|
|
|
0
|
$self->error("Found the JSON Data block and was " |
|
332
|
|
|
|
|
|
|
. "able to parse it, but it had no location markers" |
|
333
|
|
|
|
|
|
|
. "in it. Maybe Google changed their " |
|
334
|
|
|
|
|
|
|
. "JSON data structure?") and return undef; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
=head2 path() |
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
Usage : my $path = $geo->path( $from, $OptionalWaypoints, $to ); |
|
341
|
|
|
|
|
|
|
Function : get driving directions between two points |
|
342
|
|
|
|
|
|
|
Returns : a Geo::Google::Path object |
|
343
|
|
|
|
|
|
|
Args : 1. a Geo::Google::Location object (from) |
|
344
|
|
|
|
|
|
|
2. optional Geo::Google::Location waypoints |
|
345
|
|
|
|
|
|
|
3. a Geo::Google::Location object (final destination) |
|
346
|
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=cut |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
sub path { |
|
350
|
0
|
|
|
0
|
1
|
0
|
my ( $self, @locations ) = @_; |
|
351
|
0
|
|
|
|
|
0
|
my $json = new JSON (skipinvalid => 1, barekey => 1, |
|
352
|
|
|
|
|
|
|
quotapos => 1, unmapping => 1 ); |
|
353
|
0
|
|
|
|
|
0
|
my $response_json = undef; |
|
354
|
|
|
|
|
|
|
|
|
355
|
0
|
0
|
|
|
|
0
|
if(scalar(@locations) < 2) { |
|
356
|
0
|
|
|
|
|
0
|
$self->error("Less than two locations were passed to the path function"); |
|
357
|
0
|
|
|
|
|
0
|
return undef; |
|
358
|
|
|
|
|
|
|
} |
|
359
|
|
|
|
|
|
|
#check each @locations element to see if it is a Geo::Google::Location |
|
360
|
0
|
|
|
|
|
0
|
for (my $i=0; $i<=$#locations; $i++) { |
|
361
|
0
|
0
|
|
|
|
0
|
if(!$locations[$i]->isa('Geo::Google::Location')) { |
|
362
|
0
|
|
|
|
|
0
|
$self->error("Location " . ($i+1) |
|
363
|
|
|
|
|
|
|
. " passed to the path function is not a " |
|
364
|
|
|
|
|
|
|
. "Geo::Google::Location" |
|
365
|
|
|
|
|
|
|
. " object, or subclass thereof"); |
|
366
|
0
|
|
|
|
|
0
|
return undef; |
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
} |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# construct the google search text |
|
371
|
0
|
|
|
|
|
0
|
my $googlesearch = "from: " . join(', ', $locations[0]->lines); |
|
372
|
0
|
|
|
|
|
0
|
for (my $i=1; $i<=$#locations; $i++){ |
|
373
|
0
|
|
|
|
|
0
|
$googlesearch .= " to:" . join(', ', $locations[$i]->lines); |
|
374
|
|
|
|
|
|
|
} |
|
375
|
0
|
|
|
|
|
0
|
my $page = get( sprintf( LQ, uri_escape( $googlesearch ) ) ); |
|
376
|
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# See if google returned no results |
|
378
|
0
|
0
|
|
|
|
0
|
if ( $page =~ /did\snot\smatch\sany\slocations/i ) { |
|
|
|
0
|
|
|
|
|
|
|
379
|
0
|
0
|
|
|
|
0
|
$self->error( "Google couldn't find one of the locations you provided for your directions query") and return undef; |
|
380
|
|
|
|
|
|
|
} |
|
381
|
|
|
|
|
|
|
# See if google didn't recognize an input, but suggested |
|
382
|
|
|
|
|
|
|
# a correction to the input that it does recognize |
|
383
|
|
|
|
|
|
|
elsif ( $page =~ m#didyou#s ) |
|
384
|
|
|
|
|
|
|
{ |
|
385
|
|
|
|
|
|
|
# Parse the JSON to unescape the escaped unicode characters in the URLs we need to parse |
|
386
|
0
|
|
|
|
|
0
|
my ( $strJSON ) = $page =~ m#loadVPage\((.+), "\w+"\);}//]]>#s; |
|
387
|
0
|
|
|
|
|
0
|
my $suggestion_json = $json->jsonToObj($strJSON); |
|
388
|
|
|
|
|
|
|
# Did you mean: |
|
389
|
0
|
|
|
|
|
0
|
my ( $first_suggestion ) = $suggestion_json->{panel} =~ m#(saddr=.+?)" onclick#s; |
|
390
|
|
|
|
|
|
|
# Get the directions using google's first suggestion |
|
391
|
0
|
|
|
|
|
0
|
$page = get ( _html_unescape("http://maps.google.com/maps?output=js&$1") ); |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# warn the user using the error method, but don't return undef. |
|
394
|
0
|
|
|
|
|
0
|
$self->error("Google suggested a different address for your query. Using the google suggestion instead."); |
|
395
|
|
|
|
|
|
|
} |
|
396
|
|
|
|
|
|
|
# attept to locate the JSON formatted data block |
|
397
|
0
|
0
|
|
|
|
0
|
if ($page =~ m#loadVPage\((.+), "\w+"\);}//]]>#s) { |
|
398
|
|
|
|
|
|
|
# Extract the JSON data structure from the response. |
|
399
|
0
|
|
|
|
|
0
|
$response_json = $json->jsonToObj( $1 ); |
|
400
|
|
|
|
|
|
|
} |
|
401
|
|
|
|
|
|
|
else { |
|
402
|
0
|
0
|
|
|
|
0
|
$self->error( "Unable to locate the JSON format data in Google's response.") and return undef; |
|
403
|
|
|
|
|
|
|
} |
|
404
|
|
|
|
|
|
|
|
|
405
|
0
|
|
|
|
|
0
|
my @points; |
|
406
|
|
|
|
|
|
|
my @enc_points; |
|
407
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i<=$#{$response_json->{"overlays"}->{"polylines"}}; $i++) { |
|
|
0
|
|
|
|
|
0
|
|
|
408
|
0
|
|
|
|
|
0
|
$enc_points[$i] = $response_json->{"overlays"}->{"polylines"}->[$i]->{"points"}; |
|
409
|
0
|
|
|
|
|
0
|
$points[$i] = [ _decode($enc_points[$i]) ]; |
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# extract a series of directions from HTML inside the panel |
|
413
|
|
|
|
|
|
|
# portion of the JSON data response, stuffing them in @html_segs |
|
414
|
0
|
|
|
|
|
0
|
my @html_segs; |
|
415
|
0
|
|
|
|
|
0
|
my $stepsfound = 0; |
|
416
|
|
|
|
|
|
|
|
|
417
|
0
|
|
|
|
|
0
|
my $panel = $response_json->{'panel'}; |
|
418
|
0
|
|
|
|
|
0
|
$panel =~ s/ / /g; |
|
419
|
|
|
|
|
|
|
|
|
420
|
0
|
|
|
|
|
0
|
my @subpaths = $panel =~ m#(\s*)#gs; #ddspt_table
|
421
|
|
|
|
|
|
|
#my ( $subpanel ) = $response_json->{'panel'} =~ m##s; |
|
422
|
|
|
|
|
|
|
|
|
423
|
0
|
|
|
|
|
0
|
foreach my $subpath ( @subpaths ) { |
|
424
|
0
|
|
|
|
|
0
|
my @segments = split m# | \s*
|
425
|
0
|
|
|
|
|
0
|
foreach my $segment ( @segments ) { |
|
426
|
|
|
|
|
|
|
#skip irrelevant waypoint rows |
|
427
|
0
|
0
|
0
|
|
|
0
|
if ( $subpath =~ m#ddwpt_table#s && $segment !~ m#ddptlnk#s ) { next } |
|
|
0
|
|
|
|
|
0
|
|
|
428
|
|
|
|
|
|
|
|
|
429
|
0
|
|
|
|
|
0
|
my ( $id, $pointIndex ) = $segment =~ m#id="(.+?)" polypoint="(.+?)"#s; |
|
430
|
0
|
|
|
|
|
0
|
my ( $html ) = $segment =~ m#"dirsegtext_\d+_\d+">(.+?) | #s;
|
431
|
0
|
|
|
|
|
0
|
my ( $distance ) = $segment =~ m#"sxdist".+?>(.+?)<#s; |
|
432
|
0
|
|
|
|
|
0
|
my ( $time ) = $segment =~ m#"segtime nw pw">(.+?)<#s; |
|
433
|
|
|
|
|
|
|
|
|
434
|
0
|
0
|
|
|
|
0
|
if ( ! defined( $id ) ) { |
|
435
|
0
|
0
|
|
|
|
0
|
if ( $subpath =~ m#waypoint="(.+?)"#s ) { |
|
436
|
0
|
|
|
|
|
0
|
$id = "waypoint_$1"; |
|
437
|
0
|
|
|
|
|
0
|
$html = $locations[$1]->title(); |
|
438
|
0
|
|
|
|
|
0
|
($pointIndex) = $segment =~ m#polypoint="(.+?)"#s; |
|
439
|
|
|
|
|
|
|
} |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
|
|
442
|
0
|
0
|
|
|
|
0
|
next unless $id; |
|
443
|
|
|
|
|
|
|
|
|
444
|
0
|
0
|
|
|
|
0
|
if ( ! $time ) { |
|
445
|
|
|
|
|
|
|
#some segments are different (why? what is the pattern?) |
|
446
|
0
|
|
|
|
|
0
|
my ( $d2, $t2 ) = $segment =~ m#timedist ul.+?>(.+?)\(about&\#160;(.+?)\) | #s;
|
447
|
0
|
|
|
|
|
0
|
$time = $t2; |
|
448
|
0
|
|
0
|
|
|
0
|
$distance ||= $d2; |
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
#some segments have no associated point, e.g. when there are long-distance driving segments |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
#some segments have time xor distance (not both) |
|
454
|
0
|
|
0
|
|
|
0
|
$distance ||= ''; $distance = decode_entities( $distance ); $distance =~ s/\s+/ /g; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
455
|
0
|
|
0
|
|
|
0
|
$time ||= ''; $time = decode_entities( $time ); $time =~ s/\s+/ /g; |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
456
|
|
|
|
|
|
|
|
|
457
|
0
|
|
|
|
|
0
|
push (@html_segs, { |
|
458
|
|
|
|
|
|
|
distance => $distance, |
|
459
|
|
|
|
|
|
|
time => $time, |
|
460
|
|
|
|
|
|
|
pointIndex => $pointIndex, |
|
461
|
|
|
|
|
|
|
id => $id, |
|
462
|
|
|
|
|
|
|
html => $html |
|
463
|
|
|
|
|
|
|
}); |
|
464
|
0
|
|
|
|
|
0
|
$stepsfound++; |
|
465
|
|
|
|
|
|
|
} |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
|
|
468
|
0
|
0
|
|
|
|
0
|
if ($stepsfound == 0) { |
|
469
|
0
|
0
|
|
|
|
0
|
$self->error("Found the HTML directions from the JSON " |
|
470
|
|
|
|
|
|
|
. "reponse, but was not able to extract " |
|
471
|
|
|
|
|
|
|
. "the driving directions from the HTML") and return undef; |
|
472
|
|
|
|
|
|
|
} |
|
473
|
0
|
|
|
|
|
0
|
my @segments = (); |
|
474
|
|
|
|
|
|
|
# Problem: When you create a Geo::Google::Location by |
|
475
|
|
|
|
|
|
|
# looking it up on Google from an address, it returns coordinates |
|
476
|
|
|
|
|
|
|
# with millionth of a degree precision. Coordinates that come out |
|
477
|
|
|
|
|
|
|
# the polyline string only have hundred thousandth of a degree |
|
478
|
|
|
|
|
|
|
# precision. This means that the correlation algorithm won't find |
|
479
|
|
|
|
|
|
|
# the start, stop or waypoints in the polyline unless we round |
|
480
|
|
|
|
|
|
|
# start, stop and waypoint coordinates to the hundred-thousandth |
|
481
|
|
|
|
|
|
|
# degree precision. |
|
482
|
0
|
|
|
|
|
0
|
foreach my $location (@locations) { |
|
483
|
0
|
|
|
|
|
0
|
$location->{'latitude'} = sprintf("%3.5f", $location->{'latitude'} ); |
|
484
|
0
|
|
|
|
|
0
|
$location->{'longitude'} = sprintf("%3.5f", $location->{'longitude'} ); |
|
485
|
|
|
|
|
|
|
} |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
# Correlate the arrays of lats and longs we decoded from the |
|
488
|
|
|
|
|
|
|
# JSON object with the segments we extracted from the panel |
|
489
|
|
|
|
|
|
|
# HTML and put the result into an array of |
|
490
|
|
|
|
|
|
|
# Geo::Google::Location objects |
|
491
|
0
|
|
|
|
|
0
|
my @points_subset = ( $locations[0] ); |
|
492
|
0
|
|
|
|
|
0
|
push (@segments, Geo::Google::Segment->new( |
|
493
|
|
|
|
|
|
|
pointIndex => $html_segs[0]{'pointIndex'}, |
|
494
|
|
|
|
|
|
|
id => $html_segs[0]{'id'}, |
|
495
|
|
|
|
|
|
|
html => $html_segs[0]{"html"}, |
|
496
|
|
|
|
|
|
|
distance => $html_segs[0]{'distance'}, |
|
497
|
|
|
|
|
|
|
time => $html_segs[0]{'time'}, |
|
498
|
|
|
|
|
|
|
from => $locations[0], |
|
499
|
|
|
|
|
|
|
to => $locations[0], |
|
500
|
|
|
|
|
|
|
points => [@points_subset]) |
|
501
|
|
|
|
|
|
|
); |
|
502
|
0
|
|
|
|
|
0
|
shift @html_segs; |
|
503
|
0
|
|
|
|
|
0
|
for (my $i = 0; $i <= $#points; $i++) { |
|
504
|
|
|
|
|
|
|
# start/points cause us problems because they're often the same |
|
505
|
|
|
|
|
|
|
# the same pointindex as the first segment of the directions |
|
506
|
|
|
|
|
|
|
# pulling the first html_seg off the stack now makes the next |
|
507
|
|
|
|
|
|
|
# control loop easier to maintain. |
|
508
|
0
|
|
|
|
|
0
|
@points_subset = (); |
|
509
|
|
|
|
|
|
|
|
|
510
|
0
|
|
|
|
|
0
|
my $m = 0; |
|
511
|
0
|
|
|
|
|
0
|
my @pointset = @{$points[$i]}; |
|
|
0
|
|
|
|
|
0
|
|
|
512
|
0
|
|
|
|
|
0
|
while ( @pointset ) { |
|
513
|
0
|
|
|
|
|
0
|
my $lat = shift @pointset; |
|
514
|
0
|
|
|
|
|
0
|
my $lon = shift @pointset; |
|
515
|
0
|
|
|
|
|
0
|
$m++; |
|
516
|
0
|
|
|
|
|
0
|
my %html_seg; |
|
517
|
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
# Check to see if the lat and long belong to a start, stop or waypoint |
|
519
|
0
|
|
|
|
|
0
|
my $pointislocation = -1; |
|
520
|
0
|
|
|
|
|
0
|
for (my $j=0; $j <= $#locations; $j++) { |
|
521
|
0
|
0
|
0
|
|
|
0
|
if ( ( $lat == $locations[$j]->latitude() ) && ( $lon == $locations[$j]->longitude() ) ) { $pointislocation = $j; last; } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
522
|
|
|
|
|
|
|
} |
|
523
|
|
|
|
|
|
|
# If the point that just came off the pointset array is a start, stop or waypoint, use that start/stop/waypoint. |
|
524
|
|
|
|
|
|
|
# otherwise, create a new point for the lat/long that just came off the pointset array. |
|
525
|
0
|
|
|
|
|
0
|
my $point; |
|
526
|
0
|
0
|
|
|
|
0
|
if ( $pointislocation >= 0 ){ $point = $locations[$pointislocation]; } |
|
|
0
|
|
|
|
|
0
|
|
|
527
|
0
|
|
|
|
|
0
|
else { $point = Geo::Google::Location->new( latitude => $lat, longitude => $lon ); } |
|
528
|
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
0
|
push @points_subset, $point; |
|
530
|
|
|
|
|
|
|
|
|
531
|
0
|
0
|
|
|
|
0
|
if ( $html_segs[1] ) { |
|
|
|
0
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
# There's a segment after the one we're working on |
|
533
|
|
|
|
|
|
|
# This tests to see if we need to wrap up the current segment |
|
534
|
0
|
0
|
|
|
|
0
|
if ( defined( $html_segs[1]{'pointIndex'} ) ) { |
|
535
|
0
|
0
|
0
|
|
|
0
|
next unless ((($m == $html_segs[1]{'pointIndex'}) && ($#html_segs > 1) ) || (! @pointset) ); |
|
|
|
|
0
|
|
|
|
|
|
536
|
|
|
|
|
|
|
} |
|
537
|
0
|
|
|
|
|
0
|
%html_seg = %{shift @html_segs}; |
|
|
0
|
|
|
|
|
0
|
|
|
538
|
0
|
|
|
|
|
0
|
push @segments, Geo::Google::Segment->new( |
|
539
|
|
|
|
|
|
|
pointIndex => $html_seg{'pointIndex'}, |
|
540
|
|
|
|
|
|
|
id => $html_seg{'id'}, |
|
541
|
|
|
|
|
|
|
html => decode_entities($html_seg{"html"}), |
|
542
|
|
|
|
|
|
|
distance => $html_seg{'distance'}, |
|
543
|
|
|
|
|
|
|
time => $html_seg{'time'}, |
|
544
|
|
|
|
|
|
|
from => $points_subset[0], |
|
545
|
|
|
|
|
|
|
to => $point, |
|
546
|
|
|
|
|
|
|
points => [@points_subset] |
|
547
|
|
|
|
|
|
|
); |
|
548
|
0
|
|
|
|
|
0
|
@points_subset = (); |
|
549
|
|
|
|
|
|
|
} elsif ($html_segs[0]) { # We're working on the last segment |
|
550
|
|
|
|
|
|
|
# This tests to see if we need to wrap up the last segment |
|
551
|
0
|
0
|
|
|
|
0
|
next unless (! $pointset[0]); |
|
552
|
0
|
|
|
|
|
0
|
%html_seg = %{shift @html_segs}; |
|
|
0
|
|
|
|
|
0
|
|
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
# An attempt to get the last point in the last segment |
|
555
|
|
|
|
|
|
|
# set. Google doesn't include it in their polylines. |
|
556
|
0
|
|
|
|
|
0
|
push @points_subset, $locations[$i+1]; |
|
557
|
0
|
|
|
|
|
0
|
push @segments, Geo::Google::Segment->new( |
|
558
|
|
|
|
|
|
|
pointIndex => $html_seg{'pointIndex'}, |
|
559
|
|
|
|
|
|
|
id => $html_seg{'id'}, |
|
560
|
|
|
|
|
|
|
html => decode_entities($html_seg{"html"}), |
|
561
|
|
|
|
|
|
|
distance => $html_seg{'distance'}, |
|
562
|
|
|
|
|
|
|
time => $html_seg{'time'}, |
|
563
|
|
|
|
|
|
|
from => $points_subset[0], |
|
564
|
|
|
|
|
|
|
to => $locations[$i+1], |
|
565
|
|
|
|
|
|
|
points => [@points_subset] |
|
566
|
|
|
|
|
|
|
); |
|
567
|
0
|
|
|
|
|
0
|
@points_subset = (); |
|
568
|
|
|
|
|
|
|
} else { # we accidentally closed out the last segment early |
|
569
|
0
|
|
|
|
|
0
|
push @{ $segments[$#segments]->{points} }, $point; |
|
|
0
|
|
|
|
|
0
|
|
|
570
|
|
|
|
|
|
|
} |
|
571
|
|
|
|
|
|
|
} |
|
572
|
|
|
|
|
|
|
} |
|
573
|
|
|
|
|
|
|
# Dirty: add the final waypoint |
|
574
|
0
|
|
|
|
|
0
|
push (@segments, Geo::Google::Segment->new( |
|
575
|
|
|
|
|
|
|
pointIndex => $html_segs[0]{'pointIndex'}, |
|
576
|
|
|
|
|
|
|
id => $html_segs[0]{'id'}, |
|
577
|
|
|
|
|
|
|
html => $html_segs[0]{"html"}, |
|
578
|
|
|
|
|
|
|
distance => $html_segs[0]{'distance'}, |
|
579
|
|
|
|
|
|
|
time => $html_segs[0]{'time'}, |
|
580
|
|
|
|
|
|
|
from => $locations[$#locations], |
|
581
|
|
|
|
|
|
|
to => $locations[$#locations], |
|
582
|
|
|
|
|
|
|
points => [ ($locations[$#locations]) ]) |
|
583
|
|
|
|
|
|
|
); |
|
584
|
|
|
|
|
|
|
# Extract the total information using a regex on the panel hash. At the end of the "printheader", we're looking for: |
|
585
|
|
|
|
|
|
|
# | 9.4 mi – about 17 mins | |
|
586
|
|
|
|
|
|
|
# Replace XML numeric character references with spaces to make the next regex less dependent upon Google's precise formatting choices |
|
587
|
0
|
|
|
|
|
0
|
$response_json->{"printheader"} =~ s/\d+;/ /g; |
|
588
|
0
|
0
|
|
|
|
0
|
if ( $response_json->{"printheader"} =~ m#(\d+\.?\d*)\s*(mi|km|m)\s*about\s*(.+?) |
$#s ){