gmaps4rails 1.0.2 → 1.1.0

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