georgebellos/real_estate

View on GitHub
app/assets/javascripts/gmaps4rails/gmaps4rails.googlemaps.js.coffee

Summary

Maintainability
Test Coverage
#######################################################################################################
##############################################  Google maps  ##########################################
#######################################################################################################

class @Gmaps4RailsGoogle extends Gmaps4Rails

  constructor: ->
    super
    #Map settings
    @map_options =
      disableDefaultUI:       false
      disableDoubleClickZoom: false
      type:                   "ROADMAP" # HYBRID, ROADMAP, SATELLITE, TERRAIN

    #markers + info styling
    @markers_conf =
      clusterer_gridSize:      50
      clusterer_maxZoom:       5
      custom_cluster_pictures: null
      custom_infowindow_class: null

    @mergeWithDefault("map_options")
    @mergeWithDefault("markers_conf")

    @kml_options =
      clickable: true
      preserveViewport: false
      suppressInfoWindows: false

    #Polygon Styling
    @polygons_conf =         # default style for polygons
      strokeColor: "#FFAA00"
      strokeOpacity: 0.8
      strokeWeight: 2
      fillColor: "#000000"
      fillOpacity: 0.35
      clickable: false

    #Circle Styling
    @circles_conf =           #default style for circles
      fillColor: "#00AAFF"
      fillOpacity: 0.35
      strokeColor: "#FFAA00"
      strokeOpacity: 0.8
      strokeWeight: 2
      clickable: false
      zIndex: null

    #Direction Settings
    @direction_conf =
      panel_id:           null
      display_panel:      false
      origin:             null
      destination:        null
      waypoints:          []       #[{location: "toulouse,fr", stopover: true}, {location: "Clermont-Ferrand, fr", stopover: true}]
      optimizeWaypoints:  false
      unitSystem:         "METRIC" #IMPERIAL
      avoidHighways:      false
      avoidTolls:         false
      region:             null
      travelMode:         "DRIVING" #WALKING, BICYCLING

  #////////////////////////////////////////////////////
  #/////////////// Basic Objects         //////////////
  #////////////////////////////////////////////////////

  createPoint : (lat, lng) ->
    return new google.maps.Point(lat, lng)

  createLatLng : (lat, lng) ->
    return new google.maps.LatLng(lat, lng)

  createLatLngBounds : ->
    return new google.maps.LatLngBounds()

  createMap : ->
    defaultOptions =
      maxZoom:                @map_options.maxZoom
      minZoom:                @map_options.minZoom
      zoom:                   @map_options.zoom
      center:                 @createLatLng(@map_options.center_latitude, @map_options.center_longitude)
      mapTypeId:              google.maps.MapTypeId[@map_options.type]
      mapTypeControl:         @map_options.mapTypeControl
      disableDefaultUI:       @map_options.disableDefaultUI
      disableDoubleClickZoom: @map_options.disableDoubleClickZoom
      draggable:              @map_options.draggable

    mergedOptions = @mergeObjectWithDefault @map_options.raw, defaultOptions

    return new google.maps.Map document.getElementById(@map_options.id), mergedOptions


  createMarkerImage : (markerPicture, markerSize, origin, anchor, scaledSize) ->
    return new google.maps.MarkerImage(markerPicture, markerSize, origin, anchor, scaledSize)

  createSize : (width, height) ->
    return new google.maps.Size(width, height)

  #////////////////////////////////////////////////////
  #////////////////////// Markers /////////////////////
  #////////////////////////////////////////////////////

  createMarker : (args) ->
    markerLatLng = @createLatLng(args.Lat, args.Lng)
    #Marker sizes are expressed as a Size of X,Y
    if args.marker_picture == "" and args.rich_marker == null
      defaultOptions = {position: markerLatLng, map: @serviceObject, title: args.marker_title, draggable: args.marker_draggable, zIndex: args.zindex}
      mergedOptions  = @mergeObjectWithDefault @markers_conf.raw, defaultOptions
      return new google.maps.Marker mergedOptions

    if (args.rich_marker != null)
      return new RichMarker({
        position: markerLatLng
        map:       @serviceObject
        draggable: args.marker_draggable
        content:   args.rich_marker
        flat:      if args.marker_anchor == null then false else args.marker_anchor[1]
        anchor:    if args.marker_anchor == null then 0     else args.marker_anchor[0]
        zIndex:    args.zindex
      })

    #default behavior
    #calculate MarkerImage anchor location
    imageAnchorPosition  = @createImageAnchorPosition args.marker_anchor
    shadowAnchorPosition = @createImageAnchorPosition args.shadow_anchor
    #create or retrieve existing MarkerImages
    markerImage = @createOrRetrieveImage(args.marker_picture, args.marker_width, args.marker_height, imageAnchorPosition)
    shadowImage = @createOrRetrieveImage(args.shadow_picture, args.shadow_width, args.shadow_height, shadowAnchorPosition)
    defaultOptions = {position: markerLatLng, map: @serviceObject, icon: markerImage, title: args.marker_title, draggable: args.marker_draggable, shadow: shadowImage,  zIndex: args.zindex}
    mergedOptions  = @mergeObjectWithDefault @markers_conf.raw, defaultOptions
    return new google.maps.Marker mergedOptions

  #checks if obj is included in arr Array and returns the position or false
  includeMarkerImage : (arr, obj) ->
    for object, index in arr
      return index if object.url == obj
    return false

  #checks if MarkerImage exists before creating a new one
  #returns a MarkerImage or false if ever something wrong is passed as argument
  createOrRetrieveImage : (currentMarkerPicture, markerWidth, markerHeight, imageAnchorPosition) ->
    return null if (currentMarkerPicture == "" or currentMarkerPicture == null )

    test_image_index = @includeMarkerImage(@markerImages, currentMarkerPicture)
    switch test_image_index
      when false
        markerImage = @createMarkerImage(currentMarkerPicture, @createSize(markerWidth, markerHeight), null, imageAnchorPosition, null )
        @markerImages.push(markerImage)
        return markerImage
        break
      else
        return @markerImages[test_image_index] if typeof test_image_index == 'number'
        return false

  #clear markers
  clearMarkers : ->
    for marker in @markers
      @clearMarker marker

  #show and hide markers
  showMarkers : ->
    for marker in @markers
      @showMarker marker

  hideMarkers : ->
    for marker in @markers
      @hideMarker marker

  clearMarker : (marker) ->
    marker.serviceObject.setMap(null)

  showMarker : (marker) ->
    marker.serviceObject.setVisible(true)

  hideMarker : (marker) ->
    marker.serviceObject.setVisible(false)

  extendBoundsWithMarkers : ->
    for marker in @markers
      @boundsObject.extend(marker.serviceObject.position)

  #////////////////////////////////////////////////////
  #/////////////////// Clusterer //////////////////////
  #////////////////////////////////////////////////////

  createClusterer : (markers_array) ->
    return new MarkerClusterer( @serviceObject, markers_array, {  maxZoom: @markers_conf.clusterer_maxZoom, gridSize: @markers_conf.clusterer_gridSize, styles: @customClusterer() })

  clearClusterer : ->
    @markerClusterer.clearMarkers()

  #creates clusters
  clusterize : ->
    if @markers_conf.do_clustering == true
      #first clear the existing clusterer if any
      @clearClusterer() if @markerClusterer != null

      markers_array = new Array
      for marker in @markers
        markers_array.push(marker.serviceObject)

      @markerClusterer = @createClusterer(markers_array)

  #////////////////////////////////////////////////////
  #/////////////////// INFO WINDOW ////////////////////
  #////////////////////////////////////////////////////

  #// creates infowindows
  createInfoWindow : (marker_container) ->
    if typeof(@jsTemplate) == "function" or marker_container.description?
      marker_container.description = @jsTemplate(marker_container) if typeof(@jsTemplate) == "function"
      if @markers_conf.custom_infowindow_class != null
        #creating custom infowindow
        boxText = document.createElement("div")
        boxText.setAttribute("class", @markers_conf.custom_infowindow_class) #to customize
        boxText.innerHTML = marker_container.description
        marker_container.infowindow = new InfoBox(@infobox(boxText))
        currentMap = this
        google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject))
      else
        #create default infowindow
        marker_container.infowindow = new google.maps.InfoWindow({content: marker_container.description })
        #add the listener associated
        currentMap = this
        google.maps.event.addListener(marker_container.serviceObject, 'click', @openInfoWindow(currentMap, marker_container.infowindow, marker_container.serviceObject))

  openInfoWindow : (currentMap, infoWindow, marker) ->
    return ->
      # Close the latest selected marker before opening the current one.
      currentMap.visibleInfoWindow.close() if currentMap.visibleInfoWindow != null
      infoWindow.open(currentMap.serviceObject, marker)
      currentMap.visibleInfoWindow = infoWindow

  #////////////////////////////////////////////////////
  #/////////////////        KML      //////////////////
  #////////////////////////////////////////////////////

  createKmlLayer : (kml) ->
    kml_options = kml.options || {}
    kml_options = @mergeObjectWithDefault(kml_options, @kml_options)
    kml =  new google.maps.KmlLayer( kml.url, kml_options)
    kml.setMap(@serviceObject)
    return kml

  #////////////////////////////////////////////////////
  #/////////////////// POLYLINES //////////////////////
  #////////////////////////////////////////////////////

  #creates a single polyline, triggered by create_polylines
  create_polyline : (polyline) ->
    polyline_coordinates = []

    #2 cases here, either we have a coded array of LatLng or we have an Array of LatLng
    for element in polyline
      #if we have a coded array
      if element.coded_array?
        decoded_array = new google.maps.geometry.encoding.decodePath(element.coded_array)
        #loop through every point in the array
        for point in decoded_array
          polyline_coordinates.push(point)

      #or we have an array of latlng
      else
        #by convention, a single polyline could be customized in the first array or it uses default values
        if element == polyline[0]
          strokeColor   = element.strokeColor   || @polylines_conf.strokeColor
          strokeOpacity = element.strokeOpacity || @polylines_conf.strokeOpacity
          strokeWeight  = element.strokeWeight  || @polylines_conf.strokeWeight
          clickable     = element.clickable     || @polylines_conf.clickable
          zIndex        = element.zIndex        || @polylines_conf.zIndex

        #add latlng if positions provided
        if element.lat? && element.lng?
          latlng = @createLatLng(element.lat, element.lng)
          polyline_coordinates.push(latlng)

    # Construct the polyline
    new_poly = new google.maps.Polyline
      path:         polyline_coordinates
      strokeColor:  strokeColor
      strokeOpacity: strokeOpacity
      strokeWeight: strokeWeight
      clickable:    clickable
      zIndex:       zIndex

    #save polyline
    polyline.serviceObject = new_poly
    new_poly.setMap(@serviceObject)

  
  updateBoundsWithPolylines: ()->
    for polyline in @polylines
      polyline_points = polyline.serviceObject.latLngs.getArray()[0].getArray()
      for point in polyline_points
        @boundsObject.extend point
  
  #////////////////////////////////////////////////////
  #/////////////////        KML      //////////////////
  #////////////////////////////////////////////////////

  create_kml : ->
    for kml in @kml
      kml.serviceObject = @createKmlLayer kml
            
  #////////////////////////////////////////////////////
  #/////////////////// Other methods //////////////////
  #////////////////////////////////////////////////////

  fitBounds : ->
    @serviceObject.fitBounds(@boundsObject) unless @boundsObject.isEmpty()

  centerMapOnUser : ->
    @serviceObject.setCenter(@userLocation)
  
  updateBoundsWithPolygons: ()->
    for polygon in @polygons
      polygon_points = polygon.serviceObject.latLngs.getArray()[0].getArray()
      for point in polygon_points
        @boundsObject.extend point

  updateBoundsWithCircles: ()->
    for circle in @circles
      @boundsObject.extend(circle.serviceObject.getBounds().getNorthEast())
      @boundsObject.extend(circle.serviceObject.getBounds().getSouthWest())

  extendMapBounds: ()->
    for bound in @map_options.bounds
      #create points from bounds provided
      @boundsObject.extend @createLatLng(bound.lat, bound.lng)
  
  adaptMapToBounds:()->
    #if autozoom is false, take user info into account
    if !@map_options.auto_zoom
      map_center = @boundsObject.getCenter()
      @map_options.center_latitude  = map_center.lat()
      @map_options.center_longitude = map_center.lng()
      @serviceObject.setCenter(map_center)
    else
      @fitBounds()