google4r-maps 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +37 -0
- data/LICENSE +22 -0
- data/README +94 -0
- data/lib/google4r/maps.rb +804 -0
- data/test/geocoder_test.rb +187 -0
- data/test/gicon_test.rb +93 -0
- data/test/gmap2_test.rb +304 -0
- data/test/gmarker_test.rb +129 -0
- metadata +54 -0
data/CHANGES
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
= google4r-maps Changelog
|
2
|
+
|
3
|
+
== HEAD
|
4
|
+
|
5
|
+
== 0.1.0 (2007-05-12)
|
6
|
+
|
7
|
+
* Polished the README a bit.
|
8
|
+
* Added GMap2#onload_js, GMap2#onload_js= to set Javascript to execute after the map has been loaded.
|
9
|
+
* Replaced "options.icon" with "options['icon']" in GMarker creation code. This caused problems with markers with custom icons *and* popup windows in MSIE.
|
10
|
+
* Added support for client (required for enterprise keys).
|
11
|
+
* Split google4r into google4r/maps and google4r/checkout.
|
12
|
+
* MerchantCode#create_from_element raises an ArgumentError instead of a RuntimeError on invalid elements now.
|
13
|
+
* Changed the #create_from_element code to set the properties directly into the objects instead of building Hashes to collect the values first.
|
14
|
+
* "Resolved" the problem of converting currency amounts which are fractional numbers into the amount of the minor currency to be passed into the Money class: Instead of converting the fractional number into a float and multiplying by 100, all nonnumeric characters are stripped from the string, the resulting number is converted into an integer and this integer is then passed to Money.new.
|
15
|
+
* Added example to NotificationHandler of how to use the class.
|
16
|
+
* Added link to simple_http_auth plugin in NotificationHandler which allows for easy HTTP Auth Basic protection of Rails controllers.
|
17
|
+
* Renamed CheckoutCommand#cart to CheckoutCommand#shopping_cart
|
18
|
+
* CheckoutCommand raises ArgumentError instead of RuntimeError on invalid clazz parameter.
|
19
|
+
* Added "Howto freeze google4r in a Rails project" to README
|
20
|
+
* Google4R::Checkout::Command#to_xml raises an NotImplementedError instead of a RuntimeError now.
|
21
|
+
* Geocoder returns nil on 603 (G_UNAVAILABLE_ADDRESS) results now.
|
22
|
+
* Added support for registering GUnload() to be called on window's unload event.
|
23
|
+
* The generated Javascript that creates the GMap2() instance (and thus modifies the map div) has been put into a function <var name>_loader that is called in an onload handler. This should remove a problem with MSIE.
|
24
|
+
* Using Syck as the YAML parser now. However, a workaround is needed because of http://code.whytheluckystiff.net/syck/ticket/27.
|
25
|
+
* Only require the json gem if objects have no "to_json" method already added to them. This solves a problem with converting arbitrary objects to json because the json gem seemingly only converts simple types.
|
26
|
+
* Added GMarker#info_window_html.
|
27
|
+
* Added support for onclick handlers in markers.
|
28
|
+
* Added support for the value :auto of GMap2#zoom and GMap2#center.
|
29
|
+
* Fixed buggy generation of GMarker Javascript.
|
30
|
+
* Adding website folder to contain a webgen based website.
|
31
|
+
* Fixed a problem with generating XML from Hashes in the "private" data of shopping carts and items.
|
32
|
+
* The parse for the <order-adjustment> tag does not expect to see a <shipping> tag in every case any more.
|
33
|
+
* Extending Google4R::Maps by the classes GMap2, GIcon and GMarker to allow for easy Google Maps HTML generation.
|
34
|
+
|
35
|
+
== 0.0.1 (2007-02-17)
|
36
|
+
|
37
|
+
* initial release, be prepared for some API changes that move the API closer to the XML API
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Unless noted otherwise, the files of this projects are licensed under a MIT
|
2
|
+
style license:
|
3
|
+
|
4
|
+
Copyright (c) 2007 Manuel Holtgrewe
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to
|
8
|
+
deal in the Software without restriction, including without limitation the
|
9
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
10
|
+
sell copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
14
|
+
all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
22
|
+
IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
= google4r-maps
|
2
|
+
|
3
|
+
google4r-maps is a library to access the Google Maps API from Ruby.
|
4
|
+
|
5
|
+
It contains a class to use Google's geocoding service and to generate the Javascript for for the map component from Ruby.
|
6
|
+
|
7
|
+
== Example
|
8
|
+
|
9
|
+
require 'google4r/maps'
|
10
|
+
|
11
|
+
map = GMap2.new("map", "map")
|
12
|
+
map.to_html # => %q{<div id="map"></div><script type="text/javascript">var map = new GMap2...}
|
13
|
+
|
14
|
+
=== License
|
15
|
+
|
16
|
+
google4r is distributed under an MIT style license.
|
17
|
+
|
18
|
+
== Google Maps HTML Generation
|
19
|
+
|
20
|
+
google4r contains a library for generating the HTML and Javascript for your the GMap2 widget. You could do the following, for example:
|
21
|
+
|
22
|
+
map = Google4R::Maps::Map.new("var_name", "dom_id")
|
23
|
+
icon = map.create_icon([7.4419, -122.1419])
|
24
|
+
icon.title = "Google Headquarters"
|
25
|
+
|
26
|
+
map.to_html
|
27
|
+
|
28
|
+
The last line would then generate the necessary HTML and Javascript to display a new GMap2 <div id="dom_id">. The JS variable containing the GMap2 instance is called "var_name".
|
29
|
+
|
30
|
+
TODO: Add example JS output.
|
31
|
+
|
32
|
+
Note that we put great care into making the JS generation use anonymous functions in most places instead of global variables. This should make the generated JS pretty robust. Additionally, the JS does not rey on Prototype or any other JS library then Google Maps.
|
33
|
+
|
34
|
+
== Google Maps HTML Generation
|
35
|
+
|
36
|
+
google4r contains a library for generating the HTML and Javascript for your the GMap2 widget. You could do the following, for example:
|
37
|
+
|
38
|
+
map = Google4R::Maps::Map.new("var_name", "dom_id")
|
39
|
+
icon = map.create_icon([7.4419, -122.1419])
|
40
|
+
icon.title = "Google Headquarters"
|
41
|
+
|
42
|
+
map.to_html
|
43
|
+
|
44
|
+
The last line would then generate the necessary HTML and Javascript to display a new GMap2 <div id="dom_id">. The JS variable containing the GMap2 instance is called "var_name".
|
45
|
+
|
46
|
+
TODO: Add example JS output.
|
47
|
+
|
48
|
+
Note that we put great care into making the JS generation use anonymous functions in most places instead of global variables. This should make the generated JS pretty robust. Additionally, the JS does not rey on Prototype or any other JS library then Google Maps.
|
49
|
+
|
50
|
+
== Google Maps Geocoder
|
51
|
+
|
52
|
+
The only part of the Google Maps API that can be standalone with sense is the Geocoder. The Google4R::Maps::Geocoder class allows to geocode address strings, i.e. finds a number of known locations that match the query to a certain degree. The information about the "known locations" includes the latitude and longitude of the location.
|
53
|
+
|
54
|
+
=== Queries To Try Out
|
55
|
+
|
56
|
+
<b>Querying for this string</b>:: <b>will yield n results</b>
|
57
|
+
Helena:: 0
|
58
|
+
1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA:: 1
|
59
|
+
Janitell Rd, Colorado Springs, CO:: 2
|
60
|
+
|
61
|
+
=== Maps Tests
|
62
|
+
|
63
|
+
Note that you wil have to place a file called 'key.rb' in the directory 'test/maps' to be able to run unit tests. It should have the following contents:
|
64
|
+
|
65
|
+
GOOGLE_MAPS_KEY = '<your google maps key>'
|
66
|
+
|
67
|
+
== Dependencies
|
68
|
+
|
69
|
+
The unit tests use Mocha so you have to install the gem "mocha" to run the tests.
|
70
|
+
|
71
|
+
== How To: Freeze a google4r version in a Rails project
|
72
|
+
|
73
|
+
<code>rake rails:freeze:gems</code> only works for the Rails gems. So, how do you freeze your own gems like google4r? It turns out to be pretty straightforward:
|
74
|
+
|
75
|
+
cd RAILS_ROOT
|
76
|
+
cd vendor
|
77
|
+
gem unback google4r-maps
|
78
|
+
ls
|
79
|
+
# ... google4r-maps-0.1.1 ...
|
80
|
+
|
81
|
+
Then, open RAILS_ROOT/config/environment.rb in your favourite text editor and add the following lines at the top of the file just below <code>require File.join(File.dirname(__FILE__), 'boot')</code>:
|
82
|
+
|
83
|
+
# Freeze non-Rails gem.
|
84
|
+
Dir.glob(File.join(RAILS_ROOT, 'vendor', '*', 'lib')) do |path|
|
85
|
+
$LOAD_PATH << path
|
86
|
+
end
|
87
|
+
|
88
|
+
Now you can use the following in your own code:
|
89
|
+
|
90
|
+
require 'google4r/maps'
|
91
|
+
|
92
|
+
== Acknowledgement
|
93
|
+
|
94
|
+
Some ideas but no code have been taken from the Cartographer project.
|
@@ -0,0 +1,804 @@
|
|
1
|
+
#--
|
2
|
+
# Project: google4r
|
3
|
+
# File: lib/google4r/geocoder.rb
|
4
|
+
# Author: Manuel Holtgrewe <purestorm at ggnore dot net>
|
5
|
+
# Copyright: (c) 2006 by Manuel Holtgrewe
|
6
|
+
# License: MIT License as follows:
|
7
|
+
#
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
9
|
+
# a copy of this software and associated documentation files (the
|
10
|
+
# "Software"), to deal in the Software without restriction, including
|
11
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
12
|
+
# distribute, sublicense, and/or sell copies of the Software, and to permit
|
13
|
+
# persons to whom the Software is furnished to do so, subject to the
|
14
|
+
# following conditions:
|
15
|
+
#
|
16
|
+
# The above copyright notice and this permission notice shall be included
|
17
|
+
# in all copies or substantial portions of the Software.
|
18
|
+
#
|
19
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
20
|
+
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
21
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
22
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
23
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
24
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
25
|
+
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
26
|
+
#++
|
27
|
+
# This file defines the code which is necessary to access the Google Maps
|
28
|
+
# Geocoder.
|
29
|
+
|
30
|
+
require 'net/http'
|
31
|
+
require 'uri'
|
32
|
+
require 'yaml'
|
33
|
+
|
34
|
+
module Google4R
|
35
|
+
# === On Javascript Generation
|
36
|
+
#
|
37
|
+
# We have decided not to create variables for the created GIcons and GMarkers.
|
38
|
+
# Instead, the generated Javascript are anonymous functions which return the
|
39
|
+
# objects.
|
40
|
+
#
|
41
|
+
# For example, if you create a marker then the following JS code could be created:
|
42
|
+
#
|
43
|
+
# function() {
|
44
|
+
# var marker = new GMarker(new GLatLng(42, 42));
|
45
|
+
# marker.clickable = false;
|
46
|
+
# }
|
47
|
+
#
|
48
|
+
# This function is then used to add markers to the map in the following way:
|
49
|
+
#
|
50
|
+
# var map = new GMap(/* ... */)
|
51
|
+
# // ...
|
52
|
+
# map.addMarker(function(){
|
53
|
+
# var marker = new GMarker(new GLatLng(42, 42));
|
54
|
+
# marker.clickable = false;
|
55
|
+
# }());
|
56
|
+
#
|
57
|
+
# The reasoning behind is that the only thing you want to touch in the generated
|
58
|
+
# JS is the GMap2 instance. If you want to remove any markers then you better create
|
59
|
+
# them directly in your Javascript.
|
60
|
+
#--
|
61
|
+
# TODO: All those line collecting and then joining could prove *slow*.
|
62
|
+
# TODO: Allow Rails-ish :foo => 'bar' options for all attributes to constructors.
|
63
|
+
#++
|
64
|
+
module Maps
|
65
|
+
# Thrown if no API has been given or it is incorrect.
|
66
|
+
class KeyException < Exception
|
67
|
+
end
|
68
|
+
|
69
|
+
# Thrown when it was impossible to connect to the server.
|
70
|
+
class ConnectionException < Exception
|
71
|
+
end
|
72
|
+
|
73
|
+
# GMap2 instances can be converted to the HTML/JS that is necessary to render a GMap2
|
74
|
+
# widget.
|
75
|
+
#
|
76
|
+
# The API documentation provided by Google will be insightful:
|
77
|
+
# http://www.google.com/apis/maps/documentation/reference.html#GMap2
|
78
|
+
#
|
79
|
+
# === Example
|
80
|
+
#
|
81
|
+
# map = GMap2.new("map", "map")
|
82
|
+
# map.to_html # => %q{<div id="map"></div><script type="text/javascript">var map = new GMap2...}
|
83
|
+
#--
|
84
|
+
# TODO: Use GMarkerManager instead of adding Markers directly?
|
85
|
+
#++
|
86
|
+
class GMap2
|
87
|
+
# Default for zoom if zoom is :auto and no markers have been added.
|
88
|
+
DEFAULT_ZOOM = 13
|
89
|
+
|
90
|
+
# Default for center if center is :auto and no markers have been added.
|
91
|
+
DEFAULT_CENTER = [ 0, 0 ].freeze
|
92
|
+
|
93
|
+
# An array of GIcon objects. Use #create_icon to create new GIcon object.
|
94
|
+
attr_reader :icons
|
95
|
+
|
96
|
+
# An array of GMarker objects. Use #create_marker to create new GMarke object.
|
97
|
+
attr_reader :markers
|
98
|
+
|
99
|
+
# The name of the Javascript variable to create to hold the GMap2 instance.
|
100
|
+
attr_accessor :name
|
101
|
+
|
102
|
+
# The id of the <div> id to create.
|
103
|
+
attr_accessor :div_id
|
104
|
+
|
105
|
+
# Array with the size of the map: [ width, height ]. The map will use its container's
|
106
|
+
# size if unset. Defaults to nil.
|
107
|
+
attr_accessor :size
|
108
|
+
|
109
|
+
# String with the CSS name of the cursor to use when dragging the map. Defaults to
|
110
|
+
# nil.
|
111
|
+
attr_accessor :draggable_cursor
|
112
|
+
|
113
|
+
# String with the CSS name of the cursor to use when the map is draggable. Defaults to
|
114
|
+
# nil.
|
115
|
+
attr_accessor :dragging_cursor
|
116
|
+
|
117
|
+
# An array with control to display on the map. Valid entries are :small_map, :large_map,
|
118
|
+
# :small_zoom, :scale, :map_type. They are directly mapped to the controls documented on
|
119
|
+
# http://www.google.com/apis/maps/documentation/reference.html#GControlImpl.
|
120
|
+
# Defaults to [ :large_map, :scale, :map_type ].
|
121
|
+
attr_accessor :controls
|
122
|
+
|
123
|
+
# The map types to allow by default. Can be one of :normal, :satellite and :hybrid. These
|
124
|
+
# symbols are mapped to G_NORMAL_MAP, G_SATELLITE_MAP and G_HYBRID_MAP in JS. Defaults
|
125
|
+
# to :normal.
|
126
|
+
attr_accessor :map_type
|
127
|
+
|
128
|
+
# true iff dragging is enabled - defaults to true
|
129
|
+
attr_accessor :dragging_enabled
|
130
|
+
|
131
|
+
# true iff info windows are show - defaults to true
|
132
|
+
attr_accessor :info_window_enabled
|
133
|
+
|
134
|
+
# true iff zooming by double clicking is enabled - defaults to false
|
135
|
+
attr_accessor :double_click_zoom_enabled
|
136
|
+
|
137
|
+
# true iff smooth zooming is enabled - defaults to false
|
138
|
+
attr_accessor :continuous_zoom_enabled
|
139
|
+
|
140
|
+
# String with arbitrary Javascript that you want executed after the map has been
|
141
|
+
# initialized and all markers have been placed on the map. Defaults to nil.
|
142
|
+
attr_accessor :onload_js
|
143
|
+
|
144
|
+
# Zoom level of the map. Defaults to :auto. If set to :auto then the map is zoomed so that
|
145
|
+
# all markers are visible if the map is centered over them. Ranges from 17 (closest to
|
146
|
+
# earth) to 0 (world view).
|
147
|
+
# If no markers have been added and zoom is :auto then zoom is set to 13.
|
148
|
+
attr_accessor :zoom
|
149
|
+
|
150
|
+
# Array with [ latitude, longitude ] of the center of the map. Defaults to :auto.
|
151
|
+
# If set to :auto then the map will be centered over all added markers. If set to
|
152
|
+
# :auto and no markers have been added then the value [ 0, 0 ] will be used.
|
153
|
+
attr_accessor :center
|
154
|
+
|
155
|
+
# Hash with options that do not directly map into GMap2 properties and that only
|
156
|
+
# influence the Javascript that is generated.
|
157
|
+
#
|
158
|
+
# At the moment, valid keys are:
|
159
|
+
#
|
160
|
+
# :register_gunload:: Boolean. true iff to add a callback to GUnload() to the "onunload"
|
161
|
+
# event of <body>. Defaults to true.
|
162
|
+
attr_reader :options
|
163
|
+
|
164
|
+
# Create a new MapWidget instance.
|
165
|
+
#
|
166
|
+
# === Parameters
|
167
|
+
#
|
168
|
+
# name:: The name of the Javascript variable to create to hold the GMap2 instance.
|
169
|
+
# div_id:: The id of the <div /> tag to create.
|
170
|
+
#--
|
171
|
+
# options:: A hash with options.
|
172
|
+
#
|
173
|
+
# Valid options entries are:
|
174
|
+
#
|
175
|
+
# :size:: Array ([ width, height ]) with two integers. By default, the map will use the size of its container.
|
176
|
+
# :draggable_cursor:: A string with the CSS name of the cursor to use when dragging the map.
|
177
|
+
# :dragging_cursor:: A string with the CSS name of the cursor to use when the map is draggable.
|
178
|
+
#++
|
179
|
+
#
|
180
|
+
# === Example
|
181
|
+
#
|
182
|
+
# GMap2.new("map", "map")
|
183
|
+
def initialize(name, div_id)
|
184
|
+
@icons = Array.new
|
185
|
+
@markers = Array.new
|
186
|
+
@options = { :register_gunload => true }
|
187
|
+
|
188
|
+
# defaults
|
189
|
+
@controls = [ :large_map, :scale, :map_type ]
|
190
|
+
@map_type = :normal
|
191
|
+
@dragging_enabled = true
|
192
|
+
@info_window_enabled = true
|
193
|
+
@double_click_zoom_enabled = false
|
194
|
+
@continuous_zoom_enabled = false
|
195
|
+
@center = :auto
|
196
|
+
@zoom = :auto
|
197
|
+
@onload_js = nil
|
198
|
+
|
199
|
+
# initialization
|
200
|
+
@name = name
|
201
|
+
@div_id = div_id
|
202
|
+
end
|
203
|
+
|
204
|
+
# Creates the <div> tag and the <script> tag and puts self.to_js into the <script>
|
205
|
+
# tag.
|
206
|
+
def to_html
|
207
|
+
result = Array.new
|
208
|
+
result << %Q{<div id="#{@div_id}"></div>}
|
209
|
+
result << %Q{<script type="text/javascript" charset="utf-8">}
|
210
|
+
result << %Q{//<![CDATA[}
|
211
|
+
result << %Q{/* Create a variable to hold the GMap2 instance and the icons in. */}
|
212
|
+
result << %Q{var #{@name};}
|
213
|
+
result << %Q{var #{@name}_icons;}
|
214
|
+
result << ""
|
215
|
+
# Yes, there are some really nice things that MSIE forces you to do!
|
216
|
+
result << %Q|function #{@name}_loader() {|
|
217
|
+
result << self.to_js
|
218
|
+
result << %Q|}|
|
219
|
+
result << ""
|
220
|
+
result << %Q|if (window.addEventListener) { /* not MSIE */|
|
221
|
+
result << %Q| window.addEventListener('load', function() { #{@name}_loader(); }, false);|
|
222
|
+
result << %Q|} else { /* MSIE */|
|
223
|
+
result << %Q| window.attachEvent('onload', function() { #{@name}_loader(); }, false);|
|
224
|
+
result << %Q|}|
|
225
|
+
result << ""
|
226
|
+
|
227
|
+
# Add optional Javascript - like the GUnload() call.
|
228
|
+
result << %Q|/* Optional Javascript */|
|
229
|
+
if options[:register_gunload] then
|
230
|
+
result << %Q|if (window.addEventListener) { /* not MSIE */|
|
231
|
+
result << %Q| window.addEventListener('unload', function() { GUnload(); }, false);|
|
232
|
+
result << %Q|} else { /* MSIE */|
|
233
|
+
result << %Q| window.attachEvent('onunload', function() { GUnload(); }, false);|
|
234
|
+
result << %Q|}|
|
235
|
+
end
|
236
|
+
|
237
|
+
result << %Q{// ]]>}
|
238
|
+
result << %Q{</script>}
|
239
|
+
|
240
|
+
return result.join("\n")
|
241
|
+
end
|
242
|
+
|
243
|
+
# Creates the necessary HTML for the GMap.
|
244
|
+
def to_js
|
245
|
+
lines = Array.new
|
246
|
+
|
247
|
+
# Create GMap2 instance.
|
248
|
+
options = []
|
249
|
+
options << %Q{draggableCursor: "#{@draggable_cursor}"} unless @draggable_cursor.nil?
|
250
|
+
options << %Q{draggingCursor: "#{@dragging_cursor}"} unless @dragging_cursor.nil?
|
251
|
+
options << %Q{size: new GSize(#{@size[0]}, #{@size[1]})} unless @size.nil?
|
252
|
+
|
253
|
+
# Calculate the center value if the map is to be centered over all markers.
|
254
|
+
center = (@center == :auto) ? self.calculate_auto_center : @center
|
255
|
+
|
256
|
+
# Let the JS GMap2 object resolve automatic zoom levels if necessary.
|
257
|
+
zoom =
|
258
|
+
if @zoom == :auto then
|
259
|
+
if @markers.length == 0 then
|
260
|
+
DEFAULT_ZOOM
|
261
|
+
else
|
262
|
+
bounds = self.calculate_auto_bounds
|
263
|
+
"#{@name}.getBoundsZoomLevel(new GLatLngBounds(new GLatLng(#{bounds[0]}, #{bounds[1]}), new GLatLng(#{bounds[2]}, #{bounds[3]})))"
|
264
|
+
end
|
265
|
+
else
|
266
|
+
@zoom
|
267
|
+
end
|
268
|
+
|
269
|
+
lines << %Q{/* Create GMap2 instance. */}
|
270
|
+
lines << %Q{#{@name} = new GMap2(document.getElementById("#{@div_id}"), { #{options.join(', ')} });}
|
271
|
+
lines << %Q{#{@name}.setCenter(new GLatLng(#{center[0]}, #{center[1]}), #{zoom});}
|
272
|
+
lines << ""
|
273
|
+
|
274
|
+
# Set map options.
|
275
|
+
lines << %Q{/* Set map options. */}
|
276
|
+
lines << %Q{#{@name}.setMapType(G_#{@map_type.to_s.upcase}_MAP);}
|
277
|
+
|
278
|
+
if @dragging_enabled then
|
279
|
+
lines << %Q{#{@name}.enableDragging();}
|
280
|
+
else
|
281
|
+
lines << %Q{#{@name}.disableDragging();}
|
282
|
+
end
|
283
|
+
if @info_window_enabled then
|
284
|
+
lines << %Q{#{@name}.enableInfoWindow();}
|
285
|
+
else
|
286
|
+
lines << %Q{#{@name}.disableInfoWindow();}
|
287
|
+
end
|
288
|
+
if @double_click_zoom_enabled then
|
289
|
+
lines << %Q{#{@name}.enableDoubleClickZoom();}
|
290
|
+
else
|
291
|
+
lines << %Q{#{@name}.disableDoubleClickZoom();}
|
292
|
+
end
|
293
|
+
if @continuous_zoom_enabled then
|
294
|
+
lines << %Q{#{@name}.enableContinuousZoom();}
|
295
|
+
else
|
296
|
+
lines << %Q{#{@name}.disableContinuousZoom();}
|
297
|
+
end
|
298
|
+
lines << ""
|
299
|
+
|
300
|
+
# Add controls.
|
301
|
+
lines << %Q{/* Add controls to the map. */}
|
302
|
+
self.controls.each { |control| lines << %Q{#{@name}.addControl(new #{control_constructor(control)});} }
|
303
|
+
lines << ""
|
304
|
+
|
305
|
+
# Add icons.
|
306
|
+
lines << %Q{/* Create global variable holding all icons. */}
|
307
|
+
lines << %Q{#{@name}_icons = new Array();}
|
308
|
+
self.icons.each { |icon| lines << "#{@name}_icons.push(#{icon.to_js}());"}
|
309
|
+
lines << ""
|
310
|
+
|
311
|
+
# Add markers.
|
312
|
+
lines << %Q{/* Add markers to the map. */}
|
313
|
+
self.markers.each do |marker|
|
314
|
+
icon_index = icons.index(marker.icon)
|
315
|
+
icon_str =
|
316
|
+
if icon_index.nil? then
|
317
|
+
""
|
318
|
+
else
|
319
|
+
"#{@name}_icons[#{icon_index}]"
|
320
|
+
end
|
321
|
+
lines << %Q{#{@name}.addOverlay(#{marker.to_js}(#{icon_str}));}
|
322
|
+
end
|
323
|
+
lines << ""
|
324
|
+
|
325
|
+
# Add user supplied Javascript.
|
326
|
+
lines << %Q{/* User supplied Javascript */}
|
327
|
+
if not @onload_js.nil? then
|
328
|
+
lines << @onload_js.to_s
|
329
|
+
end
|
330
|
+
lines << ""
|
331
|
+
|
332
|
+
return lines.join("\n")
|
333
|
+
end
|
334
|
+
|
335
|
+
# Creates a new GIcon instance that is then available for the GMarker instances on this
|
336
|
+
# GMap2.
|
337
|
+
#
|
338
|
+
# The parameters passed to this method are the same as to the constructor of the GIcon
|
339
|
+
# class.
|
340
|
+
def create_icon(*options)
|
341
|
+
result = GIcon.new(*options)
|
342
|
+
@icons << result
|
343
|
+
return result
|
344
|
+
end
|
345
|
+
|
346
|
+
# Creates a new GMarker instance on this GMap2 instance. Use this factory method to create
|
347
|
+
# your GMarker instances instead of the GMarker constructor directly.
|
348
|
+
#
|
349
|
+
# The parameters passed to this method are the same as the ones passed to the constructor
|
350
|
+
# of the GMarker instance.
|
351
|
+
def create_marker(*options)
|
352
|
+
result = GMarker.new(*options)
|
353
|
+
@markers << result
|
354
|
+
return result
|
355
|
+
end
|
356
|
+
|
357
|
+
protected
|
358
|
+
|
359
|
+
# Returns the constructor for the GControl instance that belongs to the symbol
|
360
|
+
# passed. The known symbols are the ones also accepted in the controls attribute.
|
361
|
+
def control_constructor(symbol)
|
362
|
+
@control_constructors ||=
|
363
|
+
{
|
364
|
+
:small_map => 'GSmallMapControl()',
|
365
|
+
:large_map => 'GLargeMapControl()',
|
366
|
+
:small_zoom => 'GSmallZoomControl()',
|
367
|
+
:scale => 'GScaleControl()',
|
368
|
+
:map_type => 'GMapTypeControl()'
|
369
|
+
}
|
370
|
+
|
371
|
+
return @control_constructors[symbol]
|
372
|
+
end
|
373
|
+
|
374
|
+
# Calculates the center of all markers and returns the [ latitude, longitude ]
|
375
|
+
# of this point.
|
376
|
+
# Returns [ 0, 0 ] if
|
377
|
+
def calculate_auto_center
|
378
|
+
min_lat, min_lng, max_lat, max_lng = self.calculate_auto_bounds
|
379
|
+
|
380
|
+
return [ (max_lat + min_lat) / 2, (max_lng + min_lng) / 2 ]
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns smallest rectangle [ min_lat, min_lng, max_lat, max_lng ] that contains
|
384
|
+
# all markers of this GMarker.
|
385
|
+
def calculate_auto_bounds
|
386
|
+
return [ 0, 0, 0, 0 ] if @markers.length == 0
|
387
|
+
|
388
|
+
max_lat, max_lng = @markers.first.point
|
389
|
+
min_lat, min_lng = @markers.first.point
|
390
|
+
|
391
|
+
@markers.slice(1, @markers.length).each do |marker|
|
392
|
+
if marker.point[0] < min_lat then min_lat = marker.point[0] end
|
393
|
+
if marker.point[0] > max_lat then max_lat = marker.point[0] end
|
394
|
+
if marker.point[1] < min_lng then min_lng = marker.point[1] end
|
395
|
+
if marker.point[1] > max_lng then max_lng = marker.point[1] end
|
396
|
+
end
|
397
|
+
|
398
|
+
return [ min_lat, min_lng, max_lat, max_lng ]
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# Javascript GMarker instances represent markers. The Ruby class GMarker allows you to
|
403
|
+
# generate the Javascript which creates a new GMarker.
|
404
|
+
#
|
405
|
+
# Use GMap2#create_marker to create new markers instead of intanciating them directly.
|
406
|
+
#
|
407
|
+
# The Google API documentation can be found here: http://www.google.com/apis/maps/documentation/reference.html#GMarker
|
408
|
+
#
|
409
|
+
# === Example
|
410
|
+
#
|
411
|
+
# map = GMap2.new("map", "map", :size => [ 100, 200 ])
|
412
|
+
#
|
413
|
+
# my_icon = map.create_icon
|
414
|
+
# ...
|
415
|
+
#
|
416
|
+
# marker = GMarker.new([ -42.0, 0 ])
|
417
|
+
# marker.title = "Nice Title!"
|
418
|
+
# marker.icon = my_icon
|
419
|
+
# map.markers << marker
|
420
|
+
#--
|
421
|
+
# TODO: Add support for info windows.
|
422
|
+
#++
|
423
|
+
class GMarker
|
424
|
+
# A GIcon instance that represents the icon to use for your class. The GIcon object
|
425
|
+
# must also be available in the belonging GMap. The default icon provided by Google
|
426
|
+
# is used iff this value is nil.
|
427
|
+
attr_accessor :icon
|
428
|
+
|
429
|
+
# An array with the [ latitude, longitude ] of the GMarker instance.
|
430
|
+
attr_accessor :point
|
431
|
+
|
432
|
+
# True iff to keep the marker underneath the cursor. Defaults to false.
|
433
|
+
attr_accessor :drag_cross_move
|
434
|
+
|
435
|
+
# String to display when hovering long enough over the marker.
|
436
|
+
attr_accessor :title
|
437
|
+
|
438
|
+
# True iff the marker is to respond to click events. Defaults to true.
|
439
|
+
attr_accessor :clickable
|
440
|
+
|
441
|
+
# True iff the marker is to be draggable. Defaults to false.
|
442
|
+
attr_accessor :draggable
|
443
|
+
|
444
|
+
# True iff the marker is to bounce on the map when dropped. Defaults to false.
|
445
|
+
attr_accessor :bouncy
|
446
|
+
|
447
|
+
# "Gravity" to use for bouncy markers. Defaults to 1.
|
448
|
+
attr_accessor :bounce_gravity
|
449
|
+
|
450
|
+
# An array of Javascript to execute when the user clicks on the map. Each Javascript
|
451
|
+
# string will be wrapped into a function so global variables are not available outside
|
452
|
+
# this function.
|
453
|
+
#
|
454
|
+
# The marker object will available as "marker" in the handler code you pass in.
|
455
|
+
#
|
456
|
+
# Example: <code>function(){ alert("My marker is " + marker); }</code>
|
457
|
+
attr_accessor :onclick_handlers
|
458
|
+
|
459
|
+
# HTML String value to display in the window opened by the JS method GMarker.openInfoWindowHtml()
|
460
|
+
# when the marker is clicked on. If unset then no window is opened.
|
461
|
+
attr_accessor :info_window_html
|
462
|
+
|
463
|
+
# Creates a new GMarker instance at the given [ latitude, longitude ] point.
|
464
|
+
def initialize(point)
|
465
|
+
@point = point
|
466
|
+
|
467
|
+
@onclick_handlers = Array.new
|
468
|
+
|
469
|
+
# initialize defaults
|
470
|
+
@drag_cross_move = false
|
471
|
+
@clickable = true
|
472
|
+
@draggable = false
|
473
|
+
@bouncy = false
|
474
|
+
@bounce_gravity = 1
|
475
|
+
end
|
476
|
+
|
477
|
+
# Creates the Javascript to create a new marker. Creates an anonymous function
|
478
|
+
# returning a marker as documented in Google4R::Maps.
|
479
|
+
def to_js
|
480
|
+
lines = Array.new
|
481
|
+
|
482
|
+
# build constructor options
|
483
|
+
options = []
|
484
|
+
options << "dragCrossMove: #{(@drag_cross_move == true).to_s}"
|
485
|
+
options << "title: #{@title.inspect}" unless @title.nil?
|
486
|
+
options << "clickable: #{(@clickable == true).to_s}"
|
487
|
+
options << "bouncy: #{(@bouncy == true).to_s}"
|
488
|
+
options << "bounceGravity: #{@bounce_gravity}"
|
489
|
+
|
490
|
+
# build JS to create GMarker
|
491
|
+
lines << "var options = { #{options.join(", ")} }"
|
492
|
+
lines << "if (icon != null) options['icon'] = icon;"
|
493
|
+
lines << ""
|
494
|
+
lines << "var marker = new GMarker(new GLatLng(#{@point.join(", ")}), options);"
|
495
|
+
|
496
|
+
# set options settable with accessor
|
497
|
+
if @draggable then
|
498
|
+
lines << "marker.enableDragging();"
|
499
|
+
else
|
500
|
+
lines << "marker.disableDragging();"
|
501
|
+
end
|
502
|
+
lines << ""
|
503
|
+
|
504
|
+
# build event handler setup
|
505
|
+
lines << "/* Setup event handlers. */"
|
506
|
+
onclick_handlers.each do |function_body|
|
507
|
+
lines << %Q{GEvent.addListener(marker, "click", function() {
|
508
|
+
#{function_body}
|
509
|
+
});}
|
510
|
+
end
|
511
|
+
|
512
|
+
# add event handler generated for the info_window_html attribute
|
513
|
+
if not info_window_html.nil? then
|
514
|
+
lines << %Q{GEvent.addListener(marker, "click", function() {
|
515
|
+
marker.openInfoWindowHtml(#{@info_window_html.inspect});
|
516
|
+
});}
|
517
|
+
end
|
518
|
+
|
519
|
+
# build result
|
520
|
+
result = Array.new
|
521
|
+
result << "function(icon) {"
|
522
|
+
result += lines.map { |str| " " + str }
|
523
|
+
result << " "
|
524
|
+
result << " return marker;"
|
525
|
+
result << "}"
|
526
|
+
|
527
|
+
return result.join("\n")
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
# The Ruby GIcon class wraps around the Javascript GIcon class to create custom icons for markers.
|
532
|
+
#
|
533
|
+
# You should not create GIcon instances directly but use the GMap2#create_icon factory method
|
534
|
+
# for this.
|
535
|
+
#
|
536
|
+
# See http://www.google.com/apis/maps/documentation/reference.html#GIcon for more details on the attributes.
|
537
|
+
class GIcon
|
538
|
+
# True iff to create the GIcon object based on G_DEFAULT_ICON.
|
539
|
+
#--
|
540
|
+
# TODO: We might choose to allow Ruby GIcon instances here to allow creation based on those GIcons *later* on.
|
541
|
+
#++
|
542
|
+
attr_accessor :copy
|
543
|
+
|
544
|
+
# URL of the image to use as the foreground.
|
545
|
+
attr_accessor :image
|
546
|
+
|
547
|
+
# URL of the image to use as the shadow.
|
548
|
+
attr_accessor :shadow
|
549
|
+
|
550
|
+
# The pixel size of the foreground image of the icon as [width, height].
|
551
|
+
attr_accessor :icon_size
|
552
|
+
|
553
|
+
# The pixel size of the shadow image as [width, height].
|
554
|
+
attr_accessor :shadow_size
|
555
|
+
|
556
|
+
# Pixel coordinates relative to the top left corner of the icon image at which
|
557
|
+
# the icon is to be anchored to the map as [width, height].
|
558
|
+
attr_accessor :icon_anchor
|
559
|
+
|
560
|
+
# Pixel coordinates relative to the top left corner of the icon image at which
|
561
|
+
# the info window is to be anchored to the map as [width, height].
|
562
|
+
attr_accessor :info_window_anchor
|
563
|
+
|
564
|
+
# The URL of the foreground icon image used for printed maps as [width, height].
|
565
|
+
# It must be the same size as the main icon image given by image.
|
566
|
+
attr_accessor :print_image
|
567
|
+
|
568
|
+
# The URL of the foreground icon image used for printed maps in Firefox/Mozilla.
|
569
|
+
# It must be the same size as the main icon image given by image.
|
570
|
+
attr_accessor :moz_print_image
|
571
|
+
|
572
|
+
# The URL of the shadow image used for printed maps. It should be a GIF image since
|
573
|
+
# most browsers cannot print PNG images.
|
574
|
+
attr_accessor :print_shadow
|
575
|
+
|
576
|
+
# The URL of a virtually transparent version of the foreground icon image used to capture
|
577
|
+
# click events in Internet Explorer. This image should be a 24-bit PNG version of the main
|
578
|
+
# icon image with 1% opacity, but the same shape and size as the main icon.
|
579
|
+
attr_accessor :transparent
|
580
|
+
|
581
|
+
# An array of [width, height] specifications to use to identify the clickable part in
|
582
|
+
# other browsers than MSIE.
|
583
|
+
attr_accessor :image_map
|
584
|
+
|
585
|
+
# If the parameter copy is set to true then the generated Javascript will base the icon
|
586
|
+
# on G_DEFAULT_ICON. You only have to specify the parameters you want to override in this
|
587
|
+
# case.
|
588
|
+
#
|
589
|
+
# G_DEFAULT_ICON based markers - what does this mean? A part of the generated code will
|
590
|
+
# look like this:
|
591
|
+
#
|
592
|
+
# var marker = new GIcon(G_DEFAULT_ICON)
|
593
|
+
# marker.image = "..."
|
594
|
+
# // other properties of the marker
|
595
|
+
def initialize(copy=false)
|
596
|
+
@copy = (copy == true)
|
597
|
+
end
|
598
|
+
|
599
|
+
# Creates an anonymous Javascript function that creates a GIcon. See the notes on the
|
600
|
+
# Google4R::Maps module on the generated Javascript. The generated Javascript looks
|
601
|
+
# as follows:
|
602
|
+
#
|
603
|
+
# function() {
|
604
|
+
# var icon = new GIcon();
|
605
|
+
# icon.image = "image";
|
606
|
+
# icon.shadow = "shadow";
|
607
|
+
# icon.printImage = "print image";
|
608
|
+
# icon.printShadow = "print shadow";
|
609
|
+
# icon.transparent = "transparent";
|
610
|
+
# icon.iconSize = new GSize(1, 2);
|
611
|
+
# icon.shadowSize = new GSize(3, 4);
|
612
|
+
# icon.iconAnchor = new GPoint(5, 6);
|
613
|
+
# icon.infoWindowAnchor = new GPoint(7, 8);
|
614
|
+
# icon.imageMap = [ 10, 11, 12, 13, 14, 15 ];
|
615
|
+
#
|
616
|
+
# return icon;
|
617
|
+
# }
|
618
|
+
def to_js
|
619
|
+
lines = []
|
620
|
+
|
621
|
+
if @copy then
|
622
|
+
lines << %Q{var icon = new GIcon(G_DEFAULT_ICON);}
|
623
|
+
else
|
624
|
+
lines << %Q{var icon = new GIcon();}
|
625
|
+
end
|
626
|
+
|
627
|
+
# String properties.
|
628
|
+
[
|
629
|
+
[ :image, 'image' ], [ :shadow, 'shadow' ], [ :print_image, 'printImage' ], [ :moz_print_image, 'mozPrintImage' ],
|
630
|
+
[ :print_shadow, 'printShadow' ], [ :transparent, 'transparent' ]
|
631
|
+
].each do |ruby_name, js_name|
|
632
|
+
lines << %Q{icon.#{js_name} = #{self.send(ruby_name).dump};} unless self.send(ruby_name).nil?
|
633
|
+
end
|
634
|
+
|
635
|
+
# GSize properties.
|
636
|
+
[ [ :icon_size, 'iconSize' ], [ :shadow_size, 'shadowSize' ] ].each do |ruby_name, js_name|
|
637
|
+
lines << %Q{icon.#{js_name} = new GSize(#{self.send(ruby_name).join(', ')});} unless self.send(ruby_name).nil?
|
638
|
+
end
|
639
|
+
|
640
|
+
# GPoint properties.
|
641
|
+
[ [ :icon_anchor, 'iconAnchor' ], [ :info_window_anchor, 'infoWindowAnchor' ] ].each do |ruby_name, js_name|
|
642
|
+
lines << %Q{icon.#{js_name} = new GPoint(#{self.send(ruby_name).join(', ')});} unless self.send(ruby_name).nil?
|
643
|
+
end
|
644
|
+
|
645
|
+
# The image map.
|
646
|
+
lines << %Q{icon.imageMap = [ #{@image_map.flatten.join(', ')} ];} unless @image_map.nil?
|
647
|
+
|
648
|
+
# Build result string.
|
649
|
+
result = Array.new
|
650
|
+
|
651
|
+
result << "function() {"
|
652
|
+
result += lines.map { |str| " " + str }
|
653
|
+
result << " "
|
654
|
+
result << " return icon;"
|
655
|
+
result << "}"
|
656
|
+
return result.join("\n")
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# Allows to geocode a location.
|
661
|
+
#
|
662
|
+
# Uses the Google Maps API to find information about locations specified by strings. You need
|
663
|
+
# a Google Maps API key to use the Geocoder.
|
664
|
+
#
|
665
|
+
# The result has the same format as documented in [1] (within <kml>) if it is directly converted
|
666
|
+
# into Ruby: A value consisting of nested Hashes and Arrays.
|
667
|
+
#
|
668
|
+
# After querying, you can access the last server response code using #last_status_code.
|
669
|
+
#
|
670
|
+
# Notice that you can also use Google's geolocator service to locate ZIPs by querying for the
|
671
|
+
# ZIP and country name.
|
672
|
+
#
|
673
|
+
# Usage Example:
|
674
|
+
#
|
675
|
+
# api_key = 'abcdefg'
|
676
|
+
# geocoder = Google4R::Maps::Geocoder.new(api_key)
|
677
|
+
# result = geocoder.query("1 Infinite Loop, Cupertino")
|
678
|
+
# puts result["Placemark"][0]["address"] # => "1 Infinite Loop, Cupertino, CA 95014"
|
679
|
+
#
|
680
|
+
# 1:: http://www.google.de/apis/maps/documentation/#Geocoding_Structured
|
681
|
+
class Geocoder
|
682
|
+
# Returns the last status code returned by the server.
|
683
|
+
attr_reader :last_status_code
|
684
|
+
|
685
|
+
# The hardcoded URL of Google's geolocator API.
|
686
|
+
GET_URL = 'http://maps.google.com/maps/geo?q=%s&output=%s&key=%s'.freeze
|
687
|
+
|
688
|
+
# Response code constants.
|
689
|
+
G_GEO_SUCCESS = 200
|
690
|
+
G_GEO_SERVER_ERROR = 500
|
691
|
+
G_GEO_MISSING_ADDRESS = 601
|
692
|
+
G_GEO_UNKNOWN_ADDRESS = 602
|
693
|
+
G_UNAVAILABLE_ADDRESS = 603
|
694
|
+
G_GEO_BAD_KEY = 610
|
695
|
+
|
696
|
+
# Creates a new Geocoder object. You have to supply a valid Google Maps
|
697
|
+
# API key.
|
698
|
+
#
|
699
|
+
# === Parameters
|
700
|
+
#
|
701
|
+
# key::The Google Maps API key.
|
702
|
+
# client::Your Google Maps client ID (this is only required for enterprise keys).
|
703
|
+
def initialize(key, client=nil)
|
704
|
+
@api_key = key
|
705
|
+
@client = client
|
706
|
+
end
|
707
|
+
|
708
|
+
# === Parameters
|
709
|
+
#
|
710
|
+
# query:: The place to locate.
|
711
|
+
#
|
712
|
+
# === Exceptions
|
713
|
+
#
|
714
|
+
# Throws a KeyException if the key for this Geocoder instance is invalid and
|
715
|
+
# throws a ConnectionException if the Geocoder instance could not connect to
|
716
|
+
# Google's server or an server error occured.
|
717
|
+
#
|
718
|
+
# === Return Values
|
719
|
+
#
|
720
|
+
# Returns data in the same format as documented in [1]
|
721
|
+
#
|
722
|
+
# Example of returned values:
|
723
|
+
#
|
724
|
+
# {
|
725
|
+
# "name": "1600 Amphitheatre Parkway, Mountain View, CA, USA",
|
726
|
+
# "Status": {
|
727
|
+
# "code": 200,
|
728
|
+
# "request": "geocode"
|
729
|
+
# },
|
730
|
+
# "Placemark": [
|
731
|
+
# {
|
732
|
+
# "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
|
733
|
+
# "AddressDetails": {
|
734
|
+
# "Country": {
|
735
|
+
# "CountryNameCode": "US",
|
736
|
+
# "AdministrativeArea": {
|
737
|
+
# "AdministrativeAreaName": "CA",
|
738
|
+
# "SubAdministrativeArea": {
|
739
|
+
# "SubAdministrativeAreaName": "Santa Clara",
|
740
|
+
# "Locality": {
|
741
|
+
# "LocalityName": "Mountain View",
|
742
|
+
# "Thoroughfare": {
|
743
|
+
# "ThoroughfareName": "1600 Amphitheatre Pkwy"
|
744
|
+
# },
|
745
|
+
# "PostalCode": {
|
746
|
+
# "PostalCodeNumber": "94043"
|
747
|
+
# }
|
748
|
+
# }
|
749
|
+
# }
|
750
|
+
# }
|
751
|
+
# },
|
752
|
+
# "Accuracy": 8
|
753
|
+
# },
|
754
|
+
# "Point": {
|
755
|
+
# "coordinates": [-122.083739, 37.423021, 0]
|
756
|
+
# }
|
757
|
+
# }
|
758
|
+
# ]
|
759
|
+
# }
|
760
|
+
#
|
761
|
+
# 1:: http://www.google.de/apis/maps/documentation/#Geocoding_Structured
|
762
|
+
#++
|
763
|
+
# TODO: Remove the workaround below once this ticket is cleared and the change widely distributed: http://code.whytheluckystiff.net/syck/ticket/27
|
764
|
+
#--
|
765
|
+
def query(query)
|
766
|
+
# Check that a Google Maps key has been specified.
|
767
|
+
raise KeyException.new("Cannot use Google geocoder without an API key.") if @api_key.nil?
|
768
|
+
|
769
|
+
# Compute the URL to send a GET query to.
|
770
|
+
url = URI.escape(GET_URL % [ query, 'json', @api_key.to_s ])
|
771
|
+
url += "&client=#{@client}" unless @client.nil?
|
772
|
+
|
773
|
+
# Perform the query via HTTP.
|
774
|
+
response =
|
775
|
+
begin
|
776
|
+
Net::HTTP.get_response(URI.parse(url))
|
777
|
+
rescue Exception => e
|
778
|
+
raise ConnectionException.new("Could not connect to '#{url}': #{e.message}")
|
779
|
+
end
|
780
|
+
body = response.body
|
781
|
+
|
782
|
+
# Parse the response JSON. We can simply use YAML::load here. I discovered this
|
783
|
+
# on why's page: http://redhanded.hobix.com/inspect/yamlIsJson.html
|
784
|
+
#
|
785
|
+
# We need a workaround to parse the ultra compact JSON from Google, however, because
|
786
|
+
# of this bug: http://code.whytheluckystiff.net/syck/ticket/27
|
787
|
+
result = YAML::load(body.gsub(/([:,])([^\s])/, '\1 \2'))
|
788
|
+
|
789
|
+
@last_status_code = result['Status']['code']
|
790
|
+
|
791
|
+
# Check that the query was successful.
|
792
|
+
if @last_status_code == G_GEO_BAD_KEY then
|
793
|
+
raise KeyException.new("Invalid API key: '#{@api_key}'.")
|
794
|
+
elsif @last_status_code == G_GEO_SERVER_ERROR then
|
795
|
+
raise ConnectionException.new("There was an error when connecting to the server. Result code was: #{status}.")
|
796
|
+
elsif [ G_GEO_MISSING_ADDRESS, G_GEO_UNKNOWN_ADDRESS, G_UNAVAILABLE_ADDRESS ].include?(@last_status_code) then
|
797
|
+
return nil
|
798
|
+
end
|
799
|
+
|
800
|
+
return result
|
801
|
+
end
|
802
|
+
end
|
803
|
+
end
|
804
|
+
end
|