google4r-maps 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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