gmaps4rails 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -51,6 +51,8 @@ To make Rails serve the assets (javascripts, stylesheets and marker images) you
51
51
  - finally you can just copy assets to your application's public/stylesheets, public/javascripts/gmaps4rails and public/images. It's recommended to do this in production so that you can let your webserver serve them rather than go through Rails each time they are requested. There's a generator to help you with that:
52
52
 
53
53
  rails generate gmaps4rails:install
54
+
55
+ This will copy the coffeescript files if you're running Rails 3.1 or the js files
54
56
 
55
57
  == Basic configuration
56
58
  In your model, add:
@@ -1,5 +1,5 @@
1
1
  //= require ./gmaps4rails.base.js
2
2
  //= require ./gmaps4rails.googlemaps.js
3
- //= require ./gmaps4rails.mapquest.js
4
3
  //= require ./gmaps4rails.openlayers.js
5
- //= require ./gmaps4rails.bing.js
4
+ //= require ./gmaps4rails.bing.js
5
+ //= require ./gmaps4rails.mapquest.js
@@ -0,0 +1,468 @@
1
+ Gmaps = {}
2
+
3
+ Gmaps.loadMaps = ->
4
+ #loop through all variable names.
5
+ #there should only be maps inside so it trigger their load function
6
+ for key, value of Gmaps
7
+ if key != "loadMaps"
8
+ window["load_" + key]()
9
+
10
+ window.Gmaps = Gmaps
11
+
12
+ class @Gmaps4Rails
13
+
14
+ constructor: ->
15
+ #map config
16
+ @map = null #contains the map we're working on
17
+ @visibleInfoWindow = null #contains the current opened infowindow
18
+ @userLocation = null #contains user's location if geolocalization was performed and successful
19
+
20
+ #empty slots
21
+ @geolocationFailure = -> false #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean
22
+ @callback = -> false #to let user set a custom callback function
23
+ @customClusterer = -> false #to let user set custom clusterer pictures
24
+ @infobox = -> false #to let user use custom infoboxes
25
+
26
+ @default_map_options =
27
+ id: 'map'
28
+ draggable: true
29
+ detect_location: false # should the browser attempt to use geolocation detection features of HTML5?
30
+ center_on_user: false # centers map on the location detected through the browser
31
+ center_latitude: 0
32
+ center_longitude: 0
33
+ zoom: 7
34
+ maxZoom: null
35
+ minZoom: null
36
+ auto_adjust : true # adjust the map to the markers if set to true
37
+ auto_zoom: true # zoom given by auto-adjust
38
+ bounds: [] # adjust map to these limits. Should be [{"lat": , "lng": }]
39
+
40
+ @default_markers_conf =
41
+ #Marker config
42
+ title: ""
43
+ #MarkerImage config
44
+ picture : ""
45
+ width: 22
46
+ length: 32
47
+ draggable: false # how to modify: <%= gmaps( "markers" => { "data" => @object.to_gmaps4rails, "options" => { "draggable" => true }}) %>
48
+ #clustering config
49
+ do_clustering: true # do clustering if set to true
50
+ randomize: false # Google maps can't display two markers which have the same coordinates. This randomizer enables to prevent this situation from happening.
51
+ max_random_distance: 100 # in meters. Each marker coordinate could be altered by this distance in a random direction
52
+ list_container: null # id of the ul that will host links to all markers
53
+ offset: 0 # used when adding_markers to an existing map. Because new markers are concated with previous one, offset is here to prevent the existing from being re-created.
54
+
55
+ #Stored variables
56
+ @markers = [] # contains all markers. A marker contains the following: {"description": , "longitude": , "title":, "latitude":, "picture": "", "width": "", "length": "", "sidebar": "", "serviceObject": google_marker}
57
+ @boundsObject = null # contains current bounds from markers, polylines etc...
58
+ @polygons = [] # contains raw data, array of arrays (first element could be a hash containing options)
59
+ @polylines = [] # contains raw data, array of arrays (first element could be a hash containing options)
60
+ @circles = [] # contains raw data, array of hash
61
+ @markerClusterer = null # contains all marker clusterers
62
+ @markerImages = []
63
+
64
+ #tnitializes the map
65
+ initialize : ->
66
+ @map = @createMap()
67
+ if (@map_options.detect_location == true or @map_options.center_on_user == true)
68
+ @findUserLocation(this)
69
+ #resets sidebar if needed
70
+ @resetSidebarContent()
71
+
72
+ findUserLocation : (map_object) ->
73
+ if (navigator.geolocation)
74
+ #try to retrieve user's position
75
+ positionSuccessful = (position) ->
76
+ map_object.userLocation = map_object.createLatLng(position.coords.latitude, position.coords.longitude)
77
+ #change map's center to focus on user's geoloc if asked
78
+ if(map_object.map_options.center_on_user == true)
79
+ map_object.centerMapOnUser()
80
+ positionFailure = ->
81
+ map_object.geolocationFailure(true)
82
+
83
+ navigator.geolocation.getCurrentPosition( positionSuccessful, positionFailure)
84
+ else
85
+ #failure but the navigator doesn't handle geolocation
86
+ map_object.geolocationFailure(false)
87
+
88
+
89
+ #////////////////////////////////////////////////////
90
+ #//////////////////// DIRECTIONS ////////////////////
91
+ #////////////////////////////////////////////////////
92
+
93
+ create_direction : ->
94
+ directionsDisplay = new google.maps.DirectionsRenderer()
95
+ directionsService = new google.maps.DirectionsService()
96
+
97
+ directionsDisplay.setMap(@map)
98
+ #display panel only if required
99
+ if this.direction_conf.display_panel
100
+ directionsDisplay.setPanel(document.getElementById(@direction_conf.panel_id))
101
+
102
+ directionsDisplay.setOptions
103
+ suppressMarkers: false
104
+ suppressInfoWindows: false
105
+ suppressPolylines: false
106
+
107
+ request =
108
+ origin: @direction_conf.origin
109
+ destination: @direction_conf.destination
110
+ waypoints: @direction_conf.waypoints
111
+ optimizeWaypoints: @direction_conf.optimizeWaypoints
112
+ unitSystem: google.maps.DirectionsUnitSystem[@direction_conf.unitSystem]
113
+ avoidHighways: @direction_conf.avoidHighways
114
+ avoidTolls: @direction_conf.avoidTolls
115
+ region: @direction_conf.region
116
+ travelMode: google.maps.DirectionsTravelMode[@direction_conf.travelMode]
117
+ language: "en"
118
+
119
+ directionsService.route request, (response, status) ->
120
+ if (status == google.maps.DirectionsStatus.OK)
121
+ directionsDisplay.setDirections(response)
122
+
123
+ #////////////////////////////////////////////////////
124
+ #///////////////////// CIRCLES //////////////////////
125
+ #////////////////////////////////////////////////////
126
+
127
+ #Loops through all circles
128
+ #Loops through all circles and draws them
129
+ create_circles : ->
130
+ for circle in @circles
131
+ @create_circle circle
132
+
133
+ create_circle : (circle) ->
134
+ #by convention, default style configuration could be integrated in the first element
135
+ if circle == @circles[0]
136
+ @circles_conf.strokeColor = circle.strokeColor if circle.strokeColor?
137
+ @circles_conf.strokeOpacity = circle.strokeOpacity if circle.strokeOpacity?
138
+ @circles_conf.strokeWeight = circle.strokeWeight if circle.strokeWeight?
139
+ @circles_conf.fillColor = circle.fillColor if circle.fillColor?
140
+ @circles_conf.fillOpacity = circle.fillOpacity if circle.fillOpacity?
141
+
142
+ if circle.lat? and circle.lng?
143
+ # always check if a config is given, if not, use defaults
144
+ # NOTE: is there a cleaner way to do this? Maybe a hash merge of some sort?
145
+ newCircle = new google.maps.Circle
146
+ center: this.createLatLng(circle.lat, circle.lng)
147
+ strokeColor: circle.strokeColor || this.circles_conf.strokeColor
148
+ strokeOpacity: circle.strokeOpacity || this.circles_conf.strokeOpacity
149
+ strokeWeight: circle.strokeWeight || this.circles_conf.strokeWeight
150
+ fillOpacity: circle.fillOpacity || this.circles_conf.fillOpacity
151
+ fillColor: circle.fillColor || this.circles_conf.fillColor
152
+ clickable: circle.clickable || this.circles_conf.clickable
153
+ zIndex: circle.zIndex || this.circles_conf.zIndex
154
+ radius: circle.radius
155
+
156
+ circle.serviceObject = newCircle
157
+ newCircle.setMap(@map)
158
+
159
+ # clear circles
160
+ clear_circles : ->
161
+ for circle in @circles
162
+ @clear_circle circle
163
+
164
+ clear_circle : (circle) ->
165
+ circle.serviceObject.setMap(null)
166
+
167
+ hide_circles : ->
168
+ for circle in @circles
169
+ @hide_circle circle
170
+
171
+ hide_circle : (circle) ->
172
+ circle.serviceObject.setMap(null)
173
+
174
+ show_circles : ->
175
+ for circle in @circles
176
+ @show_circle @circle
177
+
178
+ show_circle : (circle) ->
179
+ circle.serviceObject.setMap(@map)
180
+
181
+ #////////////////////////////////////////////////////
182
+ #///////////////////// POLYGONS /////////////////////
183
+ #////////////////////////////////////////////////////
184
+
185
+ #polygons is an array of arrays. It loops.
186
+ create_polygons : ->
187
+ for polygon in @polygons
188
+ @create_polygon(polygon)
189
+
190
+ #creates a single polygon, triggered by create_polygons
191
+ create_polygon : (polygon) ->
192
+ polygon_coordinates = []
193
+
194
+ #Polygon points are in an Array, that's why looping is necessary
195
+ for point in polygon
196
+ latlng = @createLatLng(point.lat, point.lng)
197
+ polygon_coordinates.push(latlng)
198
+ #first element of an Array could contain specific configuration for this particular polygon. If no config given, use default
199
+ if point == polygon[0]
200
+ strokeColor = this.polygons[i][j].strokeColor || this.polygons_conf.strokeColor
201
+ strokeOpacity = this.polygons[i][j].strokeOpacity || this.polygons_conf.strokeOpacity
202
+ strokeWeight = this.polygons[i][j].strokeWeight || this.polygons_conf.strokeWeight
203
+ fillColor = this.polygons[i][j].fillColor || this.polygons_conf.fillColor
204
+ fillOpacity = this.polygons[i][j].fillOpacity || this.polygons_conf.fillOpacity
205
+
206
+ #Construct the polygon
207
+ new_poly = new google.maps.Polygon
208
+ paths: polygon_coordinates
209
+ strokeColor: strokeColor
210
+ strokeOpacity: strokeOpacity
211
+ strokeWeight: strokeWeight
212
+ fillColor: fillColor
213
+ fillOpacity: fillOpacity
214
+ clickable: false
215
+
216
+ #save polygon in list
217
+ polygon.serviceObject = new_poly
218
+ new_poly.setMap(@map)
219
+
220
+ #////////////////////////////////////////////////////
221
+ #/////////////////// POLYLINES //////////////////////
222
+ #////////////////////////////////////////////////////
223
+
224
+ #polylines is an array of arrays. It loops.
225
+ create_polylines : ->
226
+ for polyline in @polylines
227
+ @create_polyline polyline
228
+
229
+ #creates a single polyline, triggered by create_polylines
230
+ create_polyline : (polyline) ->
231
+ polyline_coordinates = []
232
+
233
+ #2 cases here, either we have a coded array of LatLng or we have an Array of LatLng
234
+ for element in polyline
235
+ #if we have a coded array
236
+ if element.coded_array?
237
+ decoded_array = new google.maps.geometry.encoding.decodePath(element.coded_array)
238
+ #loop through every point in the array
239
+ for point in decoded_array.length
240
+ polyline_coordinates.push(point)
241
+ polyline_coordinates.push(point)
242
+
243
+ #or we have an array of latlng
244
+ else
245
+ #by convention, a single polyline could be customized in the first array or it uses default values
246
+ if element == polyline[0]
247
+ strokeColor = element.strokeColor || @polylines_conf.strokeColor
248
+ strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity
249
+ strokeWeight = element.strokeWeight || @polylines_conf.strokeWeight
250
+
251
+ #add latlng if positions provided
252
+ if element.lat? && element.lng?
253
+ latlng = @createLatLng(element.lat, element.lng)
254
+ polyline_coordinates.push(latlng)
255
+
256
+ # Construct the polyline
257
+ new_poly = new google.maps.Polyline
258
+ path: polyline_coordinates
259
+ strokeColor: strokeColor
260
+ strokeOpacity: strokeOpacity
261
+ strokeWeight: strokeWeight
262
+ clickable: false
263
+
264
+ #save polyline
265
+ polyline.serviceObject = new_poly
266
+ new_poly.setMap(@map)
267
+
268
+ #////////////////////////////////////////////////////
269
+ #///////////////////// MARKERS //////////////////////
270
+ #////////////////////////////////////////////////////
271
+
272
+ #creates, clusterizes and adjusts map
273
+ create_markers : ->
274
+ @createServiceMarkersFromMarkers()
275
+ @clusterize()
276
+
277
+ #create google.maps Markers from data provided by user
278
+ createServiceMarkersFromMarkers : ->
279
+ for index in [@markers_conf.offset..(@markers.length-1)]
280
+ #extract options, test if value passed or use default
281
+ Lat = @markers[index].lat
282
+ Lng = @markers[index].lng
283
+
284
+ #alter coordinates if randomize is true
285
+ if @markers_conf.randomize
286
+ LatLng = @randomize(Lat, Lng)
287
+ #retrieve coordinates from the æarray
288
+ Lat = LatLng[0]
289
+ Lng = LatLng[1]
290
+
291
+ #save object
292
+ @markers[index].serviceObject = @createMarker
293
+ "marker_picture": if @markers[index].picture then @markers[index].picture else this.markers_conf.picture
294
+ "marker_width": if @markers[index].width then @markers[index].width else this.markers_conf.width
295
+ "marker_height": if @markers[index].height then @markers[index].height else this.markers_conf.length
296
+ "marker_title": if @markers[index].title then @markers[index].title else null
297
+ "marker_anchor": if @markers[index].marker_anchor then @markers[index].marker_anchor else null
298
+ "shadow_anchor": if @markers[index].shadow_anchor then @markers[index].shadow_anchor else null
299
+ "shadow_picture": if @markers[index].shadow_picture then @markers[index].shadow_picture else null
300
+ "shadow_width": if @markers[index].shadow_width then @markers[index].shadow_width else null
301
+ "shadow_height": if @markers[index].shadow_height then @markers[index].shadow_height else null
302
+ "marker_draggable": if @markers[index].draggable then @markers[index].draggable else @markers_conf.draggable
303
+ "rich_marker": if @markers[index].rich_marker then @markers[index].rich_marker else null
304
+ "Lat": Lat
305
+ "Lng": Lng
306
+ "index": index
307
+
308
+ #add infowindowstuff if enabled
309
+ @createInfoWindow(@markers[index])
310
+ #create sidebar if enabled
311
+ @createSidebar(@markers[index])
312
+
313
+ @markers_conf.offset = @markers.length
314
+
315
+
316
+ #creates Image Anchor Position or return null if nothing passed
317
+ createImageAnchorPosition : (anchorLocation) ->
318
+ if (anchorLocation == null)
319
+ return null
320
+ else
321
+ return @createPoint(anchorLocation[0], anchorLocation[1])
322
+
323
+
324
+ #replace old markers with new markers on an existing map
325
+ replaceMarkers : (new_markers) ->
326
+ @clearMarkers()
327
+ #reset previous markers
328
+ @markers = new Array
329
+ #reset current bounds
330
+ @boundsObject = @createLatLngBounds()
331
+ #reset sidebar content if exists
332
+ @resetSidebarContent()
333
+ #add new markers
334
+ @addMarkers(new_markers)
335
+
336
+
337
+ #add new markers to on an existing map
338
+ addMarkers : (new_markers) ->
339
+ #update the list of markers to take into account
340
+ @markers = @markers.concat(new_markers)
341
+ #put markers on the map
342
+ @create_markers()
343
+ @adjustMapToBounds()
344
+
345
+ #////////////////////////////////////////////////////
346
+ #///////////////////// SIDEBAR //////////////////////
347
+ #////////////////////////////////////////////////////
348
+
349
+ #//creates sidebar
350
+ createSidebar : (marker_container) ->
351
+ if (@markers_conf.list_container)
352
+ ul = document.getElementById(@markers_conf.list_container)
353
+ li = document.createElement('li')
354
+ aSel = document.createElement('a')
355
+ aSel.href = 'javascript:void(0);'
356
+ html = if marker_container.sidebar? then marker_container.sidebar else "Marker"
357
+ aSel.innerHTML = html
358
+ currentMap = this
359
+ aSel.onclick = this.sidebar_element_handler(currentMap, marker_container.serviceObject, 'click')
360
+ li.appendChild(aSel)
361
+ ul.appendChild(li)
362
+
363
+ #moves map to marker clicked + open infowindow
364
+ sidebar_element_handler : (currentMap, marker, eventType) ->
365
+ return () ->
366
+ currentMap.map.panTo(marker.position)
367
+ google.maps.event.trigger(marker, eventType)
368
+
369
+
370
+ resetSidebarContent : ->
371
+ if @markers_conf.list_container isnt null
372
+ ul = document.getElementById(@markers_conf.list_container)
373
+ ul.innerHTML = ""
374
+
375
+ #////////////////////////////////////////////////////
376
+ #////////////////// MISCELLANEOUS ///////////////////
377
+ #////////////////////////////////////////////////////
378
+
379
+ #to make the map fit the different LatLng points
380
+ adjustMapToBounds : ->
381
+
382
+ #FIRST_STEP: retrieve all bounds
383
+ #create the bounds object only if necessary
384
+ if @map_options.auto_adjust or @map_options.bounds isnt null
385
+ @boundsObject = @createLatLngBounds()
386
+
387
+ #if autodjust is true, must get bounds from markers polylines etc...
388
+ if @map_options.auto_adjust
389
+ #from markers
390
+ @extendBoundsWithMarkers()
391
+ #from polylines:
392
+ for polyline in @polylines
393
+ polyline_points = polyline.serviceObject.latLngs.getArray()[0].getArray()
394
+ for point in polyline
395
+ @boundsObject.extend point
396
+
397
+ #from polygons:
398
+ for polygon in @polygons
399
+ polygon_points = polygon.serviceObject.latLngs.getArray()[0].getArray()
400
+ for point in polygon
401
+ @boundsObject.extend point
402
+
403
+ #from circles
404
+ for circle in @circles
405
+ @boundsObject.extend(circle.serviceObject.getBounds().getNorthEast())
406
+ @boundsObject.extend(circle.serviceObject.getBounds().getSouthWest())
407
+
408
+ #in every case, I've to take into account the bounds set up by the user
409
+ for bound in @map_options.bounds
410
+ #create points from bounds provided
411
+ #TODO:only works with google maps
412
+ bound = @createLatLng(bound.lat, bound.lng)
413
+ @boundsObject.extend bound
414
+
415
+ #SECOND_STEP: ajust the map to the bounds
416
+ if @map_options.auto_adjust or @map_options.bounds.length > 0
417
+
418
+ #if autozoom is false, take user info into account
419
+ if !@map_options.auto_zoom
420
+ map_center = @boundsObject.getCenter()
421
+ @map_options.center_latitude = map_center.lat()
422
+ @map_options.center_longitude = map_center.lng()
423
+ @map.setCenter(map_center)
424
+ else
425
+ @fitBounds()
426
+
427
+ #////////////////////////////////////////////////////
428
+ #///////////////// KML //////////////////
429
+ #////////////////////////////////////////////////////
430
+
431
+ create_kml : ->
432
+ for kml in @kml
433
+ kml.serviceObject = @createKmlLayer kml
434
+
435
+ #////////////////////////////////////////////////////
436
+ #///////////////// Basic functions //////////////////
437
+ #///////////////////tests coded//////////////////////
438
+
439
+ #//basic function to check existence of a variable
440
+ exists : (var_name) ->
441
+ return (var_name != "" and typeof var_name != "undefined")
442
+
443
+
444
+ #randomize
445
+ randomize : (Lat0, Lng0) ->
446
+ #distance in meters between 0 and max_random_distance (positive or negative)
447
+ dx = @markers_conf.max_random_distance * @random()
448
+ dy = @markers_conf.max_random_distance * @random()
449
+ Lat = parseFloat(Lat0) + (180/Math.PI)*(dy/6378137)
450
+ Lng = parseFloat(Lng0) + ( 90/Math.PI)*(dx/6378137)/Math.cos(Lat0)
451
+ return [Lat, Lng]
452
+
453
+ mergeObjectWithDefault : (object1, object2) ->
454
+ copy_object1 = object1
455
+ for key, value of object2
456
+ unless copy_object1[key]?
457
+ copy_object1[key] = value
458
+ return copy_object1
459
+
460
+ mergeWithDefault : (objectName) ->
461
+ default_object = @["default_" + objectName]
462
+ object = @[objectName]
463
+ @[objectName] = @mergeObjectWithDefault(object, default_object)
464
+ return true
465
+
466
+ #gives a value between -1 and 1
467
+ random : -> return(Math.random() * 2 -1)
468
+