app/assets/javascripts/gmaps4rails/gmaps4rails.base.js.coffee
Gmaps = {}
Gmaps.triggerOldOnload = ->
Gmaps.oldOnload() if typeof(Gmaps.oldOnload) == 'function'
Gmaps.loadMaps = ->
#loop through all variable names.
#there should only be maps inside so it trigger their load function
for key, value of Gmaps
searchLoadIncluded = key.search(/load/)
if searchLoadIncluded == -1
load_function_name = "load_" + key
Gmaps[load_function_name]()
window.Gmaps = Gmaps
class @Gmaps4Rails
constructor: ->
#map config
@map = null #DEPRECATED: will still contain a copy of serviceObject below as transition
@serviceObject = null #contains the map we're working on
@visibleInfoWindow = null #contains the current opened infowindow
@userLocation = null #contains user's location if geolocalization was performed and successful
#empty slots
@geolocationSuccess = -> false #triggered when geolocation succeeds. Can be customized.
@geolocationFailure = -> false #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean
@callback = -> false #to let user set a custom callback function
@customClusterer = -> false #to let user set custom clusterer pictures
@infobox = -> false #to let user use custom infoboxes
@jsTemplate = false #to let user create infowindows client side
@default_map_options =
id: 'map'
draggable: true
detect_location: false # should the browser attempt to use geolocation detection features of HTML5?
center_on_user: false # centers map on the location detected through the browser
center_latitude: 0
center_longitude: 0
zoom: 7
maxZoom: null
minZoom: null
auto_adjust : true # adjust the map to the markers if set to true
auto_zoom: true # zoom given by auto-adjust
bounds: [] # adjust map to these limits. Should be [{"lat": , "lng": }]
raw: {} # raw json to pass additional options
@default_markers_conf =
#Marker config
title: ""
#MarkerImage config
picture : ""
width: 22
length: 32
draggable: false # how to modify: <%= gmaps( "markers" => { "data" => @object.to_gmaps4rails, "options" => { "draggable" => true }}) %>
#clustering config
do_clustering: false # do clustering if set to true
randomize: false # Google maps can't display two markers which have the same coordinates. This randomizer enables to prevent this situation from happening.
max_random_distance: 100 # in meters. Each marker coordinate could be altered by this distance in a random direction
list_container: null # id of the ul that will host links to all markers
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.
raw: {} # raw json to pass additional options
#Stored variables
@markers = [] # contains all markers. A marker contains the following: {"description": , "longitude": , "title":, "latitude":, "picture": "", "width": "", "length": "", "sidebar": "", "serviceObject": google_marker}
@boundsObject = null # contains current bounds from markers, polylines etc...
@polygons = [] # contains raw data, array of arrays (first element could be a hash containing options)
@polylines = [] # contains raw data, array of arrays (first element could be a hash containing options)
@circles = [] # contains raw data, array of hash
@markerClusterer = null # contains all marker clusterers
@markerImages = []
#Polyline Styling
@polylines_conf = #default style for polylines
strokeColor: "#FF0000"
strokeOpacity: 1
strokeWeight: 2
clickable: false
zIndex: null
#tnitializes the map
initialize : ->
@serviceObject = @createMap()
@map = @serviceObject #beware, soon deprecated
if (@map_options.detect_location == true or @map_options.center_on_user == true)
@findUserLocation(this)
#resets sidebar if needed
@resetSidebarContent()
findUserLocation : (map_object) ->
if (navigator.geolocation)
#try to retrieve user's position
positionSuccessful = (position) ->
map_object.userLocation = map_object.createLatLng(position.coords.latitude, position.coords.longitude)
#change map's center to focus on user's geoloc if asked
if(map_object.map_options.center_on_user == true)
map_object.centerMapOnUser()
map_object.geolocationSuccess()
positionFailure = ->
map_object.geolocationFailure(true)
navigator.geolocation.getCurrentPosition( positionSuccessful, positionFailure)
else
#failure but the navigator doesn't handle geolocation
map_object.geolocationFailure(false)
#////////////////////////////////////////////////////
#//////////////////// DIRECTIONS ////////////////////
#////////////////////////////////////////////////////
create_direction : ->
directionsDisplay = new google.maps.DirectionsRenderer()
directionsService = new google.maps.DirectionsService()
directionsDisplay.setMap(@serviceObject)
#display panel only if required
if @direction_conf.display_panel
directionsDisplay.setPanel(document.getElementById(@direction_conf.panel_id))
directionsDisplay.setOptions
suppressMarkers: false
suppressInfoWindows: false
suppressPolylines: false
request =
origin: @direction_conf.origin
destination: @direction_conf.destination
waypoints: @direction_conf.waypoints
optimizeWaypoints: @direction_conf.optimizeWaypoints
unitSystem: google.maps.DirectionsUnitSystem[@direction_conf.unitSystem]
avoidHighways: @direction_conf.avoidHighways
avoidTolls: @direction_conf.avoidTolls
region: @direction_conf.region
travelMode: google.maps.DirectionsTravelMode[@direction_conf.travelMode]
language: "en"
directionsService.route request, (response, status) ->
if (status == google.maps.DirectionsStatus.OK)
directionsDisplay.setDirections(response)
#////////////////////////////////////////////////////
#///////////////////// CIRCLES //////////////////////
#////////////////////////////////////////////////////
#Loops through all circles
#Loops through all circles and draws them
create_circles : ->
for circle in @circles
@create_circle circle
create_circle : (circle) ->
#by convention, default style configuration could be integrated in the first element
if circle == @circles[0]
@circles_conf.strokeColor = circle.strokeColor if circle.strokeColor?
@circles_conf.strokeOpacity = circle.strokeOpacity if circle.strokeOpacity?
@circles_conf.strokeWeight = circle.strokeWeight if circle.strokeWeight?
@circles_conf.fillColor = circle.fillColor if circle.fillColor?
@circles_conf.fillOpacity = circle.fillOpacity if circle.fillOpacity?
if circle.lat? and circle.lng?
# always check if a config is given, if not, use defaults
# NOTE: is there a cleaner way to do this? Maybe a hash merge of some sort?
newCircle = new google.maps.Circle
center: @createLatLng(circle.lat, circle.lng)
strokeColor: circle.strokeColor || @circles_conf.strokeColor
strokeOpacity: circle.strokeOpacity || @circles_conf.strokeOpacity
strokeWeight: circle.strokeWeight || @circles_conf.strokeWeight
fillOpacity: circle.fillOpacity || @circles_conf.fillOpacity
fillColor: circle.fillColor || @circles_conf.fillColor
clickable: circle.clickable || @circles_conf.clickable
zIndex: circle.zIndex || @circles_conf.zIndex
radius: circle.radius
circle.serviceObject = newCircle
newCircle.setMap(@serviceObject)
# clear circles
clear_circles : ->
for circle in @circles
@clear_circle circle
clear_circle : (circle) ->
circle.serviceObject.setMap(null)
hide_circles : ->
for circle in @circles
@hide_circle circle
hide_circle : (circle) ->
circle.serviceObject.setMap(null)
show_circles : ->
for circle in @circles
@show_circle @circle
show_circle : (circle) ->
circle.serviceObject.setMap(@serviceObject)
#////////////////////////////////////////////////////
#///////////////////// POLYGONS /////////////////////
#////////////////////////////////////////////////////
#polygons is an array of arrays. It loops.
create_polygons : ->
for polygon in @polygons
@create_polygon(polygon)
#creates a single polygon, triggered by create_polygons
create_polygon : (polygon) ->
polygon_coordinates = []
#Polygon points are in an Array, that's why looping is necessary
for point in polygon
latlng = @createLatLng(point.lat, point.lng)
polygon_coordinates.push(latlng)
#first element of an Array could contain specific configuration for this particular polygon. If no config given, use default
if point == polygon[0]
strokeColor = point.strokeColor || @polygons_conf.strokeColor
strokeOpacity = point.strokeOpacity || @polygons_conf.strokeOpacity
strokeWeight = point.strokeWeight || @polygons_conf.strokeWeight
fillColor = point.fillColor || @polygons_conf.fillColor
fillOpacity = point.fillOpacity || @polygons_conf.fillOpacity
clickable = point.clickable || @polygons_conf.clickable
#Construct the polygon
new_poly = new google.maps.Polygon
paths: polygon_coordinates
strokeColor: strokeColor
strokeOpacity: strokeOpacity
strokeWeight: strokeWeight
fillColor: fillColor
fillOpacity: fillOpacity
clickable: clickable
map: @serviceObject
#save polygon in list
polygon.serviceObject = new_poly
#////////////////////////////////////////////////////
#///////////////////// MARKERS //////////////////////
#////////////////////////////////////////////////////
#creates, clusterizes and adjusts map
create_markers : ->
@createServiceMarkersFromMarkers()
@clusterize()
#create google.maps Markers from data provided by user
createServiceMarkersFromMarkers : ->
for marker, index in @markers
if not @markers[index].serviceObject?
#extract options, test if value passed or use default
Lat = @markers[index].lat
Lng = @markers[index].lng
#alter coordinates if randomize is true
if @markers_conf.randomize
LatLng = @randomize(Lat, Lng)
#retrieve coordinates from the array
Lat = LatLng[0]
Lng = LatLng[1]
#save object
@markers[index].serviceObject = @createMarker
"marker_picture": if @markers[index].picture then @markers[index].picture else @markers_conf.picture
"marker_width": if @markers[index].width then @markers[index].width else @markers_conf.width
"marker_height": if @markers[index].height then @markers[index].height else @markers_conf.length
"marker_title": if @markers[index].title then @markers[index].title else null
"marker_anchor": if @markers[index].marker_anchor then @markers[index].marker_anchor else null
"shadow_anchor": if @markers[index].shadow_anchor then @markers[index].shadow_anchor else null
"shadow_picture": if @markers[index].shadow_picture then @markers[index].shadow_picture else null
"shadow_width": if @markers[index].shadow_width then @markers[index].shadow_width else null
"shadow_height": if @markers[index].shadow_height then @markers[index].shadow_height else null
"marker_draggable": if @markers[index].draggable then @markers[index].draggable else @markers_conf.draggable
"rich_marker": if @markers[index].rich_marker then @markers[index].rich_marker else null
"zindex": if @markers[index].zindex then @markers[index].zindex else null
"Lat": Lat
"Lng": Lng
"index": index
#add infowindowstuff if enabled
@createInfoWindow(@markers[index])
#create sidebar if enabled
@createSidebar(@markers[index])
@markers_conf.offset = @markers.length
#creates Image Anchor Position or return null if nothing passed
createImageAnchorPosition : (anchorLocation) ->
if (anchorLocation == null)
return null
else
return @createPoint(anchorLocation[0], anchorLocation[1])
#replace old markers with new markers on an existing map
replaceMarkers : (new_markers, adjustBounds = true) ->
@clearMarkers()
#reset previous markers
@markers = new Array
#reset current bounds
@boundsObject = @createLatLngBounds() if adjustBounds
#reset sidebar content if exists
@resetSidebarContent()
#add new markers
@markers_conf.offset = 0
@addMarkers(new_markers, adjustBounds)
#add new markers to on an existing map
addMarkers : (new_markers, adjustBounds = true) ->
#update the list of markers to take into account
@markers = @markers.concat(new_markers)
#put markers on the map
@create_markers()
@adjustMapToBounds() if adjustBounds
#////////////////////////////////////////////////////
#///////////////////// SIDEBAR //////////////////////
#////////////////////////////////////////////////////
#//creates sidebar
createSidebar : (marker_container) ->
if (@markers_conf.list_container)
ul = document.getElementById(@markers_conf.list_container)
li = document.createElement('li')
aSel = document.createElement('a')
aSel.href = 'javascript:void(0);'
html = if marker_container.sidebar? then marker_container.sidebar else "Marker"
aSel.innerHTML = html
currentMap = this
aSel.onclick = @sidebar_element_handler(currentMap, marker_container.serviceObject, 'click')
li.appendChild(aSel)
ul.appendChild(li)
#moves map to marker clicked + open infowindow
sidebar_element_handler : (currentMap, marker, eventType) ->
return () ->
currentMap.map.panTo(marker.position)
google.maps.event.trigger(marker, eventType)
resetSidebarContent : ->
if @markers_conf.list_container isnt null
ul = document.getElementById(@markers_conf.list_container)
ul.innerHTML = ""
#////////////////////////////////////////////////////
#////////////////// MISCELLANEOUS ///////////////////
#////////////////////////////////////////////////////
#to make the map fit the different LatLng points
adjustMapToBounds : ->
#FIRST_STEP: retrieve all bounds
#create the bounds object only if necessary
if @map_options.auto_adjust or @map_options.bounds isnt null
@boundsObject = @createLatLngBounds()
#if autodjust is true, must get bounds from markers polylines etc...
if @map_options.auto_adjust
#from markers
@extendBoundsWithMarkers()
#from polylines:
@updateBoundsWithPolylines()
#from polygons:
@updateBoundsWithPolygons()
#from circles
@updateBoundsWithCircles()
#in every case, I've to take into account the bounds set up by the user
@extendMapBounds()
#SECOND_STEP: ajust the map to the bounds
@adaptMapToBounds()
#////////////////////////////////////////////////////
#/////////////////// POLYLINES //////////////////////
#////////////////////////////////////////////////////
#replace old markers with new markers on an existing map
replacePolylines : (new_polylines) ->
#reset previous polylines and kill them from map
@destroy_polylines()
#set new polylines
@polylines = new_polylines
#create
@create_polylines()
#.... and adjust map boundaries
@adjustMapToBounds()
destroy_polylines : ->
for polyline in @polylines
#delete polylines from map
polyline.serviceObject.setMap(null)
#empty array
@polylines = []
#polylines is an array of arrays. It loops.
create_polylines : ->
for polyline in @polylines
@create_polyline polyline
#////////////////////////////////////////////////////
#///////////////// Basic functions //////////////////
#///////////////////tests coded//////////////////////
#//basic function to check existence of a variable
exists : (var_name) ->
return (var_name != "" and typeof var_name != "undefined")
#randomize
randomize : (Lat0, Lng0) ->
#distance in meters between 0 and max_random_distance (positive or negative)
dx = @markers_conf.max_random_distance * @random()
dy = @markers_conf.max_random_distance * @random()
Lat = parseFloat(Lat0) + (180/Math.PI)*(dy/6378137)
Lng = parseFloat(Lng0) + ( 90/Math.PI)*(dx/6378137)/Math.cos(Lat0)
return [Lat, Lng]
mergeObjectWithDefault : (object1, object2) ->
copy_object1 = {}
for key, value of object1
copy_object1[key] = value
for key, value of object2
unless copy_object1[key]?
copy_object1[key] = value
return copy_object1
mergeWithDefault : (objectName) ->
default_object = @["default_" + objectName]
object = @[objectName]
@[objectName] = @mergeObjectWithDefault(object, default_object)
return true
#gives a value between -1 and 1
random : -> return(Math.random() * 2 -1)