pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

commit d73496c2c6af68951c6085c40ada1e6cd538304d
parent 484b95a47ce5ac6b32a83aa487c03a65afe02144
Author: Jul <jul@9o.is>
Date:   Wed,  9 Jul 2014 05:03:23 -0400

Merge branch 'atm-map' into 2.0-SNAPSHOT

Diffstat:
MGruntfile.js | 3++-
Mbuild.config.js | 3++-
Mproject/BuildSettings.scala | 2+-
Msrc/main/scala/inc/pyc/config/Site.scala | 14+++++++-------
Asrc/main/scala/inc/pyc/lib/Geocode.scala | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/Atm.scala | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/main/scala/inc/pyc/model/SearchedLocation.scala | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/main/scala/inc/pyc/model/SearchedPostal.scala | 46----------------------------------------------
Msrc/main/scala/inc/pyc/snippet/AtmSnip.scala | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/main/scala/inc/pyc/snippet/NgAlert.scala | 7+++++--
Msrc/main/scala/inc/pyc/snippet/UtilSnips.scala | 4+++-
Msrc/main/webapp/app/App.js | 211+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Asrc/main/webapp/atm.html | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main/webapp/faqs.html | 8+++++---
Msrc/main/webapp/index.html | 230++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Msrc/main/webapp/less/custom.less | 46+++++++++++++++++++++++++++++++++++++++++++++-
Msrc/main/webapp/less/overrides.less | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Asrc/main/webapp/less/pages/atm.less | 23+++++++++++++++++++++++
Msrc/main/webapp/less/pages/index.less | 461++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/main/webapp/less/styles.less | 7++++---
Msrc/main/webapp/less/variables.less | 4++--
Asrc/main/webapp/templates-hidden/base-defaultless.html | 10++++++++++
Dsrc/main/webapp/templates-hidden/parts/find-atm-form.html | 26--------------------------
Msrc/main/webapp/templates-hidden/parts/notify-atm-form.html | 2+-
Msrc/main/webapp/vendor/angular-google-maps.min.js | 8189++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
25 files changed, 9647 insertions(+), 219 deletions(-)

diff --git a/Gruntfile.js b/Gruntfile.js @@ -52,7 +52,8 @@ module.exports = function(grunt) { "angular": false, "_": false, "ko": false, - "App": true + "App": true, + "google": false } }, src: [ diff --git a/build.config.js b/build.config.js @@ -65,7 +65,8 @@ module.exports = { "<%= dirs.vendor %>/liftAjax.js", "<%= dirs.vendor %>/angular-file-upload.js", "<%= dirs.vendor %>/angular-google-analytics.js", - "<%= dirs.vendor %>/angular-wizard.js" + "<%= dirs.vendor %>/angular-wizard.js", + "<%= dirs.vendor %>/ngAnimate.js" ], css: [ ], diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala @@ -59,7 +59,7 @@ object BuildSettings { resolvers ++= Seq[Resolver]( "Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases", "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", - "tuhlmann" at "https://bitbucket.org/agynamix/mvn-repo/raw/master/snapshots/", + "tuhlmann" at "https://bitbucket.org/agynamix/mvn-repo/raw/master/snapshots/", s3resolver.value("PYC Releases", s3("releases-pyc-inc")), s3resolver.value("PYC Snapshots", s3("snapshots-pyc-inc")) ) diff --git a/src/main/scala/inc/pyc/config/Site.scala b/src/main/scala/inc/pyc/config/Site.scala @@ -1,7 +1,7 @@ package inc.pyc package config -import model.User +import model.{User, Atm} import model.EmailResetToken._ import net.liftweb._ @@ -71,12 +71,11 @@ object Site extends Locs { val verifyID = MenuLoc(Menu.i("Verify ID") / "settings" / "admin" / "verify" >> SettingsGroup >> HasRole("admin")) - // Profile pages - private val profileParamMenu = Menu.param[User]("User", "Profile", - User.findByUsername _, - _.username.get - ) / "user" >> Loc.CalcValue(() => User.currentUser) - lazy val profileLoc = profileParamMenu.toLoc + // ATM pages + private val atmProfileParamMenu = Menu.param[Atm]( + "ATM", "Page", Atm.findByName _, _.name.get) / "atm" + lazy val atmProfileLoc = atmProfileParamMenu.toLoc + @@ -98,6 +97,7 @@ object Site extends Locs { purchaseLimit.menu, verifyID.menu, emailResetToken.menu, + atmProfileParamMenu, Menu.i("Error") / "error" >> Hidden, Menu.i("404") / "404" >> Hidden, Menu.i("Status") / "ping" >> Hidden >> CalcStateless(() => true ) >> EarlyResponse(() => Full(OkResponse())), diff --git a/src/main/scala/inc/pyc/lib/Geocode.scala b/src/main/scala/inc/pyc/lib/Geocode.scala @@ -0,0 +1,95 @@ +package inc.pyc +package lib + +import dispatch._, Defaults._ +import net.liftweb._ +import util.Helpers._ +import json._ + +/** + * Google Geocoding API -- Reverse Address Lookup + * https://developers.google.com/maps/documentation/geocoding/ + */ +object Geocode { + + private val endPoint = "http://maps.googleapis.com/maps/api/geocode/json" + + private val defaultParams = List(("language", "en")) + + def geolocation(address: String, params: List[(String, String)] = defaultParams): Future[Either[String, GeoPoint]] = { + for(lookup <- lookupAddress(address, params)) + yield for { + response <- lookup.right + result <- getResult(response).right + } yield result.geometry.location + } + + /** + * Lookup address information + */ + def lookupAddress(address: String, params: List[(String, String)] = defaultParams): Future[Either[String, Response]] = { + implicit val formats = DefaultFormats + val req = url(endPoint) <<? (("address", address) :: ("address", address) :: params) + + def extract(json: String): Either[String, Response] = + tryo(parse(json).extract[Response]) toRight { + "Can't extract response from Google Geocode API" + } + + def checkStatus(response: Response): Either[String, Response] = { + if(response.status == "OK") Right(response) + else Left(response.status) + } + + for (jsonEither <- request(req)) + yield for { + json <- jsonEither.right + value <- extract(json).right + okValue <- checkStatus(value).right + } yield okValue + } + + /** + * Given a list of results by Google's API, this just gets the first one in the list. + */ + private def getResult(response: Response): Either[String, Result] = + response.results.headOption.toRight("No results found when retrieving Google's returned results.") + + /** + * Request and get a json response. + */ + private def request(req: Req): Future[Either[String, String]] = { + val res = Http(req OK as.String).either + + for (e <- (res).left) + yield s"Can't connect to Google Geocode API: ${e.getMessage}" + } + + case class Response( + status: String, + results: List[Result]) + + case class Result( + types: List[String], + formatted_address: String, + address_components: List[AddressComponent], + geometry: Geometry, + partial_match: Option[Boolean]) + + case class AddressComponent( + long_name: String, + short_name: String, + types: List[String]) + + case class Geometry( + location: GeoPoint, + location_type: String, + viewport: ViewPort, + bounds: Option[ViewPort]) + + case class ViewPort( + southwest: GeoPoint, + northeast: GeoPoint) + + case class GeoPoint(lat: Double, lng: Double) +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/Atm.scala b/src/main/scala/inc/pyc/model/Atm.scala @@ -0,0 +1,156 @@ +package inc.pyc +package model + +import lib._ +import field._ +import net.liftweb._ +import common.Box +import json._ +import JsonDSL._ +import mongodb.record._ +import mongodb.record.field._ +import record.field._ +import com.foursquare.rogue._ +import org.joda.time._, format._ + + +class Atm private () extends MongoRecord[Atm] with ObjectIdPk[Atm] { + def meta = Atm + + object name extends StringField(this, 64) + object address extends StringField(this, 255) + object city extends StringField(this, 64) + object state extends StringField(this, 64) + object postal extends PostalCodeField(this, country) + object phone extends StringField(this, 64) + object owner extends StringField(this, 64) + object website extends StringField(this, 255) + object model extends StringField(this, 64) + object times extends BsonRecordListField(this, TimeOpen) + + object country extends CountryField(this) { + override def defaultValue = Countries.USA + } + + object timezone extends TimeZoneField(this) { + override def defaultValue = "America/New_York" + } + + /** The Geolocation of the atm. */ + object loc extends MongoCaseClassField[Atm, LatLong](this) { + import Geocode._ + + def apply(geo: GeoPoint): Atm = { + val latLong = LatLong(geo.lat, geo.lng) + apply(latLong) + } + } + + override def asJValue: JObject = { + val json = super.asJValue merge + ("status" -> status) ~ + ("statusLevel" -> statusLevel) + + (json transform { + case JField("lat", lat) => JField("latitude", lat) + case JField("long", lng) => JField("longitude", lng) + }) + .asInstanceOf[JObject] + } + + + private def timeForToday: TimeOpen = { + val now = DateTime.now + val today = now.dayOfWeek.get + times.get(today) + } + + /** + * Whether the ATM is available for service or not. + */ + private def isOpen: Boolean = { + val comparator = DateTimeComparator.getTimeOnlyInstance() + val formatter = DateTimeFormat.forPattern("HH:mm") + val time = timeForToday + val now = DateTime.now + val open = formatter.parseDateTime(time.open.get) + val close = formatter.parseDateTime(time.close.get) + + (comparator.compare(now, open) == 1) && (comparator.compare(now, close) == -1) + } + + private def status = if(isOpen) "Now Open" else "Now Closed" + private def statusLevel = if(isOpen) "success" else "warning" +} + +object Atm extends Atm with RogueMetaRecord[Atm] { + import mongodb.BsonDSL._ + import Geocode._ + + override def collectionName = "atm.atms" + + ensureIndex((name.name -> 1), unique = true) + ensureIndex(loc.name -> "2d", unique = true) + + def findByName(in: String): Box[Atm] = find(name.name, in) + + /** + * Searches for ATM's within a certain distance from a geolocation. + */ + def nearby(geo: GeoPoint, kilometers: Double) = { + val radius = Degrees((kilometers / 6378.137).toDegrees) + this.where(_.loc near (geo.lat, geo.lng, radius)).fetch() + } +} + + +/** + * Range of time open for one day. + */ +class TimeOpen private () extends BsonRecord[TimeOpen] { + def meta = TimeOpen + + /* Open/Close format HH:mm */ + object open extends StringField(this, 10) + object close extends StringField(this, 10) +} + +object TimeOpen extends TimeOpen with BsonMetaRecord[TimeOpen] { + def create(open: String, close: String) = + TimeOpen.createRecord.open(open).close(close) +} + + + + +object FeaturedAtm { + import TimeOpen._ + + private val name = "Coffeetime@Hellasbakery" + + lazy val atm: Atm = Atm.findByName(name) openOr { + + val times = + create("8:00", "18:00") :: + create("6:00", "20:00") :: + create("6:00", "20:00") :: + create("6:00", "20:00") :: + create("6:00", "20:00") :: + create("6:00", "20:00") :: + create("6:00", "20:00") :: Nil + + Atm.createRecord + .name(name) + .loc(LatLong(42.654131, -73.75055600000002)) + .address("5 Clinton Square") + .city("Albany") + .state("New York") + .postal("12304") + .phone("(518) 698-9070") + .owner("Paul Paterakis") + .website("http://www.coffeetimehellas.com/") + .model("skyhook") + .times(times) + .save() + } +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/SearchedLocation.scala b/src/main/scala/inc/pyc/model/SearchedLocation.scala @@ -0,0 +1,56 @@ +package inc.pyc +package model + +import lib._ +import net.liftweb._ +import mongodb.record._ +import mongodb.record.field._ +import record.field._ +import com.foursquare.rogue.LatLong + +class SearchedLocation private () extends MongoRecord[SearchedLocation] with ObjectIdPk[SearchedLocation] { + def meta = SearchedLocation + + /** The total amount of times this was searched. */ + object total extends IntField(this) + + /** The string that was searched. */ + object name extends OptionalStringField(this, 255) + + /** The Geolocation of `name` the place. */ + object loc extends MongoCaseClassField[SearchedLocation, LatLong](this) { + import Geocode._ + + def apply(geo: GeoPoint): SearchedLocation = { + val latLong = LatLong(geo.lat, geo.lng) + apply(latLong) + } + } + + + override def save(safe: Boolean = true) = { + val qry = SearchedLocation.find(this) + + if(qry.get().isDefined) { + qry.modify(_.total inc 1).updateOne() + this + } else { + total(total.get + 1) + super.save() + } + } +} + +object SearchedLocation extends SearchedLocation with RogueMetaRecord[SearchedLocation] { + import mongodb.BsonDSL._ + + override def collectionName = "atm.search" + + ensureIndex(loc.name -> "2d", unique = true) + + def find(search: SearchedLocation) = this.where(_.loc eqs search.loc.get) + + object usa extends CountryField(SearchedLocation.createRecord) { + override def defaultValue = Countries.USA + } +} +\ No newline at end of file diff --git a/src/main/scala/inc/pyc/model/SearchedPostal.scala b/src/main/scala/inc/pyc/model/SearchedPostal.scala @@ -1,45 +0,0 @@ -package inc.pyc -package model - -import lib.RogueMetaRecord - -import net.liftweb._ -import mongodb.record._ -import mongodb.record.field._ -import record.field._ - -class SearchedPostal private () extends MongoRecord[SearchedPostal] with ObjectIdPk[SearchedPostal] { - def meta = SearchedPostal - - /* The total amount of times postal code was searched. */ - object total extends IntField(this) - - /* The postal code that was searched. */ - object postal extends PostalCodeField(this, meta.usa) - - override def save(safe: Boolean = true) = { - val qry = SearchedPostal.find(this) - - if(qry.get().isDefined) { - qry.modify(_.total inc 1).updateOne() - this - } else { - total(total.get + 1) - super.save() - } - } -} - -object SearchedPostal extends SearchedPostal with RogueMetaRecord[SearchedPostal] { - import mongodb.BsonDSL._ - - override def collectionName = "atm.find" - - ensureIndex((postal.name -> 1), true) - - def find(postal: SearchedPostal) = this.where(_.postal eqs postal.postal.get) - - object usa extends CountryField(SearchedPostal.createRecord) { - override def defaultValue = Countries.USA - } -} -\ No newline at end of file diff --git a/src/main/scala/inc/pyc/snippet/AtmSnip.scala b/src/main/scala/inc/pyc/snippet/AtmSnip.scala @@ -1,13 +1,15 @@ package inc.pyc package snippet +import lib._ import config._ import model._ import field._ import xml._ import net.liftweb._ import common._ -import json.JsonAST._ +import json._ +import JsonDSL._ import util._ import Helpers._ import http._ @@ -16,6 +18,9 @@ import JsCmds._ import SHtml._ import JE.JsVar import net.liftmodules.mongoauth.MongoAuth +import dispatch._, Defaults._ +import net.liftmodules.extras.SnippetHelper + class AtmApplication extends AngularSnippet { @@ -73,27 +78,52 @@ class AtmApplication extends AngularSnippet { } class FindAtm extends AngularSnippet { - - def roundTrips: List[RoundTripInfo] = List("submit" -> submit _) - def submit(model: JValue): JValue = { - val rec = SearchedPostal.createRecord - rec.setFieldsFromJValue(model) + def roundTrips: List[RoundTripInfo] = List( + "submit" -> submit _, "init" -> init _) + + /** Distance to check for nearby ATM's */ + val distance = 100 // kilometers (~62 miles) + + def submit(model: JValue): JValue = + for { + JString(address) <- model \ "address" + } yield + Geocode.geolocation(address)() match { + case Right(geo) => + + SearchedLocation.createRecord.name(address).loc(geo).save() + val atms = Atm.nearby(geo, distance) + + if(atms.isEmpty) + NgAlert.danger(("loc" -> + ("latitude" -> geo.lat) ~ + ("longitude" -> geo.lng) + )) + else + NgAlert.success(JArray(atms.zipWithIndex map { + case (rec, i) => + rec.asJValue merge + ("id" -> (i+1)) ~ + ("showWindow" -> (i==0)) ~ + ("page" -> "/atm/hellasbakery") + })) - rec.validate match { - case Nil => - rec.save() - NgAlert.success - case errors => - NgAlert.danger( - <i class="fa-fw fa fa-thumbs-o-down"></i> ++ - <span>Invalid submission</span>, - errors - ) - } + case Left(err) => + + //TODO notify me + NgAlert.danger + } + + /* Set featured ATM on map */ + def init(o: JValue): JValue = { + FeaturedAtm.atm.asJValue merge + ("id" -> 1) ~ + ("showWindow" -> true) } } + class NearAtmNotify extends AngularSnippet { def roundTrips: List[RoundTripInfo] = List("submit" -> submit _) @@ -118,4 +148,26 @@ class NearAtmNotify extends AngularSnippet { ) } } -} -\ No newline at end of file +} + + + +sealed trait AtmSnip extends SnippetHelper with Logger { + protected def atm: Box[Atm] + + protected def serve(snip: Atm => JValue): JValue = + for (atm <- atm ?~ "Atm not found") yield snip(atm) +} + +class AtmProfile extends AtmSnip with AngularSnippet { + + override def atm = Site.atmProfileLoc.currentValue + + def roundTrips: List[RoundTripInfo] = List("init" -> init _) + + def init(o: JValue): JValue = serve { + atm => atm.asJValue + } +} + + diff --git a/src/main/scala/inc/pyc/snippet/NgAlert.scala b/src/main/scala/inc/pyc/snippet/NgAlert.scala @@ -12,13 +12,15 @@ import json.JsonAST._ */ object NgAlert extends Logger { - private def msgBox(msgType: String, msg: NodeSeq): JValue = + private def msgBox(msgType: String, msg: NodeSeq, data: Option[JValue] = None): JValue = JObject(List( JField("msg_type", JString(msgType)), - JField("msg", JString(msg.toString)))) + JField("msg", JString(msg.toString)), + JField("data", data.getOrElse(JNull)))) def success(msg: NodeSeq): JValue = msgBox("success", msg) def success(msg: String): JValue = success(Text(msg)) + def success(data: JValue): JValue = msgBox("success", Text(""), Some(data)) def success: JValue = success(Text("")) def danger(msg: NodeSeq, errors: List[FieldError]): JValue = { @@ -29,6 +31,7 @@ object NgAlert extends Logger { } def danger(msg: String, errors: List[FieldError] = Nil): JValue = danger(Text(msg), errors) + def danger(data: JValue): JValue = msgBox("danger", Text(""), Some(data)) def danger: JValue = danger(Text(""), Nil) def info(msg: NodeSeq): JValue = msgBox("info", msg) diff --git a/src/main/scala/inc/pyc/snippet/UtilSnips.scala b/src/main/scala/inc/pyc/snippet/UtilSnips.scala @@ -4,7 +4,7 @@ package snippet import model.field._ import xml._ import net.liftweb._ -import common._ +import common.Box import http._ import util._ import js._ @@ -14,12 +14,14 @@ import Helpers._ import json.JsonAST._ import net.liftmodules._ import extras._, snippet._ +import dispatch._, Defaults._ object Assets extends AssetLoader trait AngularSnippet { implicit def boxedJValueToJValue(in: Box[JValue]): JValue = in openOr JNull implicit def listJValueToJValue(in: List[JValue]): JValue = in.headOption.getOrElse(JNull) + implicit def listFutureJValueToJValue(in: List[Future[JValue]]): JValue = in.headOption.getOrElse(Future(JNull))() def roundTrips: List[RoundTripInfo] diff --git a/src/main/webapp/app/App.js b/src/main/webapp/app/App.js @@ -1,12 +1,27 @@ var app = angular.module("app", [ - 'google-maps', 'ui.bootstrap', 'ui.router', 'ui.mask', - 'angularFileUpload', 'angular-google-analytics', 'ngThumb', - 'match', 'disabler', 'ngAlert', 'Forms', 'mgo-angular-wizard', 'ngReallyClick']); + 'google-maps', + 'ui.bootstrap', + 'ui.router', + 'ui.mask', + 'angularFileUpload', + 'angular-google-analytics', + 'ngThumb', + 'match', + 'disabler', + 'ngAlert', + 'Forms', + 'mgo-angular-wizard', + 'ngAnimate', + 'ngReallyClick']); + var ZIP_CODE_REGEXP = /^(\d{5}(-\d{4})?|[A-Z]\d[A-Z] *\d[A-Z]\d)$/; var PASSWORD_REGEXP = /^(?=.*[^a-zA-Z])\S{8,}$/; var UNSAFE_URL_REGEXP = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/; +var screen_xs = 480; +var screen_md = 992; + app.controller('NearAtmNotifyAlert', ['$scope', '$controller', function($scope, $controller) { $controller('AlertCtrl', {$scope: $scope}); @@ -386,16 +401,192 @@ app.controller('VerifyIdCtrl', ['$scope', '$controller', '$rootScope', function( }]); -app.controller('GMapCtrl', ['$scope', function($scope) { - $scope.lat = 40.778202; - $scope.long = -74.122381; - $scope.zoom = 12; + +/** Functions used for info about ATM */ +app.controller('AtmHelperFuncs', ['$scope', function($scope) { + $scope.timeRange = function(open, clos) { + var openv = open.split(":"); + var opend = new Date(2014, 1, 1, openv[0], openv[1]); + var closv = clos.split(":"); + var closd = new Date(2014, 1, 1, closv[0], closv[1]); + return formatAMPM(opend) + " - " + formatAMPM(closd); + }; + + var formatAMPM = function(date) { + var hours = date.getHours(); + var minutes = date.getMinutes(); + var ampm = hours >= 12 ? 'pm' : 'am'; + hours = hours % 12; + hours = hours ? hours : 12; // the hour '0' should be '12' + minutes = minutes < 10 ? '0'+minutes : minutes; + var strTime = hours + ':' + minutes + ' ' + ampm; + return strTime; + }; + + $scope.today = function() { + return new Date().getDay(); + }; + + $scope.day = function(i) { + if(i===0) {return "Sunday";} + if(i===1) {return "Monday";} + if(i===2) {return "Tuesday";} + if(i===3) {return "Wednesday";} + if(i===4) {return "Thursday";} + if(i===5) {return "Friday";} + if(i===6) {return "Saturday";} + }; + + $scope.imageAtm = function(marker, i) { + return "https://s3.amazonaws.com/assets-pyc/atm/"+marker.name+"/"+i+".jpg"; + }; + + $scope.pageAtm = function(marker) { + return "/atm/"+marker.name; + }; +}]); + +app.controller('FindAtmCtrl', ['$scope', '$timeout', '$location', '$anchorScroll', '$controller', function($scope, $timeout, $location, $anchorScroll, $controller) { + $controller('LoadedFormCtrl', {$scope: $scope, $controller: $controller}); + $controller('AtmHelperFuncs', {$scope: $scope}); + + $scope.init('FindAtm', 'init', function() { + $scope.markers = [angular.copy($scope.model)]; + $scope.featuredMarker = $scope.markers[0]; + $scope.currentMarker = $scope.featuredMarker; + $scope.model = {}; + + $scope.map.center = { + latitude: $scope.featuredMarker.loc.latitude, + longitude: $scope.featuredMarker.loc.longitude + }; + + setDynamicMarkers(); + }); + + $scope.nonfound = false; + + $scope.mapExpanded = window.innerWidth < screen_xs; + + $scope.expandMap = function() { + $scope.mapExpanded = true; + $location.hash("blusep"); + $anchorScroll(); + }; + + $scope.search = function() { + var success = function(alert) { + $scope.nonfound = false; + + if(alert.data) { + $scope.markers = angular.copy(alert.data); + $scope.currentMarker = $scope.markers[0]; + $scope.map.center = angular.copy($scope.currentMarker.loc); + setDynamicMarkers(); + } + }; + + var failure = function(alert) { + $scope.nonfound = true; + + if(alert.data.loc) { + $scope.map.center = alert.data.loc; + } + }; + + $scope.submit('FindAtm', 'submit', success, failure); + }; + + $scope.markers = []; + $scope.featuredMarker = {}; + + var setDynamicMarkers = function() { $timeout(function() { + var dynamicMarkers = $scope.markers; + + _.each(dynamicMarkers, function(marker) { + marker.closeClick = function() { + marker.showWindow = false; + $scope.currentMarker = null; + $scope.$apply(); + }; + marker.onClicked = function() { + $scope.onMarkerClicked(marker); + }; + }); + + $scope.map.dynamicMarkers = dynamicMarkers; + google.maps.event.trigger(mapObj, 'resize'); + }, 2000); }; + + /** Google Map Object */ + var mapObj = null; + $scope.map = { center: { - latitude: $scope.lat, - longitude: $scope.long + latitude: 40.742383, + longitude: -74.038943 }, - zoom: $scope.zoom + zoom: 15, + dynamicMarkers: [], + markers: { + fit: false + }, + icon: "https://s3.amazonaws.com/assets-pyc/blue_marker2.png", + events: { + tilesloaded: function (map) { + $scope.$apply(function () { + mapObj = map; + }); + }, + resize: function (map) { + $scope.$apply(function () { + + // pan view on resize (markers were added to map) + if(window.innerWidth >= screen_md) { + map.panBy(-150,-150); + } else if(window.innerWidth >= screen_xs) { + map.panBy(0,-230); + } else { + map.panBy(0,-40); + } + }); + } + }, + infoWindow: { + options: { + pixelOffset: { 'width': 0, 'height': -44 } + } + } + }; + + $scope.currentMarker = {}; + + $scope.onMarkerClicked = function(marker) { + if ($scope.currentMarker) { + $scope.currentMarker.closeClick(); + } + $scope.currentMarker = marker; + marker.showWindow = true; + $scope.$apply(); + }; + + + $scope.searchPlaceholder = function() { + var marker = $scope.currentMarker; + if(marker) { + return marker.address + ", " + marker.city + ", " + marker.state; + } else { + return "Search address, business name ... "; + } }; + +}]); + + +app.controller('AtmProfileCtrl', ['$scope', '$controller', function($scope, $controller) { + $controller('LoadedFormCtrl', {$scope: $scope, $controller: $controller}); + $controller('AtmHelperFuncs', {$scope: $scope}); + + $scope.init('AtmProfile', 'init'); }]); \ No newline at end of file diff --git a/src/main/webapp/atm.html b/src/main/webapp/atm.html @@ -0,0 +1,63 @@ +<div data-lift="surround?with=base-defaultless&at=content"> +<div id="atm-profile" data-lift="AtmProfile" ng-controller="AtmProfileCtrl" ng-cloak> + + <div class="row"> + <div class="col-xs-12"> + <div class="jumbotron wide"> + <h1>{{model.name}}</h1> + </div> + </div> + </div> + + <div class="row no-gutter"> + <div class="col-xs-12 col-sm-6"> + <div id="atm-info" class="jumbotron wide"> + + <div class="well well-sm bg-{{model.statusLevel}} text-white"> + <h5 class="store-status">{{model.status}}</h5> + </div> + + <address class="jumbo bold"> + {{model.address}}<br> + {{model.city}}, {{model.state}} {{model.postal}}<br> + <abbr title="Phone">P:</abbr> {{model.phone}}<br> + <abbr title="Owner">O:</abbr> {{model.owner}}<br> + <a href="{{model.website}}" target="_blank">{{model.website}}</a> + </address> + + <table class="small"> + <tbody> + <tr ng-repeat="time in model.times" + ng-class="{'today': today() == $index}"> + <td>{{ day($index) }}</td> + <td>{{ timeRange(time.open, time.close) }}</td> + </tr> + </tbody> + </table> + </div> + </div> + <div class="col-xs-12 col-sm-6 atm-images"> + <div class="row"> + <div class="col-xs-6"> + <img ng-src="{{ imageAtm(model,0) }}" /> + </div> + <div class="col-xs-6"> + <img ng-src="{{ imageAtm(model,1) }}" /> + </div> + </div> + <div class="row"> + <div class="col-xs-6"> + <img ng-src="{{ imageAtm(model,2) }}" /> + </div> + <div class="col-xs-6"> + <img ng-src="{{ imageAtm(model,3) }}" /> + </div> + </div> + </div> + </div> + + + + +</div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/faqs.html b/src/main/webapp/faqs.html @@ -17,10 +17,12 @@ <li class="padding-top-10"> <h3 class="bold">In which states is PYC available?</h3> <p> - We're rolling out our first machine at Hellas Bakery, Albany, NY in June 27. - ATMs may soon be available in the state of New Jersey. If you’re located in + We established our first machine at Hellas Bakery, Schenectady, NY in June 27. + ATMs may soon be available in other areas -- stay tuned. If you’re located in another state but would like to be the next customer to benefit from our service, - <a href="/locations">fill out this form so we can know you’re interested</a>. + <a href="/locations">fill out this form so we can know you’re interested</a>. If + you're a merchant and interested in having us host an ATM in your establishment, + <a href="/##index-cols">fill out the ATM application in the front page</a>. </p> </li> diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html @@ -1,29 +1,153 @@ <div data-lift="surround?with=base-wrap&at=content"> - <div id="content-index" class="row no-gutter"> + <div id="content-index" class="row no-gutter" data-lift="FindAtm" ng-controller="FindAtmCtrl" ng-class="{'mapexpanded': mapExpanded, 'nonfound': nonfound}"> <div class="col-xs-12 semi-content-index"> - <div class="row"> - <div id="buysell-index" class="col-sm-12 col-md-9"> + <div id="intro" class="row"> + + <div class="col-sm-12 col-md-9"> <div class="hidden-xs hidden-sm bitcoin-bg"></div> <div class="jumbotron"> - <h1>Buy Bitcoin Instantly!</h1> - <p>Coffeetime@Hellasbakery in Albany now provides Bitcoin ATM services - that are safe and secure, easy to use and user-friendly. Sign up now! + <h1 class="fade-in one">Buy Bitcoin Instantly!</h1> + <p> + <span class="jumbo bold fade-in one">ATM SERVICE OPERATOR FOR THE BITCOIN CURRENCY</span> </p> - <p data-lift="test_cond.loggedOut"> - <a href="/register" - class="btn btn-primary btn-lg" role="button"> Free Sign Up - Today </a> + + <p id="find-action" class="hidden-xxs"> + <a class="btn btn-primary btn-lg" ng-click="expandMap()" role="button"> + Find ATM Now + </a> + </p> </div> </div> - <div class="hidden-xs hidden-sm col-md-3"> - <img class="bitcoin-solid-logo" src="https://s3.amazonaws.com/assets-pyc/bitcoin.png" width="200" /> + <div class="hidden-xs hidden-sm col-md-3" ng-cloak> + <img class="bitcoin-solid-logo" width="200" src="https://s3.amazonaws.com/assets-pyc/bitcoin.png"></img> </div> </div> - <div id="applyatm-index" class="col-xs-12 col-sm-6 col-md-4"> + <div id="blusep" class="row"><div class="col-xs-12"></div></div> + + <div id="atm-map" ng-class="{'nonfound': nonfound}" class="row" ng-click="expandMap()"> + <div class="col-xs-12"> + <div class="margin-xs-fix"> + <div ng-show="nonfound" class="layer-info" ng-cloak> + <div class="row"> + <div class="col-xs-12 col-md-6 col-lg-6"> + <div id="coming-soon-msg" class="fade-in"> + <h1 class="text-white">ATMs Coming Soon</h1> + <p class="message text-white"> + There are no Bitcoin ATMs near your location. + Fill out the form to receive a notification + when an ATM is near your home town. + </p> + </div> + </div> + <div + class="col-xs-10 col-md-5 col-lg-4 col-xs-offset-1 col-md-offset-0 col-lg-offset-1"> + <span data-lift="embed?what=/templates-hidden/parts/notify-atm-form"></span> + </div> + </div> + </div> + + <div class="layer-overlay"> + <div ng-show="nonfound || !mapExpanded" class="layer-inner animate-showhide"></div> + </div> + + <div class="map-panel" ng-cloak> + <div class="well search-box fade-in two"> + + <form name="form" ng-submit="search()" novalidate ng-cloak> + <div class="input-group"> + <input + required + class="form-control" + autofocus="true" + type="text" + name="address" + placeholder="{{ searchPlaceholder() }}" + ng-model="model.address" + ng-maxlength="100"></input> + + <div class="input-group-btn"> + <button class="btn btn-default btn-primary" type="submit" + ng-disabled="form.$invalid" disabler ng-model="loading"> + + <i class="fa fa-search"></i> Search ATM + </button> + </div> + </div> + </form> + + </div> + + <div class="well preview-box hidden-xxs" ng-hide="currentMarker == null || nonfound"> + <div class="row"> + <div class="col-xs-4 col-md-12 col-lg-4"> + + <div class="row"> + <div class="col-md-5 col-lg-12"> + <a href="{{ pageAtm(currentMarker) }}" target="_blank"> + <img class="img-thumbnail atm-profile" ng-src="{{ imageAtm(currentMarker,0) }}" /> + </a> + </div> + <div class="col-md-7 col-lg-12"> + <a href="{{ pageAtm(currentMarker) }}" target="_blank" class="atm-profile-name break-word nounderline"> + {{currentMarker.name}} + </a> + </div> + </div> + </div> + <div class="col-xs-8 col-md-12 col-lg-8 business-times"> + <table class="small"> + <tbody> + <tr ng-repeat="time in currentMarker.times" + ng-class="{'today': today() == $index}"> + <td>{{ day($index) }}</td> + <td>{{ timeRange(time.open, time.close) }}</td> + </tr> + </tbody> + </table> + + </div> + + </div> + </div> + </div> + + <google-map + ng-cloak + center="map.center" + pan="true" + zoom="map.zoom" + draggable="true" + refresh="'true'" + events="map.events" + options="{disableDefaultUI: true, scrollwheel: false}"> + + <marker ng-repeat="m in map.dynamicMarkers" coords="m.loc" click="m.onClicked()" icon="map.icon" options="{title: m.name}"> + <window show="m.showWindow" closeClick="m.closeClick()" options="map.infoWindow.options" ng-cloak> + <div> + <address> + <strong>{{m.name}}</strong> <a href="{{ pageAtm(m) }}" target="_blank">more info</a><br> + {{m.address}}<br> + {{m.city}}, {{m.state}} {{m.postal}}<br> + <abbr title="Phone">P:</abbr> {{m.phone}}<br> + <a href="{{m.website}}" target="_blank">{{m.website}}</a> + <h6 class="store-status text-{{m.statusLevel}}">{{m.status}}</h6> + </address> + </div> + </window> + </marker> + + </google-map> + + </div> + </div> + </div> + + <div id="index-cols" ng-class="{mapexpanded: mapExpanded, nonfound: nonfound}" class="row slideDown"> + <div id="index-col-first" class="col-xs-12 col-sm-6 col-md-4"> <div class="row"> <div class="col-xs-12"> @@ -57,31 +181,43 @@ </div> </div> - <div id="applyatm-index-form" class="row"> + <div id="index-col-first-form" class="row fade-in"> <div class="col-xs-12 padding-top-20"> - <div class="text-primary"> - <h3>Bitcoin ATM Application</h3> - </div> <span data-lift="embed?what=/templates-hidden/parts/apply-atm-form"></span> </div> </div> </div> - <div id="findatm-index" class="col-xs-12 col-sm-6 col-md-4 bg-secondary-gradient"> + <div id="index-col-second" class="col-xs-12 col-sm-6 col-md-4 bg-secondary-gradient margin-xs-fix"> <div class="row"> <div class="col-xs-12"> - <h2 class="text-white bold">FIND ATM LOCATIONS</h2> + <h2 class="text-white bold">What Is Bitcoin?</h2> + </div> + </div> + + <div id="atm-bitcoin-video" class="row" data-lift="lazy-load"> + + <div data-lift="ignore" class="col-xs-8 col-sm-6 col-md-12 padding-bottom-20"> + <a data-lift="Menu.item?name=What Is Bitcoin" type="button" class="btn btn-default btn-lg btn-block"> + <small>Click Here to </small><strong>Understand</strong><br> + <img src="https://s3.amazonaws.com/assets-pyc/bitcoin_logo.png" alt="Click Here to Learn More About Bitcoin" style="width:100%"></img> + </a> + </div> + + <div class="col-xs-12 padding-top-10 margin-bottom-10"> + <iframe src="//www.youtube.com/embed/Gc2en3nHxA4" frameborder="0" + allowfullscreen></iframe> </div> </div> - <div class="row"> - <div class="col-xs-12 padding-top-10"> - <span data-lift="embed?what=/templates-hidden/parts/find-atm-form"></span> + <div class="row "> + <div class="col-xs-12"> + <h2 class="text-white bold">Merchants Needed!</h2> </div> </div> - - <div class="row"> + + <div class="row "> <div class="col-xs-12 padding-20"> <img src="https://maps.googleapis.com/maps/api/staticmap?center=40.736755,-74.033353&zoom=13&size=360x240&sensor=false" @@ -89,53 +225,51 @@ </div> </div> - <div class="row"> + <div class="row "> <div class="col-xs-12 text-white"> - <h5>We're in the process of finding a business in the + <h5 class="bold">We're in the process of finding a business in the Hoboken, NJ area to host a Bitcoin ATM.</h5> </div> </div> + </div> - <div id="whoweare-index" class="col-xs-12 col-sm-12 col-md-4 bg-primary-gradient text-white"> + <div id="index-col-third" class="col-xs-12 col-sm-12 col-md-4 bg-primary-gradient text-white"> + + <div class="row"> <div class="col-xs-12"> - <h2 class="text-white bold">WHO WE ARE</h2> + <h2 class="text-white bold">Who We Are</h2> </div> </div> - + <div class="row"> - <div class="col-xs-12 padding-top-10"> - <p> + <div class="col-xs-12 padding-top-10 text-white"> + <h6 class="bold"> PYC is an ATM service operator for the bitcoin currency. We enable customers to retrieve bitcoin instantly, just as they retrieve cash from a bank ATM. - - </p> + <a href="/about" class="text-gray-midlight">Click here to learn more.</a> + </h6> </div> </div> - - <div id="atm-bitcoin-video" class="row" ng-cloak> + + <div class="row"> <div class="col-xs-12"> - <div class="page-content"> - <h3>What is Bitcoin?</h3> - </div> + <h2 class="text-white bold">Follow Us On Twitter</h2> </div> + </div> + + <div class="row margin-top-10" data-lift="lazy-load"> + <div class="col-xs-12"> - <div data-lift="ignore" class="col-xs-8 col-sm-6 col-md-12 padding-bottom-20"> - <a data-lift="Menu.item?name=What Is Bitcoin" type="button" class="btn btn-default btn-lg btn-block"> - <small>Click Here to </small><strong>Understand</strong><br> - <img src="https://s3.amazonaws.com/assets-pyc/bitcoin_logo.png" alt="Click Here to Learn More About Bitcoin" style="width:100%"></img> - </a> - </div> + <a class="twitter-timeline" href="https://twitter.com/pyc_inc" data-widget-id="484237404899774464">Tweets by @pyc_inc</a> + <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script> - <div class="col-xs-12 margin-bottom-10"> - <iframe src="//www.youtube.com/embed/Gc2en3nHxA4" frameborder="0" - allowfullscreen></iframe> </div> </div> </div> - + </div> </div> </div> </div> diff --git a/src/main/webapp/less/custom.less b/src/main/webapp/less/custom.less @@ -129,12 +129,28 @@ color: @white !important; } +.text-gray-midlight { + color: @gray-mid-light !important; +} + .text-bitcoin { color: @bitcoin-color; } .bg-tertiary { - background-color: @brand-tertiary; + background-color: @brand-tertiary !important; +} + +.bg-warning { + background-color: @brand-warning !important; +} + +.bg-danger { + background-color: @brand-danger !important; +} + +.bg-success { + background-color: @brand-success !important; } .pagination-centered { @@ -179,4 +195,32 @@ h1,h2,h3,h4,h5,h6,h7 { &.bold { font-weight: @headings-font-weight + 100; } +} + +.break-word { + word-wrap:break-word; +} + +.hidden-xxs { + @media(max-width: @screen-xxs-max) { + .hidden(); + } +} + +.nounderline { + text-decoration: none !important; +} + +.boxshadow { + .box-shadow(0 10px 15px @gray-dark); +} + +.visible-xxs { + @media(max-width: @screen-xxs-max) { + .show(); + } + + @media(min-width: @screen-xs-min) { + .hidden(); + } } \ No newline at end of file diff --git a/src/main/webapp/less/overrides.less b/src/main/webapp/less/overrides.less @@ -14,7 +14,7 @@ body { background: @white; padding:0 14px 10px 14px; @media (max-width: @screen-xs-max) { - padding:0 4px 10px 4px; + padding:0 0 10px 0; } } @@ -50,9 +50,27 @@ body { font-family: @font-family-sans-serif !important; } - p { + .fa, .fa *, .fa-stack, .fa-stack * { + font-family: 'FontAwesome' !important; + } + + .jumbo { font-size: @jumbotron-font-size; + + &.bold { font-weight: 400; } + + @media(max-width: @screen-xxs-max) { + font-size: @jumbotron-font-size / 1.45; + } } + + @media(max-width: @screen-xs-max) { + h1 { font-size: 45px; } + } + + @media(max-width: @screen-xxs-max) { + h1 { font-size: 25px; } + } } /******************************************** @@ -261,4 +279,89 @@ body { @media(max-width: @screen-xxs-max) { .well-sm(); } +} + +/************************************** + * Google Maps remove overrides + *************************************/ +.gm-style { + font-family: @font-family-sans-serif; + font-size: @font-size-base; + font-weight: normal; + + .gm-style-iw { + font-size: @font-size-base; + font-weight: normal; + } +} + +/*************************************** + * Animations + ***************************************/ + +.ease-inout-transition(@t) { + -webkit-transition: all ease-in-out @t; + -moz-transition: all ease-in-out @t; + -o-transition: all ease-in-out @t; + transition: all ease-in-out @t; +} + +// +// Ng Hide +// +.animate-showhide.ng-hide-add, +.animate-showhide.ng-hide-remove { + display: block !important; + .ease-inout-transition(0.25s); + &.one { .ease-inout-transition(1.2s); } + &.two { .ease-inout-transition(1.6s); } +} + +.animate-showhide.ng-hide-add.ng-hide-add-active, +.animate-showhide.ng-hide-remove { + opacity: 0; +} + +.animate-showhide.ng-hide-add, +.animate-showhide.ng-hide-remove.ng-hide-remove-active { + opacity: 1; +} + + + +@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@keyframes fadeIn { from { opacity:0; } to { opacity:1; } } + +.fade-in { + opacity:0; /* make things invisible upon start */ + -webkit-animation:fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */ + -moz-animation:fadeIn ease-in 1; + animation:fadeIn ease-in 1; + + -webkit-animation-fill-mode:forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1)*/ + -moz-animation-fill-mode:forwards; + animation-fill-mode:forwards; + + -webkit-animation-duration:1s; + -moz-animation-duration:1s; + animation-duration:1s; +} + +.fade-in.one { +-webkit-animation-delay: 0.7s; +-moz-animation-delay: 0.7s; +animation-delay: 0.7s; +} + +.fade-in.two { +-webkit-animation-delay: 1.35s; +-moz-animation-delay:1.35s; +animation-delay: 1.35s; +} + +.fade-in.three { +-webkit-animation-delay: 1.6s; +-moz-animation-delay: 1.6s; +animation-delay: 1.6s; } \ No newline at end of file diff --git a/src/main/webapp/less/pages/atm.less b/src/main/webapp/less/pages/atm.less @@ -0,0 +1,22 @@ +#atm-profile { + + .store-status { + font-weight: 600; + } + + .jumbotron{ + &.wide { + padding: 30px 0; + margin: 0; + } + + &#atm-info { + padding-top: 0; + } + } + + .atm-images img { + width: 100%; + border: 3px solid white; + } +} +\ No newline at end of file diff --git a/src/main/webapp/less/pages/index.less b/src/main/webapp/less/pages/index.less @@ -1,60 +1,339 @@ #content-index { &.no-gutter { - margin-left: -14px; - margin-right: -14px; + @media(min-width: @screen-sm-min) { + margin-left: -14px; + margin-right: -14px; + } } + + /**************************** + * Excuse me for the mess + *****************************/ + + + @intro-div-height: 300px; + @intro-div-height-sm: 300px; + @intro-div-height-xs: 222px; + @intro-div-height-xxs: 145px; + + @blusep-height: 15px; + @index-page-height: 925px; - @buysell-height: 275px; @semi-content-padding: @jumbotron-padding / 1.5; - @media(max-width: @screen-sm-max) { - #applyatm-index { - min-height: 0; - } + @atm-map-height: 600px; + @atm-map-height-xs: 360px; + @atm-map-height-sm: 600px; - #buysell-index { - min-height: 0; - margin-top: @jumbotron-padding; + // nonfound + @atm-map-height-nonfound-sm: @atm-map-height + 250; + @atm-map-height-nonfound-xs: @atm-map-height + 450; + + + @atm-map-margin : @atm-map-height / 20; + @atm-map-margin-xs: @atm-map-height-xs / 20; + @atm-map-margin-sm: @atm-map-height-sm / 20; + + @atm-search-box-min-height: 72px; + @atm-search-box-min-height-xxs: @atm-search-box-min-height - 20; + + + + @z-layer-info: @gmap-text-overlay-zindex + 10; + @z-map-panel-box: @z-layer-info + 10; + @z-index-cols: @z-map-panel-box + 50; + + + @map-open-margin: @intro-div-height + @atm-map-height; + @map-open-margin-sm: @intro-div-height-sm + @atm-map-height-sm; + @map-open-margin-xs: @intro-div-height-xs + @atm-map-height-xs; + + @map-open-margin-nonfound-sm: @intro-div-height + @atm-map-height-nonfound-sm; + @map-open-margin-nonfound-xs: @intro-div-height-xs + @atm-map-height-nonfound-xs; + + @map-closed-margin: @intro-div-height + @atm-search-box-min-height + (@atm-map-margin * 2); + @map-closed-margin-xs: @intro-div-height-xs + @atm-search-box-min-height + (@atm-map-margin * 2); + @map-closed-margin-xxs: @intro-div-height-xxs + @atm-search-box-min-height-xxs + (@atm-map-margin * 2); + + + // height of 3rd index column when viewport is small + @index-col-third-height-sm: 900px; + + + // random fudge of content-index for mobile devices + // TODO: what to do? get rid of this! + @content-index-fudge-xs: 750px; + + // content-index height for map closed + @content-index-height: 1px + @index-page-height + @map-closed-margin; + @content-index-height-sm: 1px + @index-page-height + @map-closed-margin + @index-col-third-height-sm; + @content-index-height-xs: 1px + @index-page-height + @map-closed-margin-xs + @index-col-third-height-sm + @content-index-fudge-xs; + @content-index-height-xxs: 1px + @index-page-height + @map-closed-margin-xs + @index-col-third-height-sm + @content-index-fudge-xs; + + // content-index height for map expanded + @content-index-height-mapexp: 1px + @index-page-height + @map-open-margin; + @content-index-height-mapexp-sm: 1px + @index-page-height + @map-open-margin-sm + @index-col-third-height-sm; + @content-index-height-mapexp-xs: 1px + @index-page-height + @map-open-margin-xs + @index-col-third-height-sm + @atm-map-height + @content-index-fudge-xs; + @content-index-height-mapexp-xxs: 1px + @index-page-height + @map-open-margin-xs + @index-col-third-height-sm + @content-index-fudge-xs; + + @content-index-height-mapexp-nonfound-sm: 1px + @index-page-height + @map-open-margin-nonfound-sm + @index-col-third-height-sm; + @content-index-height-mapexp-nonfound-xs: 1px + @index-page-height + @map-open-margin-nonfound-xs + @atm-map-height + @content-index-fudge-xs; + @content-index-height-mapexp-nonfound-xxs: 1px + @index-page-height + @map-open-margin-nonfound-xs + @content-index-fudge-xs; + + + #intro { + min-height: @intro-div-height - @blusep-height !important; + margin: 0; + + .bitcoin-solid-logo { + width: 200px; + margin-top: (@intro-div-height - @blusep-height) / 6; + } + + .jumbotron { + background: transparent; + padding-bottom: 0; + } + + @media(max-width: @screen-sm-max) { + .text-center(); + } + + @media(max-width: @screen-xs-max) { + min-height: @intro-div-height-xs - @blusep-height !important; + .jumbotron { padding-top: 0 } } - #whoweare-index, #findatm-index { + @media(max-width: @screen-xxs-max) { + min-height: @intro-div-height-xxs - @blusep-height !important; + } + } + + #blusep.row { + > div { + min-height: @blusep-height; + background: url('https://s3.amazonaws.com/assets-pyc/bitcoin-network-strip.jpg'); + box-shadow: 0px 0px 9px rgba(0, 0, 0, 0.8) inset; + } + } + + #atm-map { + .angular-google-map-container, .layer-overlay, layer-info { + width: 100%; + height: @atm-map-height; + + @media(max-width: @screen-xs-max) { + height: @atm-map-height-xs; + } + + @media(max-width: @screen-sm-max) { + height: @atm-map-height-sm; + } + } + + &.nonfound { + .angular-google-map-container, .layer-overlay, layer-info { + + @media(max-width: @screen-sm-max) { + height: @atm-map-height-nonfound-sm + 80; + } + + @media(max-width: 600px) { + height: @atm-map-height-nonfound-xs + 100; + } + } + } + + .layer-overlay { + position: absolute; + + .layer-inner { + #gradient > .horizontal-three-colors(rgba(0,0,0,0.6), rgba(0,0,0,0.3), 50%, rgba(0,0,0,0.6)); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: @gmap-overlay-zindex; + } + } + + .layer-info { + position: absolute; + z-index: @z-layer-info; + margin: @atm-map-margin; + + @media(min-width: @screen-md-min) { + width: 100%; + } + + #coming-soon-msg { + @title-font-size: 50px; + @message-font-size: 20px; + + @media (max-width: @screen-sm-max) { + .text-center(); + padding: 50px; + } + + padding-top: 50px; + + h1 { + @media (max-width: @screen-xxs-max) { + font-size: @title-font-size * 0.75; + padding: 5px; + } + + @media (min-width: @screen-xs) { + font-size: @title-font-size; + padding: 45px @atm-map-margin 0px; + } + } + + p.message { + @media (max-width: @screen-xxs-max){ + font-size: @message-font-size * 0.85; + padding: 5px; + } + + @media (min-width: @screen-xs) { + font-size: @message-font-size; + padding: 0 @atm-map-margin; + } + } + } + } + + .map-panel { + position: absolute; + top: @atm-map-margin; + left: @atm-map-margin; + + @media(max-width: @screen-sm-max) { + left: 50%; // center search-box + } + + .search-box { + margin-bottom: 30px; + } + + .search-box, .preview-box { + border-radius: 5px; + z-index: @z-map-panel-box; + .box-shadow(-2px -2px 8px 0px rgba(50, 50, 50, 0.75)); + + @media(max-width: @screen-sm-max) { + width: 450px; + position: relative; + left: -50%; // center search-box + margin-left: 0; + } + + @media(max-width: @screen-xxs-max) { + width: 300px; + margin-left: 15px; + } + + @media(min-width: @screen-md-min) { + width: 450px; + min-height: @atm-search-box-min-height; + } + + @media(min-width: @screen-lg-min) { + width: 600px; + } + } + + .preview-box { + @media(min-width: @screen-xs-min) { + img.atm-profile { width: 100px; } + .business-times { padding-left: 20px; } + } + + @media(max-width: @screen-xs-max) { + padding: 10px 40px; + } + + @media(min-width: @screen-md-min) { + width: 50%; + margin-top: 10px; + img.atm-profile { width: 75px; } + .business-times { + margin-top: 10px; + padding-left: 0; + } + } + + @media(min-width: @screen-lg-min) { + width: 60%; + img.atm-profile { width: 100px; } + .business-times { + margin-top: 0; + padding-left: 20px; + } + } + + .atm-profile-name { + vertical-align: middle; + } + + table tr.today { + font-weight: bold; + } + } + } + + .gm-style { + .gm-style-iw { + .store-status { + font-weight: 600; + } + } + } + + } + + + @media(max-width: @screen-sm-max) { + #index-col-first { min-height: 0; } - & > div[class^="col-"], .semi-content-index > div[class^="col-"] { - padding-left: @semi-content-padding + 20; - padding-right: @semi-content-padding + 20; + #index-col-third { + min-height: @index-col-third-height-sm; } } - @media(min-width: @screen-md) { - #buysell-index { - min-height: @buysell-height; - padding-left: 0; // jumbotron - .jumbotron { margin-bottom: 0 } + @media(max-width: @screen-xs-max) { + #notify-atm-form { + margin-left: 15px; } - #whoweare-index, #findatm-index { + & > div[class^="col-"], .semi-content-index > #index-cols > div[class^="col-"] { + padding-left: @semi-content-padding + 30; + padding-right: (@semi-content-padding + 30) / 2; + } + } + + @media(min-width: @screen-md-min) { + + #index-col-third, #index-col-second { min-height: @index-page-height; } - #findatm-index { + #index-col-second { border-bottom-left-radius: 5px; } } - @media(min-width: @screen-sm) { - #applyatm-index, #findatm-index { + @media(min-width: @screen-sm-min) { + #index-col-first, #index-col-second { min-height: @index-page-height; } - - #findatm-index { - border-top-left-radius: 5px; - } - - & > div[class^="col-"], .semi-content-index > div[class^="col-"] { + + & > div[class^="col-"], .semi-content-index > #index-cols > div[class^="col-"] { padding-left: @semi-content-padding; padding-right: @semi-content-padding; } @@ -72,12 +351,12 @@ padding-right: 0; } - #applyatm-index { + #index-col-first { background-color: @white; } // formats the atm application form - #applyatm-index-form { + #index-col-first-form { fieldset { padding:0 } footer { background:none } } @@ -93,15 +372,117 @@ } } - .jumbotron { - background: transparent; + #index-cols, #atm-map { + width: 100%; + height: 100%; + position: absolute; + left: 0; + } + + #atm-map { + top: @intro-div-height; + + @media(max-width: @screen-xs-max) { + top: @intro-div-height-xs; + } + + @media(max-width: @screen-xxs-max) { + top: @intro-div-height-xxs; + } + } + + + + // position index-cols for map expansion + #index-cols, .slideDown.mapexpanded-add { + -webkit-transition:0.25s ease-in-out all; + transition:0.25s ease-in-out all; + + box-shadow: 0px -5px 15px 0px rgba(50, 50, 50, 0.75); + top: @map-closed-margin; // map closed @media(max-width: @screen-xs-max) { - padding-left: @jumbotron-padding + 10; - padding-right: @jumbotron-padding + 10; + top: @map-closed-margin-xs; + } + + @media(max-width: @screen-xxs-max) { + top: @map-closed-margin-xxs; + } + + &.mapexpanded, .slideDown.mapexpanded-add-active { + top: @map-open-margin; // map open + + @media(max-width: @screen-sm-max) { + top: @map-open-margin-sm; + } - p { - font-size: @jumbotron-font-size / 1.2; + @media(max-width: @screen-xxs-max) { + top: @map-open-margin-xs; + } + + &.nonfound { + + @media(max-width: @screen-sm-max) { + top: @map-open-margin-nonfound-sm; + } + + @media(max-width: 600px) { + top: @map-open-margin-nonfound-sm + 200; + } + + @media(max-width: @screen-xxs-max) { + top: @map-open-margin-nonfound-xs; + } + + } + } + + z-index: @z-index-cols; + } + + + // height of whole content + + @media(min-width: @screen-md-min) { + height: @content-index-height !important; + + &.mapexpanded { + height: @content-index-height-mapexp !important; + } + } + + @media(max-width: @screen-sm-max) { + height: @content-index-height-sm !important; + + &.mapexpanded { + height: @content-index-height-mapexp-sm !important; + + &.nonfound { + height: @content-index-height-mapexp-nonfound-sm !important; + } + } + } + + @media(max-width: @screen-xs-max) { + height: @content-index-height-xs !important; + + &.mapexpanded { + height: @content-index-height-mapexp-xs !important; + + &.nonfound { + height: @content-index-height-mapexp-nonfound-xs !important; + } + } + } + + @media(max-width: @screen-xxs-max) { + height: @content-index-height-xxs !important; + + &.mapexpanded { + height: @content-index-height-mapexp-xxs !important; + + &.nonfound { + height: @content-index-height-mapexp-nonfound-xxs !important; } } } @@ -122,8 +503,8 @@ left: 300px; } } - - .bitcoin-solid-logo { - margin-top: @buysell-height / 4; + + #twitter-widget-0 { + width: 100% !important; } } \ No newline at end of file diff --git a/src/main/webapp/less/styles.less b/src/main/webapp/less/styles.less @@ -29,7 +29,7 @@ //@import "@{BootstrapPath}/glyphicons.less"; @import "@{BootstrapPath}/dropdowns.less"; //@import "@{BootstrapPath}/button-groups.less"; - //@import "@{BootstrapPath}/input-groups.less"; +@import "@{BootstrapPath}/input-groups.less"; @import "@{BootstrapPath}/navs.less"; @import "@{BootstrapPath}/navbar.less"; //@import "@{BootstrapPath}/breadcrumbs.less"; @@ -49,8 +49,8 @@ // Components w/ JavaScript //@import "@{BootstrapPath}/modals.less"; - //@import "@{BootstrapPath}/tooltip.less"; - //@import "@{BootstrapPath}/popovers.less"; +@import "@{BootstrapPath}/tooltip.less"; +@import "@{BootstrapPath}/popovers.less"; //@import "@{BootstrapPath}/carousel.less"; // Utility classes @@ -154,6 +154,7 @@ @import "@{PagesPath}/index.less"; @import "@{PagesPath}/locations.less"; @import "@{PagesPath}/purchase_limit.less"; +@import "@{PagesPath}/atm.less"; /********************************************************* * Overrides diff --git a/src/main/webapp/less/variables.less b/src/main/webapp/less/variables.less @@ -35,7 +35,7 @@ @greenDark: #496949; @greenBright: #40AC2B; @red: #a90329; -@yellow: #b09b5b; +@yellow: #A4781B; @orange: #F79220; //#f7921d; //#C79121; @orangeDark: #A57225; @pink: #ac5287; @@ -58,7 +58,7 @@ @brand-secondary: @orange; @brand-tertiary: #95A9C1; @brand-success: #739E73; -@brand-warning: @orange; +@brand-warning: @yellow; @brand-danger: @red; @brand-info: @blue; @bitcoin-color: @orange; diff --git a/src/main/webapp/templates-hidden/base-defaultless.html b/src/main/webapp/templates-hidden/base-defaultless.html @@ -0,0 +1,9 @@ +<div data-lift="surround?with=base-wrap;at=content"> + <div class="row"> + <div class="col-xs-10 col-xs-offset-1"> + <div class="main-content"> + <div id="content"></div> + </div> + </div> + </div> +</div> +\ No newline at end of file diff --git a/src/main/webapp/templates-hidden/parts/find-atm-form.html b/src/main/webapp/templates-hidden/parts/find-atm-form.html @@ -1,25 +0,0 @@ -<div data-lift="FindAtm" ng-controller="FindAtmCtrl" ng-cloak> - <form name="form" class="smart-form client-form" ng-submit="search()" novalidate> - <div class="row" style="margin:0"> - <div class="col-xs-5"> - <section> - <label class="input"> - <input name="postal" ng-model="model.postal" type="text" placeholder="zip code" ng-pattern="zip_code_regex" required> - <b class="tooltip tooltip-top-left">Enter zip code.</b> - <span class="ng-hide help-block text-white small" ng-show="form.postal.$error.pattern"> - Invalid zip code - </span> - </label> - </section> - </div> - <div class="col-xs-3 col-md-4"> - <button type="submit" class="btn btn-primary btn-sm btn-block" ng-disabled="form.$invalid" disabler ng-model="loading">Find ATM</button> - </div> - </div> - <div class="row" style="margin:0"> - <div class="col-xs-12" style="margin-top:-10px;"> - <span class="help-block text-white">Find nearby ATMs by searching with your zip code.</span> - </div> - </div> - </form> -</div> -\ No newline at end of file diff --git a/src/main/webapp/templates-hidden/parts/notify-atm-form.html b/src/main/webapp/templates-hidden/parts/notify-atm-form.html @@ -1,4 +1,4 @@ -<div data-lift="NearAtmNotify" ng-controller="NearAtmNotifyCtrl" ng-cloak> +<div id="notify-atm-form" data-lift="NearAtmNotify" ng-controller="NearAtmNotifyCtrl" class="boxshadow" ng-cloak> <form name="form" class="smart-form client-form" ng-submit="save()" novalidate> <header>Notify me when there is a nearby ATM</header> <fieldset> diff --git a/src/main/webapp/vendor/angular-google-maps.min.js b/src/main/webapp/vendor/angular-google-maps.min.js @@ -1,7 +1,8186 @@ -/*! angular-google-maps 1.0.18 2014-04-02 +/*! angular-google-maps 1.1.6 2014-06-28 * AngularJS directives for Google Maps * git: https://github.com/nlaplante/angular-google-maps.git */ -function InfoBox(a){a=a||{},google.maps.OverlayView.apply(this,arguments),this.content_=a.content||"",this.disableAutoPan_=a.disableAutoPan||!1,this.maxWidth_=a.maxWidth||0,this.pixelOffset_=a.pixelOffset||new google.maps.Size(0,0),this.position_=a.position||new google.maps.LatLng(0,0),this.zIndex_=a.zIndex||null,this.boxClass_=a.boxClass||"infoBox",this.boxStyle_=a.boxStyle||{},this.closeBoxMargin_=a.closeBoxMargin||"2px",this.closeBoxURL_=a.closeBoxURL||"http://www.google.com/intl/en_us/mapfiles/close.gif",""===a.closeBoxURL&&(this.closeBoxURL_=""),this.infoBoxClearance_=a.infoBoxClearance||new google.maps.Size(1,1),"undefined"==typeof a.visible&&(a.visible="undefined"==typeof a.isHidden?!0:!a.isHidden),this.isHidden_=!a.visible,this.alignBottom_=a.alignBottom||!1,this.pane_=a.pane||"floatPane",this.enableEventPropagation_=a.enableEventPropagation||!1,this.div_=null,this.closeListener_=null,this.moveListener_=null,this.contextListener_=null,this.eventListeners_=null,this.fixedWidthSet_=null}function ClusterIcon(a,b){a.getMarkerClusterer().extend(ClusterIcon,google.maps.OverlayView),this.cluster_=a,this.className_=a.getMarkerClusterer().getClusterClass(),this.styles_=b,this.center_=null,this.div_=null,this.sums_=null,this.visible_=!1,this.setMap(a.getMap())}function Cluster(a){this.markerClusterer_=a,this.map_=a.getMap(),this.gridSize_=a.getGridSize(),this.minClusterSize_=a.getMinimumClusterSize(),this.averageCenter_=a.getAverageCenter(),this.markers_=[],this.center_=null,this.bounds_=null,this.clusterIcon_=new ClusterIcon(this,a.getStyles())}function MarkerClusterer(a,b,c){this.extend(MarkerClusterer,google.maps.OverlayView),b=b||[],c=c||{},this.markers_=[],this.clusters_=[],this.listeners_=[],this.activeMap_=null,this.ready_=!1,this.gridSize_=c.gridSize||60,this.minClusterSize_=c.minimumClusterSize||2,this.maxZoom_=c.maxZoom||null,this.styles_=c.styles||[],this.title_=c.title||"",this.zoomOnClick_=!0,void 0!==c.zoomOnClick&&(this.zoomOnClick_=c.zoomOnClick),this.averageCenter_=!1,void 0!==c.averageCenter&&(this.averageCenter_=c.averageCenter),this.ignoreHidden_=!1,void 0!==c.ignoreHidden&&(this.ignoreHidden_=c.ignoreHidden),this.enableRetinaIcons_=!1,void 0!==c.enableRetinaIcons&&(this.enableRetinaIcons_=c.enableRetinaIcons),this.imagePath_=c.imagePath||MarkerClusterer.IMAGE_PATH,this.imageExtension_=c.imageExtension||MarkerClusterer.IMAGE_EXTENSION,this.imageSizes_=c.imageSizes||MarkerClusterer.IMAGE_SIZES,this.calculator_=c.calculator||MarkerClusterer.CALCULATOR,this.batchSize_=c.batchSize||MarkerClusterer.BATCH_SIZE,this.batchSizeIE_=c.batchSizeIE||MarkerClusterer.BATCH_SIZE_IE,this.clusterClass_=c.clusterClass||"cluster",-1!==navigator.userAgent.toLowerCase().indexOf("msie")&&(this.batchSize_=this.batchSizeIE_),this.setupStyles_(),this.addMarkers(b,!0),this.setMap(a)}function inherits(a,b){function c(){}c.prototype=b.prototype,a.superClass_=b.prototype,a.prototype=new c,a.prototype.constructor=a}function MarkerLabel_(a,b){this.marker_=a,this.handCursorURL_=a.handCursorURL,this.labelDiv_=document.createElement("div"),this.labelDiv_.style.cssText="position: absolute; overflow: hidden;",this.eventDiv_=document.createElement("div"),this.eventDiv_.style.cssText=this.labelDiv_.style.cssText,this.eventDiv_.setAttribute("onselectstart","return false;"),this.eventDiv_.setAttribute("ondragstart","return false;"),this.crossDiv_=MarkerLabel_.getSharedCross(b)}function MarkerWithLabel(a){a=a||{},a.labelContent=a.labelContent||"",a.labelAnchor=a.labelAnchor||new google.maps.Point(0,0),a.labelClass=a.labelClass||"markerLabels",a.labelStyle=a.labelStyle||{},a.labelInBackground=a.labelInBackground||!1,"undefined"==typeof a.labelVisible&&(a.labelVisible=!0),"undefined"==typeof a.raiseOnDrag&&(a.raiseOnDrag=!0),"undefined"==typeof a.clickable&&(a.clickable=!0),"undefined"==typeof a.draggable&&(a.draggable=!1),"undefined"==typeof a.optimized&&(a.optimized=!1),a.crossImage=a.crossImage||"http"+("https:"===document.location.protocol?"s":"")+"://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png",a.handCursor=a.handCursor||"http"+("https:"===document.location.protocol?"s":"")+"://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur",a.optimized=!1,this.label=new MarkerLabel_(this,a.crossImage,a.handCursor),google.maps.Marker.apply(this,arguments)}(function(){angular.element(document).ready(function(){return(google||("undefined"!=typeof google&&null!==google?google.maps:void 0)||null!=google.maps.InfoWindow)&&(google.maps.InfoWindow.prototype._open=google.maps.InfoWindow.prototype.open,google.maps.InfoWindow.prototype._close=google.maps.InfoWindow.prototype.close,google.maps.InfoWindow.prototype._isOpen=!1,google.maps.InfoWindow.prototype.open=function(a,b){this._isOpen=!0,this._open(a,b)},google.maps.InfoWindow.prototype.close=function(){this._isOpen=!1,this._close()},google.maps.InfoWindow.prototype.isOpen=function(a){return null==a&&(a=void 0),null==a?this._isOpen:this._isOpen=a},window.InfoBox)?(window.InfoBox.prototype._open=window.InfoBox.prototype.open,window.InfoBox.prototype._close=window.InfoBox.prototype.close,window.InfoBox.prototype._isOpen=!1,window.InfoBox.prototype.open=function(a,b){this._isOpen=!0,this._open(a,b)},window.InfoBox.prototype.close=function(){this._isOpen=!1,this._close()},window.InfoBox.prototype.isOpen=function(a){return null==a&&(a=void 0),null==a?this._isOpen:this._isOpen=a}):void 0})}).call(this),function(){_.intersectionObjects=function(a,b,c){var d;return null==c&&(c=void 0),d=_.map(a,function(a){return _.find(b,function(b){return null!=c?c(a,b):_.isEqual(a,b)})}),_.filter(d,function(a){return null!=a})}}.call(this),function(){!function(){var a;return a=angular.module("google-maps",[]),a.factory("debounce",["$timeout",function(a){return function(b){var c;return c=0,function(){var d,e,f;return f=this,d=arguments,c++,e=function(a){return function(){return a===c?b.apply(f,d):void 0}}(c),a(e,0,!0)}}}])}()}.call(this),function(){this.ngGmapModule=function(a,b){var c,d;return null==b&&(b=function(){}),"string"==typeof a&&(a=a.split(".")),c=this[d=a.shift()]||(this[d]={}),c.ngGmapModule||(c.ngGmapModule=this.ngGmapModule),a.length?c.ngGmapModule(a,b):b.call(c)}}.call(this),function(){angular.module("google-maps").factory("array-sync",["add-events",function(a){var b;return b=function(b,c,d){var e,f,g,h;return e=!1,g=c.$eval(d),f=a(b,{set_at:function(a){var c;if(!e&&(c=b.getAt(a),c&&c.lng&&c.lat))return g[a].latitude=c.lat(),g[a].longitude=c.lng()},insert_at:function(a){var c;if(!e&&(c=b.getAt(a),c&&c.lng&&c.lat))return g.splice(a,0,{latitude:c.lat(),longitude:c.lng()})},remove_at:function(a){return e?void 0:g.splice(a,1)}}),h=c.$watchCollection(d,function(a){var c,d,f,g,h,i,j;if(e=!0,h=b,a){for(c=0,i=h.getLength(),f=a.length,d=Math.min(i,f),g=void 0;d>c;)j=h.getAt(c),g=a[c],(j.lat()!==g.latitude||j.lng()!==g.longitude)&&h.setAt(c,new google.maps.LatLng(g.latitude,g.longitude)),c++;for(;f>c;)g=a[c],h.push(new google.maps.LatLng(g.latitude,g.longitude)),c++;for(;i>c;)h.pop(),c++}return e=!1}),function(){return f&&(f(),f=null),h?(h(),h=null):void 0}}}])}.call(this),function(){angular.module("google-maps").factory("add-events",["$timeout",function(a){var b,c;return b=function(b,c,d){return google.maps.event.addListener(b,c,function(){return d.apply(this,arguments),a(function(){},!0)})},c=function(a,c,d){var e;return d?b(a,c,d):(e=[],angular.forEach(c,function(c,d){return e.push(b(a,d,c))}),function(){return angular.forEach(e,function(a){return _.isFunction(a)&&a(),null!==a.e&&_.isFunction(a.e)?a.e():void 0}),e=null})}}])}.call(this),function(){var a=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};this.ngGmapModule("oo",function(){var b;return b=["extended","included"],this.BaseObject=function(){function c(){}return c.extend=function(c){var d,e,f;for(d in c)e=c[d],a.call(b,d)<0&&(this[d]=e);return null!=(f=c.extended)&&f.apply(0),this},c.include=function(c){var d,e,f;for(d in c)e=c[d],a.call(b,d)<0&&(this.prototype[d]=e);return null!=(f=c.included)&&f.apply(0),this},c}()})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.managers",function(){return this.ClustererMarkerManager=function(b){function d(b,c,e){this.clear=a(this.clear,this),this.draw=a(this.draw,this),this.removeMany=a(this.removeMany,this),this.remove=a(this.remove,this),this.addMany=a(this.addMany,this),this.add=a(this.add,this);var f;d.__super__.constructor.call(this),f=this,this.opt_options=e,this.clusterer=null!=e&&void 0===c?new MarkerClusterer(b,void 0,e):null!=e&&null!=c?new MarkerClusterer(b,c,e):new MarkerClusterer(b),this.clusterer.setIgnoreHidden(!0),this.$log=directives.api.utils.Logger,this.noDrawOnSingleAddRemoves=!0,this.$log.info(this)}return c(d,b),d.prototype.add=function(a){return this.clusterer.addMarker(a,this.noDrawOnSingleAddRemoves)},d.prototype.addMany=function(a){return this.clusterer.addMarkers(a)},d.prototype.remove=function(a){return this.clusterer.removeMarker(a,this.noDrawOnSingleAddRemoves)},d.prototype.removeMany=function(a){return this.clusterer.addMarkers(a)},d.prototype.draw=function(){return this.clusterer.repaint()},d.prototype.clear=function(){return this.clusterer.clearMarkers(),this.clusterer.repaint()},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.managers",function(){return this.MarkerManager=function(b){function d(b){this.handleOptDraw=a(this.handleOptDraw,this),this.clear=a(this.clear,this),this.draw=a(this.draw,this),this.removeMany=a(this.removeMany,this),this.remove=a(this.remove,this),this.addMany=a(this.addMany,this),this.add=a(this.add,this);var c;d.__super__.constructor.call(this),c=this,this.gMap=b,this.gMarkers=[],this.$log=directives.api.utils.Logger,this.$log.info(this)}return c(d,b),d.prototype.add=function(a,b){return this.handleOptDraw(a,b,!0),this.gMarkers.push(a)},d.prototype.addMany=function(a){var b,c,d,e;for(e=[],c=0,d=a.length;d>c;c++)b=a[c],e.push(this.add(b));return e},d.prototype.remove=function(a,b){var c,d;return this.handleOptDraw(a,b,!1),b?(c=void 0,null!=this.gMarkers.indexOf?c=this.gMarkers.indexOf(a):(d=0,_.find(this.gMarkers,function(b){d+=1,b===a&&(c=d)})),null!=c?this.gMarkers.splice(c,1):void 0):void 0},d.prototype.removeMany=function(){var a,b,c,d,e;for(d=this.gMarkers,e=[],b=0,c=d.length;c>b;b++)a=d[b],e.push(this.remove(a));return e},d.prototype.draw=function(){var a,b,c,d,e,f,g,h,i,j=this;for(a=[],h=this.gMarkers,c=function(b){return b.isDrawn?void 0:b.doAdd?b.setMap(j.gMap):a.push(b)},d=0,f=h.length;f>d;d++)b=h[d],c(b);for(i=[],e=0,g=a.length;g>e;e++)b=a[e],i.push(this.remove(b,!0));return i},d.prototype.clear=function(){var a,b,c,d;for(d=this.gMarkers,b=0,c=d.length;c>b;b++)a=d[b],a.setMap(null);return delete this.gMarkers,this.gMarkers=[]},d.prototype.handleOptDraw=function(a,b,c){return b===!0?(a.setMap(c?this.gMap:null),a.isDrawn=!0):(a.isDrawn=!1,a.doAdd=c)},d}(oo.BaseObject)})}.call(this),function(){this.ngGmapModule("directives.api.utils",function(){return this.AsyncProcessor={handleLargeArray:function(a,b,c,d,e,f){var g;return null==e&&(e=100),null==f&&(f=0),void 0===a||a.length<=0?void d():(g=function(){var h,i;for(h=e,i=f;h--&&i<a.length;)b(a[i]),++i;return i<a.length?(f=i,null!=c&&c(),setTimeout(g,1)):d()})()}}})}.call(this),function(){this.ngGmapModule("directives.api.utils",function(){return this.ChildEvents={onChildCreation:function(){}}})}.call(this),function(){this.ngGmapModule("directives.api.utils",function(){return this.GmapUtil={getLabelPositionPoint:function(a){var b,c;return void 0===a?void 0:(a=/^([\d\.]+)\s([\d\.]+)$/.exec(a),b=a[1],c=a[2],b&&c?new google.maps.Point(b,c):void 0)},createMarkerOptions:function(a,b,c,d){var e;return null==d&&(d=void 0),null==c&&(c={}),e=angular.extend({},c,{position:null!=c.position?c.position:new google.maps.LatLng(a.latitude,a.longitude),icon:null!=c.icon?c.icon:b,visible:null!=c.visible?c.visible:null!=a.latitude&&null!=a.longitude}),null!=d&&(e.map=d),e},createWindowOptions:function(a,b,c,d){return null!=c&&null!=d?angular.extend({},d,{content:null!=d.content?d.content:c,position:null!=d.position?d.position:angular.isObject(a)?a.getPosition():new google.maps.LatLng(b.coords.latitude,b.coords.longitude)}):d?d:void 0},defaultDelay:50}})}.call(this),function(){var a={}.hasOwnProperty,b=function(b,c){function d(){this.constructor=b}for(var e in c)a.call(c,e)&&(b[e]=c[e]);return d.prototype=c.prototype,b.prototype=new d,b.__super__=c.prototype,b};this.ngGmapModule("directives.api.utils",function(){return this.Linked=function(a){function c(a,b,c,d){this.scope=a,this.element=b,this.attrs=c,this.ctrls=d}return b(c,a),c}(oo.BaseObject)})}.call(this),function(){this.ngGmapModule("directives.api.utils",function(){var a;return this.Logger={logger:void 0,doLog:!1,info:function(b){return a.doLog?null!=a.logger?a.logger.info(b):console.info(b):void 0},error:function(b){return a.doLog?null!=a.logger?a.logger.error(b):console.error(b):void 0}},a=this.Logger})}.call(this),function(){this.ngGmapModule("directives.api.utils",function(){return this.ModelsWatcher={didModelsChange:function(a,b){var c,d;return _.isArray(a)?a===b?!1:(d=_.intersectionObjects(a,b).length!==b.length,c=!0,d||(c=a.length!==b.length),c):(directives.api.utils.Logger.error("models property must be an array newValue of: "+a.toString()+" is not!!"),!1)}}})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.child",function(){return this.MarkerLabelChildModel=function(b){function d(b,c){this.destroy=a(this.destroy,this),this.draw=a(this.draw,this),this.setPosition=a(this.setPosition,this),this.setZIndex=a(this.setZIndex,this),this.setVisible=a(this.setVisible,this),this.setAnchor=a(this.setAnchor,this),this.setMandatoryStyles=a(this.setMandatoryStyles,this),this.setStyles=a(this.setStyles,this),this.setContent=a(this.setContent,this),this.setTitle=a(this.setTitle,this),this.getSharedCross=a(this.getSharedCross,this);var e,f,g;d.__super__.constructor.call(this),e=this,this.marker=b,this.marker.set("labelContent",c.labelContent),this.marker.set("labelAnchor",this.getLabelPositionPoint(c.labelAnchor)),this.marker.set("labelClass",c.labelClass||"labels"),this.marker.set("labelStyle",c.labelStyle||{opacity:100}),this.marker.set("labelInBackground",c.labelInBackground||!1),c.labelVisible||this.marker.set("labelVisible",!0),c.raiseOnDrag||this.marker.set("raiseOnDrag",!0),c.clickable||this.marker.set("clickable",!0),c.draggable||this.marker.set("draggable",!1),c.optimized||this.marker.set("optimized",!1),c.crossImage=null!=(f=c.crossImage)?f:document.location.protocol+"//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png",c.handCursor=null!=(g=c.handCursor)?g:document.location.protocol+"//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur",this.markerLabel=new MarkerLabel_(this.marker,c.crossImage,c.handCursor),this.marker.set("setMap",function(a){return google.maps.Marker.prototype.setMap.apply(this,arguments),e.markerLabel.setMap(a)}),this.marker.setMap(this.marker.getMap())}return c(d,b),d.include(directives.api.utils.GmapUtil),d.prototype.getSharedCross=function(a){return this.markerLabel.getSharedCross(a)},d.prototype.setTitle=function(){return this.markerLabel.setTitle()},d.prototype.setContent=function(){return this.markerLabel.setContent()},d.prototype.setStyles=function(){return this.markerLabel.setStyles()},d.prototype.setMandatoryStyles=function(){return this.markerLabel.setMandatoryStyles()},d.prototype.setAnchor=function(){return this.markerLabel.setAnchor()},d.prototype.setVisible=function(){return this.markerLabel.setVisible()},d.prototype.setZIndex=function(){return this.markerLabel.setZIndex()},d.prototype.setPosition=function(){return this.markerLabel.setPosition()},d.prototype.draw=function(){return this.markerLabel.draw()},d.prototype.destroy=function(){return null!=this.markerLabel.labelDiv_.parentNode&&null!=this.markerLabel.eventDiv_.parentNode?this.markerLabel.onRemove():void 0},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.child",function(){return this.MarkerChildModel=function(b){function d(b,c,d,e,f,g,h,i,j){var k,l=this;this.index=b,this.model=c,this.parentScope=d,this.gMap=e,this.defaults=g,this.doClick=h,this.gMarkerManager=i,this.watchDestroy=a(this.watchDestroy,this),this.setLabelOptions=a(this.setLabelOptions,this),this.isLabelDefined=a(this.isLabelDefined,this),this.setOptions=a(this.setOptions,this),this.setIcon=a(this.setIcon,this),this.setCoords=a(this.setCoords,this),this.destroy=a(this.destroy,this),this.maybeSetScopeValue=a(this.maybeSetScopeValue,this),this.createMarker=a(this.createMarker,this),this.setMyScope=a(this.setMyScope,this),k=this,this.iconKey=this.parentScope.icon,this.coordsKey=this.parentScope.coords,this.clickKey=this.parentScope.click(),this.labelContentKey=this.parentScope.labelContent,this.optionsKey=this.parentScope.options,this.labelOptionsKey=this.parentScope.labelOptions,this.myScope=this.parentScope.$new(!1),this.$injector=j,this.myScope.model=this.model,this.setMyScope(this.model,void 0,!0),this.createMarker(this.model),this.myScope.$watch("model",function(a,b){return a!==b?l.setMyScope(a,b):void 0},!0),this.$log=directives.api.utils.Logger,this.$log.info(k),this.watchDestroy(this.myScope)}return c(d,b),d.include(directives.api.utils.GmapUtil),d.prototype.setMyScope=function(a,b,c){var d=this;return null==b&&(b=void 0),null==c&&(c=!1),this.maybeSetScopeValue("icon",a,b,this.iconKey,this.evalModelHandle,c,this.setIcon),this.maybeSetScopeValue("coords",a,b,this.coordsKey,this.evalModelHandle,c,this.setCoords),this.maybeSetScopeValue("labelContent",a,b,this.labelContentKey,this.evalModelHandle,c),_.isFunction(this.clickKey)&&this.$injector?this.myScope.click=function(){return d.$injector.invoke(d.clickKey,void 0,{$markerModel:a})}:this.maybeSetScopeValue("click",a,b,this.clickKey,this.evalModelHandle,c),this.createMarker(a,b,c)},d.prototype.createMarker=function(a,b,c){var d=this;return null==b&&(b=void 0),null==c&&(c=!1),this.maybeSetScopeValue("options",a,b,this.optionsKey,function(a,b){var c;return void 0===a?void 0:(c="self"===b?a:a[b],void 0===c?c=void 0===b?d.defaults:d.myScope.options:c)},c,this.setOptions)},d.prototype.evalModelHandle=function(a,b){return void 0===a?void 0:"self"===b?a:a[b]},d.prototype.maybeSetScopeValue=function(a,b,c,d,e,f,g){var h,i;return null==g&&(g=void 0),void 0===c?(this.myScope[a]=e(b,d),void(f||null!=g&&g(this.myScope))):(i=e(c,d),h=e(b,d),h===i||this.myScope[a]===h||(this.myScope[a]=h,f)?void 0:(null!=g&&g(this.myScope),this.gMarkerManager.draw()))},d.prototype.destroy=function(){return this.myScope.$destroy()},d.prototype.setCoords=function(a){return a.$id===this.myScope.$id&&void 0!==this.gMarker?null!=a.coords?null==this.scope.coords.latitude||null==this.scope.coords.longitude?void this.$log.error("MarkerChildMarker cannot render marker as scope.coords as no position on marker: "+JSON.stringify(this.model)):(this.gMarker.setPosition(new google.maps.LatLng(a.coords.latitude,a.coords.longitude)),this.gMarker.setVisible(null!=a.coords.latitude&&null!=a.coords.longitude),this.gMarkerManager.remove(this.gMarker),this.gMarkerManager.add(this.gMarker)):this.gMarkerManager.remove(this.gMarker):void 0},d.prototype.setIcon=function(a){return a.$id===this.myScope.$id&&void 0!==this.gMarker?(this.gMarkerManager.remove(this.gMarker),this.gMarker.setIcon(a.icon),this.gMarkerManager.add(this.gMarker),this.gMarker.setPosition(new google.maps.LatLng(a.coords.latitude,a.coords.longitude)),this.gMarker.setVisible(a.coords.latitude&&null!=a.coords.longitude)):void 0},d.prototype.setOptions=function(a){var b,c=this;if(a.$id===this.myScope.$id&&(null!=this.gMarker&&(this.gMarkerManager.remove(this.gMarker),delete this.gMarker),null!=(b=a.coords)?b:"function"==typeof a.icon?a.icon(null!=a.options):void 0))return this.opts=this.createMarkerOptions(a.coords,a.icon,a.options),delete this.gMarker,this.gMarker=this.isLabelDefined(a)?new MarkerWithLabel(this.setLabelOptions(this.opts,a)):new google.maps.Marker(this.opts),this.gMarkerManager.add(this.gMarker),google.maps.event.addListener(this.gMarker,"click",function(){return c.doClick&&null!=c.myScope.click?c.myScope.click():void 0})},d.prototype.isLabelDefined=function(a){return null!=a.labelContent},d.prototype.setLabelOptions=function(a,b){return a.labelAnchor=this.getLabelPositionPoint(b.labelAnchor),a.labelClass=b.labelClass,a.labelContent=b.labelContent,a},d.prototype.watchDestroy=function(a){var b=this;return a.$on("$destroy",function(){var a;return null!=b.gMarker&&(b.gMarkerManager.remove(b.gMarker),delete b.gMarker),a=void 0})},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.child",function(){return this.WindowChildModel=function(b){function d(b,c,d,e,f,g,h,i,j,k){this.element=j,null==k&&(k=!1),this.destroy=a(this.destroy,this),this.hideWindow=a(this.hideWindow,this),this.getLatestPosition=a(this.getLatestPosition,this),this.showWindow=a(this.showWindow,this),this.handleClick=a(this.handleClick,this),this.watchCoords=a(this.watchCoords,this),this.watchShow=a(this.watchShow,this),this.createGWin=a(this.createGWin,this),this.scope=b,this.googleMapsHandles=[],this.opts=c,this.mapCtrl=e,this.markerCtrl=f,this.isIconVisibleOnClick=d,this.initialMarkerVisibility=null!=this.markerCtrl?this.markerCtrl.getVisible():!1,this.$log=directives.api.utils.Logger,this.$http=g,this.$templateCache=h,this.$compile=i,this.createGWin(),null!=this.markerCtrl&&this.markerCtrl.setClickable(!0),this.handleClick(),this.watchShow(),this.watchCoords(),this.needToManualDestroy=k,this.$log.info(this)}return c(d,b),d.include(directives.api.utils.GmapUtil),d.prototype.createGWin=function(){var a,b,c=this;return null==this.gWin&&null!=this.markerCtrl&&(a=null!=this.opts?this.opts:{},b=null!=this.element&&_.isFunction(this.element.html)?this.element.html():this.element,this.opts=null!=this.markerCtrl?this.createWindowOptions(this.markerCtrl,this.scope,b,a):{}),null!=this.opts&&void 0===this.gWin?(this.gWin=this.opts.boxClass&&window.InfoBox&&"function"==typeof window.InfoBox?new window.InfoBox(this.opts):new google.maps.InfoWindow(this.opts),this.googleMapsHandles.push(google.maps.event.addListener(this.gWin,"closeclick",function(){return null!=c.markerCtrl&&c.markerCtrl.setVisible(c.initialMarkerVisibility),c.gWin.isOpen(!1),null!=c.scope.closeClick?c.scope.closeClick():void 0}))):void 0},d.prototype.watchShow=function(){var a=this;return this.scope.$watch("show",function(b){return b?a.showWindow():a.hideWindow()})},d.prototype.watchCoords=function(){var a,b=this;return a=null!=this.markerCtrl?this.scope.$parent:this.scope,a.$watch("coords",function(a,c){return a!==c?null==a?b.hideWindow():null==a.latitude||null==a.longitude?void b.$log.error("WindowChildMarker cannot render marker as scope.coords as no position on marker: "+JSON.stringify(b.model)):b.gWin.setPosition(new google.maps.LatLng(a.latitude,a.longitude)):void 0},!0)},d.prototype.handleClick=function(){var a=this;return null!=this.markerCtrl?this.googleMapsHandles.push(google.maps.event.addListener(this.markerCtrl,"click",function(){var b;return null==a.gWin&&a.createGWin(),b=a.markerCtrl.getPosition(),null!=a.gWin&&(a.gWin.setPosition(b),a.showWindow()),a.initialMarkerVisibility=a.markerCtrl.getVisible(),a.markerCtrl.setVisible(a.isIconVisibleOnClick)})):void 0},d.prototype.showWindow=function(){var a,b=this;return a=function(){return!b.gWin||!b.scope.show&&null!=b.scope.show||b.gWin.isOpen()?void 0:b.gWin.open(b.mapCtrl)},this.scope.templateUrl?this.gWin?this.$http.get(this.scope.templateUrl,{cache:this.$templateCache}).then(function(c){var d,e;return e=b.scope.$new(),angular.isDefined(b.scope.templateParameter)&&(e.parameter=b.scope.templateParameter),d=b.$compile(c.data)(e),b.gWin.setContent(d[0]),a()}):void 0:a()},d.prototype.getLatestPosition=function(){return null!=this.gWin&&null!=this.markerCtrl?this.gWin.setPosition(this.markerCtrl.getPosition()):void 0},d.prototype.hideWindow=function(){return null!=this.gWin&&this.gWin.isOpen()?this.gWin.close():void 0},d.prototype.destroy=function(){var a;return this.hideWindow(),_.each(this.googleMapsHandles,function(a){return google.maps.event.removeListener(a)}),this.googleMapsHandles.length=0,null!=this.scope&&this.needToManualDestroy&&this.scope.$destroy(),delete this.gWin,a=void 0},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.parent",function(){return this.IMarkerParentModel=function(b){function d(b,c,d,e,f){var g,h=this;this.scope=b,this.element=c,this.attrs=d,this.mapCtrl=e,this.$timeout=f,this.linkInit=a(this.linkInit,this),this.onDestroy=a(this.onDestroy,this),this.onWatch=a(this.onWatch,this),this.watch=a(this.watch,this),this.validateScope=a(this.validateScope,this),this.onTimeOut=a(this.onTimeOut,this),g=this,this.$log=directives.api.utils.Logger,this.validateScope(b)&&(this.doClick=angular.isDefined(d.click),null!=b.options&&(this.DEFAULTS=b.options),this.$timeout(function(){return h.watch("coords",b),h.watch("icon",b),h.watch("options",b),h.onTimeOut(b),b.$on("$destroy",function(){return h.onDestroy(b)})}))}return c(d,b),d.prototype.DEFAULTS={},d.prototype.isFalse=function(a){return-1!==["false","FALSE",0,"n","N","no","NO"].indexOf(a)},d.prototype.onTimeOut=function(){},d.prototype.validateScope=function(a){var b;return null==a?!1:(b=null!=a.coords,b||this.$log.error(this.constructor.name+": no valid coords attribute found"),b)},d.prototype.watch=function(a,b){var c=this;return b.$watch(a,function(d,e){return d!==e?c.onWatch(a,b,d,e):void 0},!0)},d.prototype.onWatch=function(){throw new Exception("Not Implemented!!")},d.prototype.onDestroy=function(){throw new Exception("Not Implemented!!")},d.prototype.linkInit=function(){throw new Exception("Not Implemented!!")},d}(oo.BaseObject)})}.call(this),function(){var a={}.hasOwnProperty,b=function(b,c){function d(){this.constructor=b}for(var e in c)a.call(c,e)&&(b[e]=c[e]);return d.prototype=c.prototype,b.prototype=new d,b.__super__=c.prototype,b};this.ngGmapModule("directives.api.models.parent",function(){return this.IWindowParentModel=function(a){function c(a,b,c,d,e,f,g,h){var i;i=this,this.$log=directives.api.utils.Logger,this.$timeout=e,this.$compile=f,this.$http=g,this.$templateCache=h,null!=a.options&&(this.DEFAULTS=a.options)}return b(c,a),c.include(directives.api.utils.GmapUtil),c.prototype.DEFAULTS={},c}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.parent",function(){return this.LayerParentModel=function(b){function d(b,c,d,e,f,g,h){var i=this;return this.scope=b,this.element=c,this.attrs=d,this.mapCtrl=e,this.$timeout=f,this.onLayerCreated=null!=g?g:void 0,this.$log=null!=h?h:directives.api.utils.Logger,this.createGoogleLayer=a(this.createGoogleLayer,this),null==this.attrs.type?void this.$log.info("type attribute for the layer directive is mandatory. Layer creation aborted!!"):(this.createGoogleLayer(),this.gMap=void 0,this.doShow=!0,void this.$timeout(function(){return i.gMap=e.getMap(),angular.isDefined(i.attrs.show)&&(i.doShow=i.scope.show),null!==i.doShow&&i.doShow&&null!==i.gMap&&i.layer.setMap(i.gMap),i.scope.$watch("show",function(a,b){return a!==b?(i.doShow=a,i.layer.setMap(a?i.gMap:null)):void 0},!0),i.scope.$watch("options",function(a,b){return a!==b?(i.layer.setMap(null),i.layer=null,i.createGoogleLayer()):void 0},!0),i.scope.$on("$destroy",function(){return i.layer.setMap(null)})}))}return c(d,b),d.prototype.createGoogleLayer=function(){var a=this;return this.layer=null==this.attrs.options?void 0===this.attrs.namespace?new google.maps[this.attrs.type]:new google.maps[this.attrs.namespace][this.attrs.type]:void 0===this.attrs.namespace?new google.maps[this.attrs.type](this.scope.options):new google.maps[this.attrs.namespace][this.attrs.type](this.scope.options),this.$timeout(function(){var b;return null!=a.layer&&null!=a.onLayerCreated&&(b=a.onLayerCreated(a.scope,a.layer))?b(a.layer):void 0})},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.parent",function(){return this.MarkerParentModel=function(b){function d(b,c,e,f,g){this.onDestroy=a(this.onDestroy,this),this.onWatch=a(this.onWatch,this),this.onTimeOut=a(this.onTimeOut,this);var h;d.__super__.constructor.call(this,b,c,e,f,g),h=this}return c(d,b),d.include(directives.api.utils.GmapUtil),d.prototype.onTimeOut=function(a){var b,c=this;return b=this.createMarkerOptions(a.coords,a.icon,a.options,this.mapCtrl.getMap()),this.scope.gMarker=new google.maps.Marker(b),google.maps.event.addListener(this.scope.gMarker,"click",function(){return c.doClick&&null!=a.click?c.$timeout(function(){return c.scope.click()}):void 0}),this.setEvents(this.scope.gMarker,a),this.$log.info(this)},d.prototype.onWatch=function(a,b){switch(a){case"coords":return null!=b.coords&&null!=this.scope.gMarker?(this.scope.gMarker.setMap(this.mapCtrl.getMap()),this.scope.gMarker.setPosition(new google.maps.LatLng(b.coords.latitude,b.coords.longitude)),this.scope.gMarker.setVisible(null!=b.coords.latitude&&null!=b.coords.longitude),this.scope.gMarker.setOptions(b.options)):this.scope.gMarker.setMap(null);case"icon":if(null!=b.icon&&null!=b.coords&&null!=this.scope.gMarker)return this.scope.gMarker.setOptions(b.options),this.scope.gMarker.setIcon(b.icon),this.scope.gMarker.setMap(null),this.scope.gMarker.setMap(this.mapCtrl.getMap()),this.scope.gMarker.setPosition(new google.maps.LatLng(b.coords.latitude,b.coords.longitude)),this.scope.gMarker.setVisible(b.coords.latitude&&null!=b.coords.longitude);break;case"options":if(null!=b.coords&&null!=b.icon&&b.options)return null!=this.scope.gMarker&&this.scope.gMarker.setMap(null),delete this.scope.gMarker,this.scope.gMarker=new google.maps.Marker(this.createMarkerOptions(b.coords,b.icon,b.options,this.mapCtrl.getMap()))}},d.prototype.onDestroy=function(){var a;return void 0===this.scope.gMarker?void(a=void 0):(this.scope.gMarker.setMap(null),delete this.scope.gMarker,a=void 0) -},d.prototype.setEvents=function(a,b){return angular.isDefined(b.events)&&null!=b.events&&angular.isObject(b.events)?_.compact(_.each(b.events,function(c,d){return b.events.hasOwnProperty(d)&&angular.isFunction(b.events[d])?google.maps.event.addListener(a,d,function(){return c.apply(b,[a,d,arguments])}):void 0})):void 0},d}(directives.api.models.parent.IMarkerParentModel)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.parent",function(){return this.MarkersParentModel=function(b){function d(b,c,e,f,g,h){this.fit=a(this.fit,this),this.onDestroy=a(this.onDestroy,this),this.onWatch=a(this.onWatch,this),this.reBuildMarkers=a(this.reBuildMarkers,this),this.createMarkers=a(this.createMarkers,this),this.validateScope=a(this.validateScope,this),this.onTimeOut=a(this.onTimeOut,this);var i;d.__super__.constructor.call(this,b,c,e,f,g,h),i=this,this.markersIndex=0,this.gMarkerManager=void 0,this.scope=b,this.scope.markerModels=[],this.bigGulp=directives.api.utils.AsyncProcessor,this.$timeout=g,this.$injector=h,this.$log.info(this)}return c(d,b),d.include(directives.api.utils.ModelsWatcher),d.prototype.onTimeOut=function(a){return this.watch("models",a),this.watch("doCluster",a),this.watch("clusterOptions",a),this.watch("fit",a),this.createMarkers(a)},d.prototype.validateScope=function(a){var b;return b=angular.isUndefined(a.models)||void 0===a.models,b&&this.$log.error(this.constructor.name+": no valid models attribute found"),d.__super__.validateScope.call(this,a)||b},d.prototype.createMarkers=function(a){var b,c=this;return null!=a.doCluster&&a.doCluster===!0?null!=a.clusterOptions?void 0===this.gMarkerManager?this.gMarkerManager=new directives.api.managers.ClustererMarkerManager(this.mapCtrl.getMap(),void 0,a.clusterOptions):this.gMarkerManager.opt_options!==a.clusterOptions&&(this.gMarkerManager=new directives.api.managers.ClustererMarkerManager(this.mapCtrl.getMap(),void 0,a.clusterOptions)):this.gMarkerManager=new directives.api.managers.ClustererMarkerManager(this.mapCtrl.getMap()):this.gMarkerManager=new directives.api.managers.MarkerManager(this.mapCtrl.getMap()),b=[],a.isMarkerModelsReady=!1,this.bigGulp.handleLargeArray(a.models,function(d){var e;return a.doRebuild=!0,e=new directives.api.models.child.MarkerChildModel(c.markersIndex,d,a,c.mapCtrl,c.$timeout,c.DEFAULTS,c.doClick,c.gMarkerManager,c.$injector),c.$log.info("child",e,"markers",b),b.push(e),c.markersIndex++},function(){},function(){return c.gMarkerManager.draw(),a.markerModels=b,angular.isDefined(c.attrs.fit)&&null!=a.fit&&a.fit&&c.fit(),a.isMarkerModelsReady=!0,null!=a.onMarkerModelsReady?a.onMarkerModelsReady(a):void 0})},d.prototype.reBuildMarkers=function(a){if(a.doRebuild||void 0===a.doRebuild)return _.each(a.markerModels,function(a){return a.destroy()}),this.markersIndex=0,null!=this.gMarkerManager&&this.gMarkerManager.clear(),this.createMarkers(a)},d.prototype.onWatch=function(a,b,c,d){return"models"!==a||this.didModelsChange(c,d)?"options"===a&&null!=c?void(this.DEFAULTS=c):this.reBuildMarkers(b):void 0},d.prototype.onDestroy=function(a){var b,c,d,e;for(e=a.markerModels,c=0,d=e.length;d>c;c++)b=e[c],b.destroy();return null!=this.gMarkerManager?this.gMarkerManager.clear():void 0},d.prototype.fit=function(){var a,b;return this.mapCtrl&&null!=this.scope.markerModels&&this.scope.markerModels.length>0&&(a=new google.maps.LatLngBounds,b=!1,_.each(this.scope.markerModels,function(c){return null!=c.gMarker?(b||(b=!0),a.extend(c.gMarker.getPosition())):void 0}),b)?this.mapCtrl.getMap().fitBounds(a):void 0},d}(directives.api.models.parent.IMarkerParentModel)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api.models.parent",function(){return this.WindowsParentModel=function(b){function d(b,c,e,f,g,h,i,j,k){this.interpolateContent=a(this.interpolateContent,this),this.setChildScope=a(this.setChildScope,this),this.createWindow=a(this.createWindow,this),this.setContentKeys=a(this.setContentKeys,this),this.createChildScopesWindows=a(this.createChildScopesWindows,this),this.onMarkerModelsReady=a(this.onMarkerModelsReady,this),this.watchOurScope=a(this.watchOurScope,this),this.destroy=a(this.destroy,this),this.watchDestroy=a(this.watchDestroy,this),this.watchModels=a(this.watchModels,this),this.watch=a(this.watch,this);var l,m,n,o,p,q=this;for(d.__super__.constructor.call(this,b,c,e,f,g,h,i,j,k),m=this,this.$interpolate=k,this.windows=[],this.windwsIndex=0,this.scopePropNames=["show","coords","templateUrl","templateParameter","isIconVisibleOnClick","closeClick"],p=this.scopePropNames,n=0,o=p.length;o>n;n++)l=p[n],this[l+"Key"]=void 0;this.linked=new directives.api.utils.Linked(b,c,e,f),this.models=void 0,this.contentKeys=void 0,this.isIconVisibleOnClick=void 0,this.firstTime=!0,this.bigGulp=directives.api.utils.AsyncProcessor,this.$log.info(m),this.$timeout(function(){return q.watchOurScope(b),q.createChildScopesWindows()},50)}return c(d,b),d.include(directives.api.utils.ModelsWatcher),d.prototype.watch=function(a,b,c){var d=this;return a.$watch(b,function(a,e){return a!==e?(d[c]="function"==typeof a?a():a,_.each(d.windows,function(a){return a.scope[b]="self"===d[c]?a:a[d[c]]})):void 0},!0)},d.prototype.watchModels=function(a){var b=this;return a.$watch("models",function(a,c){return b.didModelsChange(a,c)?(b.destroy(),b.createChildScopesWindows()):void 0})},d.prototype.watchDestroy=function(a){var b=this;return a.$on("$destroy",function(){return b.destroy()})},d.prototype.destroy=function(){return _.each(this.windows,function(a){return a.destroy()}),delete this.windows,this.windows=[],this.windowsIndex=0},d.prototype.watchOurScope=function(a){var b=this;return _.each(this.scopePropNames,function(c){var d;return d=c+"Key",b[d]="function"==typeof a[c]?a[c]():a[c],b.watch(a,c,d)})},d.prototype.onMarkerModelsReady=function(a){var b=this;return this.destroy(),this.models=a.models,this.firstTime&&this.watchDestroy(a),this.setContentKeys(a.models),this.bigGulp.handleLargeArray(a.markerModels,function(a){return b.createWindow(a.model,a.gMarker,b.gMap)},function(){},function(){return b.firstTime=!1})},d.prototype.createChildScopesWindows=function(){var a,b,c=this;if(this.isIconVisibleOnClick=!0,angular.isDefined(this.linked.attrs.isiconvisibleonclick)&&(this.isIconVisibleOnClick=this.linked.scope.isIconVisibleOnClick),this.gMap=this.linked.ctrls[0].getMap(),a=this.linked.ctrls.length>1&&null!=this.linked.ctrls[1]?this.linked.ctrls[1].getMarkersScope():void 0,b=angular.isUndefined(this.linked.scope.models),b&&(void 0===a||void 0===a.markerModels&&void 0===a.models))return void this.$log.info("No models to create windows from! Need direct models or models derrived from markers!");if(null!=this.gMap){if(null!=this.linked.scope.models)return this.models=this.linked.scope.models,this.firstTime&&(this.watchModels(this.linked.scope),this.watchDestroy(this.linked.scope)),this.setContentKeys(this.linked.scope.models),this.bigGulp.handleLargeArray(this.linked.scope.models,function(a){return c.createWindow(a,void 0,c.gMap)},function(){},function(){return c.firstTime=!1});if(a.onMarkerModelsReady=this.onMarkerModelsReady,a.isMarkerModelsReady)return this.onMarkerModelsReady(a)}},d.prototype.setContentKeys=function(a){return a.length>0?this.contentKeys=Object.keys(a[0]):void 0},d.prototype.createWindow=function(a,b,c){var d,e,f,g=this;return d=this.linked.scope.$new(!1),this.setChildScope(d,a),d.$watch("model",function(a,b){return a!==b?g.setChildScope(d,a):void 0},!0),f=this.interpolateContent(this.linked.element.html(),a),e=this.createWindowOptions(b,d,f,this.DEFAULTS),this.windows.push(new directives.api.models.child.WindowChildModel(d,e,this.isIconVisibleOnClick,c,b,this.$http,this.$templateCache,this.$compile,void 0,!0))},d.prototype.setChildScope=function(a,b){var c,d,e,f,g,h=this;for(g=this.scopePropNames,d=function(c){var d,e;return d=c+"Key",e="self"===h[d]?b:b[h[d]],e!==a[c]?a[c]=e:void 0},e=0,f=g.length;f>e;e++)c=g[e],d(c);return a.model=b},d.prototype.interpolateContent=function(a,b){var c,d,e,f,g,h;if(void 0!==this.contentKeys&&0!==this.contentKeys.length){for(c=this.$interpolate(a),d={},h=this.contentKeys,f=0,g=h.length;g>f;f++)e=h[f],d[e]=b[e];return c(d)}},d}(directives.api.models.parent.IWindowParentModel)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.ILabel=function(b){function d(b){this.link=a(this.link,this);var c;c=this,this.restrict="ECMA",this.replace=!0,this.template=void 0,this.require=void 0,this.transclude=!0,this.priority=-100,this.scope={labelContent:"=content",labelAnchor:"@anchor",labelClass:"@class",labelStyle:"=style"},this.$log=directives.api.utils.Logger,this.$timeout=b}return c(d,b),d.prototype.link=function(){throw new Exception("Not Implemented!!")},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.IMarker=function(b){function d(b){this.link=a(this.link,this);var c;c=this,this.$log=directives.api.utils.Logger,this.$timeout=b,this.restrict="ECMA",this.require="^googleMap",this.priority=-1,this.transclude=!0,this.replace=!0,this.scope={coords:"=coords",icon:"=icon",click:"&click",options:"=options",events:"=events"}}return c(d,b),d.prototype.controller=["$scope","$element",function(){throw new Exception("Not Implemented!!")}],d.prototype.link=function(){throw new Exception("Not implemented!!")},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.IWindow=function(b){function d(b,c,d,e){var f;this.$timeout=b,this.$compile=c,this.$http=d,this.$templateCache=e,this.link=a(this.link,this),f=this,this.restrict="ECMA",this.template=void 0,this.transclude=!0,this.priority=-100,this.require=void 0,this.replace=!0,this.scope={coords:"=coords",show:"=show",templateUrl:"=templateurl",templateParameter:"=templateparameter",isIconVisibleOnClick:"=isiconvisibleonclick",closeClick:"&closeclick",options:"=options"},this.$log=directives.api.utils.Logger}return c(d,b),d.include(directives.api.utils.ChildEvents),d.prototype.link=function(){throw new Exception("Not Implemented!!")},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Label=function(b){function d(b){this.link=a(this.link,this);var c;d.__super__.constructor.call(this,b),c=this,this.require="^marker",this.template='<span class="angular-google-maps-marker-label" ng-transclude></span>',this.$log.info(this)}return c(d,b),d.prototype.link=function(a,b,c,d){return this.$timeout(function(){var b,c;return c=d.getMarkerScope().gMarker,null!=c&&(b=new directives.api.models.child.MarkerLabelChildModel(c,a)),a.$on("$destroy",function(){return b.destroy()})},directives.api.utils.GmapUtil.defaultDelay+25)},d}(directives.api.ILabel)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Layer=function(b){function d(b){this.$timeout=b,this.link=a(this.link,this),this.$log=directives.api.utils.Logger,this.restrict="ECMA",this.require="^googleMap",this.priority=-1,this.transclude=!0,this.template='<span class="angular-google-map-layer" ng-transclude></span>',this.replace=!0,this.scope={show:"=show",type:"=type",namespace:"=namespace",options:"=options",onCreated:"&oncreated"}}return c(d,b),d.prototype.link=function(a,b,c,d){return null!=c.oncreated?new directives.api.models.parent.LayerParentModel(a,b,c,d,this.$timeout,a.onCreated):new directives.api.models.parent.LayerParentModel(a,b,c,d,this.$timeout)},d}(oo.BaseObject)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Marker=function(b){function d(b){this.link=a(this.link,this);var c;d.__super__.constructor.call(this,b),c=this,this.template='<span class="angular-google-map-marker" ng-transclude></span>',this.$log.info(this)}return c(d,b),d.prototype.controller=["$scope","$element",function(a){return{getMarkerScope:function(){return a}}}],d.prototype.link=function(a,b,c,d){return new directives.api.models.parent.MarkerParentModel(a,b,c,d,this.$timeout)},d}(directives.api.IMarker)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Markers=function(b){function d(b,c){this.link=a(this.link,this);var e;d.__super__.constructor.call(this,b),e=this,this.template='<span class="angular-google-map-markers" ng-transclude></span>',this.scope.models="=models",this.scope.doCluster="=docluster",this.scope.clusterOptions="=clusteroptions",this.scope.fit="=fit",this.scope.labelContent="=labelcontent",this.scope.labelAnchor="@labelanchor",this.scope.labelClass="@labelclass",this.$timeout=b,this.$injector=c,this.$log.info(this)}return c(d,b),d.prototype.controller=["$scope","$element",function(a){return{getMarkersScope:function(){return a}}}],d.prototype.link=function(a,b,c,d){return new directives.api.models.parent.MarkersParentModel(a,b,c,d,this.$timeout,this.$injector)},d}(directives.api.IMarker)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Window=function(b){function d(b,c,e,f){this.link=a(this.link,this);var g;d.__super__.constructor.call(this,b,c,e,f),g=this,this.require=["^googleMap","^?marker"],this.template='<span class="angular-google-maps-window" ng-transclude></span>',this.$log.info(g)}return c(d,b),d.include(directives.api.utils.GmapUtil),d.prototype.link=function(a,b,c,d){var e=this;return this.$timeout(function(){var f,g,h,i,j,k,l,m;return h=!0,angular.isDefined(c.isiconvisibleonclick)&&(h=a.isIconVisibleOnClick),i=d[0].getMap(),j=d.length>1&&null!=d[1]?d[1].getMarkerScope().gMarker:void 0,f=null!=a.options?a.options:{},g=null!=a&&null!=a.coords&&null!=a.coords.latitude&&null!=a.coords.longitude,l=g?e.createWindowOptions(j,a,b.html(),f):f,null!=i&&(m=new directives.api.models.child.WindowChildModel(a,l,h,i,j,e.$http,e.$templateCache,e.$compile,b)),a.$on("$destroy",function(){return m.destroy()}),null!=d[1]&&(k=d[1].getMarkerScope(),k.$watch("coords",function(a){return null==a?m.hideWindow():void 0}),k.$watch("coords.latitude",function(a,b){return a!==b?m.getLatestPosition():void 0})),null!=e.onChildCreation&&null!=m?e.onChildCreation(m):void 0},directives.api.utils.GmapUtil.defaultDelay+25)},d}(directives.api.IWindow)})}.call(this),function(){var a=function(a,b){return function(){return a.apply(b,arguments)}},b={}.hasOwnProperty,c=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a};this.ngGmapModule("directives.api",function(){return this.Windows=function(b){function d(b,c,e,f,g){this.link=a(this.link,this);var h;d.__super__.constructor.call(this,b,c,e,f),h=this,this.$interpolate=g,this.require=["^googleMap","^?markers"],this.template='<span class="angular-google-maps-windows" ng-transclude></span>',this.scope.models="=models",this.$log.info(h)}return c(d,b),d.prototype.link=function(a,b,c,d){return new directives.api.models.parent.WindowsParentModel(a,b,c,d,this.$timeout,this.$compile,this.$http,this.$templateCache,this.$interpolate)},d}(directives.api.IWindow)})}.call(this),function(){angular.module("google-maps").directive("googleMap",["$log","$timeout",function(a){"use strict";var b,c,d;return d=function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},directives.api.utils.Logger.logger=a,b={mapTypeId:google.maps.MapTypeId.ROADMAP},c=function(a){return new google.maps.LatLng(a.latitude,a.longitude)},{self:this,restrict:"ECMA",transclude:!0,replace:!1,template:'<div class="angular-google-map"><div class="angular-google-map-container"></div><div ng-transclude style="display: none"></div></div>',scope:{center:"=center",zoom:"=zoom",dragging:"=dragging",control:"=",windows:"=windows",options:"=options",events:"=events",styles:"=styles",bounds:"=bounds"},controller:["$scope",function(a){return{getMap:function(){return a.map}}}],link:function(e,f,g){var h,i,j,k,l,m,n,o;if(!angular.isDefined(e.center)||!angular.isDefined(e.center.latitude)||!angular.isDefined(e.center.longitude))return void a.error("angular-google-maps: could not find a valid center property");if(!angular.isDefined(e.zoom))return void a.error("angular-google-maps: map zoom property not set");if(i=angular.element(f),i.addClass("angular-google-map"),l={options:{}},g.options&&(l.options=e.options),g.styles&&(l.styles=e.styles),g.type&&(n=g.type.toUpperCase(),google.maps.MapTypeId.hasOwnProperty(n)?l.mapTypeId=google.maps.MapTypeId[g.type.toUpperCase()]:a.error('angular-google-maps: invalid map type "'+g.type+'"')),o=new google.maps.Map(i.find("div")[1],angular.extend({},b,l,{center:new google.maps.LatLng(e.center.latitude,e.center.longitude),draggable:d(g.draggable),zoom:e.zoom,bounds:e.bounds})),h=!1,google.maps.event.addListener(o,"dragstart",function(){return h=!0,_.defer(function(){return e.$apply(function(a){return null!=a.dragging?a.dragging=h:void 0})})}),google.maps.event.addListener(o,"dragend",function(){return h=!1,_.defer(function(){return e.$apply(function(a){return null!=a.dragging?a.dragging=h:void 0})})}),google.maps.event.addListener(o,"drag",function(){var a;return a=o.center,_.defer(function(){return e.$apply(function(b){return b.center.latitude=a.lat(),b.center.longitude=a.lng()})})}),google.maps.event.addListener(o,"zoom_changed",function(){return e.zoom!==o.zoom?_.defer(function(){return e.$apply(function(a){return a.zoom=o.zoom})}):void 0}),m=!1,google.maps.event.addListener(o,"center_changed",function(){var a;return a=o.center,m?void 0:_.defer(function(){return e.$apply(function(b){return o.dragging||(b.center.latitude!==a.lat()&&(b.center.latitude=a.lat()),b.center.longitude===a.lng())?void 0:b.center.longitude=a.lng()})})}),google.maps.event.addListener(o,"idle",function(){var a,b,c;return a=o.getBounds(),b=a.getNorthEast(),c=a.getSouthWest(),_.defer(function(){return e.$apply(function(a){return null!==a.bounds&&void 0!==a.bounds&&void 0!==a.bounds?(a.bounds.northeast={latitude:b.lat(),longitude:b.lng()},a.bounds.southwest={latitude:c.lat(),longitude:c.lng()}):void 0})})}),angular.isDefined(e.events)&&null!==e.events&&angular.isObject(e.events)){k=function(a){return function(){return e.events[a].apply(e,[o,a,arguments])}};for(j in e.events)e.events.hasOwnProperty(j)&&angular.isFunction(e.events[j])&&google.maps.event.addListener(o,j,k(j))}return e.map=o,null!=g.control&&null!=e.control&&(e.control.refresh=function(a){var b;if(null!=o)return google.maps.event.trigger(o,"resize"),null!=(null!=a?a.latitude:void 0)&&null!=(null!=a?a.latitude:void 0)?(b=c(a),d(g.pan)?o.panTo(b):o.setCenter(b)):void 0},e.control.getGMap=function(){return o}),e.$watch("center",function(b,f){var i;return i=c(b),b===f||i.lat()===o.center.lat()&&i.lng()===o.center.lng()?void 0:(m=!0,h||((null==b.latitude||null==b.longitude)&&a.error("Invalid center for newValue: "+JSON.stringify(b)),d(g.pan)&&e.zoom===o.zoom?o.panTo(i):o.setCenter(i)),m=!1)},!0),e.$watch("zoom",function(a,b){return a!==b&&a!==o.zoom?_.defer(function(){return o.setZoom(a)}):void 0}),e.$watch("bounds",function(b,c){var d,e,f;if(b!==c)return null==b.northeast.latitude||null==b.northeast.longitude||null==b.southwest.latitude||null==b.southwest.longitude?void a.error("Invalid map bounds for new value: "+JSON.stringify(b)):(e=new google.maps.LatLng(b.northeast.latitude,b.northeast.longitude),f=new google.maps.LatLng(b.southwest.latitude,b.southwest.longitude),d=new google.maps.LatLngBounds(f,e),o.fitBounds(d))}),e.$watch("options",function(a,b){return _.isEqual(a,b)||(l.options=a,null==o)?void 0:o.setOptions(l)},!0),e.$watch("styles",function(a,b){return _.isEqual(a,b)||(l.styles=a,null==o)?void 0:o.setOptions(l)},!0)}}}])}.call(this),function(){angular.module("google-maps").directive("marker",["$timeout",function(a){return new directives.api.Marker(a)}])}.call(this),function(){angular.module("google-maps").directive("markers",["$timeout","$injector",function(a,b){return new directives.api.Markers(a,b)}])}.call(this),function(){angular.module("google-maps").directive("markerLabel",["$log","$timeout",function(a,b){return new directives.api.Label(b)}])}.call(this),function(){angular.module("google-maps").directive("polygon",["$log","$timeout",function(a,b){var c,d,e,f,g;return g=function(a){var b;for(b=0;b<a.length;){if(angular.isUndefined(a[b].latitude)||angular.isUndefined(a[b].longitude))return!1;b++}return!0},d=function(a){var b,c;for(c=new google.maps.MVCArray,b=0;b<a.length;)c.push(new google.maps.LatLng(a[b].latitude,a[b].longitude)),b++;return c},e=function(a,b){var c,d;for(c=new google.maps.LatLngBounds,d=0;d<b.length;)c.extend(b.getAt(d)),d++;return a.fitBounds(c)},f=function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},c={},{restrict:"ECA",require:"^googleMap",replace:!0,scope:{path:"=path",stroke:"=stroke",clickable:"=",draggable:"=",editable:"=",geodesic:"=",icons:"=icons",visible:"="},link:function(h,i,j,k){return angular.isUndefined(h.path)||null===h.path||h.path.length<2||!g(h.path)?void a.error("polyline: no valid path attribute found"):b(function(){var a,b,g,i,l,m,n,o;return a=k.getMap(),i=d(h.path),b=angular.extend({},c,{map:a,path:i,strokeColor:h.stroke&&h.stroke.color,strokeOpacity:h.stroke&&h.stroke.opacity,strokeWeight:h.stroke&&h.stroke.weight}),angular.forEach({clickable:!0,draggable:!1,editable:!1,geodesic:!1,visible:!0},function(a,c){return b[c]=angular.isUndefined(h[c])||null===h[c]?a:h[c]}),o=new google.maps.Polyline(b),f(j.fit)&&e(a,i),angular.isDefined(h.editable)&&h.$watch("editable",function(a){return o.setEditable(a)}),angular.isDefined(h.draggable)&&h.$watch("draggable",function(a){return o.setDraggable(a)}),angular.isDefined(h.visible)&&h.$watch("visible",function(a){return o.setVisible(a)}),m=void 0,g=void 0,l=void 0,n=o.getPath(),m=google.maps.event.addListener(n,"set_at",function(a){var b;return b=n.getAt(a),b&&b.lng&&b.lat?(h.path[a].latitude=b.lat(),h.path[a].longitude=b.lng(),h.$apply()):void 0}),g=google.maps.event.addListener(n,"insert_at",function(a){var b;return b=n.getAt(a),b&&b.lng&&b.lat?(h.path.splice(a,0,{latitude:b.lat(),longitude:b.lng()}),h.$apply()):void 0}),l=google.maps.event.addListener(n,"remove_at",function(a){return h.path.splice(a,1),h.$apply()}),h.$watch("path",function(b){var c,d,g,h,i,k,l;if(i=o.getPath(),b!==i){if(!b)return o.setMap(null);for(o.setMap(a),c=0,k=i.getLength(),g=b.length,d=Math.min(k,g);d>c;)l=i.getAt(c),h=b[c],(l.lat()!==h.latitude||l.lng()!==h.longitude)&&i.setAt(c,new google.maps.LatLng(h.latitude,h.longitude)),c++;for(;g>c;)h=b[c],i.push(new google.maps.LatLng(h.latitude,h.longitude)),c++;for(;k>c;)i.pop(),c++;if(f(j.fit))return e(a,i)}},!0),h.$on("$destroy",function(){return o.setMap(null),m(),m=null,g(),g=null,l(),l=null})})}}}])}.call(this),function(){angular.module("google-maps").directive("polyline",["$log","$timeout","array-sync",function(a,b,c){var d,e,f,g,h;return h=function(a){var b;for(b=0;b<a.length;){if(angular.isUndefined(a[b].latitude)||angular.isUndefined(a[b].longitude))return!1;b++}return!0},e=function(a){var b,c;for(c=new google.maps.MVCArray,b=0;b<a.length;)c.push(new google.maps.LatLng(a[b].latitude,a[b].longitude)),b++;return c},f=function(a,b){var c,d;for(c=new google.maps.LatLngBounds,d=0;d<b.length;)c.extend(b.getAt(d)),d++;return a.fitBounds(c)},g=function(a){return angular.isDefined(a)&&null!==a&&a===!0||"1"===a||"y"===a||"true"===a},d={},{restrict:"ECA",replace:!0,require:"^googleMap",scope:{path:"=path",stroke:"=stroke",clickable:"=",draggable:"=",editable:"=",geodesic:"=",icons:"=icons",visible:"="},link:function(i,j,k,l){return angular.isUndefined(i.path)||null===i.path||i.path.length<2||!h(i.path)?void a.error("polyline: no valid path attribute found"):b(function(){var a,b,h,j;return b=function(a){var b;return b=angular.extend({},d,{map:h,path:a,strokeColor:i.stroke&&i.stroke.color,strokeOpacity:i.stroke&&i.stroke.opacity,strokeWeight:i.stroke&&i.stroke.weight}),angular.forEach({clickable:!0,draggable:!1,editable:!1,geodesic:!1,visible:!0},function(a,c){return b[c]=angular.isUndefined(i[c])||null===i[c]?a:i[c]}),b},h=l.getMap(),j=new google.maps.Polyline(b(e(i.path))),g(k.fit)&&f(h,pathPoints),angular.isDefined(i.editable)&&i.$watch("editable",function(a){return j.setEditable(a)}),angular.isDefined(i.draggable)&&i.$watch("draggable",function(a){return j.setDraggable(a)}),angular.isDefined(i.visible)&&i.$watch("visible",function(a){return j.setVisible(a)}),angular.isDefined(i.geodesic)&&i.$watch("geodesic",function(){return j.setOptions(b(j.getPath()))}),angular.isDefined(i.stroke)&&angular.isDefined(i.stroke.weight)&&i.$watch("stroke.weight",function(){return j.setOptions(b(j.getPath()))}),angular.isDefined(i.stroke)&&angular.isDefined(i.stroke.color)&&i.$watch("stroke.color",function(){return j.setOptions(b(j.getPath()))}),a=c(j.getPath(),i,"path"),i.$on("$destroy",function(){return j.setMap(null),a?(a(),a=null):void 0})})}}}])}.call(this),function(){angular.module("google-maps").directive("window",["$timeout","$compile","$http","$templateCache",function(a,b,c,d){return new directives.api.Window(a,b,c,d)}])}.call(this),function(){angular.module("google-maps").directive("windows",["$timeout","$compile","$http","$templateCache","$interpolate",function(a,b,c,d,e){return new directives.api.Windows(a,b,c,d,e)}])}.call(this),function(){angular.module("google-maps").directive("layer",["$timeout",function(a){return new directives.api.Layer(a)}])}.call(this),InfoBox.prototype=new google.maps.OverlayView,InfoBox.prototype.createInfoBoxDiv_=function(){var a,b,c,d=this,e=function(a){a.cancelBubble=!0,a.stopPropagation&&a.stopPropagation()},f=function(a){a.returnValue=!1,a.preventDefault&&a.preventDefault(),d.enableEventPropagation_||e(a)};if(!this.div_){if(this.div_=document.createElement("div"),this.setBoxStyle_(),"undefined"==typeof this.content_.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+this.content_:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(this.content_)),this.getPanes()[this.pane_].appendChild(this.div_),this.addClickHandler_(),this.div_.style.width?this.fixedWidthSet_=!0:0!==this.maxWidth_&&this.div_.offsetWidth>this.maxWidth_?(this.div_.style.width=this.maxWidth_,this.div_.style.overflow="auto",this.fixedWidthSet_=!0):(c=this.getBoxWidths_(),this.div_.style.width=this.div_.offsetWidth-c.left-c.right+"px",this.fixedWidthSet_=!1),this.panBox_(this.disableAutoPan_),!this.enableEventPropagation_){for(this.eventListeners_=[],b=["mousedown","mouseover","mouseout","mouseup","click","dblclick","touchstart","touchend","touchmove"],a=0;a<b.length;a++)this.eventListeners_.push(google.maps.event.addDomListener(this.div_,b[a],e));this.eventListeners_.push(google.maps.event.addDomListener(this.div_,"mouseover",function(){this.style.cursor="default"}))}this.contextListener_=google.maps.event.addDomListener(this.div_,"contextmenu",f),google.maps.event.trigger(this,"domready")}},InfoBox.prototype.getCloseBoxImg_=function(){var a="";return""!==this.closeBoxURL_&&(a="<img",a+=" src='"+this.closeBoxURL_+"'",a+=" align=right",a+=" style='",a+=" position: relative;",a+=" cursor: pointer;",a+=" margin: "+this.closeBoxMargin_+";",a+="'>"),a},InfoBox.prototype.addClickHandler_=function(){var a;""!==this.closeBoxURL_?(a=this.div_.firstChild,this.closeListener_=google.maps.event.addDomListener(a,"click",this.getCloseClickHandler_())):this.closeListener_=null},InfoBox.prototype.getCloseClickHandler_=function(){var a=this;return function(b){b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation(),google.maps.event.trigger(a,"closeclick"),a.close()}},InfoBox.prototype.panBox_=function(a){var b,c,d=0,e=0;if(!a&&(b=this.getMap(),b instanceof google.maps.Map)){b.getBounds().contains(this.position_)||b.setCenter(this.position_),c=b.getBounds();var f=b.getDiv(),g=f.offsetWidth,h=f.offsetHeight,i=this.pixelOffset_.width,j=this.pixelOffset_.height,k=this.div_.offsetWidth,l=this.div_.offsetHeight,m=this.infoBoxClearance_.width,n=this.infoBoxClearance_.height,o=this.getProjection().fromLatLngToContainerPixel(this.position_);if(o.x<-i+m?d=o.x+i-m:o.x+k+i+m>g&&(d=o.x+k+i+m-g),this.alignBottom_?o.y<-j+n+l?e=o.y+j-n-l:o.y+j+n>h&&(e=o.y+j+n-h):o.y<-j+n?e=o.y+j-n:o.y+l+j+n>h&&(e=o.y+l+j+n-h),0!==d||0!==e){{b.getCenter()}b.panBy(d,e)}}},InfoBox.prototype.setBoxStyle_=function(){var a,b;if(this.div_){this.div_.className=this.boxClass_,this.div_.style.cssText="",b=this.boxStyle_;for(a in b)b.hasOwnProperty(a)&&(this.div_.style[a]=b[a]);"undefined"!=typeof this.div_.style.opacity&&""!==this.div_.style.opacity&&(this.div_.style.filter="alpha(opacity="+100*this.div_.style.opacity+")"),this.div_.style.position="absolute",this.div_.style.visibility="hidden",null!==this.zIndex_&&(this.div_.style.zIndex=this.zIndex_)}},InfoBox.prototype.getBoxWidths_=function(){var a,b={top:0,bottom:0,left:0,right:0},c=this.div_;return document.defaultView&&document.defaultView.getComputedStyle?(a=c.ownerDocument.defaultView.getComputedStyle(c,""),a&&(b.top=parseInt(a.borderTopWidth,10)||0,b.bottom=parseInt(a.borderBottomWidth,10)||0,b.left=parseInt(a.borderLeftWidth,10)||0,b.right=parseInt(a.borderRightWidth,10)||0)):document.documentElement.currentStyle&&c.currentStyle&&(b.top=parseInt(c.currentStyle.borderTopWidth,10)||0,b.bottom=parseInt(c.currentStyle.borderBottomWidth,10)||0,b.left=parseInt(c.currentStyle.borderLeftWidth,10)||0,b.right=parseInt(c.currentStyle.borderRightWidth,10)||0),b},InfoBox.prototype.onRemove=function(){this.div_&&(this.div_.parentNode.removeChild(this.div_),this.div_=null) -},InfoBox.prototype.draw=function(){this.createInfoBoxDiv_();var a=this.getProjection().fromLatLngToDivPixel(this.position_);this.div_.style.left=a.x+this.pixelOffset_.width+"px",this.alignBottom_?this.div_.style.bottom=-(a.y+this.pixelOffset_.height)+"px":this.div_.style.top=a.y+this.pixelOffset_.height+"px",this.div_.style.visibility=this.isHidden_?"hidden":"visible"},InfoBox.prototype.setOptions=function(a){"undefined"!=typeof a.boxClass&&(this.boxClass_=a.boxClass,this.setBoxStyle_()),"undefined"!=typeof a.boxStyle&&(this.boxStyle_=a.boxStyle,this.setBoxStyle_()),"undefined"!=typeof a.content&&this.setContent(a.content),"undefined"!=typeof a.disableAutoPan&&(this.disableAutoPan_=a.disableAutoPan),"undefined"!=typeof a.maxWidth&&(this.maxWidth_=a.maxWidth),"undefined"!=typeof a.pixelOffset&&(this.pixelOffset_=a.pixelOffset),"undefined"!=typeof a.alignBottom&&(this.alignBottom_=a.alignBottom),"undefined"!=typeof a.position&&this.setPosition(a.position),"undefined"!=typeof a.zIndex&&this.setZIndex(a.zIndex),"undefined"!=typeof a.closeBoxMargin&&(this.closeBoxMargin_=a.closeBoxMargin),"undefined"!=typeof a.closeBoxURL&&(this.closeBoxURL_=a.closeBoxURL),"undefined"!=typeof a.infoBoxClearance&&(this.infoBoxClearance_=a.infoBoxClearance),"undefined"!=typeof a.isHidden&&(this.isHidden_=a.isHidden),"undefined"!=typeof a.visible&&(this.isHidden_=!a.visible),"undefined"!=typeof a.enableEventPropagation&&(this.enableEventPropagation_=a.enableEventPropagation),this.div_&&this.draw()},InfoBox.prototype.setContent=function(a){this.content_=a,this.div_&&(this.closeListener_&&(google.maps.event.removeListener(this.closeListener_),this.closeListener_=null),this.fixedWidthSet_||(this.div_.style.width=""),"undefined"==typeof a.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+a:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(a)),this.fixedWidthSet_||(this.div_.style.width=this.div_.offsetWidth+"px","undefined"==typeof a.nodeType?this.div_.innerHTML=this.getCloseBoxImg_()+a:(this.div_.innerHTML=this.getCloseBoxImg_(),this.div_.appendChild(a))),this.addClickHandler_()),google.maps.event.trigger(this,"content_changed")},InfoBox.prototype.setPosition=function(a){this.position_=a,this.div_&&this.draw(),google.maps.event.trigger(this,"position_changed")},InfoBox.prototype.setZIndex=function(a){this.zIndex_=a,this.div_&&(this.div_.style.zIndex=a),google.maps.event.trigger(this,"zindex_changed")},InfoBox.prototype.setVisible=function(a){this.isHidden_=!a,this.div_&&(this.div_.style.visibility=this.isHidden_?"hidden":"visible")},InfoBox.prototype.getContent=function(){return this.content_},InfoBox.prototype.getPosition=function(){return this.position_},InfoBox.prototype.getZIndex=function(){return this.zIndex_},InfoBox.prototype.getVisible=function(){var a;return a="undefined"==typeof this.getMap()||null===this.getMap()?!1:!this.isHidden_},InfoBox.prototype.show=function(){this.isHidden_=!1,this.div_&&(this.div_.style.visibility="visible")},InfoBox.prototype.hide=function(){this.isHidden_=!0,this.div_&&(this.div_.style.visibility="hidden")},InfoBox.prototype.open=function(a,b){var c=this;b&&(this.position_=b.getPosition(),this.moveListener_=google.maps.event.addListener(b,"position_changed",function(){c.setPosition(this.getPosition())})),this.setMap(a),this.div_&&this.panBox_()},InfoBox.prototype.close=function(){var a;if(this.closeListener_&&(google.maps.event.removeListener(this.closeListener_),this.closeListener_=null),this.eventListeners_){for(a=0;a<this.eventListeners_.length;a++)google.maps.event.removeListener(this.eventListeners_[a]);this.eventListeners_=null}this.moveListener_&&(google.maps.event.removeListener(this.moveListener_),this.moveListener_=null),this.contextListener_&&(google.maps.event.removeListener(this.contextListener_),this.contextListener_=null),this.setMap(null)},ClusterIcon.prototype.onAdd=function(){var a,b,c=this;this.div_=document.createElement("div"),this.div_.className=this.className_,this.visible_&&this.show(),this.getPanes().overlayMouseTarget.appendChild(this.div_),this.boundsChangedListener_=google.maps.event.addListener(this.getMap(),"bounds_changed",function(){b=a}),google.maps.event.addDomListener(this.div_,"mousedown",function(){a=!0,b=!1}),google.maps.event.addDomListener(this.div_,"click",function(d){if(a=!1,!b){var e,f,g=c.cluster_.getMarkerClusterer();google.maps.event.trigger(g,"click",c.cluster_),google.maps.event.trigger(g,"clusterclick",c.cluster_),g.getZoomOnClick()&&(f=g.getMaxZoom(),e=c.cluster_.getBounds(),g.getMap().fitBounds(e),setTimeout(function(){g.getMap().fitBounds(e),null!==f&&g.getMap().getZoom()>f&&g.getMap().setZoom(f+1)},100)),d.cancelBubble=!0,d.stopPropagation&&d.stopPropagation()}}),google.maps.event.addDomListener(this.div_,"mouseover",function(){var a=c.cluster_.getMarkerClusterer();google.maps.event.trigger(a,"mouseover",c.cluster_)}),google.maps.event.addDomListener(this.div_,"mouseout",function(){var a=c.cluster_.getMarkerClusterer();google.maps.event.trigger(a,"mouseout",c.cluster_)})},ClusterIcon.prototype.onRemove=function(){this.div_&&this.div_.parentNode&&(this.hide(),google.maps.event.removeListener(this.boundsChangedListener_),google.maps.event.clearInstanceListeners(this.div_),this.div_.parentNode.removeChild(this.div_),this.div_=null)},ClusterIcon.prototype.draw=function(){if(this.visible_){var a=this.getPosFromLatLng_(this.center_);this.div_.style.top=a.y+"px",this.div_.style.left=a.x+"px"}},ClusterIcon.prototype.hide=function(){this.div_&&(this.div_.style.display="none"),this.visible_=!1},ClusterIcon.prototype.show=function(){if(this.div_){var a="",b=this.backgroundPosition_.split(" "),c=parseInt(b[0].trim(),10),d=parseInt(b[1].trim(),10),e=this.getPosFromLatLng_(this.center_);this.div_.style.cssText=this.createCss(e),a="<img src='"+this.url_+"' style='position: absolute; top: "+d+"px; left: "+c+"px; ",this.cluster_.getMarkerClusterer().enableRetinaIcons_||(a+="clip: rect("+-1*d+"px, "+(-1*c+this.width_)+"px, "+(-1*d+this.height_)+"px, "+-1*c+"px);"),a+="'>",this.div_.innerHTML=a+"<div style='position: absolute;top: "+this.anchorText_[0]+"px;left: "+this.anchorText_[1]+"px;color: "+this.textColor_+";font-size: "+this.textSize_+"px;font-family: "+this.fontFamily_+";font-weight: "+this.fontWeight_+";font-style: "+this.fontStyle_+";text-decoration: "+this.textDecoration_+";text-align: center;width: "+this.width_+"px;line-height:"+this.height_+"px;'>"+this.sums_.text+"</div>",this.div_.title="undefined"==typeof this.sums_.title||""===this.sums_.title?this.cluster_.getMarkerClusterer().getTitle():this.sums_.title,this.div_.style.display=""}this.visible_=!0},ClusterIcon.prototype.useStyle=function(a){this.sums_=a;var b=Math.max(0,a.index-1);b=Math.min(this.styles_.length-1,b);var c=this.styles_[b];this.url_=c.url,this.height_=c.height,this.width_=c.width,this.anchorText_=c.anchorText||[0,0],this.anchorIcon_=c.anchorIcon||[parseInt(this.height_/2,10),parseInt(this.width_/2,10)],this.textColor_=c.textColor||"black",this.textSize_=c.textSize||11,this.textDecoration_=c.textDecoration||"none",this.fontWeight_=c.fontWeight||"bold",this.fontStyle_=c.fontStyle||"normal",this.fontFamily_=c.fontFamily||"Arial,sans-serif",this.backgroundPosition_=c.backgroundPosition||"0 0"},ClusterIcon.prototype.setCenter=function(a){this.center_=a},ClusterIcon.prototype.createCss=function(a){var b=[];return b.push("cursor: pointer;"),b.push("position: absolute; top: "+a.y+"px; left: "+a.x+"px;"),b.push("width: "+this.width_+"px; height: "+this.height_+"px;"),b.join("")},ClusterIcon.prototype.getPosFromLatLng_=function(a){var b=this.getProjection().fromLatLngToDivPixel(a);return b.x-=this.anchorIcon_[1],b.y-=this.anchorIcon_[0],b.x=parseInt(b.x,10),b.y=parseInt(b.y,10),b},Cluster.prototype.getSize=function(){return this.markers_.length},Cluster.prototype.getMarkers=function(){return this.markers_},Cluster.prototype.getCenter=function(){return this.center_},Cluster.prototype.getMap=function(){return this.map_},Cluster.prototype.getMarkerClusterer=function(){return this.markerClusterer_},Cluster.prototype.getBounds=function(){var a,b=new google.maps.LatLngBounds(this.center_,this.center_),c=this.getMarkers();for(a=0;a<c.length;a++)b.extend(c[a].getPosition());return b},Cluster.prototype.remove=function(){this.clusterIcon_.setMap(null),this.markers_=[],delete this.markers_},Cluster.prototype.addMarker=function(a){var b,c,d;if(this.isMarkerAlreadyAdded_(a))return!1;if(this.center_){if(this.averageCenter_){var e=this.markers_.length+1,f=(this.center_.lat()*(e-1)+a.getPosition().lat())/e,g=(this.center_.lng()*(e-1)+a.getPosition().lng())/e;this.center_=new google.maps.LatLng(f,g),this.calculateBounds_()}}else this.center_=a.getPosition(),this.calculateBounds_();if(a.isAdded=!0,this.markers_.push(a),c=this.markers_.length,d=this.markerClusterer_.getMaxZoom(),null!==d&&this.map_.getZoom()>d)a.getMap()!==this.map_&&a.setMap(this.map_);else if(c<this.minClusterSize_)a.getMap()!==this.map_&&a.setMap(this.map_);else if(c===this.minClusterSize_)for(b=0;c>b;b++)this.markers_[b].setMap(null);else a.setMap(null);return this.updateIcon_(),!0},Cluster.prototype.isMarkerInClusterBounds=function(a){return this.bounds_.contains(a.getPosition())},Cluster.prototype.calculateBounds_=function(){var a=new google.maps.LatLngBounds(this.center_,this.center_);this.bounds_=this.markerClusterer_.getExtendedBounds(a)},Cluster.prototype.updateIcon_=function(){var a=this.markers_.length,b=this.markerClusterer_.getMaxZoom();if(null!==b&&this.map_.getZoom()>b)return void this.clusterIcon_.hide();if(a<this.minClusterSize_)return void this.clusterIcon_.hide();var c=this.markerClusterer_.getStyles().length,d=this.markerClusterer_.getCalculator()(this.markers_,c);this.clusterIcon_.setCenter(this.center_),this.clusterIcon_.useStyle(d),this.clusterIcon_.show()},Cluster.prototype.isMarkerAlreadyAdded_=function(a){var b;if(this.markers_.indexOf)return-1!==this.markers_.indexOf(a);for(b=0;b<this.markers_.length;b++)if(a===this.markers_[b])return!0;return!1},MarkerClusterer.prototype.onAdd=function(){var a=this;this.activeMap_=this.getMap(),this.ready_=!0,this.repaint(),this.listeners_=[google.maps.event.addListener(this.getMap(),"zoom_changed",function(){a.resetViewport_(!1),(this.getZoom()===(this.get("minZoom")||0)||this.getZoom()===this.get("maxZoom"))&&google.maps.event.trigger(this,"idle")}),google.maps.event.addListener(this.getMap(),"idle",function(){a.redraw_()})]},MarkerClusterer.prototype.onRemove=function(){var a;for(a=0;a<this.markers_.length;a++)this.markers_[a].getMap()!==this.activeMap_&&this.markers_[a].setMap(this.activeMap_);for(a=0;a<this.clusters_.length;a++)this.clusters_[a].remove();for(this.clusters_=[],a=0;a<this.listeners_.length;a++)google.maps.event.removeListener(this.listeners_[a]);this.listeners_=[],this.activeMap_=null,this.ready_=!1},MarkerClusterer.prototype.draw=function(){},MarkerClusterer.prototype.setupStyles_=function(){var a,b;if(!(this.styles_.length>0))for(a=0;a<this.imageSizes_.length;a++)b=this.imageSizes_[a],this.styles_.push({url:this.imagePath_+(a+1)+"."+this.imageExtension_,height:b,width:b})},MarkerClusterer.prototype.fitMapToMarkers=function(){var a,b=this.getMarkers(),c=new google.maps.LatLngBounds;for(a=0;a<b.length;a++)c.extend(b[a].getPosition());this.getMap().fitBounds(c)},MarkerClusterer.prototype.getGridSize=function(){return this.gridSize_},MarkerClusterer.prototype.setGridSize=function(a){this.gridSize_=a},MarkerClusterer.prototype.getMinimumClusterSize=function(){return this.minClusterSize_},MarkerClusterer.prototype.setMinimumClusterSize=function(a){this.minClusterSize_=a},MarkerClusterer.prototype.getMaxZoom=function(){return this.maxZoom_},MarkerClusterer.prototype.setMaxZoom=function(a){this.maxZoom_=a},MarkerClusterer.prototype.getStyles=function(){return this.styles_},MarkerClusterer.prototype.setStyles=function(a){this.styles_=a},MarkerClusterer.prototype.getTitle=function(){return this.title_},MarkerClusterer.prototype.setTitle=function(a){this.title_=a},MarkerClusterer.prototype.getZoomOnClick=function(){return this.zoomOnClick_},MarkerClusterer.prototype.setZoomOnClick=function(a){this.zoomOnClick_=a},MarkerClusterer.prototype.getAverageCenter=function(){return this.averageCenter_},MarkerClusterer.prototype.setAverageCenter=function(a){this.averageCenter_=a},MarkerClusterer.prototype.getIgnoreHidden=function(){return this.ignoreHidden_},MarkerClusterer.prototype.setIgnoreHidden=function(a){this.ignoreHidden_=a},MarkerClusterer.prototype.getEnableRetinaIcons=function(){return this.enableRetinaIcons_},MarkerClusterer.prototype.setEnableRetinaIcons=function(a){this.enableRetinaIcons_=a},MarkerClusterer.prototype.getImageExtension=function(){return this.imageExtension_},MarkerClusterer.prototype.setImageExtension=function(a){this.imageExtension_=a},MarkerClusterer.prototype.getImagePath=function(){return this.imagePath_},MarkerClusterer.prototype.setImagePath=function(a){this.imagePath_=a},MarkerClusterer.prototype.getImageSizes=function(){return this.imageSizes_},MarkerClusterer.prototype.setImageSizes=function(a){this.imageSizes_=a},MarkerClusterer.prototype.getCalculator=function(){return this.calculator_},MarkerClusterer.prototype.setCalculator=function(a){this.calculator_=a},MarkerClusterer.prototype.getBatchSizeIE=function(){return this.batchSizeIE_},MarkerClusterer.prototype.setBatchSizeIE=function(a){this.batchSizeIE_=a},MarkerClusterer.prototype.getClusterClass=function(){return this.clusterClass_},MarkerClusterer.prototype.setClusterClass=function(a){this.clusterClass_=a},MarkerClusterer.prototype.getMarkers=function(){return this.markers_},MarkerClusterer.prototype.getTotalMarkers=function(){return this.markers_.length},MarkerClusterer.prototype.getClusters=function(){return this.clusters_},MarkerClusterer.prototype.getTotalClusters=function(){return this.clusters_.length},MarkerClusterer.prototype.addMarker=function(a,b){this.pushMarkerTo_(a),b||this.redraw_()},MarkerClusterer.prototype.addMarkers=function(a,b){var c;for(c in a)a.hasOwnProperty(c)&&this.pushMarkerTo_(a[c]);b||this.redraw_()},MarkerClusterer.prototype.pushMarkerTo_=function(a){if(a.getDraggable()){var b=this;google.maps.event.addListener(a,"dragend",function(){b.ready_&&(this.isAdded=!1,b.repaint())})}a.isAdded=!1,this.markers_.push(a)},MarkerClusterer.prototype.removeMarker=function(a,b){var c=this.removeMarker_(a);return!b&&c&&this.repaint(),c},MarkerClusterer.prototype.removeMarkers=function(a,b){var c,d,e=!1;for(c=0;c<a.length;c++)d=this.removeMarker_(a[c]),e=e||d;return!b&&e&&this.repaint(),e},MarkerClusterer.prototype.removeMarker_=function(a){var b,c=-1;if(this.markers_.indexOf)c=this.markers_.indexOf(a);else for(b=0;b<this.markers_.length;b++)if(a===this.markers_[b]){c=b;break}return-1===c?!1:(a.setMap(null),this.markers_.splice(c,1),!0)},MarkerClusterer.prototype.clearMarkers=function(){this.resetViewport_(!0),this.markers_=[]},MarkerClusterer.prototype.repaint=function(){var a=this.clusters_.slice();this.clusters_=[],this.resetViewport_(!1),this.redraw_(),setTimeout(function(){var b;for(b=0;b<a.length;b++)a[b].remove()},0)},MarkerClusterer.prototype.getExtendedBounds=function(a){var b=this.getProjection(),c=new google.maps.LatLng(a.getNorthEast().lat(),a.getNorthEast().lng()),d=new google.maps.LatLng(a.getSouthWest().lat(),a.getSouthWest().lng()),e=b.fromLatLngToDivPixel(c);e.x+=this.gridSize_,e.y-=this.gridSize_;var f=b.fromLatLngToDivPixel(d);f.x-=this.gridSize_,f.y+=this.gridSize_;var g=b.fromDivPixelToLatLng(e),h=b.fromDivPixelToLatLng(f);return a.extend(g),a.extend(h),a},MarkerClusterer.prototype.redraw_=function(){this.createClusters_(0)},MarkerClusterer.prototype.resetViewport_=function(a){var b,c;for(b=0;b<this.clusters_.length;b++)this.clusters_[b].remove();for(this.clusters_=[],b=0;b<this.markers_.length;b++)c=this.markers_[b],c.isAdded=!1,a&&c.setMap(null)},MarkerClusterer.prototype.distanceBetweenPoints_=function(a,b){var c=6371,d=(b.lat()-a.lat())*Math.PI/180,e=(b.lng()-a.lng())*Math.PI/180,f=Math.sin(d/2)*Math.sin(d/2)+Math.cos(a.lat()*Math.PI/180)*Math.cos(b.lat()*Math.PI/180)*Math.sin(e/2)*Math.sin(e/2),g=2*Math.atan2(Math.sqrt(f),Math.sqrt(1-f)),h=c*g;return h},MarkerClusterer.prototype.isMarkerInBounds_=function(a,b){return b.contains(a.getPosition())},MarkerClusterer.prototype.addToClosestCluster_=function(a){var b,c,d,e,f=4e4,g=null;for(b=0;b<this.clusters_.length;b++)d=this.clusters_[b],e=d.getCenter(),e&&(c=this.distanceBetweenPoints_(e,a.getPosition()),f>c&&(f=c,g=d));g&&g.isMarkerInClusterBounds(a)?g.addMarker(a):(d=new Cluster(this),d.addMarker(a),this.clusters_.push(d))},MarkerClusterer.prototype.createClusters_=function(a){var b,c,d,e=this;if(this.ready_){0===a&&(google.maps.event.trigger(this,"clusteringbegin",this),"undefined"!=typeof this.timerRefStatic&&(clearTimeout(this.timerRefStatic),delete this.timerRefStatic)),d=this.getMap().getZoom()>3?new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),this.getMap().getBounds().getNorthEast()):new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472,-178.48388434375),new google.maps.LatLng(-85.08136444384544,178.00048865625));var f=this.getExtendedBounds(d),g=Math.min(a+this.batchSize_,this.markers_.length);for(b=a;g>b;b++)c=this.markers_[b],!c.isAdded&&this.isMarkerInBounds_(c,f)&&(!this.ignoreHidden_||this.ignoreHidden_&&c.getVisible())&&this.addToClosestCluster_(c);g<this.markers_.length?this.timerRefStatic=setTimeout(function(){e.createClusters_(g)},0):(delete this.timerRefStatic,google.maps.event.trigger(this,"clusteringend",this))}},MarkerClusterer.prototype.extend=function(a,b){return function(a){var b;for(b in a.prototype)this.prototype[b]=a.prototype[b];return this}.apply(a,[b])},MarkerClusterer.CALCULATOR=function(a,b){for(var c=0,d="",e=a.length.toString(),f=e;0!==f;)f=parseInt(f/10,10),c++;return c=Math.min(c,b),{text:e,index:c,title:d}},MarkerClusterer.BATCH_SIZE=2e3,MarkerClusterer.BATCH_SIZE_IE=500,MarkerClusterer.IMAGE_PATH="http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m",MarkerClusterer.IMAGE_EXTENSION="png",MarkerClusterer.IMAGE_SIZES=[53,56,66,78,90],"function"!=typeof String.prototype.trim&&(String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g,"")}),inherits(MarkerLabel_,google.maps.OverlayView),MarkerLabel_.getSharedCross=function(a){var b;return"undefined"==typeof MarkerLabel_.getSharedCross.crossDiv&&(b=document.createElement("img"),b.style.cssText="position: absolute; z-index: 1000002; display: none;",b.style.marginLeft="-8px",b.style.marginTop="-9px",b.src=a,MarkerLabel_.getSharedCross.crossDiv=b),MarkerLabel_.getSharedCross.crossDiv},MarkerLabel_.prototype.onAdd=function(){var a,b,c,d,e,f,g,h=this,i=!1,j=!1,k=20,l="url("+this.handCursorURL_+")",m=function(a){a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.stopPropagation&&a.stopPropagation()},n=function(){h.marker_.setAnimation(null)};this.getPanes().overlayImage.appendChild(this.labelDiv_),this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_),"undefined"==typeof MarkerLabel_.getSharedCross.processed&&(this.getPanes().overlayImage.appendChild(this.crossDiv_),MarkerLabel_.getSharedCross.processed=!0),this.listeners_=[google.maps.event.addDomListener(this.eventDiv_,"mouseover",function(a){(h.marker_.getDraggable()||h.marker_.getClickable())&&(this.style.cursor="pointer",google.maps.event.trigger(h.marker_,"mouseover",a))}),google.maps.event.addDomListener(this.eventDiv_,"mouseout",function(a){!h.marker_.getDraggable()&&!h.marker_.getClickable()||j||(this.style.cursor=h.marker_.getCursor(),google.maps.event.trigger(h.marker_,"mouseout",a))}),google.maps.event.addDomListener(this.eventDiv_,"mousedown",function(a){j=!1,h.marker_.getDraggable()&&(i=!0,this.style.cursor=l),(h.marker_.getDraggable()||h.marker_.getClickable())&&(google.maps.event.trigger(h.marker_,"mousedown",a),m(a))}),google.maps.event.addDomListener(document,"mouseup",function(b){var c;if(i&&(i=!1,h.eventDiv_.style.cursor="pointer",google.maps.event.trigger(h.marker_,"mouseup",b)),j){if(e){c=h.getProjection().fromLatLngToDivPixel(h.marker_.getPosition()),c.y+=k,h.marker_.setPosition(h.getProjection().fromDivPixelToLatLng(c));try{h.marker_.setAnimation(google.maps.Animation.BOUNCE),setTimeout(n,1406)}catch(f){}}h.crossDiv_.style.display="none",h.marker_.setZIndex(a),d=!0,j=!1,b.latLng=h.marker_.getPosition(),google.maps.event.trigger(h.marker_,"dragend",b)}}),google.maps.event.addListener(h.marker_.getMap(),"mousemove",function(d){var l;i&&(j?(d.latLng=new google.maps.LatLng(d.latLng.lat()-b,d.latLng.lng()-c),l=h.getProjection().fromLatLngToDivPixel(d.latLng),e&&(h.crossDiv_.style.left=l.x+"px",h.crossDiv_.style.top=l.y+"px",h.crossDiv_.style.display="",l.y-=k),h.marker_.setPosition(h.getProjection().fromDivPixelToLatLng(l)),e&&(h.eventDiv_.style.top=l.y+k+"px"),google.maps.event.trigger(h.marker_,"drag",d)):(b=d.latLng.lat()-h.marker_.getPosition().lat(),c=d.latLng.lng()-h.marker_.getPosition().lng(),a=h.marker_.getZIndex(),f=h.marker_.getPosition(),g=h.marker_.getMap().getCenter(),e=h.marker_.get("raiseOnDrag"),j=!0,h.marker_.setZIndex(1e6),d.latLng=h.marker_.getPosition(),google.maps.event.trigger(h.marker_,"dragstart",d)))}),google.maps.event.addDomListener(document,"keydown",function(a){j&&27===a.keyCode&&(e=!1,h.marker_.setPosition(f),h.marker_.getMap().setCenter(g),google.maps.event.trigger(document,"mouseup",a))}),google.maps.event.addDomListener(this.eventDiv_,"click",function(a){(h.marker_.getDraggable()||h.marker_.getClickable())&&(d?d=!1:(google.maps.event.trigger(h.marker_,"click",a),m(a)))}),google.maps.event.addDomListener(this.eventDiv_,"dblclick",function(a){(h.marker_.getDraggable()||h.marker_.getClickable())&&(google.maps.event.trigger(h.marker_,"dblclick",a),m(a))}),google.maps.event.addListener(this.marker_,"dragstart",function(){j||(e=this.get("raiseOnDrag"))}),google.maps.event.addListener(this.marker_,"drag",function(){j||e&&(h.setPosition(k),h.labelDiv_.style.zIndex=1e6+(this.get("labelInBackground")?-1:1))}),google.maps.event.addListener(this.marker_,"dragend",function(){j||e&&h.setPosition(0)}),google.maps.event.addListener(this.marker_,"position_changed",function(){h.setPosition()}),google.maps.event.addListener(this.marker_,"zindex_changed",function(){h.setZIndex()}),google.maps.event.addListener(this.marker_,"visible_changed",function(){h.setVisible()}),google.maps.event.addListener(this.marker_,"labelvisible_changed",function(){h.setVisible()}),google.maps.event.addListener(this.marker_,"title_changed",function(){h.setTitle()}),google.maps.event.addListener(this.marker_,"labelcontent_changed",function(){h.setContent()}),google.maps.event.addListener(this.marker_,"labelanchor_changed",function(){h.setAnchor()}),google.maps.event.addListener(this.marker_,"labelclass_changed",function(){h.setStyles()}),google.maps.event.addListener(this.marker_,"labelstyle_changed",function(){h.setStyles()})]},MarkerLabel_.prototype.onRemove=function(){var a;for(null!==this.labelDiv_.parentNode&&this.labelDiv_.parentNode.removeChild(this.labelDiv_),null!==this.eventDiv_.parentNode&&this.eventDiv_.parentNode.removeChild(this.eventDiv_),a=0;a<this.listeners_.length;a++)google.maps.event.removeListener(this.listeners_[a])},MarkerLabel_.prototype.draw=function(){this.setContent(),this.setTitle(),this.setStyles()},MarkerLabel_.prototype.setContent=function(){var a=this.marker_.get("labelContent");"undefined"==typeof a.nodeType?(this.labelDiv_.innerHTML=a,this.eventDiv_.innerHTML=this.labelDiv_.innerHTML):(this.labelDiv_.innerHTML="",this.labelDiv_.appendChild(a),a=a.cloneNode(!0),this.eventDiv_.appendChild(a))},MarkerLabel_.prototype.setTitle=function(){this.eventDiv_.title=this.marker_.getTitle()||""},MarkerLabel_.prototype.setStyles=function(){var a,b;this.labelDiv_.className=this.marker_.get("labelClass"),this.eventDiv_.className=this.labelDiv_.className,this.labelDiv_.style.cssText="",this.eventDiv_.style.cssText="",b=this.marker_.get("labelStyle");for(a in b)b.hasOwnProperty(a)&&(this.labelDiv_.style[a]=b[a],this.eventDiv_.style[a]=b[a]);this.setMandatoryStyles()},MarkerLabel_.prototype.setMandatoryStyles=function(){this.labelDiv_.style.position="absolute",this.labelDiv_.style.overflow="hidden","undefined"!=typeof this.labelDiv_.style.opacity&&""!==this.labelDiv_.style.opacity&&(this.labelDiv_.style.MsFilter='"progid:DXImageTransform.Microsoft.Alpha(opacity='+100*this.labelDiv_.style.opacity+')"',this.labelDiv_.style.filter="alpha(opacity="+100*this.labelDiv_.style.opacity+")"),this.eventDiv_.style.position=this.labelDiv_.style.position,this.eventDiv_.style.overflow=this.labelDiv_.style.overflow,this.eventDiv_.style.opacity=.01,this.eventDiv_.style.MsFilter='"progid:DXImageTransform.Microsoft.Alpha(opacity=1)"',this.eventDiv_.style.filter="alpha(opacity=1)",this.setAnchor(),this.setPosition(),this.setVisible()},MarkerLabel_.prototype.setAnchor=function(){var a=this.marker_.get("labelAnchor");this.labelDiv_.style.marginLeft=-a.x+"px",this.labelDiv_.style.marginTop=-a.y+"px",this.eventDiv_.style.marginLeft=-a.x+"px",this.eventDiv_.style.marginTop=-a.y+"px"},MarkerLabel_.prototype.setPosition=function(a){var b=this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition());"undefined"==typeof a&&(a=0),this.labelDiv_.style.left=Math.round(b.x)+"px",this.labelDiv_.style.top=Math.round(b.y-a)+"px",this.eventDiv_.style.left=this.labelDiv_.style.left,this.eventDiv_.style.top=this.labelDiv_.style.top,this.setZIndex()},MarkerLabel_.prototype.setZIndex=function(){var a=this.marker_.get("labelInBackground")?-1:1;"undefined"==typeof this.marker_.getZIndex()?(this.labelDiv_.style.zIndex=parseInt(this.labelDiv_.style.top,10)+a,this.eventDiv_.style.zIndex=this.labelDiv_.style.zIndex):(this.labelDiv_.style.zIndex=this.marker_.getZIndex()+a,this.eventDiv_.style.zIndex=this.labelDiv_.style.zIndex)},MarkerLabel_.prototype.setVisible=function(){this.labelDiv_.style.display=this.marker_.get("labelVisible")&&this.marker_.getVisible()?"block":"none",this.eventDiv_.style.display=this.labelDiv_.style.display},inherits(MarkerWithLabel,google.maps.Marker),MarkerWithLabel.prototype.setMap=function(a){google.maps.Marker.prototype.setMap.apply(this,arguments),this.label.setMap(a)}; -\ No newline at end of file +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps.directives.api.utils", []); + + angular.module("google-maps.directives.api.managers", []); + + angular.module("google-maps.directives.api.models.child", ["google-maps.directives.api.utils"]); + + angular.module("google-maps.directives.api.models.parent", ["google-maps.directives.api.managers", "google-maps.directives.api.models.child"]); + + angular.module("google-maps.directives.api", ["google-maps.directives.api.models.parent"]); + + angular.module("google-maps", ["google-maps.directives.api"]).factory("debounce", [ + "$timeout", function($timeout) { + return function(fn) { + var nthCall; + nthCall = 0; + return function() { + var argz, later, that; + that = this; + argz = arguments; + nthCall++; + later = (function(version) { + return function() { + if (version === nthCall) { + return fn.apply(that, argz); + } + }; + })(nthCall); + return $timeout(later, 0, true); + }; + }; + } + ]); + +}).call(this); + +(function() { + angular.element(document).ready(function() { + if (!(google || (typeof google !== "undefined" && google !== null ? google.maps : void 0) || (google.maps.InfoWindow != null))) { + return; + } + google.maps.InfoWindow.prototype._open = google.maps.InfoWindow.prototype.open; + google.maps.InfoWindow.prototype._close = google.maps.InfoWindow.prototype.close; + google.maps.InfoWindow.prototype._isOpen = false; + google.maps.InfoWindow.prototype.open = function(map, anchor) { + this._isOpen = true; + this._open(map, anchor); + }; + google.maps.InfoWindow.prototype.close = function() { + this._isOpen = false; + this._close(); + }; + google.maps.InfoWindow.prototype.isOpen = function(val) { + if (val == null) { + val = void 0; + } + if (val == null) { + return this._isOpen; + } else { + return this._isOpen = val; + } + }; + /* + Do the same for InfoBox + TODO: Clean this up so the logic is defined once, wait until develop becomes master as this will be easier + */ + + if (!window.InfoBox) { + return; + } + window.InfoBox.prototype._open = window.InfoBox.prototype.open; + window.InfoBox.prototype._close = window.InfoBox.prototype.close; + window.InfoBox.prototype._isOpen = false; + window.InfoBox.prototype.open = function(map, anchor) { + this._isOpen = true; + this._open(map, anchor); + }; + window.InfoBox.prototype.close = function() { + this._isOpen = false; + this._close(); + }; + return window.InfoBox.prototype.isOpen = function(val) { + if (val == null) { + val = void 0; + } + if (val == null) { + return this._isOpen; + } else { + return this._isOpen = val; + } + }; + }); + +}).call(this); + +/* + Author Nick McCready + Intersection of Objects if the arrays have something in common each intersecting object will be returned + in an new array. +*/ + + +(function() { + _.intersectionObjects = function(array1, array2, comparison) { + var res, + _this = this; + if (comparison == null) { + comparison = void 0; + } + res = _.map(array1, function(obj1) { + return _.find(array2, function(obj2) { + if (comparison != null) { + return comparison(obj1, obj2); + } else { + return _.isEqual(obj1, obj2); + } + }); + }); + return _.filter(res, function(o) { + return o != null; + }); + }; + + _.containsObject = _.includeObject = function(obj, target, comparison) { + var _this = this; + if (comparison == null) { + comparison = void 0; + } + if (obj === null) { + return false; + } + return _.any(obj, function(value) { + if (comparison != null) { + return comparison(value, target); + } else { + return _.isEqual(value, target); + } + }); + }; + + _.differenceObjects = function(array1, array2, comparison) { + if (comparison == null) { + comparison = void 0; + } + return _.filter(array1, function(value) { + return !_.containsObject(array2, value); + }); + }; + + _.withoutObjects = function(array, array2) { + return _.differenceObjects(array, array2); + }; + + _.indexOfObject = function(array, item, comparison, isSorted) { + var i, length; + if (array == null) { + return -1; + } + i = 0; + length = array.length; + if (isSorted) { + if (typeof isSorted === "number") { + i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return (array[i] === item ? i : -1); + } + } + while (i < length) { + if (comparison != null) { + if (comparison(array[i], item)) { + return i; + } + } else { + if (_.isEqual(array[i], item)) { + return i; + } + } + i++; + } + return -1; + }; + + _["extends"] = function(arrayOfObjectsToCombine) { + return _.reduce(arrayOfObjectsToCombine, function(combined, toAdd) { + return _.extend(combined, toAdd); + }, {}); + }; + +}).call(this); + +/* + Author: Nicholas McCready & jfriend00 + _async handles things asynchronous-like :), to allow the UI to be free'd to do other things + Code taken from http://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui + + The design of any funcitonality of _async is to be like lodash/underscore and replicate it but call things + asynchronously underneath. Each should be sufficient for most things to be derrived from. + + TODO: Handle Object iteration like underscore and lodash as well.. not that important right now +*/ + + +(function() { + var async; + + async = { + each: function(array, callback, doneCallBack, pausedCallBack, chunk, index, pause) { + var doChunk; + if (chunk == null) { + chunk = 20; + } + if (index == null) { + index = 0; + } + if (pause == null) { + pause = 1; + } + if (!pause) { + throw "pause (delay) must be set from _async!"; + return; + } + if (array === void 0 || (array != null ? array.length : void 0) <= 0) { + doneCallBack(); + return; + } + doChunk = function() { + var cnt, i; + cnt = chunk; + i = index; + while (cnt-- && i < (array ? array.length : i + 1)) { + callback(array[i], i); + ++i; + } + if (array) { + if (i < array.length) { + index = i; + if (pausedCallBack != null) { + pausedCallBack(); + } + return setTimeout(doChunk, pause); + } else { + if (doneCallBack) { + return doneCallBack(); + } + } + } + }; + return doChunk(); + }, + map: function(objs, iterator, doneCallBack, pausedCallBack, chunk) { + var results; + results = []; + if (objs == null) { + return results; + } + return _async.each(objs, function(o) { + return results.push(iterator(o)); + }, function() { + return doneCallBack(results); + }, pausedCallBack, chunk); + } + }; + + window._async = async; + + angular.module("google-maps.directives.api.utils").factory("async", function() { + return window._async; + }); + +}).call(this); + +(function() { + var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + angular.module("google-maps.directives.api.utils").factory("BaseObject", function() { + var BaseObject, baseObjectKeywords; + baseObjectKeywords = ['extended', 'included']; + BaseObject = (function() { + function BaseObject() {} + + BaseObject.extend = function(obj) { + var key, value, _ref; + for (key in obj) { + value = obj[key]; + if (__indexOf.call(baseObjectKeywords, key) < 0) { + this[key] = value; + } + } + if ((_ref = obj.extended) != null) { + _ref.apply(this); + } + return this; + }; + + BaseObject.include = function(obj) { + var key, value, _ref; + for (key in obj) { + value = obj[key]; + if (__indexOf.call(baseObjectKeywords, key) < 0) { + this.prototype[key] = value; + } + } + if ((_ref = obj.included) != null) { + _ref.apply(this); + } + return this; + }; + + return BaseObject; + + })(); + return BaseObject; + }); + +}).call(this); + +/* + Useful function callbacks that should be defined at later time. + Mainly to be used for specs to verify creation / linking. + + This is to lead a common design in notifying child stuff. +*/ + + +(function() { + angular.module("google-maps.directives.api.utils").factory("ChildEvents", function() { + return { + onChildCreation: function(child) {} + }; + }); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("EventsHelper", [ + "Logger", function($log) { + return { + setEvents: function(marker, scope, model) { + if (angular.isDefined(scope.events) && (scope.events != null) && angular.isObject(scope.events)) { + return _.compact(_.map(scope.events, function(eventHandler, eventName) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + return google.maps.event.addListener(marker, eventName, function() { + return eventHandler.apply(scope, [marker, eventName, model, arguments]); + }); + } else { + return $log.info("MarkerEventHelper: invalid event listener " + eventName); + } + })); + } + } + }; + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("FitHelper", [ + "BaseObject", "Logger", function(BaseObject, $log) { + var FitHelper, _ref; + return FitHelper = (function(_super) { + __extends(FitHelper, _super); + + function FitHelper() { + _ref = FitHelper.__super__.constructor.apply(this, arguments); + return _ref; + } + + FitHelper.prototype.fit = function(gMarkers, gMap) { + var bounds, everSet, + _this = this; + if (gMap && gMarkers && gMarkers.length > 0) { + bounds = new google.maps.LatLngBounds(); + everSet = false; + return _async.each(gMarkers, function(gMarker) { + if (gMarker) { + if (!everSet) { + everSet = true; + } + return bounds.extend(gMarker.getPosition()); + } + }, function() { + if (everSet) { + return gMap.fitBounds(bounds); + } + }); + } + }; + + return FitHelper; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("GmapUtil", [ + "Logger", "$compile", function(Logger, $compile) { + var getCoords, validateCoords; + getCoords = function(value) { + if (Array.isArray(value) && value.length === 2) { + return new google.maps.LatLng(value[1], value[0]); + } else if (angular.isDefined(value.type) && value.type === "Point") { + return new google.maps.LatLng(value.coordinates[1], value.coordinates[0]); + } else { + return new google.maps.LatLng(value.latitude, value.longitude); + } + }; + validateCoords = function(coords) { + if (angular.isUndefined(coords)) { + return false; + } + if (_.isArray(coords)) { + if (coords.length === 2) { + return true; + } + } else if ((coords != null) && (coords != null ? coords.type : void 0)) { + if (coords.type === "Point" && _.isArray(coords.coordinates) && coords.coordinates.length === 2) { + return true; + } + } + if (coords && angular.isDefined((coords != null ? coords.latitude : void 0) && angular.isDefined(coords != null ? coords.longitude : void 0))) { + return true; + } + return false; + }; + return { + getLabelPositionPoint: function(anchor) { + var xPos, yPos; + if (anchor === void 0) { + return void 0; + } + anchor = /^([-\d\.]+)\s([-\d\.]+)$/.exec(anchor); + xPos = parseFloat(anchor[1]); + yPos = parseFloat(anchor[2]); + if ((xPos != null) && (yPos != null)) { + return new google.maps.Point(xPos, yPos); + } + }, + createMarkerOptions: function(coords, icon, defaults, map) { + var opts; + if (map == null) { + map = void 0; + } + if (defaults == null) { + defaults = {}; + } + opts = angular.extend({}, defaults, { + position: defaults.position != null ? defaults.position : getCoords(coords), + icon: defaults.icon != null ? defaults.icon : icon, + visible: defaults.visible != null ? defaults.visible : validateCoords(coords) + }); + if (map != null) { + opts.map = map; + } + return opts; + }, + createWindowOptions: function(gMarker, scope, content, defaults) { + if ((content != null) && (defaults != null) && ($compile != null)) { + return angular.extend({}, defaults, { + content: this.buildContent(scope, defaults, content), + position: defaults.position != null ? defaults.position : angular.isObject(gMarker) ? gMarker.getPosition() : getCoords(scope.coords) + }); + } else { + if (!defaults) { + Logger.error("infoWindow defaults not defined"); + if (!content) { + return Logger.error("infoWindow content not defined"); + } + } else { + return defaults; + } + } + }, + buildContent: function(scope, defaults, content) { + var parsed, ret; + if (defaults.content != null) { + ret = defaults.content; + } else { + if ($compile != null) { + parsed = $compile(content)(scope); + if (parsed.length > 0) { + ret = parsed[0]; + } + } else { + ret = content; + } + } + return ret; + }, + defaultDelay: 50, + isTrue: function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }, + isFalse: function(value) { + return ['false', 'FALSE', 0, 'n', 'N', 'no', 'NO'].indexOf(value) !== -1; + }, + getCoords: getCoords, + validateCoords: validateCoords, + validatePath: function(path) { + var array, i, polygon, trackMaxVertices; + i = 0; + if (angular.isUndefined(path.type)) { + if (!Array.isArray(path) || path.length < 2) { + return false; + } + while (i < path.length) { + if (!((angular.isDefined(path[i].latitude) && angular.isDefined(path[i].longitude)) || (typeof path[i].lat === "function" && typeof path[i].lng === "function"))) { + return false; + } + i++; + } + return true; + } else { + if (angular.isUndefined(path.coordinates)) { + return false; + } + if (path.type === "Polygon") { + if (path.coordinates[0].length < 4) { + return false; + } + array = path.coordinates[0]; + } else if (path.type === "MultiPolygon") { + trackMaxVertices = { + max: 0, + index: 0 + }; + _.forEach(path.coordinates, function(polygon, index) { + if (polygon[0].length > this.max) { + this.max = polygon[0].length; + return this.index = index; + } + }, trackMaxVertices); + polygon = path.coordinates[trackMaxVertices.index]; + array = polygon[0]; + if (array.length < 4) { + return false; + } + } else if (path.type === "LineString") { + if (path.coordinates.length < 2) { + return false; + } + array = path.coordinates; + } else { + return false; + } + while (i < array.length) { + if (array[i].length !== 2) { + return false; + } + i++; + } + return true; + } + }, + convertPathPoints: function(path) { + var array, i, latlng, result, trackMaxVertices; + i = 0; + result = new google.maps.MVCArray(); + if (angular.isUndefined(path.type)) { + while (i < path.length) { + latlng; + if (angular.isDefined(path[i].latitude) && angular.isDefined(path[i].longitude)) { + latlng = new google.maps.LatLng(path[i].latitude, path[i].longitude); + } else if (typeof path[i].lat === "function" && typeof path[i].lng === "function") { + latlng = path[i]; + } + result.push(latlng); + i++; + } + } else { + array; + if (path.type === "Polygon") { + array = path.coordinates[0]; + } else if (path.type === "MultiPolygon") { + trackMaxVertices = { + max: 0, + index: 0 + }; + _.forEach(path.coordinates, function(polygon, index) { + if (polygon[0].length > this.max) { + this.max = polygon[0].length; + return this.index = index; + } + }, trackMaxVertices); + array = path.coordinates[trackMaxVertices.index][0]; + } else if (path.type === "LineString") { + array = path.coordinates; + } + while (i < array.length) { + result.push(new google.maps.LatLng(array[i][1], array[i][0])); + i++; + } + } + return result; + }, + extendMapBounds: function(map, points) { + var bounds, i; + bounds = new google.maps.LatLngBounds(); + i = 0; + while (i < points.length) { + bounds.extend(points.getAt(i)); + i++; + } + return map.fitBounds(bounds); + } + }; + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("Linked", [ + "BaseObject", function(BaseObject) { + var Linked; + Linked = (function(_super) { + __extends(Linked, _super); + + function Linked(scope, element, attrs, ctrls) { + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.ctrls = ctrls; + } + + return Linked; + + })(BaseObject); + return Linked; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").service("Logger", [ + "$log", function($log) { + return { + logger: $log, + doLog: false, + info: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.info(msg); + } else { + return console.info(msg); + } + } + }, + error: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.error(msg); + } else { + return console.error(msg); + } + } + }, + warn: function(msg) { + if (this.doLog) { + if (this.logger != null) { + return this.logger.warn(msg); + } else { + return console.warn(msg); + } + } + } + }; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.utils").factory("ModelKey", [ + "BaseObject", function(BaseObject) { + var ModelKey; + return ModelKey = (function(_super) { + __extends(ModelKey, _super); + + function ModelKey(scope) { + this.scope = scope; + this.setIdKey = __bind(this.setIdKey, this); + this.modelKeyComparison = __bind(this.modelKeyComparison, this); + ModelKey.__super__.constructor.call(this); + this.defaultIdKey = "id"; + this.idKey = void 0; + } + + ModelKey.prototype.evalModelHandle = function(model, modelKey) { + if (model === void 0) { + return void 0; + } + if (modelKey === 'self') { + return model; + } else { + return model[modelKey]; + } + }; + + ModelKey.prototype.modelKeyComparison = function(model1, model2) { + var scope; + scope = this.scope.coords != null ? this.scope : this.parentScope; + if (scope == null) { + throw "No scope or parentScope set!"; + } + return this.evalModelHandle(model1, scope.coords).latitude === this.evalModelHandle(model2, scope.coords).latitude && this.evalModelHandle(model1, scope.coords).longitude === this.evalModelHandle(model2, scope.coords).longitude; + }; + + ModelKey.prototype.setIdKey = function(scope) { + return this.idKey = scope.idKey != null ? scope.idKey : this.defaultIdKey; + }; + + return ModelKey; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps.directives.api.utils").factory("ModelsWatcher", [ + "Logger", function(Logger) { + return { + figureOutState: function(idKey, scope, childObjects, comparison, callBack) { + var adds, mappedScopeModelIds, removals, + _this = this; + adds = []; + mappedScopeModelIds = {}; + removals = []; + return _async.each(scope.models, function(m) { + var child; + if (m[idKey] != null) { + mappedScopeModelIds[m[idKey]] = {}; + if (childObjects[m[idKey]] == null) { + return adds.push(m); + } else { + child = childObjects[m[idKey]]; + if (!comparison(m, child.model)) { + adds.push(m); + return removals.push(child.model); + } + } + } else { + return Logger.error("id missing for model " + (m.toString()) + ", can not use do comparison/insertion"); + } + }, function() { + return _async.each(childObjects.values(), function(c) { + var id; + if (c == null) { + Logger.error("child undefined in ModelsWatcher."); + return; + } + if (c.model == null) { + Logger.error("child.model undefined in ModelsWatcher."); + return; + } + id = c.model[idKey]; + if (mappedScopeModelIds[id] == null) { + return removals.push(c.model[idKey]); + } + }, function() { + return callBack({ + adds: adds, + removals: removals + }); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* + Simple Object Map with a lenght property to make it easy to track length/size +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + angular.module("google-maps.directives.api.utils").factory("PropMap", function() { + var PropMap, propsToPop; + propsToPop = ['get', 'put', 'remove', 'values', 'keys', 'length']; + PropMap = (function() { + function PropMap() { + this.keys = __bind(this.keys, this); + this.values = __bind(this.values, this); + this.remove = __bind(this.remove, this); + this.put = __bind(this.put, this); + this.get = __bind(this.get, this); + this.length = 0; + } + + PropMap.prototype.get = function(key) { + return this[key]; + }; + + PropMap.prototype.put = function(key, value) { + if (this[key] == null) { + this.length++; + } + return this[key] = value; + }; + + PropMap.prototype.remove = function(key) { + delete this[key]; + return this.length--; + }; + + PropMap.prototype.values = function() { + var all, keys, + _this = this; + all = []; + keys = _.keys(this); + _.each(keys, function(value) { + if (_.indexOf(propsToPop, value) === -1) { + return all.push(_this[value]); + } + }); + return all; + }; + + PropMap.prototype.keys = function() { + var all, keys, + _this = this; + keys = _.keys(this); + all = []; + _.each(keys, function(prop) { + if (_.indexOf(propsToPop, prop) === -1) { + return all.push(prop); + } + }); + return all; + }; + + return PropMap; + + })(); + return PropMap; + }); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.managers").factory("ClustererMarkerManager", [ + "Logger", "FitHelper", function($log, FitHelper) { + var ClustererMarkerManager; + ClustererMarkerManager = (function(_super) { + __extends(ClustererMarkerManager, _super); + + function ClustererMarkerManager(gMap, opt_markers, opt_options, opt_events) { + var self; + this.opt_events = opt_events; + this.fit = __bind(this.fit, this); + this.destroy = __bind(this.destroy, this); + this.clear = __bind(this.clear, this); + this.draw = __bind(this.draw, this); + this.removeMany = __bind(this.removeMany, this); + this.remove = __bind(this.remove, this); + this.addMany = __bind(this.addMany, this); + this.add = __bind(this.add, this); + ClustererMarkerManager.__super__.constructor.call(this); + self = this; + this.opt_options = opt_options; + if ((opt_options != null) && opt_markers === void 0) { + this.clusterer = new MarkerClusterer(gMap, void 0, opt_options); + } else if ((opt_options != null) && (opt_markers != null)) { + this.clusterer = new MarkerClusterer(gMap, opt_markers, opt_options); + } else { + this.clusterer = new MarkerClusterer(gMap); + } + this.attachEvents(this.opt_events, "opt_events"); + this.clusterer.setIgnoreHidden(true); + this.noDrawOnSingleAddRemoves = true; + $log.info(this); + } + + ClustererMarkerManager.prototype.add = function(gMarker) { + return this.clusterer.addMarker(gMarker, this.noDrawOnSingleAddRemoves); + }; + + ClustererMarkerManager.prototype.addMany = function(gMarkers) { + return this.clusterer.addMarkers(gMarkers); + }; + + ClustererMarkerManager.prototype.remove = function(gMarker) { + return this.clusterer.removeMarker(gMarker, this.noDrawOnSingleAddRemoves); + }; + + ClustererMarkerManager.prototype.removeMany = function(gMarkers) { + return this.clusterer.addMarkers(gMarkers); + }; + + ClustererMarkerManager.prototype.draw = function() { + return this.clusterer.repaint(); + }; + + ClustererMarkerManager.prototype.clear = function() { + this.clusterer.clearMarkers(); + return this.clusterer.repaint(); + }; + + ClustererMarkerManager.prototype.attachEvents = function(options, optionsName) { + var eventHandler, eventName, _results; + if (angular.isDefined(options) && (options != null) && angular.isObject(options)) { + _results = []; + for (eventName in options) { + eventHandler = options[eventName]; + if (options.hasOwnProperty(eventName) && angular.isFunction(options[eventName])) { + $log.info("" + optionsName + ": Attaching event: " + eventName + " to clusterer"); + _results.push(google.maps.event.addListener(this.clusterer, eventName, options[eventName])); + } else { + _results.push(void 0); + } + } + return _results; + } + }; + + ClustererMarkerManager.prototype.clearEvents = function(options) { + var eventHandler, eventName, _results; + if (angular.isDefined(options) && (options != null) && angular.isObject(options)) { + _results = []; + for (eventName in options) { + eventHandler = options[eventName]; + if (options.hasOwnProperty(eventName) && angular.isFunction(options[eventName])) { + $log.info("" + optionsName + ": Clearing event: " + eventName + " to clusterer"); + _results.push(google.maps.event.clearListeners(this.clusterer, eventName)); + } else { + _results.push(void 0); + } + } + return _results; + } + }; + + ClustererMarkerManager.prototype.destroy = function() { + this.clearEvents(this.opt_events); + this.clearEvents(this.opt_internal_events); + return this.clear(); + }; + + ClustererMarkerManager.prototype.fit = function() { + return ClustererMarkerManager.__super__.fit.call(this, this.clusterer.getMarkers(), this.clusterer.getMap()); + }; + + return ClustererMarkerManager; + + })(FitHelper); + return ClustererMarkerManager; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.managers").factory("MarkerManager", [ + "Logger", "FitHelper", function(Logger, FitHelper) { + var MarkerManager; + MarkerManager = (function(_super) { + __extends(MarkerManager, _super); + + MarkerManager.include(FitHelper); + + function MarkerManager(gMap, opt_markers, opt_options) { + this.fit = __bind(this.fit, this); + this.handleOptDraw = __bind(this.handleOptDraw, this); + this.clear = __bind(this.clear, this); + this.draw = __bind(this.draw, this); + this.removeMany = __bind(this.removeMany, this); + this.remove = __bind(this.remove, this); + this.addMany = __bind(this.addMany, this); + this.add = __bind(this.add, this); + var self; + MarkerManager.__super__.constructor.call(this); + self = this; + this.gMap = gMap; + this.gMarkers = []; + this.$log = Logger; + this.$log.info(this); + } + + MarkerManager.prototype.add = function(gMarker, optDraw, redraw) { + if (redraw == null) { + redraw = true; + } + this.handleOptDraw(gMarker, optDraw, redraw); + return this.gMarkers.push(gMarker); + }; + + MarkerManager.prototype.addMany = function(gMarkers) { + var gMarker, _i, _len, _results; + _results = []; + for (_i = 0, _len = gMarkers.length; _i < _len; _i++) { + gMarker = gMarkers[_i]; + _results.push(this.add(gMarker)); + } + return _results; + }; + + MarkerManager.prototype.remove = function(gMarker, optDraw) { + var index, tempIndex; + this.handleOptDraw(gMarker, optDraw, false); + if (!optDraw) { + return; + } + index = void 0; + if (this.gMarkers.indexOf != null) { + index = this.gMarkers.indexOf(gMarker); + } else { + tempIndex = 0; + _.find(this.gMarkers, function(marker) { + tempIndex += 1; + if (marker === gMarker) { + index = tempIndex; + } + }); + } + if (index != null) { + return this.gMarkers.splice(index, 1); + } + }; + + MarkerManager.prototype.removeMany = function(gMarkers) { + var _this = this; + return this.gMarkers.forEach(function(marker) { + return _this.remove(marker); + }); + }; + + MarkerManager.prototype.draw = function() { + var deletes, + _this = this; + deletes = []; + this.gMarkers.forEach(function(gMarker) { + if (!gMarker.isDrawn) { + if (gMarker.doAdd) { + gMarker.setMap(_this.gMap); + return gMarker.isDrawn = true; + } else { + return deletes.push(gMarker); + } + } + }); + return deletes.forEach(function(gMarker) { + gMarker.isDrawn = false; + return _this.remove(gMarker, true); + }); + }; + + MarkerManager.prototype.clear = function() { + var gMarker, _i, _len, _ref; + _ref = this.gMarkers; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + gMarker = _ref[_i]; + gMarker.setMap(null); + } + delete this.gMarkers; + return this.gMarkers = []; + }; + + MarkerManager.prototype.handleOptDraw = function(gMarker, optDraw, doAdd) { + if (optDraw === true) { + if (doAdd) { + gMarker.setMap(this.gMap); + } else { + gMarker.setMap(null); + } + return gMarker.isDrawn = true; + } else { + gMarker.isDrawn = false; + return gMarker.doAdd = doAdd; + } + }; + + MarkerManager.prototype.fit = function() { + return MarkerManager.__super__.fit.call(this, this.gMarkers, this.gMap); + }; + + return MarkerManager; + + })(FitHelper); + return MarkerManager; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps").factory("array-sync", [ + "add-events", function(mapEvents) { + return function(mapArray, scope, pathEval, pathChangedFn) { + var geojsonArray, geojsonHandlers, geojsonWatcher, isSetFromScope, legacyHandlers, legacyWatcher, mapArrayListener, scopePath, watchListener; + isSetFromScope = false; + scopePath = scope.$eval(pathEval); + if (!scope["static"]) { + legacyHandlers = { + set_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return scopePath[index] = value; + } else { + scopePath[index].latitude = value.lat(); + return scopePath[index].longitude = value.lng(); + } + }, + insert_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return scopePath.splice(index, 0, value); + } else { + return scopePath.splice(index, 0, { + latitude: value.lat(), + longitude: value.lng() + }); + } + }, + remove_at: function(index) { + if (isSetFromScope) { + return; + } + return scopePath.splice(index, 1); + } + }; + geojsonArray; + if (scopePath.type === "Polygon") { + geojsonArray = scopePath.coordinates[0]; + } else if (scopePath.type === "LineString") { + geojsonArray = scopePath.coordinates; + } + geojsonHandlers = { + set_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return; + } + geojsonArray[index][1] = value.lat(); + return geojsonArray[index][0] = value.lng(); + }, + insert_at: function(index) { + var value; + if (isSetFromScope) { + return; + } + value = mapArray.getAt(index); + if (!value) { + return; + } + if (!value.lng || !value.lat) { + return; + } + return geojsonArray.splice(index, 0, [value.lng(), value.lat()]); + }, + remove_at: function(index) { + if (isSetFromScope) { + return; + } + return geojsonArray.splice(index, 1); + } + }; + mapArrayListener = mapEvents(mapArray, angular.isUndefined(scopePath.type) ? legacyHandlers : geojsonHandlers); + } + legacyWatcher = function(newPath) { + var i, l, newLength, newValue, oldArray, oldLength, oldValue; + isSetFromScope = true; + oldArray = mapArray; + if (newPath) { + i = 0; + oldLength = oldArray.getLength(); + newLength = newPath.length; + l = Math.min(oldLength, newLength); + newValue = void 0; + while (i < l) { + oldValue = oldArray.getAt(i); + newValue = newPath[i]; + if (typeof newValue.equals === "function") { + if (!newValue.equals(oldValue)) { + oldArray.setAt(i, newValue); + } + } else { + if ((oldValue.lat() !== newValue.latitude) || (oldValue.lng() !== newValue.longitude)) { + oldArray.setAt(i, new google.maps.LatLng(newValue.latitude, newValue.longitude)); + } + } + i++; + } + while (i < newLength) { + newValue = newPath[i]; + if (typeof newValue.lat === "function" && typeof newValue.lng === "function") { + oldArray.push(newValue); + } else { + oldArray.push(new google.maps.LatLng(newValue.latitude, newValue.longitude)); + } + i++; + } + while (i < oldLength) { + oldArray.pop(); + i++; + } + } + return isSetFromScope = false; + }; + geojsonWatcher = function(newPath) { + var array, i, l, newLength, newValue, oldArray, oldLength, oldValue; + isSetFromScope = true; + oldArray = mapArray; + if (newPath) { + array; + if (scopePath.type === "Polygon") { + array = newPath.coordinates[0]; + } else if (scopePath.type === "LineString") { + array = newPath.coordinates; + } + i = 0; + oldLength = oldArray.getLength(); + newLength = array.length; + l = Math.min(oldLength, newLength); + newValue = void 0; + while (i < l) { + oldValue = oldArray.getAt(i); + newValue = array[i]; + if ((oldValue.lat() !== newValue[1]) || (oldValue.lng() !== newValue[0])) { + oldArray.setAt(i, new google.maps.LatLng(newValue[1], newValue[0])); + } + i++; + } + while (i < newLength) { + newValue = array[i]; + oldArray.push(new google.maps.LatLng(newValue[1], newValue[0])); + i++; + } + while (i < oldLength) { + oldArray.pop(); + i++; + } + } + return isSetFromScope = false; + }; + watchListener; + if (!scope["static"]) { + if (angular.isUndefined(scopePath.type)) { + watchListener = scope.$watchCollection(pathEval, legacyWatcher); + } else { + watchListener = scope.$watch(pathEval, geojsonWatcher, true); + } + } + return function() { + if (mapArrayListener) { + mapArrayListener(); + mapArrayListener = null; + } + if (watchListener) { + watchListener(); + return watchListener = null; + } + }; + }; + } + ]); + +}).call(this); + +(function() { + angular.module("google-maps").factory("add-events", [ + "$timeout", function($timeout) { + var addEvent, addEvents; + addEvent = function(target, eventName, handler) { + return google.maps.event.addListener(target, eventName, function() { + handler.apply(this, arguments); + return $timeout((function() {}), true); + }); + }; + addEvents = function(target, eventName, handler) { + var remove; + if (handler) { + return addEvent(target, eventName, handler); + } + remove = []; + angular.forEach(eventName, function(_handler, key) { + return remove.push(addEvent(target, key, _handler)); + }); + return function() { + angular.forEach(remove, function(listener) { + return google.maps.event.removeListener(listener); + }); + return remove = null; + }; + }; + return addEvents; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("MarkerLabelChildModel", [ + "BaseObject", "GmapUtil", function(BaseObject, GmapUtil) { + var MarkerLabelChildModel; + MarkerLabelChildModel = (function(_super) { + __extends(MarkerLabelChildModel, _super); + + MarkerLabelChildModel.include(GmapUtil); + + function MarkerLabelChildModel(gMarker, opt_options) { + this.destroy = __bind(this.destroy, this); + this.draw = __bind(this.draw, this); + this.setPosition = __bind(this.setPosition, this); + this.setZIndex = __bind(this.setZIndex, this); + this.setVisible = __bind(this.setVisible, this); + this.setAnchor = __bind(this.setAnchor, this); + this.setMandatoryStyles = __bind(this.setMandatoryStyles, this); + this.setStyles = __bind(this.setStyles, this); + this.setContent = __bind(this.setContent, this); + this.setTitle = __bind(this.setTitle, this); + this.getSharedCross = __bind(this.getSharedCross, this); + var self, _ref, _ref1; + MarkerLabelChildModel.__super__.constructor.call(this); + self = this; + this.marker = gMarker; + this.marker.set("labelContent", opt_options.labelContent); + this.marker.set("labelAnchor", this.getLabelPositionPoint(opt_options.labelAnchor)); + this.marker.set("labelClass", opt_options.labelClass || 'labels'); + this.marker.set("labelStyle", opt_options.labelStyle || { + opacity: 100 + }); + this.marker.set("labelInBackground", opt_options.labelInBackground || false); + if (!opt_options.labelVisible) { + this.marker.set("labelVisible", true); + } + if (!opt_options.raiseOnDrag) { + this.marker.set("raiseOnDrag", true); + } + if (!opt_options.clickable) { + this.marker.set("clickable", true); + } + if (!opt_options.draggable) { + this.marker.set("draggable", false); + } + if (!opt_options.optimized) { + this.marker.set("optimized", false); + } + opt_options.crossImage = (_ref = opt_options.crossImage) != null ? _ref : document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + opt_options.handCursor = (_ref1 = opt_options.handCursor) != null ? _ref1 : document.location.protocol + "//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + this.markerLabel = new MarkerLabel_(this.marker, opt_options.crossImage, opt_options.handCursor); + this.marker.set("setMap", function(theMap) { + google.maps.Marker.prototype.setMap.apply(this, arguments); + return self.markerLabel.setMap(theMap); + }); + this.marker.setMap(this.marker.getMap()); + } + + MarkerLabelChildModel.prototype.getSharedCross = function(crossUrl) { + return this.markerLabel.getSharedCross(crossUrl); + }; + + MarkerLabelChildModel.prototype.setTitle = function() { + return this.markerLabel.setTitle(); + }; + + MarkerLabelChildModel.prototype.setContent = function() { + return this.markerLabel.setContent(); + }; + + MarkerLabelChildModel.prototype.setStyles = function() { + return this.markerLabel.setStyles(); + }; + + MarkerLabelChildModel.prototype.setMandatoryStyles = function() { + return this.markerLabel.setMandatoryStyles(); + }; + + MarkerLabelChildModel.prototype.setAnchor = function() { + return this.markerLabel.setAnchor(); + }; + + MarkerLabelChildModel.prototype.setVisible = function() { + return this.markerLabel.setVisible(); + }; + + MarkerLabelChildModel.prototype.setZIndex = function() { + return this.markerLabel.setZIndex(); + }; + + MarkerLabelChildModel.prototype.setPosition = function() { + return this.markerLabel.setPosition(); + }; + + MarkerLabelChildModel.prototype.draw = function() { + return this.markerLabel.draw(); + }; + + MarkerLabelChildModel.prototype.destroy = function() { + if ((this.markerLabel.labelDiv_.parentNode != null) && (this.markerLabel.eventDiv_.parentNode != null)) { + return this.markerLabel.onRemove(); + } + }; + + return MarkerLabelChildModel; + + })(BaseObject); + return MarkerLabelChildModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("MarkerChildModel", [ + "ModelKey", "GmapUtil", "Logger", "$injector", "EventsHelper", function(ModelKey, GmapUtil, Logger, $injector, EventsHelper) { + var MarkerChildModel; + MarkerChildModel = (function(_super) { + __extends(MarkerChildModel, _super); + + MarkerChildModel.include(GmapUtil); + + MarkerChildModel.include(EventsHelper); + + function MarkerChildModel(model, parentScope, gMap, $timeout, defaults, doClick, gMarkerManager, idKey) { + var self, + _this = this; + this.model = model; + this.parentScope = parentScope; + this.gMap = gMap; + this.$timeout = $timeout; + this.defaults = defaults; + this.doClick = doClick; + this.gMarkerManager = gMarkerManager; + this.idKey = idKey; + this.watchDestroy = __bind(this.watchDestroy, this); + this.setLabelOptions = __bind(this.setLabelOptions, this); + this.isLabelDefined = __bind(this.isLabelDefined, this); + this.setOptions = __bind(this.setOptions, this); + this.setIcon = __bind(this.setIcon, this); + this.setCoords = __bind(this.setCoords, this); + this.destroy = __bind(this.destroy, this); + this.maybeSetScopeValue = __bind(this.maybeSetScopeValue, this); + this.createMarker = __bind(this.createMarker, this); + this.setMyScope = __bind(this.setMyScope, this); + self = this; + if (this.model[this.idKey]) { + this.id = this.model[this.idKey]; + } + this.iconKey = this.parentScope.icon; + this.coordsKey = this.parentScope.coords; + this.clickKey = this.parentScope.click(); + this.labelContentKey = this.parentScope.labelContent; + this.optionsKey = this.parentScope.options; + this.labelOptionsKey = this.parentScope.labelOptions; + MarkerChildModel.__super__.constructor.call(this, this.parentScope.$new(false)); + this.scope.model = this.model; + this.setMyScope(this.model, void 0, true); + this.createMarker(this.model); + this.scope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setMyScope(newValue, oldValue); + } + }, true); + this.$log = Logger; + this.$log.info(self); + this.watchDestroy(this.scope); + } + + MarkerChildModel.prototype.setMyScope = function(model, oldModel, isInit) { + var _this = this; + if (oldModel == null) { + oldModel = void 0; + } + if (isInit == null) { + isInit = false; + } + this.maybeSetScopeValue('icon', model, oldModel, this.iconKey, this.evalModelHandle, isInit, this.setIcon); + this.maybeSetScopeValue('coords', model, oldModel, this.coordsKey, this.evalModelHandle, isInit, this.setCoords); + this.maybeSetScopeValue('labelContent', model, oldModel, this.labelContentKey, this.evalModelHandle, isInit); + if (_.isFunction(this.clickKey) && $injector) { + return this.scope.click = function() { + return $injector.invoke(_this.clickKey, void 0, { + "$markerModel": model + }); + }; + } else { + this.maybeSetScopeValue('click', model, oldModel, this.clickKey, this.evalModelHandle, isInit); + return this.createMarker(model, oldModel, isInit); + } + }; + + MarkerChildModel.prototype.createMarker = function(model, oldModel, isInit) { + var _this = this; + if (oldModel == null) { + oldModel = void 0; + } + if (isInit == null) { + isInit = false; + } + return this.maybeSetScopeValue('options', model, oldModel, this.optionsKey, function(lModel, lModelKey) { + var value; + if (lModel === void 0) { + return void 0; + } + value = lModelKey === 'self' ? lModel : lModel[lModelKey]; + if (value === void 0) { + return value = lModelKey === void 0 ? _this.defaults : _this.scope.options; + } else { + return value; + } + }, isInit, this.setOptions); + }; + + MarkerChildModel.prototype.maybeSetScopeValue = function(scopePropName, model, oldModel, modelKey, evaluate, isInit, gSetter) { + var newValue, oldVal; + if (gSetter == null) { + gSetter = void 0; + } + if (oldModel === void 0) { + this.scope[scopePropName] = evaluate(model, modelKey); + if (!isInit) { + if (gSetter != null) { + gSetter(this.scope); + } + } + return; + } + oldVal = evaluate(oldModel, modelKey); + newValue = evaluate(model, modelKey); + if (newValue !== oldVal && this.scope[scopePropName] !== newValue) { + this.scope[scopePropName] = newValue; + if (!isInit) { + if (gSetter != null) { + gSetter(this.scope); + } + return this.gMarkerManager.draw(); + } + } + }; + + MarkerChildModel.prototype.destroy = function() { + return this.scope.$destroy(); + }; + + MarkerChildModel.prototype.setCoords = function(scope) { + if (scope.$id !== this.scope.$id || this.gMarker === void 0) { + return; + } + if ((scope.coords != null)) { + if (!this.validateCoords(this.scope.coords)) { + this.$log.error("MarkerChildMarker cannot render marker as scope.coords as no position on marker: " + (JSON.stringify(this.model))); + return; + } + this.gMarker.setPosition(this.getCoords(scope.coords)); + this.gMarker.setVisible(this.validateCoords(scope.coords)); + this.gMarkerManager.remove(this.gMarker); + return this.gMarkerManager.add(this.gMarker); + } else { + return this.gMarkerManager.remove(this.gMarker); + } + }; + + MarkerChildModel.prototype.setIcon = function(scope) { + if (scope.$id !== this.scope.$id || this.gMarker === void 0) { + return; + } + this.gMarkerManager.remove(this.gMarker); + this.gMarker.setIcon(scope.icon); + this.gMarkerManager.add(this.gMarker); + this.gMarker.setPosition(this.getCoords(scope.coords)); + return this.gMarker.setVisible(this.validateCoords(scope.coords)); + }; + + MarkerChildModel.prototype.setOptions = function(scope) { + var _ref, + _this = this; + if (scope.$id !== this.scope.$id) { + return; + } + if (this.gMarker != null) { + this.gMarkerManager.remove(this.gMarker); + delete this.gMarker; + } + if (!((_ref = scope.coords) != null ? _ref : typeof scope.icon === "function" ? scope.icon(scope.options != null) : void 0)) { + return; + } + this.opts = this.createMarkerOptions(scope.coords, scope.icon, scope.options); + delete this.gMarker; + if (this.isLabelDefined(scope)) { + this.gMarker = new MarkerWithLabel(this.setLabelOptions(this.opts, scope)); + } else { + this.gMarker = new google.maps.Marker(this.opts); + } + this.setEvents(this.gMarker, this.parentScope, this.model); + if (this.id) { + this.gMarker.key = this.id; + } + this.gMarkerManager.add(this.gMarker); + return google.maps.event.addListener(this.gMarker, 'click', function() { + if (_this.doClick && (_this.scope.click != null)) { + return _this.scope.click(); + } + }); + }; + + MarkerChildModel.prototype.isLabelDefined = function(scope) { + return scope.labelContent != null; + }; + + MarkerChildModel.prototype.setLabelOptions = function(opts, scope) { + opts.labelAnchor = this.getLabelPositionPoint(scope.labelAnchor); + opts.labelClass = scope.labelClass; + opts.labelContent = scope.labelContent; + return opts; + }; + + MarkerChildModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + var self, _ref; + if (_this.gMarker != null) { + google.maps.event.clearListeners(_this.gMarker, 'click'); + if (((_ref = _this.parentScope) != null ? _ref.events : void 0) && _.isArray(_this.parentScope.events)) { + _this.parentScope.events.forEach(function(event, eventName) { + return google.maps.event.clearListeners(this.gMarker, eventName); + }); + } + _this.gMarkerManager.remove(_this.gMarker, true); + delete _this.gMarker; + } + return self = void 0; + }); + }; + + return MarkerChildModel; + + })(ModelKey); + return MarkerChildModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("PolylineChildModel", [ + "BaseObject", "Logger", "$timeout", "array-sync", "GmapUtil", function(BaseObject, Logger, $timeout, arraySync, GmapUtil) { + var $log, PolylineChildModel; + $log = Logger; + return PolylineChildModel = (function(_super) { + __extends(PolylineChildModel, _super); + + PolylineChildModel.include(GmapUtil); + + function PolylineChildModel(scope, attrs, map, defaults, model) { + var arraySyncer, pathPoints, + _this = this; + this.scope = scope; + this.attrs = attrs; + this.map = map; + this.defaults = defaults; + this.model = model; + this.buildOpts = __bind(this.buildOpts, this); + pathPoints = this.convertPathPoints(scope.path); + this.polyline = new google.maps.Polyline(this.buildOpts(pathPoints)); + if (scope.fit) { + GmapUtil.extendMapBounds(map, pathPoints); + } + if (!scope["static"] && angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setEditable(newValue); + } + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setDraggable(newValue); + } + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setVisible(newValue); + } + }); + } + if (angular.isDefined(scope.geodesic)) { + scope.$watch("geodesic", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + if (angular.isDefined(scope.icons)) { + scope.$watch("icons", function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.polyline.setOptions(_this.buildOpts(_this.polyline.getPath())); + } + }); + } + arraySyncer = arraySync(this.polyline.getPath(), scope, "path", function(pathPoints) { + if (scope.fit) { + return GmapUtil.extendMapBounds(map, pathPoints); + } + }); + scope.$on("$destroy", function() { + _this.polyline.setMap(null); + _this.polyline = null; + _this.scope = null; + if (arraySyncer) { + arraySyncer(); + return arraySyncer = null; + } + }); + $log.info(this); + } + + PolylineChildModel.prototype.buildOpts = function(pathPoints) { + var opts, + _this = this; + opts = angular.extend({}, this.defaults, { + map: this.map, + path: pathPoints, + icons: this.scope.icons, + strokeColor: this.scope.stroke && this.scope.stroke.color, + strokeOpacity: this.scope.stroke && this.scope.stroke.opacity, + strokeWeight: this.scope.stroke && this.scope.stroke.weight + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true, + "static": false, + fit: false + }, function(defaultValue, key) { + if (angular.isUndefined(_this.scope[key]) || _this.scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = _this.scope[key]; + } + }); + if (opts["static"]) { + opts.editable = false; + } + return opts; + }; + + PolylineChildModel.prototype.destroy = function() { + return this.scope.$destroy(); + }; + + return PolylineChildModel; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.child").factory("WindowChildModel", [ + "BaseObject", "GmapUtil", "Logger", "$compile", "$http", "$templateCache", function(BaseObject, GmapUtil, Logger, $compile, $http, $templateCache) { + var WindowChildModel; + WindowChildModel = (function(_super) { + __extends(WindowChildModel, _super); + + WindowChildModel.include(GmapUtil); + + function WindowChildModel(model, scope, opts, isIconVisibleOnClick, mapCtrl, markerCtrl, element, needToManualDestroy, markerIsVisibleAfterWindowClose) { + this.model = model; + this.scope = scope; + this.opts = opts; + this.isIconVisibleOnClick = isIconVisibleOnClick; + this.mapCtrl = mapCtrl; + this.markerCtrl = markerCtrl; + this.element = element; + this.needToManualDestroy = needToManualDestroy != null ? needToManualDestroy : false; + this.markerIsVisibleAfterWindowClose = markerIsVisibleAfterWindowClose != null ? markerIsVisibleAfterWindowClose : true; + this.destroy = __bind(this.destroy, this); + this.remove = __bind(this.remove, this); + this.hideWindow = __bind(this.hideWindow, this); + this.getLatestPosition = __bind(this.getLatestPosition, this); + this.showWindow = __bind(this.showWindow, this); + this.handleClick = __bind(this.handleClick, this); + this.watchCoords = __bind(this.watchCoords, this); + this.watchShow = __bind(this.watchShow, this); + this.createGWin = __bind(this.createGWin, this); + this.watchElement = __bind(this.watchElement, this); + this.googleMapsHandles = []; + this.$log = Logger; + this.createGWin(); + if (this.markerCtrl != null) { + this.markerCtrl.setClickable(true); + } + this.handleClick(); + this.watchElement(); + this.watchShow(); + this.watchCoords(); + this.$log.info(this); + } + + WindowChildModel.prototype.watchElement = function() { + var _this = this; + return this.scope.$watch(function() { + var _ref; + if (!_this.element || !_this.html) { + return; + } + if (_this.html !== _this.element.html()) { + if (_this.gWin) { + if ((_ref = _this.opts) != null) { + _ref.content = void 0; + } + _this.remove(); + _this.createGWin(); + return _this.showHide(); + } + } + }); + }; + + WindowChildModel.prototype.createGWin = function() { + var defaults, + _this = this; + if (this.gWin == null) { + defaults = {}; + if (this.opts != null) { + if (this.scope.coords) { + this.opts.position = this.getCoords(this.scope.coords); + } + defaults = this.opts; + } + if (this.element) { + this.html = _.isObject(this.element) ? this.element.html() : this.element; + } + this.opts = this.createWindowOptions(this.markerCtrl, this.scope, this.html, defaults); + } + if ((this.opts != null) && !this.gWin) { + if (this.opts.boxClass && (window.InfoBox && typeof window.InfoBox === 'function')) { + this.gWin = new window.InfoBox(this.opts); + } else { + this.gWin = new google.maps.InfoWindow(this.opts); + } + return this.googleMapsHandles.push(google.maps.event.addListener(this.gWin, 'closeclick', function() { + var _ref; + if ((_ref = _this.markerCtrl) != null) { + _ref.setVisible(_this.markerIsVisibleAfterWindowClose); + } + _this.gWin.isOpen(false); + if (_this.scope.closeClick != null) { + return _this.scope.closeClick(); + } + })); + } + }; + + WindowChildModel.prototype.watchShow = function() { + var _this = this; + return this.scope.$watch('show', function(newValue, oldValue) { + if (newValue !== oldValue) { + if (newValue) { + return _this.showWindow(); + } else { + return _this.hideWindow(); + } + } else { + if (_this.gWin != null) { + if (newValue && !_this.gWin.getMap()) { + return _this.showWindow(); + } + } + } + }, true); + }; + + WindowChildModel.prototype.watchCoords = function() { + var scope, + _this = this; + scope = this.markerCtrl != null ? this.scope.$parent : this.scope; + return scope.$watch('coords', function(newValue, oldValue) { + var pos; + if (newValue !== oldValue) { + if (newValue == null) { + return _this.hideWindow(); + } else { + if (!_this.validateCoords(newValue)) { + _this.$log.error("WindowChildMarker cannot render marker as scope.coords as no position on marker: " + (JSON.stringify(_this.model))); + return; + } + pos = _this.getCoords(newValue); + _this.gWin.setPosition(pos); + if (_this.opts) { + return _this.opts.position = pos; + } + } + } + }, true); + }; + + WindowChildModel.prototype.handleClick = function() { + var _this = this; + if (this.markerCtrl != null) { + return this.googleMapsHandles.push(google.maps.event.addListener(this.markerCtrl, 'click', function() { + var pos; + if (_this.gWin == null) { + _this.createGWin(); + } + pos = _this.markerCtrl.getPosition(); + if (_this.gWin != null) { + _this.gWin.setPosition(pos); + if (_this.opts) { + _this.opts.position = pos; + } + _this.showWindow(); + } + _this.initialMarkerVisibility = _this.markerCtrl.getVisible(); + return _this.markerCtrl.setVisible(_this.isIconVisibleOnClick); + })); + } + }; + + WindowChildModel.prototype.showWindow = function() { + var show, + _this = this; + show = function() { + if (_this.gWin) { + if ((_this.scope.show || (_this.scope.show == null)) && !_this.gWin.isOpen()) { + return _this.gWin.open(_this.mapCtrl); + } + } + }; + if (this.scope.templateUrl) { + if (this.gWin) { + $http.get(this.scope.templateUrl, { + cache: $templateCache + }).then(function(content) { + var compiled, templateScope; + templateScope = _this.scope.$new(); + if (angular.isDefined(_this.scope.templateParameter)) { + templateScope.parameter = _this.scope.templateParameter; + } + compiled = $compile(content.data)(templateScope); + return _this.gWin.setContent(compiled[0]); + }); + } + return show(); + } else { + return show(); + } + }; + + WindowChildModel.prototype.showHide = function() { + if (this.scope.show) { + return this.showWindow(); + } else { + return this.hideWindow(); + } + }; + + WindowChildModel.prototype.getLatestPosition = function() { + if ((this.gWin != null) && (this.markerCtrl != null)) { + return this.gWin.setPosition(this.markerCtrl.getPosition()); + } + }; + + WindowChildModel.prototype.hideWindow = function() { + if ((this.gWin != null) && this.gWin.isOpen()) { + return this.gWin.close(); + } + }; + + WindowChildModel.prototype.remove = function() { + this.hideWindow(); + _.each(this.googleMapsHandles, function(h) { + return google.maps.event.removeListener(h); + }); + this.googleMapsHandles.length = 0; + return delete this.gWin; + }; + + WindowChildModel.prototype.destroy = function(manualOverride) { + var self; + if (manualOverride == null) { + manualOverride = false; + } + this.remove(); + if ((this.scope != null) && (this.needToManualDestroy || manualOverride)) { + this.scope.$destroy(); + } + return self = void 0; + }; + + return WindowChildModel; + + })(BaseObject); + return WindowChildModel; + } + ]); + +}).call(this); + +/* + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("IMarkerParentModel", [ + "ModelKey", "Logger", function(ModelKey, Logger) { + var IMarkerParentModel; + IMarkerParentModel = (function(_super) { + __extends(IMarkerParentModel, _super); + + IMarkerParentModel.prototype.DEFAULTS = {}; + + function IMarkerParentModel(scope, element, attrs, mapCtrl, $timeout) { + var self, + _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.mapCtrl = mapCtrl; + this.$timeout = $timeout; + this.linkInit = __bind(this.linkInit, this); + this.onDestroy = __bind(this.onDestroy, this); + this.onWatch = __bind(this.onWatch, this); + this.watch = __bind(this.watch, this); + this.validateScope = __bind(this.validateScope, this); + this.onTimeOut = __bind(this.onTimeOut, this); + IMarkerParentModel.__super__.constructor.call(this, this.scope); + self = this; + this.$log = Logger; + if (!this.validateScope(scope)) { + throw new String("Unable to construct IMarkerParentModel due to invalid scope"); + } + this.doClick = angular.isDefined(attrs.click); + if (scope.options != null) { + this.DEFAULTS = scope.options; + } + this.$timeout(function() { + _this.onTimeOut(scope); + _this.watch('coords', _this.scope); + _this.watch('icon', _this.scope); + _this.watch('options', _this.scope); + return scope.$on("$destroy", function() { + return _this.onDestroy(scope); + }); + }); + } + + IMarkerParentModel.prototype.onTimeOut = function(scope) {}; + + IMarkerParentModel.prototype.validateScope = function(scope) { + var ret; + if (scope == null) { + this.$log.error(this.constructor.name + ": invalid scope used"); + return false; + } + ret = scope.coords != null; + if (!ret) { + this.$log.error(this.constructor.name + ": no valid coords attribute found"); + return false; + } + return ret; + }; + + IMarkerParentModel.prototype.watch = function(propNameToWatch, scope) { + var watchFunc, + _this = this; + watchFunc = function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.onWatch(propNameToWatch, scope, newValue, oldValue); + } + }; + return scope.$watch(propNameToWatch, watchFunc, true); + }; + + IMarkerParentModel.prototype.onWatch = function(propNameToWatch, scope, newValue, oldValue) { + throw new String("OnWatch Not Implemented!!"); + }; + + IMarkerParentModel.prototype.onDestroy = function(scope) { + throw new String("OnDestroy Not Implemented!!"); + }; + + IMarkerParentModel.prototype.linkInit = function(element, mapCtrl, scope, animate) { + throw new String("LinkInit Not Implemented!!"); + }; + + return IMarkerParentModel; + + })(ModelKey); + return IMarkerParentModel; + } + ]); + +}).call(this); + +/* + - interface directive for all window(s) to derrive from +*/ + + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("IWindowParentModel", [ + "ModelKey", "GmapUtil", "Logger", function(ModelKey, GmapUtil, Logger) { + var IWindowParentModel; + IWindowParentModel = (function(_super) { + __extends(IWindowParentModel, _super); + + IWindowParentModel.include(GmapUtil); + + IWindowParentModel.prototype.DEFAULTS = {}; + + function IWindowParentModel(scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache) { + var self; + IWindowParentModel.__super__.constructor.call(this, scope); + self = this; + this.$log = Logger; + this.$timeout = $timeout; + this.$compile = $compile; + this.$http = $http; + this.$templateCache = $templateCache; + if (scope.options != null) { + this.DEFAULTS = scope.options; + } + } + + return IWindowParentModel; + + })(ModelKey); + return IWindowParentModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("LayerParentModel", [ + "BaseObject", "Logger", function(BaseObject, Logger) { + var LayerParentModel; + LayerParentModel = (function(_super) { + __extends(LayerParentModel, _super); + + function LayerParentModel(scope, element, attrs, mapCtrl, $timeout, onLayerCreated, $log) { + var _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.mapCtrl = mapCtrl; + this.$timeout = $timeout; + this.onLayerCreated = onLayerCreated != null ? onLayerCreated : void 0; + this.$log = $log != null ? $log : Logger; + this.createGoogleLayer = __bind(this.createGoogleLayer, this); + if (this.attrs.type == null) { + this.$log.info("type attribute for the layer directive is mandatory. Layer creation aborted!!"); + return; + } + this.createGoogleLayer(); + this.gMap = void 0; + this.doShow = true; + this.$timeout(function() { + _this.gMap = mapCtrl.getMap(); + if (angular.isDefined(_this.attrs.show)) { + _this.doShow = _this.scope.show; + } + if (_this.doShow && (_this.gMap != null)) { + _this.layer.setMap(_this.gMap); + } + _this.scope.$watch("show", function(newValue, oldValue) { + if (newValue !== oldValue) { + _this.doShow = newValue; + if (newValue) { + return _this.layer.setMap(_this.gMap); + } else { + return _this.layer.setMap(null); + } + } + }, true); + _this.scope.$watch("options", function(newValue, oldValue) { + if (newValue !== oldValue) { + _this.layer.setMap(null); + _this.layer = null; + return _this.createGoogleLayer(); + } + }, true); + return _this.scope.$on("$destroy", function() { + return _this.layer.setMap(null); + }); + }); + } + + LayerParentModel.prototype.createGoogleLayer = function() { + var _this = this; + if (this.attrs.options == null) { + this.layer = this.attrs.namespace === void 0 ? new google.maps[this.attrs.type]() : new google.maps[this.attrs.namespace][this.attrs.type](); + } else { + this.layer = this.attrs.namespace === void 0 ? new google.maps[this.attrs.type](this.scope.options) : new google.maps[this.attrs.namespace][this.attrs.type](this.scope.options); + } + return this.$timeout(function() { + var fn; + if ((_this.layer != null) && (_this.onLayerCreated != null)) { + fn = _this.onLayerCreated(_this.scope, _this.layer); + if (fn) { + return fn(_this.layer); + } + } + }); + }; + + return LayerParentModel; + + })(BaseObject); + return LayerParentModel; + } + ]); + +}).call(this); + +/* + Basic Directive api for a marker. Basic in the sense that this directive contains 1:1 on scope and model. + Thus there will be one html element per marker within the directive. +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("MarkerParentModel", [ + "IMarkerParentModel", "GmapUtil", "EventsHelper", function(IMarkerParentModel, GmapUtil, EventsHelper) { + var MarkerParentModel; + MarkerParentModel = (function(_super) { + __extends(MarkerParentModel, _super); + + MarkerParentModel.include(GmapUtil); + + MarkerParentModel.include(EventsHelper); + + function MarkerParentModel(scope, element, attrs, mapCtrl, $timeout, gMarkerManager, doFit) { + var self; + this.gMarkerManager = gMarkerManager; + this.doFit = doFit; + this.onDestroy = __bind(this.onDestroy, this); + this.setGMarker = __bind(this.setGMarker, this); + this.onWatch = __bind(this.onWatch, this); + this.onTimeOut = __bind(this.onTimeOut, this); + MarkerParentModel.__super__.constructor.call(this, scope, element, attrs, mapCtrl, $timeout); + self = this; + } + + MarkerParentModel.prototype.onTimeOut = function(scope) { + var opts, + _this = this; + opts = this.createMarkerOptions(scope.coords, scope.icon, scope.options, this.mapCtrl.getMap()); + this.setGMarker(new google.maps.Marker(opts)); + google.maps.event.addListener(this.scope.gMarker, 'click', function() { + if (_this.doClick && (scope.click != null)) { + return _this.$timeout(function() { + return _this.scope.click(); + }); + } + }); + this.setEvents(this.scope.gMarker, scope, scope); + return this.$log.info(this); + }; + + MarkerParentModel.prototype.onWatch = function(propNameToWatch, scope) { + switch (propNameToWatch) { + case 'coords': + if (this.validateCoords(scope.coords) && (this.scope.gMarker != null)) { + this.scope.gMarker.setMap(this.mapCtrl.getMap()); + this.scope.gMarker.setPosition(this.getCoords(scope.coords)); + this.scope.gMarker.setVisible(this.validateCoords(scope.coords)); + return this.scope.gMarker.setOptions(scope.options); + } else { + return this.scope.gMarker.setMap(null); + } + break; + case 'icon': + if ((scope.icon != null) && this.validateCoords(scope.coords) && (this.scope.gMarker != null)) { + this.scope.gMarker.setOptions(scope.options); + this.scope.gMarker.setIcon(scope.icon); + this.scope.gMarker.setMap(null); + this.scope.gMarker.setMap(this.mapCtrl.getMap()); + this.scope.gMarker.setPosition(this.getCoords(scope.coords)); + return this.scope.gMarker.setVisible(this.validateCoords(scope.coords)); + } + break; + case 'options': + if (this.validateCoords(scope.coords) && (scope.icon != null) && scope.options) { + if (this.scope.gMarker != null) { + this.scope.gMarker.setMap(null); + } + return this.setGMarker(new google.maps.Marker(this.createMarkerOptions(scope.coords, scope.icon, scope.options, this.mapCtrl.getMap()))); + } + } + }; + + MarkerParentModel.prototype.setGMarker = function(gMarker) { + if (this.scope.gMarker) { + delete this.scope.gMarker; + this.gMarkerManager.remove(this.scope.gMarker, false); + } + this.scope.gMarker = gMarker; + if (this.scope.gMarker) { + this.gMarkerManager.add(this.scope.gMarker, false); + if (this.doFit) { + return this.gMarkerManager.fit(); + } + } + }; + + MarkerParentModel.prototype.onDestroy = function(scope) { + var self; + if (!this.scope.gMarker) { + self = void 0; + return; + } + this.scope.gMarker.setMap(null); + this.gMarkerManager.remove(this.scope.gMarker, false); + delete this.scope.gMarker; + return self = void 0; + }; + + return MarkerParentModel; + + })(IMarkerParentModel); + return MarkerParentModel; + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("MarkersParentModel", [ + "IMarkerParentModel", "ModelsWatcher", "PropMap", "MarkerChildModel", "ClustererMarkerManager", "MarkerManager", function(IMarkerParentModel, ModelsWatcher, PropMap, MarkerChildModel, ClustererMarkerManager, MarkerManager) { + var MarkersParentModel; + MarkersParentModel = (function(_super) { + __extends(MarkersParentModel, _super); + + MarkersParentModel.include(ModelsWatcher); + + function MarkersParentModel(scope, element, attrs, mapCtrl, $timeout) { + this.onDestroy = __bind(this.onDestroy, this); + this.newChildMarker = __bind(this.newChildMarker, this); + this.pieceMealMarkers = __bind(this.pieceMealMarkers, this); + this.reBuildMarkers = __bind(this.reBuildMarkers, this); + this.createMarkersFromScratch = __bind(this.createMarkersFromScratch, this); + this.validateScope = __bind(this.validateScope, this); + this.onWatch = __bind(this.onWatch, this); + this.onTimeOut = __bind(this.onTimeOut, this); + var self, + _this = this; + MarkersParentModel.__super__.constructor.call(this, scope, element, attrs, mapCtrl, $timeout); + self = this; + this.scope.markerModels = new PropMap(); + this.$timeout = $timeout; + this.$log.info(this); + this.doRebuildAll = this.scope.doRebuildAll != null ? this.scope.doRebuildAll : false; + this.setIdKey(scope); + this.scope.$watch('doRebuildAll', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.doRebuildAll = newValue; + } + }); + } + + MarkersParentModel.prototype.onTimeOut = function(scope) { + this.watch('models', scope); + this.watch('doCluster', scope); + this.watch('clusterOptions', scope); + this.watch('clusterEvents', scope); + this.watch('fit', scope); + this.watch('idKey', scope); + this.gMarkerManager = void 0; + return this.createMarkersFromScratch(scope); + }; + + MarkersParentModel.prototype.onWatch = function(propNameToWatch, scope, newValue, oldValue) { + if (propNameToWatch === "idKey" && newValue !== oldValue) { + this.idKey = newValue; + } + if (this.doRebuildAll) { + return this.reBuildMarkers(scope); + } else { + return this.pieceMealMarkers(scope); + } + }; + + MarkersParentModel.prototype.validateScope = function(scope) { + var modelsNotDefined; + modelsNotDefined = angular.isUndefined(scope.models) || scope.models === void 0; + if (modelsNotDefined) { + this.$log.error(this.constructor.name + ": no valid models attribute found"); + } + return MarkersParentModel.__super__.validateScope.call(this, scope) || modelsNotDefined; + }; + + MarkersParentModel.prototype.createMarkersFromScratch = function(scope) { + var _this = this; + if (scope.doCluster) { + if (scope.clusterEvents) { + this.clusterInternalOptions = _.once(function() { + var self, _ref, _ref1, _ref2; + self = _this; + if (!_this.origClusterEvents) { + _this.origClusterEvents = { + click: (_ref = scope.clusterEvents) != null ? _ref.click : void 0, + mouseout: (_ref1 = scope.clusterEvents) != null ? _ref1.mouseout : void 0, + mouseover: (_ref2 = scope.clusterEvents) != null ? _ref2.mouseover : void 0 + }; + return _.extend(scope.clusterEvents, { + click: function(cluster) { + return self.maybeExecMappedEvent(cluster, "click"); + }, + mouseout: function(cluster) { + return self.maybeExecMappedEvent(cluster, "mouseout"); + }, + mouseover: function(cluster) { + return self.maybeExecMappedEvent(cluster, "mouseover"); + } + }); + } + })(); + } + if (scope.clusterOptions || scope.clusterEvents) { + if (this.gMarkerManager === void 0) { + this.gMarkerManager = new ClustererMarkerManager(this.mapCtrl.getMap(), void 0, scope.clusterOptions, this.clusterInternalOptions); + } else { + if (this.gMarkerManager.opt_options !== scope.clusterOptions) { + this.gMarkerManager = new ClustererMarkerManager(this.mapCtrl.getMap(), void 0, scope.clusterOptions, this.clusterInternalOptions); + } + } + } else { + this.gMarkerManager = new ClustererMarkerManager(this.mapCtrl.getMap()); + } + } else { + this.gMarkerManager = new MarkerManager(this.mapCtrl.getMap()); + } + return _async.each(scope.models, function(model) { + return _this.newChildMarker(model, scope); + }, function() { + _this.gMarkerManager.draw(); + if (scope.fit) { + return _this.gMarkerManager.fit(); + } + }); + }; + + MarkersParentModel.prototype.reBuildMarkers = function(scope) { + if (!scope.doRebuild && scope.doRebuild !== void 0) { + return; + } + this.onDestroy(scope); + return this.createMarkersFromScratch(scope); + }; + + MarkersParentModel.prototype.pieceMealMarkers = function(scope) { + var _this = this; + if ((this.scope.models != null) && this.scope.models.length > 0 && this.scope.markerModels.length > 0) { + return this.figureOutState(this.idKey, scope, this.scope.markerModels, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(child) { + if (child != null) { + child.destroy(); + return _this.scope.markerModels.remove(child.id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + return _this.newChildMarker(modelToAdd, scope); + }, function() { + if (payload.adds.length > 0 || payload.removals.length > 0) { + _this.gMarkerManager.draw(); + return scope.markerModels = _this.scope.markerModels; + } + }); + }); + }); + } else { + return this.reBuildMarkers(scope); + } + }; + + MarkersParentModel.prototype.newChildMarker = function(model, scope) { + var child; + if (model[this.idKey] == null) { + this.$log.error("Marker model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.$log.info('child', child, 'markers', this.scope.markerModels); + child = new MarkerChildModel(model, scope, this.mapCtrl, this.$timeout, this.DEFAULTS, this.doClick, this.gMarkerManager, this.idKey); + this.scope.markerModels.put(model[this.idKey], child); + return child; + }; + + MarkersParentModel.prototype.onDestroy = function(scope) { + _.each(this.scope.markerModels.values(), function(model) { + if (model != null) { + return model.destroy(); + } + }); + delete this.scope.markerModels; + this.scope.markerModels = new PropMap(); + if (this.gMarkerManager != null) { + return this.gMarkerManager.clear(); + } + }; + + MarkersParentModel.prototype.maybeExecMappedEvent = function(cluster, fnName) { + var pair, _ref; + if (_.isFunction((_ref = this.scope.clusterEvents) != null ? _ref[fnName] : void 0)) { + pair = this.mapClusterToMarkerModels(cluster); + if (this.origClusterEvents[fnName]) { + return this.origClusterEvents[fnName](pair.cluster, pair.mapped); + } + } + }; + + MarkersParentModel.prototype.mapClusterToMarkerModels = function(cluster) { + var gMarkers, mapped, + _this = this; + gMarkers = cluster.getMarkers(); + mapped = gMarkers.map(function(g) { + return _this.scope.markerModels[g.key].model; + }); + return { + cluster: cluster, + mapped: mapped + }; + }; + + return MarkersParentModel; + + })(IMarkerParentModel); + return MarkersParentModel; + } + ]); + +}).call(this); + +/* + Windows directive where many windows map to the models property +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("PolylinesParentModel", [ + "$timeout", "Logger", "ModelKey", "ModelsWatcher", "PropMap", "PolylineChildModel", function($timeout, Logger, ModelKey, ModelsWatcher, PropMap, PolylineChildModel) { + var PolylinesParentModel; + return PolylinesParentModel = (function(_super) { + __extends(PolylinesParentModel, _super); + + PolylinesParentModel.include(ModelsWatcher); + + function PolylinesParentModel(scope, element, attrs, gMap, defaults) { + var self, + _this = this; + this.scope = scope; + this.element = element; + this.attrs = attrs; + this.gMap = gMap; + this.defaults = defaults; + this.modelKeyComparison = __bind(this.modelKeyComparison, this); + this.setChildScope = __bind(this.setChildScope, this); + this.createChild = __bind(this.createChild, this); + this.pieceMeal = __bind(this.pieceMeal, this); + this.createAllNew = __bind(this.createAllNew, this); + this.watchIdKey = __bind(this.watchIdKey, this); + this.createChildScopes = __bind(this.createChildScopes, this); + this.watchOurScope = __bind(this.watchOurScope, this); + this.watchDestroy = __bind(this.watchDestroy, this); + this.rebuildAll = __bind(this.rebuildAll, this); + this.doINeedToWipe = __bind(this.doINeedToWipe, this); + this.watchModels = __bind(this.watchModels, this); + this.watch = __bind(this.watch, this); + PolylinesParentModel.__super__.constructor.call(this, scope); + self = this; + this.$log = Logger; + this.plurals = new PropMap(); + this.scopePropNames = ['path', 'stroke', 'clickable', 'draggable', 'editable', 'geodesic', 'icons', 'visible']; + _.each(this.scopePropNames, function(name) { + return _this[name + 'Key'] = void 0; + }); + this.models = void 0; + this.firstTime = true; + this.$log.info(this); + $timeout(function() { + _this.watchOurScope(scope); + return _this.createChildScopes(); + }); + } + + PolylinesParentModel.prototype.watch = function(scope, name, nameKey) { + var _this = this; + return scope.$watch(name, function(newValue, oldValue) { + if (newValue !== oldValue) { + _this[nameKey] = typeof newValue === 'function' ? newValue() : newValue; + return _async.each(_.values(_this.plurals), function(model) { + return model.scope[name] = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + }, function() {}); + } + }); + }; + + PolylinesParentModel.prototype.watchModels = function(scope) { + var _this = this; + return scope.$watch('models', function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + if (_this.doINeedToWipe(newValue)) { + return _this.rebuildAll(scope, true, true); + } else { + return _this.createChildScopes(false); + } + } + }, true); + }; + + PolylinesParentModel.prototype.doINeedToWipe = function(newValue) { + var newValueIsEmpty; + newValueIsEmpty = newValue != null ? newValue.length === 0 : true; + return this.plurals.length > 0 && newValueIsEmpty; + }; + + PolylinesParentModel.prototype.rebuildAll = function(scope, doCreate, doDelete) { + var _this = this; + return _async.each(this.plurals.values(), function(model) { + return model.destroy(); + }, function() { + if (doDelete) { + delete _this.plurals; + } + _this.plurals = new PropMap(); + if (doCreate) { + return _this.createChildScopes(); + } + }); + }; + + PolylinesParentModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + return _this.rebuildAll(scope, false, true); + }); + }; + + PolylinesParentModel.prototype.watchOurScope = function(scope) { + var _this = this; + return _.each(this.scopePropNames, function(name) { + var nameKey; + nameKey = name + 'Key'; + _this[nameKey] = typeof scope[name] === 'function' ? scope[name]() : scope[name]; + return _this.watch(scope, name, nameKey); + }); + }; + + PolylinesParentModel.prototype.createChildScopes = function(isCreatingFromScratch) { + if (isCreatingFromScratch == null) { + isCreatingFromScratch = true; + } + if (angular.isUndefined(this.scope.models)) { + this.$log.error("No models to create polylines from! I Need direct models!"); + return; + } + if (this.gMap != null) { + if (this.scope.models != null) { + this.watchIdKey(this.scope); + if (isCreatingFromScratch) { + return this.createAllNew(this.scope, false); + } else { + return this.pieceMeal(this.scope, false); + } + } + } + }; + + PolylinesParentModel.prototype.watchIdKey = function(scope) { + var _this = this; + this.setIdKey(scope); + return scope.$watch('idKey', function(newValue, oldValue) { + if (newValue !== oldValue && (newValue == null)) { + _this.idKey = newValue; + return _this.rebuildAll(scope, true, true); + } + }); + }; + + PolylinesParentModel.prototype.createAllNew = function(scope, isArray) { + var _this = this; + if (isArray == null) { + isArray = false; + } + this.models = scope.models; + if (this.firstTime) { + this.watchModels(scope); + this.watchDestroy(scope); + } + return _async.each(scope.models, function(model) { + return _this.createChild(model, _this.gMap); + }, function() { + return _this.firstTime = false; + }); + }; + + PolylinesParentModel.prototype.pieceMeal = function(scope, isArray) { + var _this = this; + if (isArray == null) { + isArray = true; + } + this.models = scope.models; + if ((scope != null) && (scope.models != null) && scope.models.length > 0 && this.plurals.length > 0) { + return this.figureOutState(this.idKey, scope, this.plurals, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(id) { + var child; + child = _this.plurals[id]; + if (child != null) { + child.destroy(); + return _this.plurals.remove(id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + return _this.createChild(modelToAdd, _this.gMap); + }, function() {}); + }); + }); + } else { + return this.rebuildAll(this.scope, true, true); + } + }; + + PolylinesParentModel.prototype.createChild = function(model, gMap) { + var child, childScope, + _this = this; + childScope = this.scope.$new(false); + this.setChildScope(childScope, model); + childScope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setChildScope(childScope, newValue); + } + }, true); + childScope["static"] = this.scope["static"]; + child = new PolylineChildModel(childScope, this.attrs, gMap, this.defaults, model); + if (model[this.idKey] == null) { + this.$log.error("Polyline model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.plurals.put(model[this.idKey], child); + return child; + }; + + PolylinesParentModel.prototype.setChildScope = function(childScope, model) { + var _this = this; + _.each(this.scopePropNames, function(name) { + var nameKey, newValue; + nameKey = name + 'Key'; + newValue = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + if (newValue !== childScope[name]) { + return childScope[name] = newValue; + } + }); + return childScope.model = model; + }; + + PolylinesParentModel.prototype.modelKeyComparison = function(model1, model2) { + return _.isEqual(this.evalModelHandle(model1, this.scope.path), this.evalModelHandle(model2, this.scope.path)); + }; + + return PolylinesParentModel; + + })(ModelKey); + } + ]); + +}).call(this); + +/* + Windows directive where many windows map to the models property +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api.models.parent").factory("WindowsParentModel", [ + "IWindowParentModel", "ModelsWatcher", "PropMap", "WindowChildModel", "Linked", function(IWindowParentModel, ModelsWatcher, PropMap, WindowChildModel, Linked) { + var WindowsParentModel; + WindowsParentModel = (function(_super) { + __extends(WindowsParentModel, _super); + + WindowsParentModel.include(ModelsWatcher); + + function WindowsParentModel(scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache, $interpolate) { + var self, + _this = this; + this.$interpolate = $interpolate; + this.interpolateContent = __bind(this.interpolateContent, this); + this.setChildScope = __bind(this.setChildScope, this); + this.createWindow = __bind(this.createWindow, this); + this.setContentKeys = __bind(this.setContentKeys, this); + this.pieceMealWindows = __bind(this.pieceMealWindows, this); + this.createAllNewWindows = __bind(this.createAllNewWindows, this); + this.watchIdKey = __bind(this.watchIdKey, this); + this.createChildScopesWindows = __bind(this.createChildScopesWindows, this); + this.watchOurScope = __bind(this.watchOurScope, this); + this.watchDestroy = __bind(this.watchDestroy, this); + this.rebuildAll = __bind(this.rebuildAll, this); + this.doINeedToWipe = __bind(this.doINeedToWipe, this); + this.watchModels = __bind(this.watchModels, this); + this.watch = __bind(this.watch, this); + WindowsParentModel.__super__.constructor.call(this, scope, element, attrs, ctrls, $timeout, $compile, $http, $templateCache); + self = this; + this.windows = new PropMap(); + this.scopePropNames = ['show', 'coords', 'templateUrl', 'templateParameter', 'isIconVisibleOnClick', 'closeClick']; + _.each(this.scopePropNames, function(name) { + return _this[name + 'Key'] = void 0; + }); + this.linked = new Linked(scope, element, attrs, ctrls); + this.models = void 0; + this.contentKeys = void 0; + this.isIconVisibleOnClick = void 0; + this.firstTime = true; + this.$log.info(self); + this.parentScope = void 0; + this.$timeout(function() { + _this.watchOurScope(scope); + _this.doRebuildAll = _this.scope.doRebuildAll != null ? _this.scope.doRebuildAll : false; + scope.$watch('doRebuildAll', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.doRebuildAll = newValue; + } + }); + return _this.createChildScopesWindows(); + }, 50); + } + + WindowsParentModel.prototype.watch = function(scope, name, nameKey) { + var _this = this; + return scope.$watch(name, function(newValue, oldValue) { + if (newValue !== oldValue) { + _this[nameKey] = typeof newValue === 'function' ? newValue() : newValue; + return _async.each(_.values(_this.windows), function(model) { + return model.scope[name] = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + }, function() {}); + } + }); + }; + + WindowsParentModel.prototype.watchModels = function(scope) { + var _this = this; + return scope.$watch('models', function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + if (_this.doRebuildAll || _this.doINeedToWipe(newValue)) { + return _this.rebuildAll(scope, true, true); + } else { + return _this.createChildScopesWindows(false); + } + } + }); + }; + + WindowsParentModel.prototype.doINeedToWipe = function(newValue) { + var newValueIsEmpty; + newValueIsEmpty = newValue != null ? newValue.length === 0 : true; + return this.windows.length > 0 && newValueIsEmpty; + }; + + WindowsParentModel.prototype.rebuildAll = function(scope, doCreate, doDelete) { + var _this = this; + return _async.each(this.windows.values(), function(model) { + return model.destroy(); + }, function() { + if (doDelete) { + delete _this.windows; + } + _this.windows = new PropMap(); + if (doCreate) { + return _this.createChildScopesWindows(); + } + }); + }; + + WindowsParentModel.prototype.watchDestroy = function(scope) { + var _this = this; + return scope.$on("$destroy", function() { + return _this.rebuildAll(scope, false, true); + }); + }; + + WindowsParentModel.prototype.watchOurScope = function(scope) { + var _this = this; + return _.each(this.scopePropNames, function(name) { + var nameKey; + nameKey = name + 'Key'; + _this[nameKey] = typeof scope[name] === 'function' ? scope[name]() : scope[name]; + return _this.watch(scope, name, nameKey); + }); + }; + + WindowsParentModel.prototype.createChildScopesWindows = function(isCreatingFromScratch) { + var markersScope, modelsNotDefined; + if (isCreatingFromScratch == null) { + isCreatingFromScratch = true; + } + /* + being that we cannot tell the difference in Key String vs. a normal value string (TemplateUrl) + we will assume that all scope values are string expressions either pointing to a key (propName) or using + 'self' to point the model as container/object of interest. + + This may force redundant information into the model, but this appears to be the most flexible approach. + */ + + this.isIconVisibleOnClick = true; + if (angular.isDefined(this.linked.attrs.isiconvisibleonclick)) { + this.isIconVisibleOnClick = this.linked.scope.isIconVisibleOnClick; + } + this.gMap = this.linked.ctrls[0].getMap(); + if (this.linked.ctrls[1] != null) { + markersScope = this.linked.ctrls.length > 1 ? this.linked.ctrls[1].getMarkersScope() : void 0; + } + modelsNotDefined = angular.isUndefined(this.linked.scope.models); + if (modelsNotDefined && (markersScope === void 0 || (markersScope.markerModels === void 0 || markersScope.models === void 0))) { + this.$log.error("No models to create windows from! Need direct models or models derrived from markers!"); + return; + } + if (this.gMap != null) { + if (this.linked.scope.models != null) { + this.watchIdKey(this.linked.scope); + if (isCreatingFromScratch) { + return this.createAllNewWindows(this.linked.scope, false); + } else { + return this.pieceMealWindows(this.linked.scope, false); + } + } else { + this.parentScope = markersScope; + this.watchIdKey(this.parentScope); + if (isCreatingFromScratch) { + return this.createAllNewWindows(markersScope, true, 'markerModels', false); + } else { + return this.pieceMealWindows(markersScope, true, 'markerModels', false); + } + } + } + }; + + WindowsParentModel.prototype.watchIdKey = function(scope) { + var _this = this; + this.setIdKey(scope); + return scope.$watch('idKey', function(newValue, oldValue) { + if (newValue !== oldValue && (newValue == null)) { + _this.idKey = newValue; + return _this.rebuildAll(scope, true, true); + } + }); + }; + + WindowsParentModel.prototype.createAllNewWindows = function(scope, hasGMarker, modelsPropToIterate, isArray) { + var _this = this; + if (modelsPropToIterate == null) { + modelsPropToIterate = 'models'; + } + if (isArray == null) { + isArray = false; + } + this.models = scope.models; + if (this.firstTime) { + this.watchModels(scope); + this.watchDestroy(scope); + } + this.setContentKeys(scope.models); + return _async.each(scope.models, function(model) { + var gMarker; + gMarker = hasGMarker ? scope[modelsPropToIterate][[model[_this.idKey]]].gMarker : void 0; + return _this.createWindow(model, gMarker, _this.gMap); + }, function() { + return _this.firstTime = false; + }); + }; + + WindowsParentModel.prototype.pieceMealWindows = function(scope, hasGMarker, modelsPropToIterate, isArray) { + var _this = this; + if (modelsPropToIterate == null) { + modelsPropToIterate = 'models'; + } + if (isArray == null) { + isArray = true; + } + this.models = scope.models; + if ((scope != null) && (scope.models != null) && scope.models.length > 0 && this.windows.length > 0) { + return this.figureOutState(this.idKey, scope, this.windows, this.modelKeyComparison, function(state) { + var payload; + payload = state; + return _async.each(payload.removals, function(child) { + if (child != null) { + child.destroy(); + return _this.windows.remove(child.id); + } + }, function() { + return _async.each(payload.adds, function(modelToAdd) { + var gMarker; + gMarker = scope[modelsPropToIterate][modelToAdd[_this.idKey]].gMarker; + return _this.createWindow(modelToAdd, gMarker, _this.gMap); + }, function() {}); + }); + }); + } else { + return this.rebuildAll(this.scope, true, true); + } + }; + + WindowsParentModel.prototype.setContentKeys = function(models) { + if (models.length > 0) { + return this.contentKeys = Object.keys(models[0]); + } + }; + + WindowsParentModel.prototype.createWindow = function(model, gMarker, gMap) { + var child, childScope, fakeElement, opts, + _this = this; + childScope = this.linked.scope.$new(false); + this.setChildScope(childScope, model); + childScope.$watch('model', function(newValue, oldValue) { + if (newValue !== oldValue) { + return _this.setChildScope(childScope, newValue); + } + }, true); + fakeElement = { + html: function() { + return _this.interpolateContent(_this.linked.element.html(), model); + } + }; + opts = this.createWindowOptions(gMarker, childScope, fakeElement.html(), this.DEFAULTS); + child = new WindowChildModel(model, childScope, opts, this.isIconVisibleOnClick, gMap, gMarker, fakeElement, true, true); + if (model[this.idKey] == null) { + this.$log.error("Window model has no id to assign a child to. This is required for performance. Please assign id, or redirect id to a different key."); + return; + } + this.windows.put(model[this.idKey], child); + return child; + }; + + WindowsParentModel.prototype.setChildScope = function(childScope, model) { + var _this = this; + _.each(this.scopePropNames, function(name) { + var nameKey, newValue; + nameKey = name + 'Key'; + newValue = _this[nameKey] === 'self' ? model : model[_this[nameKey]]; + if (newValue !== childScope[name]) { + return childScope[name] = newValue; + } + }); + return childScope.model = model; + }; + + WindowsParentModel.prototype.interpolateContent = function(content, model) { + var exp, interpModel, key, _i, _len, _ref; + if (this.contentKeys === void 0 || this.contentKeys.length === 0) { + return; + } + exp = this.$interpolate(content); + interpModel = {}; + _ref = this.contentKeys; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + key = _ref[_i]; + interpModel[key] = model[key]; + } + return exp(interpModel); + }; + + return WindowsParentModel; + + })(IWindowParentModel); + return WindowsParentModel; + } + ]); + +}).call(this); + +/* + - interface for all labels to derrive from + - to enforce a minimum set of requirements + - attributes + - content + - anchor + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("ILabel", [ + "BaseObject", "Logger", function(BaseObject, Logger) { + var ILabel; + return ILabel = (function(_super) { + __extends(ILabel, _super); + + function ILabel($timeout) { + this.link = __bind(this.link, this); + var self; + self = this; + this.restrict = 'ECMA'; + this.replace = true; + this.template = void 0; + this.require = void 0; + this.transclude = true; + this.priority = -100; + this.scope = { + labelContent: '=content', + labelAnchor: '@anchor', + labelClass: '@class', + labelStyle: '=style' + }; + this.$log = Logger; + this.$timeout = $timeout; + } + + ILabel.prototype.link = function(scope, element, attrs, ctrl) { + throw new Exception("Not Implemented!!"); + }; + + return ILabel; + + })(BaseObject); + } + ]); + +}).call(this); + +/* + - interface for all markers to derrive from + - to enforce a minimum set of requirements + - attributes + - coords + - icon + - implementation needed on watches +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IMarker", [ + "Logger", "BaseObject", function(Logger, BaseObject) { + var IMarker; + return IMarker = (function(_super) { + __extends(IMarker, _super); + + function IMarker($timeout) { + this.link = __bind(this.link, this); + var self; + self = this; + this.$log = Logger; + this.$timeout = $timeout; + this.restrict = 'ECMA'; + this.require = '^googleMap'; + this.priority = -1; + this.transclude = true; + this.replace = true; + this.scope = { + coords: '=coords', + icon: '=icon', + click: '&click', + options: '=options', + events: '=events', + fit: '=fit' + }; + } + + IMarker.prototype.controller = [ + '$scope', '$element', function($scope, $element) { + throw new Exception("Not Implemented!!"); + } + ]; + + IMarker.prototype.link = function(scope, element, attrs, ctrl) { + throw new Exception("Not implemented!!"); + }; + + return IMarker; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IPolyline", [ + "GmapUtil", "BaseObject", "Logger", function(GmapUtil, BaseObject, Logger) { + var IPolyline; + return IPolyline = (function(_super) { + __extends(IPolyline, _super); + + IPolyline.include(GmapUtil); + + function IPolyline() { + var self; + self = this; + } + + IPolyline.prototype.restrict = "ECA"; + + IPolyline.prototype.replace = true; + + IPolyline.prototype.require = "^googleMap"; + + IPolyline.prototype.scope = { + path: "=", + stroke: "=", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + icons: "=", + visible: "=", + "static": "=", + fit: "=" + }; + + IPolyline.prototype.DEFAULTS = {}; + + IPolyline.prototype.$log = Logger; + + return IPolyline; + + })(BaseObject); + } + ]); + +}).call(this); + +/* + - interface directive for all window(s) to derrive from +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("IWindow", [ + "BaseObject", "ChildEvents", "Logger", function(BaseObject, ChildEvents, Logger) { + var IWindow; + return IWindow = (function(_super) { + __extends(IWindow, _super); + + IWindow.include(ChildEvents); + + function IWindow($timeout, $compile, $http, $templateCache) { + var self; + this.$timeout = $timeout; + this.$compile = $compile; + this.$http = $http; + this.$templateCache = $templateCache; + this.link = __bind(this.link, this); + self = this; + this.restrict = 'ECMA'; + this.template = void 0; + this.transclude = true; + this.priority = -100; + this.require = void 0; + this.replace = true; + this.scope = { + coords: '=coords', + show: '=show', + templateUrl: '=templateurl', + templateParameter: '=templateparameter', + isIconVisibleOnClick: '=isiconvisibleonclick', + closeClick: '&closeclick', + options: '=options' + }; + this.$log = Logger; + } + + IWindow.prototype.link = function(scope, element, attrs, ctrls) { + throw new Exception("Not Implemented!!"); + }; + + return IWindow; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Map", [ + "$timeout", "Logger", "GmapUtil", "BaseObject", function($timeout, Logger, GmapUtil, BaseObject) { + "use strict"; + var $log, DEFAULTS, Map; + $log = Logger; + DEFAULTS = { + mapTypeId: google.maps.MapTypeId.ROADMAP + }; + return Map = (function(_super) { + __extends(Map, _super); + + Map.include(GmapUtil); + + function Map() { + this.link = __bind(this.link, this); + var self; + self = this; + } + + Map.prototype.restrict = "ECMA"; + + Map.prototype.transclude = true; + + Map.prototype.replace = false; + + Map.prototype.template = "<div class=\"angular-google-map\"><div class=\"angular-google-map-container\"></div><div ng-transclude style=\"display: none\"></div></div>"; + + Map.prototype.scope = { + center: "=center", + zoom: "=zoom", + dragging: "=dragging", + control: "=", + windows: "=windows", + options: "=options", + events: "=events", + styles: "=styles", + bounds: "=bounds" + }; + + Map.prototype.controller = [ + "$scope", function($scope) { + return { + getMap: function() { + return $scope.map; + } + }; + } + ]; + + /* + @param scope + @param element + @param attrs + */ + + + Map.prototype.link = function(scope, element, attrs) { + var dragging, el, eventName, getEventHandler, opts, settingCenterFromScope, type, _m, + _this = this; + if (!this.validateCoords(scope.center)) { + $log.error("angular-google-maps: could not find a valid center property"); + return; + } + if (!angular.isDefined(scope.zoom)) { + $log.error("angular-google-maps: map zoom property not set"); + return; + } + el = angular.element(element); + el.addClass("angular-google-map"); + opts = { + options: {} + }; + if (attrs.options) { + opts.options = scope.options; + } + if (attrs.styles) { + opts.styles = scope.styles; + } + if (attrs.type) { + type = attrs.type.toUpperCase(); + if (google.maps.MapTypeId.hasOwnProperty(type)) { + opts.mapTypeId = google.maps.MapTypeId[attrs.type.toUpperCase()]; + } else { + $log.error("angular-google-maps: invalid map type \"" + attrs.type + "\""); + } + } + _m = new google.maps.Map(el.find("div")[1], angular.extend({}, DEFAULTS, opts, { + center: this.getCoords(scope.center), + draggable: this.isTrue(attrs.draggable), + zoom: scope.zoom, + bounds: scope.bounds + })); + dragging = false; + google.maps.event.addListener(_m, "dragstart", function() { + dragging = true; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(_m, "dragend", function() { + dragging = false; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(_m, "drag", function() { + var c; + c = _m.center; + return _.defer(function() { + return scope.$apply(function(s) { + if (angular.isDefined(s.center.type)) { + s.center.coordinates[1] = c.lat(); + return s.center.coordinates[0] = c.lng(); + } else { + s.center.latitude = c.lat(); + return s.center.longitude = c.lng(); + } + }); + }); + }); + google.maps.event.addListener(_m, "zoom_changed", function() { + if (scope.zoom !== _m.zoom) { + return _.defer(function() { + return scope.$apply(function(s) { + return s.zoom = _m.zoom; + }); + }); + } + }); + settingCenterFromScope = false; + google.maps.event.addListener(_m, "center_changed", function() { + var c; + c = _m.center; + if (settingCenterFromScope) { + return; + } + return _.defer(function() { + return scope.$apply(function(s) { + if (!_m.dragging) { + if (angular.isDefined(s.center.type)) { + if (s.center.coordinates[1] !== c.lat()) { + s.center.coordinates[1] = c.lat(); + } + if (s.center.coordinates[0] !== c.lng()) { + return s.center.coordinates[0] = c.lng(); + } + } else { + if (s.center.latitude !== c.lat()) { + s.center.latitude = c.lat(); + } + if (s.center.longitude !== c.lng()) { + return s.center.longitude = c.lng(); + } + } + } + }); + }); + }); + google.maps.event.addListener(_m, "idle", function() { + var b, ne, sw; + b = _m.getBounds(); + ne = b.getNorthEast(); + sw = b.getSouthWest(); + return _.defer(function() { + return scope.$apply(function(s) { + if (s.bounds !== null && s.bounds !== undefined && s.bounds !== void 0) { + s.bounds.northeast = { + latitude: ne.lat(), + longitude: ne.lng() + }; + return s.bounds.southwest = { + latitude: sw.lat(), + longitude: sw.lng() + }; + } + }); + }); + }); + if (angular.isDefined(scope.events) && scope.events !== null && angular.isObject(scope.events)) { + getEventHandler = function(eventName) { + return function() { + return scope.events[eventName].apply(scope, [_m, eventName, arguments]); + }; + }; + for (eventName in scope.events) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + google.maps.event.addListener(_m, eventName, getEventHandler(eventName)); + } + } + } + scope.map = _m; + if ((attrs.control != null) && (scope.control != null)) { + scope.control.refresh = function(maybeCoords) { + var coords; + if (_m == null) { + return; + } + google.maps.event.trigger(_m, "resize"); + if (((maybeCoords != null ? maybeCoords.latitude : void 0) != null) && ((maybeCoords != null ? maybeCoords.latitude : void 0) != null)) { + coords = _this.getCoords(maybeCoords); + if (_this.isTrue(attrs.pan)) { + return _m.panTo(coords); + } else { + return _m.setCenter(coords); + } + } + }; + /* + I am sure you all will love this. You want the instance here you go.. BOOM! + */ + + scope.control.getGMap = function() { + return _m; + }; + } + scope.$watch("center", (function(newValue, oldValue) { + var coords; + coords = _this.getCoords(newValue); + if (coords.lat() === _m.center.lat() && coords.lng() === _m.center.lng()) { + return; + } + settingCenterFromScope = true; + if (!dragging) { + if (!_this.validateCoords(newValue)) { + $log.error("Invalid center for newValue: " + (JSON.stringify(newValue))); + } + if (_this.isTrue(attrs.pan) && scope.zoom === _m.zoom) { + _m.panTo(coords); + } else { + _m.setCenter(coords); + } + } + return settingCenterFromScope = false; + }), true); + scope.$watch("zoom", function(newValue, oldValue) { + if (newValue === _m.zoom) { + return; + } + return _.defer(function() { + return _m.setZoom(newValue); + }); + }); + scope.$watch("bounds", function(newValue, oldValue) { + var bounds, ne, sw; + if (newValue === oldValue) { + return; + } + if ((newValue.northeast.latitude == null) || (newValue.northeast.longitude == null) || (newValue.southwest.latitude == null) || (newValue.southwest.longitude == null)) { + $log.error("Invalid map bounds for new value: " + (JSON.stringify(newValue))); + return; + } + ne = new google.maps.LatLng(newValue.northeast.latitude, newValue.northeast.longitude); + sw = new google.maps.LatLng(newValue.southwest.latitude, newValue.southwest.longitude); + bounds = new google.maps.LatLngBounds(sw, ne); + return _m.fitBounds(bounds); + }); + scope.$watch("options", function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + opts.options = newValue; + if (_m != null) { + return _m.setOptions(opts); + } + } + }, true); + return scope.$watch("styles", function(newValue, oldValue) { + if (!_.isEqual(newValue, oldValue)) { + opts.styles = newValue; + if (_m != null) { + return _m.setOptions(opts); + } + } + }, true); + }; + + return Map; + + })(BaseObject); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Marker", [ + "IMarker", "MarkerParentModel", "MarkerManager", function(IMarker, MarkerParentModel, MarkerManager) { + var Marker; + return Marker = (function(_super) { + __extends(Marker, _super); + + function Marker($timeout) { + this.link = __bind(this.link, this); + var self; + Marker.__super__.constructor.call(this, $timeout); + self = this; + this.template = '<span class="angular-google-map-marker" ng-transclude></span>'; + this.$log.info(this); + } + + Marker.prototype.controller = [ + '$scope', '$element', function($scope, $element) { + return { + getMarkerScope: function() { + return $scope; + } + }; + } + ]; + + Marker.prototype.link = function(scope, element, attrs, ctrl) { + var doFit; + if (scope.fit) { + doFit = true; + } + if (!this.gMarkerManager) { + this.gMarkerManager = new MarkerManager(ctrl.getMap()); + } + return new MarkerParentModel(scope, element, attrs, ctrl, this.$timeout, this.gMarkerManager, doFit); + }; + + return Marker; + + })(IMarker); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Markers", [ + "IMarker", "MarkersParentModel", function(IMarker, MarkersParentModel) { + var Markers; + return Markers = (function(_super) { + __extends(Markers, _super); + + function Markers($timeout) { + this.link = __bind(this.link, this); + var self; + Markers.__super__.constructor.call(this, $timeout); + this.template = '<span class="angular-google-map-markers" ng-transclude></span>'; + this.scope.idKey = '=idkey'; + this.scope.doRebuildAll = '=dorebuildall'; + this.scope.models = '=models'; + this.scope.doCluster = '=docluster'; + this.scope.clusterOptions = '=clusteroptions'; + this.scope.clusterEvents = '=clusterevents'; + this.scope.labelContent = '=labelcontent'; + this.scope.labelAnchor = '@labelanchor'; + this.scope.labelClass = '@labelclass'; + this.$timeout = $timeout; + self = this; + this.$log.info(this); + } + + Markers.prototype.controller = [ + '$scope', '$element', function($scope, $element) { + return { + getMarkersScope: function() { + return $scope; + } + }; + } + ]; + + Markers.prototype.link = function(scope, element, attrs, ctrl) { + return new MarkersParentModel(scope, element, attrs, ctrl, this.$timeout); + }; + + return Markers; + + })(IMarker); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Polyline", [ + "IPolyline", "$timeout", "array-sync", "PolylineChildModel", function(IPolyline, $timeout, arraySync, PolylineChildModel) { + var Polyline, _ref; + return Polyline = (function(_super) { + __extends(Polyline, _super); + + function Polyline() { + this.link = __bind(this.link, this); + _ref = Polyline.__super__.constructor.apply(this, arguments); + return _ref; + } + + Polyline.prototype.link = function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.path) || scope.path === null || !this.validatePath(scope.path)) { + this.$log.error("polyline: no valid path attribute found"); + return; + } + return $timeout(function() { + return new PolylineChildModel(scope, attrs, mapCtrl.getMap(), _this.DEFAULTS); + }); + }; + + return Polyline; + + })(IPolyline); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Polylines", [ + "IPolyline", "$timeout", "array-sync", "PolylinesParentModel", function(IPolyline, $timeout, arraySync, PolylinesParentModel) { + var Polylines; + return Polylines = (function(_super) { + __extends(Polylines, _super); + + function Polylines() { + this.link = __bind(this.link, this); + Polylines.__super__.constructor.call(this); + this.scope.idKey = '=idkey'; + this.scope.models = '=models'; + this.$log.info(this); + } + + Polylines.prototype.link = function(scope, element, attrs, mapCtrl) { + var _this = this; + if (angular.isUndefined(scope.path) || scope.path === null) { + this.$log.error("polylines: no valid path attribute found"); + return; + } + if (!scope.models) { + this.$log.error("polylines: no models found to create from"); + return; + } + return $timeout(function() { + return new PolylinesParentModel(scope, element, attrs, mapCtrl.getMap(), _this.DEFAULTS); + }); + }; + + return Polylines; + + })(IPolyline); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Window", [ + "IWindow", "GmapUtil", "WindowChildModel", function(IWindow, GmapUtil, WindowChildModel) { + var Window; + return Window = (function(_super) { + __extends(Window, _super); + + Window.include(GmapUtil); + + function Window($timeout, $compile, $http, $templateCache) { + this.link = __bind(this.link, this); + var self; + Window.__super__.constructor.call(this, $timeout, $compile, $http, $templateCache); + self = this; + this.require = ['^googleMap', '^?marker']; + this.template = '<span class="angular-google-maps-window" ng-transclude></span>'; + this.$log.info(self); + } + + Window.prototype.link = function(scope, element, attrs, ctrls) { + var _this = this; + return this.$timeout(function() { + var defaults, hasScopeCoords, isIconVisibleOnClick, mapCtrl, markerCtrl, markerScope, opts, window; + isIconVisibleOnClick = true; + if (angular.isDefined(attrs.isiconvisibleonclick)) { + isIconVisibleOnClick = scope.isIconVisibleOnClick; + } + mapCtrl = ctrls[0].getMap(); + markerCtrl = ctrls.length > 1 && (ctrls[1] != null) ? ctrls[1].getMarkerScope().gMarker : void 0; + defaults = scope.options != null ? scope.options : {}; + hasScopeCoords = (scope != null) && _this.validateCoords(scope.coords); + opts = hasScopeCoords ? _this.createWindowOptions(markerCtrl, scope, element.html(), defaults) : defaults; + if (mapCtrl != null) { + window = new WindowChildModel({}, scope, opts, isIconVisibleOnClick, mapCtrl, markerCtrl, element); + } + scope.$on("$destroy", function() { + return window.destroy(); + }); + if (ctrls[1] != null) { + markerScope = ctrls[1].getMarkerScope(); + markerScope.$watch('coords', function(newValue, oldValue) { + if (!_this.validateCoords(newValue)) { + return window.hideWindow(); + } + if (!angular.equals(newValue, oldValue)) { + return window.getLatestPosition(); + } + }, true); + } + if ((_this.onChildCreation != null) && (window != null)) { + return _this.onChildCreation(window); + } + }, GmapUtil.defaultDelay + 25); + }; + + return Window; + + })(IWindow); + } + ]); + +}).call(this); + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps.directives.api").factory("Windows", [ + "IWindow", "WindowsParentModel", function(IWindow, WindowsParentModel) { + /* + Windows directive where many windows map to the models property + */ + + var Windows; + return Windows = (function(_super) { + __extends(Windows, _super); + + function Windows($timeout, $compile, $http, $templateCache, $interpolate) { + this.link = __bind(this.link, this); + var self; + Windows.__super__.constructor.call(this, $timeout, $compile, $http, $templateCache); + self = this; + this.$interpolate = $interpolate; + this.require = ['^googleMap', '^?markers']; + this.template = '<span class="angular-google-maps-windows" ng-transclude></span>'; + this.scope.idKey = '=idkey'; + this.scope.doRebuildAll = '=dorebuildall'; + this.scope.models = '=models'; + this.$log.info(this); + } + + Windows.prototype.link = function(scope, element, attrs, ctrls) { + return new WindowsParentModel(scope, element, attrs, ctrls, this.$timeout, this.$compile, this.$http, this.$templateCache, this.$interpolate); + }; + + return Windows; + + })(IWindow); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Nick Baugh - https://github.com/niftylettuce +*/ + + +(function() { + angular.module("google-maps").directive("googleMap", [ + "Map", function(Map) { + return new Map(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +*/ + + +(function() { + angular.module("google-maps").directive("marker", [ + "$timeout", "Marker", function($timeout, Marker) { + return new Marker($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map marker directive + +This directive is used to create a marker on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute icon optional} string url to image used for marker icon +{attribute animate optional} if set to false, the marker won't be animated (on by default) +*/ + + +(function() { + angular.module("google-maps").directive("markers", [ + "$timeout", "Markers", function($timeout, Markers) { + return new Markers($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors Bruno Queiroz, creativelikeadog@gmail.com +*/ + + +/* +Marker label directive + +This directive is used to create a marker label on an existing map. + +{attribute content required} content of the label +{attribute anchor required} string that contains the x and y point position of the label +{attribute class optional} class to DOM object +{attribute style optional} style for the label +*/ + + +/* +Basic Directive api for a label. Basic in the sense that this directive contains 1:1 on scope and model. +Thus there will be one html element per marker within the directive. +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + angular.module("google-maps").directive("markerLabel", [ + "$timeout", "ILabel", "MarkerLabelChildModel", "GmapUtil", function($timeout, ILabel, MarkerLabelChildModel, GmapUtil) { + var Label; + Label = (function(_super) { + __extends(Label, _super); + + function Label($timeout) { + this.link = __bind(this.link, this); + var self; + Label.__super__.constructor.call(this, $timeout); + self = this; + this.require = '^marker'; + this.template = '<span class="angular-google-maps-marker-label" ng-transclude></span>'; + this.$log.info(this); + } + + Label.prototype.link = function(scope, element, attrs, ctrl) { + var _this = this; + return this.$timeout(function() { + var label, markerCtrl; + markerCtrl = ctrl.getMarkerScope().gMarker; + if (markerCtrl != null) { + label = new MarkerLabelChildModel(markerCtrl, scope); + } + return scope.$on("$destroy", function() { + return label.destroy(); + }); + }, GmapUtil.defaultDelay + 25); + }; + + return Label; + + })(ILabel); + return new Label($timeout); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Rick Huizinga - https://plus.google.com/+RickHuizinga +*/ + + +(function() { + angular.module("google-maps").directive("polygon", [ + "$log", "$timeout", "array-sync", "GmapUtil", function($log, $timeout, arraySync, GmapUtil) { + /* + Check if a value is true + */ + + var DEFAULTS, isTrue; + isTrue = function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }; + "use strict"; + DEFAULTS = {}; + return { + restrict: "ECA", + replace: true, + require: "^googleMap", + scope: { + path: "=path", + stroke: "=stroke", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + fill: "=", + icons: "=icons", + visible: "=", + "static": "=", + events: "=", + zIndex: "=zindex", + fit: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + if (angular.isUndefined(scope.path) || scope.path === null || !GmapUtil.validatePath(scope.path)) { + $log.error("polygon: no valid path attribute found"); + return; + } + return $timeout(function() { + var arraySyncer, buildOpts, eventName, getEventHandler, map, pathPoints, polygon; + buildOpts = function(pathPoints) { + var opts; + opts = angular.extend({}, DEFAULTS, { + map: map, + path: pathPoints, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true, + "static": false, + fit: false, + zIndex: 0 + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + if (opts["static"]) { + opts.editable = false; + } + return opts; + }; + map = mapCtrl.getMap(); + pathPoints = GmapUtil.convertPathPoints(scope.path); + polygon = new google.maps.Polygon(buildOpts(pathPoints)); + if (scope.fit) { + GmapUtil.extendMapBounds(map, pathPoints); + } + if (!scope["static"] && angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setEditable(newValue); + } + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setDraggable(newValue); + } + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setVisible(newValue); + } + }); + } + if (angular.isDefined(scope.geodesic)) { + scope.$watch("geodesic", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.stroke) && angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.fill) && angular.isDefined(scope.fill.color)) { + scope.$watch("fill.color", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.fill) && angular.isDefined(scope.fill.opacity)) { + scope.$watch("fill.opacity", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.zIndex)) { + scope.$watch("zIndex", function(newValue, oldValue) { + if (newValue !== oldValue) { + return polygon.setOptions(buildOpts(polygon.getPath())); + } + }); + } + if (angular.isDefined(scope.events) && scope.events !== null && angular.isObject(scope.events)) { + getEventHandler = function(eventName) { + return function() { + return scope.events[eventName].apply(scope, [polygon, eventName, arguments]); + }; + }; + for (eventName in scope.events) { + if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { + polygon.addListener(eventName, getEventHandler(eventName)); + } + } + } + arraySyncer = arraySync(polygon.getPath(), scope, "path", function(pathPoints) { + if (scope.fit) { + return GmapUtil.extendMapBounds(map, pathPoints); + } + }); + return scope.$on("$destroy", function() { + polygon.setMap(null); + if (arraySyncer) { + arraySyncer(); + return arraySyncer = null; + } + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@authors +Julian Popescu - https://github.com/jpopesculian +Rick Huizinga - https://plus.google.com/+RickHuizinga +*/ + + +(function() { + angular.module("google-maps").directive("circle", [ + "$log", "$timeout", "GmapUtil", "EventsHelper", function($log, $timeout, GmapUtil, EventsHelper) { + "use strict"; + var DEFAULTS; + DEFAULTS = {}; + return { + restrict: "ECA", + replace: true, + require: "^googleMap", + scope: { + center: "=center", + radius: "=radius", + stroke: "=stroke", + fill: "=fill", + clickable: "=", + draggable: "=", + editable: "=", + geodesic: "=", + icons: "=icons", + visible: "=", + events: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + return $timeout(function() { + var buildOpts, circle, map; + buildOpts = function() { + var opts; + if (!GmapUtil.validateCoords(scope.center)) { + $log.error("circle: no valid center attribute found"); + return; + } + opts = angular.extend({}, DEFAULTS, { + map: map, + center: GmapUtil.getCoords(scope.center), + radius: scope.radius, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + geodesic: false, + visible: true + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + return opts; + }; + map = mapCtrl.getMap(); + circle = new google.maps.Circle(buildOpts()); + scope.$watchCollection('center', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watchCollection('stroke', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watchCollection('fill', function(newVals, oldVals) { + if (newVals !== oldVals) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('radius', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('clickable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('editable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('draggable', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('visible', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + scope.$watch('geodesic', function(newVal, oldVal) { + if (newVal !== oldVal) { + return circle.setOptions(buildOpts()); + } + }); + EventsHelper.setEvents(circle, scope, scope); + google.maps.event.addListener(circle, 'radius_changed', function() { + scope.radius = circle.getRadius(); + return $timeout(function() { + return scope.$apply(); + }); + }); + google.maps.event.addListener(circle, 'center_changed', function() { + if (angular.isDefined(scope.center.type)) { + scope.center.coordinates[1] = circle.getCenter().lat(); + scope.center.coordinates[0] = circle.getCenter().lng(); + } else { + scope.center.latitude = circle.getCenter().lat(); + scope.center.longitude = circle.getCenter().lng(); + } + return $timeout(function() { + return scope.$apply(); + }); + }); + return scope.$on("$destroy", function() { + return circle.setMap(null); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps").directive("polyline", [ + "Polyline", function(Polyline) { + return new Polyline(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +(function() { + angular.module("google-maps").directive("polylines", [ + "Polylines", function(Polylines) { + return new Polylines(); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +Chentsu Lin - https://github.com/ChenTsuLin +*/ + + +(function() { + angular.module("google-maps").directive("rectangle", [ + "$log", "$timeout", function($log, $timeout) { + var DEFAULTS, convertBoundPoints, fitMapBounds, isTrue, validateBoundPoints; + validateBoundPoints = function(bounds) { + if (angular.isUndefined(bounds.sw.latitude) || angular.isUndefined(bounds.sw.longitude) || angular.isUndefined(bounds.ne.latitude) || angular.isUndefined(bounds.ne.longitude)) { + return false; + } + return true; + }; + convertBoundPoints = function(bounds) { + var result; + result = new google.maps.LatLngBounds(new google.maps.LatLng(bounds.sw.latitude, bounds.sw.longitude), new google.maps.LatLng(bounds.ne.latitude, bounds.ne.longitude)); + return result; + }; + fitMapBounds = function(map, bounds) { + return map.fitBounds(bounds); + }; + /* + Check if a value is true + */ + + isTrue = function(val) { + return angular.isDefined(val) && val !== null && val === true || val === "1" || val === "y" || val === "true"; + }; + "use strict"; + DEFAULTS = {}; + return { + restrict: "ECA", + require: "^googleMap", + replace: true, + scope: { + bounds: "=", + stroke: "=", + clickable: "=", + draggable: "=", + editable: "=", + fill: "=", + visible: "=" + }, + link: function(scope, element, attrs, mapCtrl) { + if (angular.isUndefined(scope.bounds) || scope.bounds === null || angular.isUndefined(scope.bounds.sw) || scope.bounds.sw === null || angular.isUndefined(scope.bounds.ne) || scope.bounds.ne === null || !validateBoundPoints(scope.bounds)) { + $log.error("rectangle: no valid bound attribute found"); + return; + } + return $timeout(function() { + var buildOpts, dragging, map, rectangle, settingBoundsFromScope; + buildOpts = function(bounds) { + var opts; + opts = angular.extend({}, DEFAULTS, { + map: map, + bounds: bounds, + strokeColor: scope.stroke && scope.stroke.color, + strokeOpacity: scope.stroke && scope.stroke.opacity, + strokeWeight: scope.stroke && scope.stroke.weight, + fillColor: scope.fill && scope.fill.color, + fillOpacity: scope.fill && scope.fill.opacity + }); + angular.forEach({ + clickable: true, + draggable: false, + editable: false, + visible: true + }, function(defaultValue, key) { + if (angular.isUndefined(scope[key]) || scope[key] === null) { + return opts[key] = defaultValue; + } else { + return opts[key] = scope[key]; + } + }); + return opts; + }; + map = mapCtrl.getMap(); + rectangle = new google.maps.Rectangle(buildOpts(convertBoundPoints(scope.bounds))); + if (isTrue(attrs.fit)) { + fitMapBounds(map, bounds); + } + dragging = false; + google.maps.event.addListener(rectangle, "mousedown", function() { + google.maps.event.addListener(rectangle, "mousemove", function() { + dragging = true; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + google.maps.event.addListener(rectangle, "mouseup", function() { + google.maps.event.clearListeners(rectangle, 'mousemove'); + google.maps.event.clearListeners(rectangle, 'mouseup'); + dragging = false; + return _.defer(function() { + return scope.$apply(function(s) { + if (s.dragging != null) { + return s.dragging = dragging; + } + }); + }); + }); + }); + settingBoundsFromScope = false; + google.maps.event.addListener(rectangle, "bounds_changed", function() { + var b, ne, sw; + b = rectangle.getBounds(); + ne = b.getNorthEast(); + sw = b.getSouthWest(); + if (settingBoundsFromScope) { + return; + } + return _.defer(function() { + return scope.$apply(function(s) { + if (!rectangle.dragging) { + if (s.bounds !== null && s.bounds !== undefined && s.bounds !== void 0) { + s.bounds.ne = { + latitude: ne.lat(), + longitude: ne.lng() + }; + s.bounds.sw = { + latitude: sw.lat(), + longitude: sw.lng() + }; + } + } + }); + }); + }); + scope.$watch("bounds", (function(newValue, oldValue) { + var bounds; + if (_.isEqual(newValue, oldValue)) { + return; + } + settingBoundsFromScope = true; + if (!dragging) { + if ((newValue.sw.latitude == null) || (newValue.sw.longitude == null) || (newValue.ne.latitude == null) || (newValue.ne.longitude == null)) { + $log.error("Invalid bounds for newValue: " + (JSON.stringify(newValue))); + } + bounds = new google.maps.LatLngBounds(new google.maps.LatLng(newValue.sw.latitude, newValue.sw.longitude), new google.maps.LatLng(newValue.ne.latitude, newValue.ne.longitude)); + rectangle.setBounds(bounds); + } + return settingBoundsFromScope = false; + }), true); + if (angular.isDefined(scope.editable)) { + scope.$watch("editable", function(newValue, oldValue) { + return rectangle.setEditable(newValue); + }); + } + if (angular.isDefined(scope.draggable)) { + scope.$watch("draggable", function(newValue, oldValue) { + return rectangle.setDraggable(newValue); + }); + } + if (angular.isDefined(scope.visible)) { + scope.$watch("visible", function(newValue, oldValue) { + return rectangle.setVisible(newValue); + }); + } + if (angular.isDefined(scope.stroke)) { + if (angular.isDefined(scope.stroke.color)) { + scope.$watch("stroke.color", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.stroke.weight)) { + scope.$watch("stroke.weight", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.stroke.opacity)) { + scope.$watch("stroke.opacity", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + } + if (angular.isDefined(scope.fill)) { + if (angular.isDefined(scope.fill.color)) { + scope.$watch("fill.color", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + if (angular.isDefined(scope.fill.opacity)) { + scope.$watch("fill.opacity", function(newValue, oldValue) { + return rectangle.setOptions(buildOpts(rectangle.getBounds())); + }); + } + } + return scope.$on("$destroy", function() { + return rectangle.setMap(null); + }); + }); + } + }; + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +*/ + + +(function() { + angular.module("google-maps").directive("window", [ + "$timeout", "$compile", "$http", "$templateCache", "Window", function($timeout, $compile, $http, $templateCache, Window) { + return new Window($timeout, $compile, $http, $templateCache); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors +Nicolas Laplante - https://plus.google.com/108189012221374960701 +Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map info window directive + +This directive is used to create an info window on an existing map. +This directive creates a new scope. + +{attribute coords required} object containing latitude and longitude properties +{attribute show optional} map will show when this expression returns true +*/ + + +(function() { + angular.module("google-maps").directive("windows", [ + "$timeout", "$compile", "$http", "$templateCache", "$interpolate", "Windows", function($timeout, $compile, $http, $templateCache, $interpolate, Windows) { + return new Windows($timeout, $compile, $http, $templateCache, $interpolate); + } + ]); + +}).call(this); + +/* +! +The MIT License + +Copyright (c) 2010-2013 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +angular-google-maps +https://github.com/nlaplante/angular-google-maps + +@authors: +- Nicolas Laplante https://plus.google.com/108189012221374960701 +- Nicholas McCready - https://twitter.com/nmccready +*/ + + +/* +Map Layer directive + +This directive is used to create any type of Layer from the google maps sdk. +This directive creates a new scope. + +{attribute show optional} true (default) shows the trafficlayer otherwise it is hidden +*/ + + +(function() { + var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + angular.module("google-maps").directive("layer", [ + "$timeout", "Logger", "LayerParentModel", function($timeout, Logger, LayerParentModel) { + var Layer; + Layer = (function() { + function Layer($timeout) { + this.$timeout = $timeout; + this.link = __bind(this.link, this); + this.$log = Logger; + this.restrict = "ECMA"; + this.require = "^googleMap"; + this.priority = -1; + this.transclude = true; + this.template = '<span class=\"angular-google-map-layer\" ng-transclude></span>'; + this.replace = true; + this.scope = { + show: "=show", + type: "=type", + namespace: "=namespace", + options: '=options', + onCreated: '&oncreated' + }; + } + + Layer.prototype.link = function(scope, element, attrs, mapCtrl) { + if (attrs.oncreated != null) { + return new LayerParentModel(scope, element, attrs, mapCtrl, this.$timeout, scope.onCreated); + } else { + return new LayerParentModel(scope, element, attrs, mapCtrl, this.$timeout); + } + }; + + return Layer; + + })(); + return new Layer($timeout); + } + ]); + +}).call(this); +;/** + * @name InfoBox + * @version 1.1.12 [December 11, 2012] + * @author Gary Little (inspired by proof-of-concept code from Pamela Fox of Google) + * @copyright Copyright 2010 Gary Little [gary at luxcentral.com] + * @fileoverview InfoBox extends the Google Maps JavaScript API V3 <tt>OverlayView</tt> class. + * <p> + * An InfoBox behaves like a <tt>google.maps.InfoWindow</tt>, but it supports several + * additional properties for advanced styling. An InfoBox can also be used as a map label. + * <p> + * An InfoBox also fires the same events as a <tt>google.maps.InfoWindow</tt>. + */ + +/*! + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*jslint browser:true */ +/*global google */ + +/** + * @name InfoBoxOptions + * @class This class represents the optional parameter passed to the {@link InfoBox} constructor. + * @property {string|Node} content The content of the InfoBox (plain text or an HTML DOM node). + * @property {boolean} [disableAutoPan=false] Disable auto-pan on <tt>open</tt>. + * @property {number} maxWidth The maximum width (in pixels) of the InfoBox. Set to 0 if no maximum. + * @property {Size} pixelOffset The offset (in pixels) from the top left corner of the InfoBox + * (or the bottom left corner if the <code>alignBottom</code> property is <code>true</code>) + * to the map pixel corresponding to <tt>position</tt>. + * @property {LatLng} position The geographic location at which to display the InfoBox. + * @property {number} zIndex The CSS z-index style value for the InfoBox. + * Note: This value overrides a zIndex setting specified in the <tt>boxStyle</tt> property. + * @property {string} [boxClass="infoBox"] The name of the CSS class defining the styles for the InfoBox container. + * @property {Object} [boxStyle] An object literal whose properties define specific CSS + * style values to be applied to the InfoBox. Style values defined here override those that may + * be defined in the <code>boxClass</code> style sheet. If this property is changed after the + * InfoBox has been created, all previously set styles (except those defined in the style sheet) + * are removed from the InfoBox before the new style values are applied. + * @property {string} closeBoxMargin The CSS margin style value for the close box. + * The default is "2px" (a 2-pixel margin on all sides). + * @property {string} closeBoxURL The URL of the image representing the close box. + * Note: The default is the URL for Google's standard close box. + * Set this property to "" if no close box is required. + * @property {Size} infoBoxClearance Minimum offset (in pixels) from the InfoBox to the + * map edge after an auto-pan. + * @property {boolean} [isHidden=false] Hide the InfoBox on <tt>open</tt>. + * [Deprecated in favor of the <tt>visible</tt> property.] + * @property {boolean} [visible=true] Show the InfoBox on <tt>open</tt>. + * @property {boolean} alignBottom Align the bottom left corner of the InfoBox to the <code>position</code> + * location (default is <tt>false</tt> which means that the top left corner of the InfoBox is aligned). + * @property {string} pane The pane where the InfoBox is to appear (default is "floatPane"). + * Set the pane to "mapPane" if the InfoBox is being used as a map label. + * Valid pane names are the property names for the <tt>google.maps.MapPanes</tt> object. + * @property {boolean} enableEventPropagation Propagate mousedown, mousemove, mouseover, mouseout, + * mouseup, click, dblclick, touchstart, touchend, touchmove, and contextmenu events in the InfoBox + * (default is <tt>false</tt> to mimic the behavior of a <tt>google.maps.InfoWindow</tt>). Set + * this property to <tt>true</tt> if the InfoBox is being used as a map label. + */ + +/** + * Creates an InfoBox with the options specified in {@link InfoBoxOptions}. + * Call <tt>InfoBox.open</tt> to add the box to the map. + * @constructor + * @param {InfoBoxOptions} [opt_opts] + */ +function InfoBox(opt_opts) { + + opt_opts = opt_opts || {}; + + google.maps.OverlayView.apply(this, arguments); + + // Standard options (in common with google.maps.InfoWindow): + // + this.content_ = opt_opts.content || ""; + this.disableAutoPan_ = opt_opts.disableAutoPan || false; + this.maxWidth_ = opt_opts.maxWidth || 0; + this.pixelOffset_ = opt_opts.pixelOffset || new google.maps.Size(0, 0); + this.position_ = opt_opts.position || new google.maps.LatLng(0, 0); + this.zIndex_ = opt_opts.zIndex || null; + + // Additional options (unique to InfoBox): + // + this.boxClass_ = opt_opts.boxClass || "infoBox"; + this.boxStyle_ = opt_opts.boxStyle || {}; + this.closeBoxMargin_ = opt_opts.closeBoxMargin || "2px"; + this.closeBoxURL_ = opt_opts.closeBoxURL || "http://www.google.com/intl/en_us/mapfiles/close.gif"; + if (opt_opts.closeBoxURL === "") { + this.closeBoxURL_ = ""; + } + this.infoBoxClearance_ = opt_opts.infoBoxClearance || new google.maps.Size(1, 1); + + if (typeof opt_opts.visible === "undefined") { + if (typeof opt_opts.isHidden === "undefined") { + opt_opts.visible = true; + } else { + opt_opts.visible = !opt_opts.isHidden; + } + } + this.isHidden_ = !opt_opts.visible; + + this.alignBottom_ = opt_opts.alignBottom || false; + this.pane_ = opt_opts.pane || "floatPane"; + this.enableEventPropagation_ = opt_opts.enableEventPropagation || false; + + this.div_ = null; + this.closeListener_ = null; + this.moveListener_ = null; + this.contextListener_ = null; + this.eventListeners_ = null; + this.fixedWidthSet_ = null; +} + +/* InfoBox extends OverlayView in the Google Maps API v3. + */ +InfoBox.prototype = new google.maps.OverlayView(); + +/** + * Creates the DIV representing the InfoBox. + * @private + */ +InfoBox.prototype.createInfoBoxDiv_ = function () { + + var i; + var events; + var bw; + var me = this; + + // This handler prevents an event in the InfoBox from being passed on to the map. + // + var cancelHandler = function (e) { + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + // This handler ignores the current event in the InfoBox and conditionally prevents + // the event from being passed on to the map. It is used for the contextmenu event. + // + var ignoreHandler = function (e) { + + e.returnValue = false; + + if (e.preventDefault) { + + e.preventDefault(); + } + + if (!me.enableEventPropagation_) { + + cancelHandler(e); + } + }; + + if (!this.div_) { + + this.div_ = document.createElement("div"); + + this.setBoxStyle_(); + + if (typeof this.content_.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + this.content_; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(this.content_); + } + + // Add the InfoBox DIV to the DOM + this.getPanes()[this.pane_].appendChild(this.div_); + + this.addClickHandler_(); + + if (this.div_.style.width) { + + this.fixedWidthSet_ = true; + + } else { + + if (this.maxWidth_ !== 0 && this.div_.offsetWidth > this.maxWidth_) { + + this.div_.style.width = this.maxWidth_; + this.div_.style.overflow = "auto"; + this.fixedWidthSet_ = true; + + } else { // The following code is needed to overcome problems with MSIE + + bw = this.getBoxWidths_(); + + this.div_.style.width = (this.div_.offsetWidth - bw.left - bw.right) + "px"; + this.fixedWidthSet_ = false; + } + } + + this.panBox_(this.disableAutoPan_); + + if (!this.enableEventPropagation_) { + + this.eventListeners_ = []; + + // Cancel event propagation. + // + // Note: mousemove not included (to resolve Issue 152) + events = ["mousedown", "mouseover", "mouseout", "mouseup", + "click", "dblclick", "touchstart", "touchend", "touchmove"]; + + for (i = 0; i < events.length; i++) { + + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, events[i], cancelHandler)); + } + + // Workaround for Google bug that causes the cursor to change to a pointer + // when the mouse moves over a marker underneath InfoBox. + this.eventListeners_.push(google.maps.event.addDomListener(this.div_, "mouseover", function (e) { + this.style.cursor = "default"; + })); + } + + this.contextListener_ = google.maps.event.addDomListener(this.div_, "contextmenu", ignoreHandler); + + /** + * This event is fired when the DIV containing the InfoBox's content is attached to the DOM. + * @name InfoBox#domready + * @event + */ + google.maps.event.trigger(this, "domready"); + } +}; + +/** + * Returns the HTML <IMG> tag for the close box. + * @private + */ +InfoBox.prototype.getCloseBoxImg_ = function () { + + var img = ""; + + if (this.closeBoxURL_ !== "") { + + img = "<img"; + img += " src='" + this.closeBoxURL_ + "'"; + img += " align=right"; // Do this because Opera chokes on style='float: right;' + img += " style='"; + img += " position: relative;"; // Required by MSIE + img += " cursor: pointer;"; + img += " margin: " + this.closeBoxMargin_ + ";"; + img += "'>"; + } + + return img; +}; + +/** + * Adds the click handler to the InfoBox close box. + * @private + */ +InfoBox.prototype.addClickHandler_ = function () { + + var closeBox; + + if (this.closeBoxURL_ !== "") { + + closeBox = this.div_.firstChild; + this.closeListener_ = google.maps.event.addDomListener(closeBox, "click", this.getCloseClickHandler_()); + + } else { + + this.closeListener_ = null; + } +}; + +/** + * Returns the function to call when the user clicks the close box of an InfoBox. + * @private + */ +InfoBox.prototype.getCloseClickHandler_ = function () { + + var me = this; + + return function (e) { + + // 1.0.3 fix: Always prevent propagation of a close box click to the map: + e.cancelBubble = true; + + if (e.stopPropagation) { + + e.stopPropagation(); + } + + /** + * This event is fired when the InfoBox's close box is clicked. + * @name InfoBox#closeclick + * @event + */ + google.maps.event.trigger(me, "closeclick"); + + me.close(); + }; +}; + +/** + * Pans the map so that the InfoBox appears entirely within the map's visible area. + * @private + */ +InfoBox.prototype.panBox_ = function (disablePan) { + + var map; + var bounds; + var xOffset = 0, yOffset = 0; + + if (!disablePan) { + + map = this.getMap(); + + if (map instanceof google.maps.Map) { // Only pan if attached to map, not panorama + + if (!map.getBounds().contains(this.position_)) { + // Marker not in visible area of map, so set center + // of map to the marker position first. + map.setCenter(this.position_); + } + + bounds = map.getBounds(); + + var mapDiv = map.getDiv(); + var mapWidth = mapDiv.offsetWidth; + var mapHeight = mapDiv.offsetHeight; + var iwOffsetX = this.pixelOffset_.width; + var iwOffsetY = this.pixelOffset_.height; + var iwWidth = this.div_.offsetWidth; + var iwHeight = this.div_.offsetHeight; + var padX = this.infoBoxClearance_.width; + var padY = this.infoBoxClearance_.height; + var pixPosition = this.getProjection().fromLatLngToContainerPixel(this.position_); + + if (pixPosition.x < (-iwOffsetX + padX)) { + xOffset = pixPosition.x + iwOffsetX - padX; + } else if ((pixPosition.x + iwWidth + iwOffsetX + padX) > mapWidth) { + xOffset = pixPosition.x + iwWidth + iwOffsetX + padX - mapWidth; + } + if (this.alignBottom_) { + if (pixPosition.y < (-iwOffsetY + padY + iwHeight)) { + yOffset = pixPosition.y + iwOffsetY - padY - iwHeight; + } else if ((pixPosition.y + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwOffsetY + padY - mapHeight; + } + } else { + if (pixPosition.y < (-iwOffsetY + padY)) { + yOffset = pixPosition.y + iwOffsetY - padY; + } else if ((pixPosition.y + iwHeight + iwOffsetY + padY) > mapHeight) { + yOffset = pixPosition.y + iwHeight + iwOffsetY + padY - mapHeight; + } + } + + if (!(xOffset === 0 && yOffset === 0)) { + + // Move the map to the shifted center. + // + var c = map.getCenter(); + map.panBy(xOffset, yOffset); + } + } + } +}; + +/** + * Sets the style of the InfoBox by setting the style sheet and applying + * other specific styles requested. + * @private + */ +InfoBox.prototype.setBoxStyle_ = function () { + + var i, boxStyle; + + if (this.div_) { + + // Apply style values from the style sheet defined in the boxClass parameter: + this.div_.className = this.boxClass_; + + // Clear existing inline style values: + this.div_.style.cssText = ""; + + // Apply style values defined in the boxStyle parameter: + boxStyle = this.boxStyle_; + for (i in boxStyle) { + + if (boxStyle.hasOwnProperty(i)) { + + this.div_.style[i] = boxStyle[i]; + } + } + + // Fix up opacity style for benefit of MSIE: + // + if (typeof this.div_.style.opacity !== "undefined" && this.div_.style.opacity !== "") { + + this.div_.style.filter = "alpha(opacity=" + (this.div_.style.opacity * 100) + ")"; + } + + // Apply required styles: + // + this.div_.style.position = "absolute"; + this.div_.style.visibility = 'hidden'; + if (this.zIndex_ !== null) { + + this.div_.style.zIndex = this.zIndex_; + } + } +}; + +/** + * Get the widths of the borders of the InfoBox. + * @private + * @return {Object} widths object (top, bottom left, right) + */ +InfoBox.prototype.getBoxWidths_ = function () { + + var computedStyle; + var bw = {top: 0, bottom: 0, left: 0, right: 0}; + var box = this.div_; + + if (document.defaultView && document.defaultView.getComputedStyle) { + + computedStyle = box.ownerDocument.defaultView.getComputedStyle(box, ""); + + if (computedStyle) { + + // The computed styles are always in pixel units (good!) + bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; + } + + } else if (document.documentElement.currentStyle) { // MSIE + + if (box.currentStyle) { + + // The current styles may not be in pixel units, but assume they are (bad!) + bw.top = parseInt(box.currentStyle.borderTopWidth, 10) || 0; + bw.bottom = parseInt(box.currentStyle.borderBottomWidth, 10) || 0; + bw.left = parseInt(box.currentStyle.borderLeftWidth, 10) || 0; + bw.right = parseInt(box.currentStyle.borderRightWidth, 10) || 0; + } + } + + return bw; +}; + +/** + * Invoked when <tt>close</tt> is called. Do not call it directly. + */ +InfoBox.prototype.onRemove = function () { + + if (this.div_) { + + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + +/** + * Draws the InfoBox based on the current map projection and zoom level. + */ +InfoBox.prototype.draw = function () { + + this.createInfoBoxDiv_(); + + var pixPosition = this.getProjection().fromLatLngToDivPixel(this.position_); + + this.div_.style.left = (pixPosition.x + this.pixelOffset_.width) + "px"; + + if (this.alignBottom_) { + this.div_.style.bottom = -(pixPosition.y + this.pixelOffset_.height) + "px"; + } else { + this.div_.style.top = (pixPosition.y + this.pixelOffset_.height) + "px"; + } + + if (this.isHidden_) { + + this.div_.style.visibility = 'hidden'; + + } else { + + this.div_.style.visibility = "visible"; + } +}; + +/** + * Sets the options for the InfoBox. Note that changes to the <tt>maxWidth</tt>, + * <tt>closeBoxMargin</tt>, <tt>closeBoxURL</tt>, and <tt>enableEventPropagation</tt> + * properties have no affect until the current InfoBox is <tt>close</tt>d and a new one + * is <tt>open</tt>ed. + * @param {InfoBoxOptions} opt_opts + */ +InfoBox.prototype.setOptions = function (opt_opts) { + if (typeof opt_opts.boxClass !== "undefined") { // Must be first + + this.boxClass_ = opt_opts.boxClass; + this.setBoxStyle_(); + } + if (typeof opt_opts.boxStyle !== "undefined") { // Must be second + + this.boxStyle_ = opt_opts.boxStyle; + this.setBoxStyle_(); + } + if (typeof opt_opts.content !== "undefined") { + + this.setContent(opt_opts.content); + } + if (typeof opt_opts.disableAutoPan !== "undefined") { + + this.disableAutoPan_ = opt_opts.disableAutoPan; + } + if (typeof opt_opts.maxWidth !== "undefined") { + + this.maxWidth_ = opt_opts.maxWidth; + } + if (typeof opt_opts.pixelOffset !== "undefined") { + + this.pixelOffset_ = opt_opts.pixelOffset; + } + if (typeof opt_opts.alignBottom !== "undefined") { + + this.alignBottom_ = opt_opts.alignBottom; + } + if (typeof opt_opts.position !== "undefined") { + + this.setPosition(opt_opts.position); + } + if (typeof opt_opts.zIndex !== "undefined") { + + this.setZIndex(opt_opts.zIndex); + } + if (typeof opt_opts.closeBoxMargin !== "undefined") { + + this.closeBoxMargin_ = opt_opts.closeBoxMargin; + } + if (typeof opt_opts.closeBoxURL !== "undefined") { + + this.closeBoxURL_ = opt_opts.closeBoxURL; + } + if (typeof opt_opts.infoBoxClearance !== "undefined") { + + this.infoBoxClearance_ = opt_opts.infoBoxClearance; + } + if (typeof opt_opts.isHidden !== "undefined") { + + this.isHidden_ = opt_opts.isHidden; + } + if (typeof opt_opts.visible !== "undefined") { + + this.isHidden_ = !opt_opts.visible; + } + if (typeof opt_opts.enableEventPropagation !== "undefined") { + + this.enableEventPropagation_ = opt_opts.enableEventPropagation; + } + + if (this.div_) { + + this.draw(); + } +}; + +/** + * Sets the content of the InfoBox. + * The content can be plain text or an HTML DOM node. + * @param {string|Node} content + */ +InfoBox.prototype.setContent = function (content) { + this.content_ = content; + + if (this.div_) { + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + // Odd code required to make things work with MSIE. + // + if (!this.fixedWidthSet_) { + + this.div_.style.width = ""; + } + + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + + // Perverse code required to make things work with MSIE. + // (Ensures the close box does, in fact, float to the right.) + // + if (!this.fixedWidthSet_) { + this.div_.style.width = this.div_.offsetWidth + "px"; + if (typeof content.nodeType === "undefined") { + this.div_.innerHTML = this.getCloseBoxImg_() + content; + } else { + this.div_.innerHTML = this.getCloseBoxImg_(); + this.div_.appendChild(content); + } + } + + this.addClickHandler_(); + } + + /** + * This event is fired when the content of the InfoBox changes. + * @name InfoBox#content_changed + * @event + */ + google.maps.event.trigger(this, "content_changed"); +}; + +/** + * Sets the geographic location of the InfoBox. + * @param {LatLng} latlng + */ +InfoBox.prototype.setPosition = function (latlng) { + + this.position_ = latlng; + + if (this.div_) { + + this.draw(); + } + + /** + * This event is fired when the position of the InfoBox changes. + * @name InfoBox#position_changed + * @event + */ + google.maps.event.trigger(this, "position_changed"); +}; + +/** + * Sets the zIndex style for the InfoBox. + * @param {number} index + */ +InfoBox.prototype.setZIndex = function (index) { + + this.zIndex_ = index; + + if (this.div_) { + + this.div_.style.zIndex = index; + } + + /** + * This event is fired when the zIndex of the InfoBox changes. + * @name InfoBox#zindex_changed + * @event + */ + google.maps.event.trigger(this, "zindex_changed"); +}; + +/** + * Sets the visibility of the InfoBox. + * @param {boolean} isVisible + */ +InfoBox.prototype.setVisible = function (isVisible) { + + this.isHidden_ = !isVisible; + if (this.div_) { + this.div_.style.visibility = (this.isHidden_ ? "hidden" : "visible"); + } +}; + +/** + * Returns the content of the InfoBox. + * @returns {string} + */ +InfoBox.prototype.getContent = function () { + + return this.content_; +}; + +/** + * Returns the geographic location of the InfoBox. + * @returns {LatLng} + */ +InfoBox.prototype.getPosition = function () { + + return this.position_; +}; + +/** + * Returns the zIndex for the InfoBox. + * @returns {number} + */ +InfoBox.prototype.getZIndex = function () { + + return this.zIndex_; +}; + +/** + * Returns a flag indicating whether the InfoBox is visible. + * @returns {boolean} + */ +InfoBox.prototype.getVisible = function () { + + var isVisible; + + if ((typeof this.getMap() === "undefined") || (this.getMap() === null)) { + isVisible = false; + } else { + isVisible = !this.isHidden_; + } + return isVisible; +}; + +/** + * Shows the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.] + */ +InfoBox.prototype.show = function () { + + this.isHidden_ = false; + if (this.div_) { + this.div_.style.visibility = "visible"; + } +}; + +/** + * Hides the InfoBox. [Deprecated; use <tt>setVisible</tt> instead.] + */ +InfoBox.prototype.hide = function () { + + this.isHidden_ = true; + if (this.div_) { + this.div_.style.visibility = "hidden"; + } +}; + +/** + * Adds the InfoBox to the specified map or Street View panorama. If <tt>anchor</tt> + * (usually a <tt>google.maps.Marker</tt>) is specified, the position + * of the InfoBox is set to the position of the <tt>anchor</tt>. If the + * anchor is dragged to a new location, the InfoBox moves as well. + * @param {Map|StreetViewPanorama} map + * @param {MVCObject} [anchor] + */ +InfoBox.prototype.open = function (map, anchor) { + + var me = this; + + if (anchor) { + + this.position_ = anchor.getPosition(); + this.moveListener_ = google.maps.event.addListener(anchor, "position_changed", function () { + me.setPosition(this.getPosition()); + }); + } + + this.setMap(map); + + if (this.div_) { + + this.panBox_(); + } +}; + +/** + * Removes the InfoBox from the map. + */ +InfoBox.prototype.close = function () { + + var i; + + if (this.closeListener_) { + + google.maps.event.removeListener(this.closeListener_); + this.closeListener_ = null; + } + + if (this.eventListeners_) { + + for (i = 0; i < this.eventListeners_.length; i++) { + + google.maps.event.removeListener(this.eventListeners_[i]); + } + this.eventListeners_ = null; + } + + if (this.moveListener_) { + + google.maps.event.removeListener(this.moveListener_); + this.moveListener_ = null; + } + + if (this.contextListener_) { + + google.maps.event.removeListener(this.contextListener_); + this.contextListener_ = null; + } + + this.setMap(null); +};;/** + * @name MarkerClustererPlus for Google Maps V3 + * @version 2.1.1 [November 4, 2013] + * @author Gary Little + * @fileoverview + * The library creates and manages per-zoom-level clusters for large amounts of markers. + * <p> + * This is an enhanced V3 implementation of the + * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/" + * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the + * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/" + * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little. + * <p> + * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It + * adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>batchSizeIE</code>, + * and <code>calculator</code> properties as well as support for four more events. It also allows + * greater control over the styling of the text that appears on the cluster marker. The + * documentation has been significantly improved and the overall code has been simplified and + * polished. Very large numbers of markers can now be managed without causing Javascript timeout + * errors on Internet Explorer. Note that the name of the <code>clusterclick</code> event has been + * deprecated. The new name is <code>click</code>, so please change your application code now. + */ + +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +/** + * @name ClusterIconStyle + * @class This class represents the object for values in the <code>styles</code> array passed + * to the {@link MarkerClusterer} constructor. The element in this array that is used to + * style the cluster icon is determined by calling the <code>calculator</code> function. + * + * @property {string} url The URL of the cluster icon image file. Required. + * @property {number} height The display height (in pixels) of the cluster icon. Required. + * @property {number} width The display width (in pixels) of the cluster icon. Required. + * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to + * where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code> + * where <code>yoffset</code> increases as you go down from center and <code>xoffset</code> + * increases to the right of center. The default is <code>[0, 0]</code>. + * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the + * spot on the cluster icon that is to be aligned with the cluster position. The format is + * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and + * <code>xoffset</code> increases to the right of the top-left corner of the icon. The default + * anchor position is the center of the cluster icon. + * @property {string} [textColor="black"] The color of the label text shown on the + * cluster icon. + * @property {number} [textSize=11] The size (in pixels) of the label text shown on the + * cluster icon. + * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code> + * property for the label text shown on the cluster icon. + * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code> + * property for the label text shown on the cluster icon. + * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code> + * property for the label text shown on the cluster icon. + * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code> + * property for the label text shown on the cluster icon. + * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image + * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code> + * (the same format as for the CSS <code>background-position</code> property). You must set + * this property appropriately when the image defined by <code>url</code> represents a sprite + * containing multiple images. Note that the position <i>must</i> be specified in px units. + */ +/** + * @name ClusterIconInfo + * @class This class is an object containing general information about a cluster icon. This is + * the object that a <code>calculator</code> function returns. + * + * @property {string} text The text of the label to be shown on the cluster icon. + * @property {number} index The index plus 1 of the element in the <code>styles</code> + * array to be used to style the cluster icon. + * @property {string} title The tooltip to display when the mouse moves over the cluster icon. + * If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the + * value of the <code>title</code> property passed to the MarkerClusterer. + */ +/** + * A cluster icon. + * + * @constructor + * @extends google.maps.OverlayView + * @param {Cluster} cluster The cluster with which the icon is to be associated. + * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons + * to use for various cluster sizes. + * @private + */ +function ClusterIcon(cluster, styles) { + cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView); + + this.cluster_ = cluster; + this.className_ = cluster.getMarkerClusterer().getClusterClass(); + this.styles_ = styles; + this.center_ = null; + this.div_ = null; + this.sums_ = null; + this.visible_ = false; + + this.setMap(cluster.getMap()); // Note: this causes onAdd to be called +} + + +/** + * Adds the icon to the DOM. + */ +ClusterIcon.prototype.onAdd = function () { + var cClusterIcon = this; + var cMouseDownInCluster; + var cDraggingMapByCluster; + + this.div_ = document.createElement("div"); + this.div_.className = this.className_; + if (this.visible_) { + this.show(); + } + + this.getPanes().overlayMouseTarget.appendChild(this.div_); + + // Fix for Issue 157 + this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () { + cDraggingMapByCluster = cMouseDownInCluster; + }); + + google.maps.event.addDomListener(this.div_, "mousedown", function () { + cMouseDownInCluster = true; + cDraggingMapByCluster = false; + }); + + google.maps.event.addDomListener(this.div_, "click", function (e) { + cMouseDownInCluster = false; + if (!cDraggingMapByCluster) { + var theBounds; + var mz; + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when a cluster marker is clicked. + * @name MarkerClusterer#click + * @param {Cluster} c The cluster that was clicked. + * @event + */ + google.maps.event.trigger(mc, "click", cClusterIcon.cluster_); + google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name + + // The default click handler follows. Disable it by setting + // the zoomOnClick property to false. + if (mc.getZoomOnClick()) { + // Zoom into the cluster. + mz = mc.getMaxZoom(); + theBounds = cClusterIcon.cluster_.getBounds(); + mc.getMap().fitBounds(theBounds); + // There is a fix for Issue 170 here: + setTimeout(function () { + mc.getMap().fitBounds(theBounds); + // Don't zoom beyond the max zoom level + if (mz !== null && (mc.getMap().getZoom() > mz)) { + mc.getMap().setZoom(mz + 1); + } + }, 100); + } + + // Prevent event propagation to the map: + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + } + }); + + google.maps.event.addDomListener(this.div_, "mouseover", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves over a cluster marker. + * @name MarkerClusterer#mouseover + * @param {Cluster} c The cluster that the mouse moved over. + * @event + */ + google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_); + }); + + google.maps.event.addDomListener(this.div_, "mouseout", function () { + var mc = cClusterIcon.cluster_.getMarkerClusterer(); + /** + * This event is fired when the mouse moves out of a cluster marker. + * @name MarkerClusterer#mouseout + * @param {Cluster} c The cluster that the mouse moved out of. + * @event + */ + google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_); + }); +}; + + +/** + * Removes the icon from the DOM. + */ +ClusterIcon.prototype.onRemove = function () { + if (this.div_ && this.div_.parentNode) { + this.hide(); + google.maps.event.removeListener(this.boundsChangedListener_); + google.maps.event.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } +}; + + +/** + * Draws the icon. + */ +ClusterIcon.prototype.draw = function () { + if (this.visible_) { + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.top = pos.y + "px"; + this.div_.style.left = pos.x + "px"; + } +}; + + +/** + * Hides the icon. + */ +ClusterIcon.prototype.hide = function () { + if (this.div_) { + this.div_.style.display = "none"; + } + this.visible_ = false; +}; + + +/** + * Positions and shows the icon. + */ +ClusterIcon.prototype.show = function () { + if (this.div_) { + var img = ""; + // NOTE: values must be specified in px units + var bp = this.backgroundPosition_.split(" "); + var spriteH = parseInt(bp[0].trim(), 10); + var spriteV = parseInt(bp[1].trim(), 10); + var pos = this.getPosFromLatLng_(this.center_); + this.div_.style.cssText = this.createCss(pos); + img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; "; + if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) { + img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " + + ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);"; + } + img += "'>"; + this.div_.innerHTML = img + "<div style='" + + "position: absolute;" + + "top: " + this.anchorText_[0] + "px;" + + "left: " + this.anchorText_[1] + "px;" + + "color: " + this.textColor_ + ";" + + "font-size: " + this.textSize_ + "px;" + + "font-family: " + this.fontFamily_ + ";" + + "font-weight: " + this.fontWeight_ + ";" + + "font-style: " + this.fontStyle_ + ";" + + "text-decoration: " + this.textDecoration_ + ";" + + "text-align: center;" + + "width: " + this.width_ + "px;" + + "line-height:" + this.height_ + "px;" + + "'>" + this.sums_.text + "</div>"; + if (typeof this.sums_.title === "undefined" || this.sums_.title === "") { + this.div_.title = this.cluster_.getMarkerClusterer().getTitle(); + } else { + this.div_.title = this.sums_.title; + } + this.div_.style.display = ""; + } + this.visible_ = true; +}; + + +/** + * Sets the icon styles to the appropriate element in the styles array. + * + * @param {ClusterIconInfo} sums The icon label text and styles index. + */ +ClusterIcon.prototype.useStyle = function (sums) { + this.sums_ = sums; + var index = Math.max(0, sums.index - 1); + index = Math.min(this.styles_.length - 1, index); + var style = this.styles_[index]; + this.url_ = style.url; + this.height_ = style.height; + this.width_ = style.width; + this.anchorText_ = style.anchorText || [0, 0]; + this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)]; + this.textColor_ = style.textColor || "black"; + this.textSize_ = style.textSize || 11; + this.textDecoration_ = style.textDecoration || "none"; + this.fontWeight_ = style.fontWeight || "bold"; + this.fontStyle_ = style.fontStyle || "normal"; + this.fontFamily_ = style.fontFamily || "Arial,sans-serif"; + this.backgroundPosition_ = style.backgroundPosition || "0 0"; +}; + + +/** + * Sets the position at which to center the icon. + * + * @param {google.maps.LatLng} center The latlng to set as the center. + */ +ClusterIcon.prototype.setCenter = function (center) { + this.center_ = center; +}; + + +/** + * Creates the cssText style parameter based on the position of the icon. + * + * @param {google.maps.Point} pos The position of the icon. + * @return {string} The CSS style text. + */ +ClusterIcon.prototype.createCss = function (pos) { + var style = []; + style.push("cursor: pointer;"); + style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;"); + style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;"); + return style.join(""); +}; + + +/** + * Returns the position at which to place the DIV depending on the latlng. + * + * @param {google.maps.LatLng} latlng The position in latlng. + * @return {google.maps.Point} The position in pixels. + */ +ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) { + var pos = this.getProjection().fromLatLngToDivPixel(latlng); + pos.x -= this.anchorIcon_[1]; + pos.y -= this.anchorIcon_[0]; + pos.x = parseInt(pos.x, 10); + pos.y = parseInt(pos.y, 10); + return pos; +}; + + +/** + * Creates a single cluster that manages a group of proximate markers. + * Used internally, do not call this constructor directly. + * @constructor + * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this + * cluster is associated. + */ +function Cluster(mc) { + this.markerClusterer_ = mc; + this.map_ = mc.getMap(); + this.gridSize_ = mc.getGridSize(); + this.minClusterSize_ = mc.getMinimumClusterSize(); + this.averageCenter_ = mc.getAverageCenter(); + this.markers_ = []; + this.center_ = null; + this.bounds_ = null; + this.clusterIcon_ = new ClusterIcon(this, mc.getStyles()); +} + + +/** + * Returns the number of markers managed by the cluster. You can call this from + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler + * for the <code>MarkerClusterer</code> object. + * + * @return {number} The number of markers in the cluster. + */ +Cluster.prototype.getSize = function () { + return this.markers_.length; +}; + + +/** + * Returns the array of markers managed by the cluster. You can call this from + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler + * for the <code>MarkerClusterer</code> object. + * + * @return {Array} The array of markers in the cluster. + */ +Cluster.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the center of the cluster. You can call this from + * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler + * for the <code>MarkerClusterer</code> object. + * + * @return {google.maps.LatLng} The center of the cluster. + */ +Cluster.prototype.getCenter = function () { + return this.center_; +}; + + +/** + * Returns the map with which the cluster is associated. + * + * @return {google.maps.Map} The map. + * @ignore + */ +Cluster.prototype.getMap = function () { + return this.map_; +}; + + +/** + * Returns the <code>MarkerClusterer</code> object with which the cluster is associated. + * + * @return {MarkerClusterer} The associated marker clusterer. + * @ignore + */ +Cluster.prototype.getMarkerClusterer = function () { + return this.markerClusterer_; +}; + + +/** + * Returns the bounds of the cluster. + * + * @return {google.maps.LatLngBounds} the cluster bounds. + * @ignore + */ +Cluster.prototype.getBounds = function () { + var i; + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + var markers = this.getMarkers(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + return bounds; +}; + + +/** + * Removes the cluster from the map. + * + * @ignore + */ +Cluster.prototype.remove = function () { + this.clusterIcon_.setMap(null); + this.markers_ = []; + delete this.markers_; +}; + + +/** + * Adds a marker to the cluster. + * + * @param {google.maps.Marker} marker The marker to be added. + * @return {boolean} True if the marker was added. + * @ignore + */ +Cluster.prototype.addMarker = function (marker) { + var i; + var mCount; + var mz; + + if (this.isMarkerAlreadyAdded_(marker)) { + return false; + } + + if (!this.center_) { + this.center_ = marker.getPosition(); + this.calculateBounds_(); + } else { + if (this.averageCenter_) { + var l = this.markers_.length + 1; + var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l; + var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l; + this.center_ = new google.maps.LatLng(lat, lng); + this.calculateBounds_(); + } + } + + marker.isAdded = true; + this.markers_.push(marker); + + mCount = this.markers_.length; + mz = this.markerClusterer_.getMaxZoom(); + if (mz !== null && this.map_.getZoom() > mz) { + // Zoomed in past max zoom, so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount < this.minClusterSize_) { + // Min cluster size not reached so show the marker. + if (marker.getMap() !== this.map_) { + marker.setMap(this.map_); + } + } else if (mCount === this.minClusterSize_) { + // Hide the markers that were showing. + for (i = 0; i < mCount; i++) { + this.markers_[i].setMap(null); + } + } else { + marker.setMap(null); + } + + this.updateIcon_(); + return true; +}; + + +/** + * Determines if a marker lies within the cluster's bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker lies in the bounds. + * @ignore + */ +Cluster.prototype.isMarkerInClusterBounds = function (marker) { + return this.bounds_.contains(marker.getPosition()); +}; + + +/** + * Calculates the extended bounds of the cluster with the grid. + */ +Cluster.prototype.calculateBounds_ = function () { + var bounds = new google.maps.LatLngBounds(this.center_, this.center_); + this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds); +}; + + +/** + * Updates the cluster icon. + */ +Cluster.prototype.updateIcon_ = function () { + var mCount = this.markers_.length; + var mz = this.markerClusterer_.getMaxZoom(); + + if (mz !== null && this.map_.getZoom() > mz) { + this.clusterIcon_.hide(); + return; + } + + if (mCount < this.minClusterSize_) { + // Min cluster size not yet reached. + this.clusterIcon_.hide(); + return; + } + + var numStyles = this.markerClusterer_.getStyles().length; + var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles); + this.clusterIcon_.setCenter(this.center_); + this.clusterIcon_.useStyle(sums); + this.clusterIcon_.show(); +}; + + +/** + * Determines if a marker has already been added to the cluster. + * + * @param {google.maps.Marker} marker The marker to check. + * @return {boolean} True if the marker has already been added. + */ +Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) { + var i; + if (this.markers_.indexOf) { + return this.markers_.indexOf(marker) !== -1; + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + return true; + } + } + } + return false; +}; + + +/** + * @name MarkerClustererOptions + * @class This class represents the optional parameter passed to + * the {@link MarkerClusterer} constructor. + * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square. + * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or + * <code>null</code> if clustering is to be enabled at all zoom levels. + * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is + * clicked. You may want to set this to <code>false</code> if you have installed a handler + * for the <code>click</code> event and it deals with zooming on its own. + * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be + * the average position of all markers in the cluster. If set to <code>false</code>, the + * cluster marker is positioned at the location of the first marker added to the cluster. + * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster + * before the markers are hidden and a cluster marker appears. + * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You + * may want to set this to <code>true</code> to ensure that hidden markers are not included + * in the marker count that appears on a cluster marker (this count is the value of the + * <code>text</code> property of the result returned by the default <code>calculator</code>). + * If set to <code>true</code> and you change the visibility of a marker being clustered, be + * sure to also call <code>MarkerClusterer.repaint()</code>. + * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster + * marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a + * different tooltip for each cluster marker.) + * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine + * the text to be displayed on a cluster marker and the index indicating which style to use + * for the cluster marker. The input parameters for the function are (1) the array of markers + * represented by a cluster marker and (2) the number of cluster icon styles. It returns a + * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a + * <code>text</code> property which is the number of markers in the cluster and an + * <code>index</code> property which is one higher than the lowest integer such that + * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles + * array, whichever is less. The <code>styles</code> array element used has an index of + * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a + * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code> + * for a cluster icon representing 125 markers so the element used in the <code>styles</code> + * array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code> + * property that contains the text of the tooltip to be used for the cluster marker. If + * <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code> + * property for the MarkerClusterer. + * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles + * for the cluster markers. Use this class to define CSS styles that are not set up by the code + * that processes the <code>styles</code> array. + * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles + * of the cluster markers to be used. The element to be used to style a given cluster marker + * is determined by the function defined by the <code>calculator</code> property. + * The default is an array of {@link ClusterIconStyle} elements whose properties are derived + * from the values for <code>imagePath</code>, <code>imageExtension</code>, and + * <code>imageSizes</code>. + * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that + * have sizes that are some multiple (typically double) of their actual display size. Icons such + * as these look better when viewed on high-resolution monitors such as Apple's Retina displays. + * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons. + * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the + * number of markers to be processed in a single batch when using a browser other than + * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead). + * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is + * being used, markers are processed in several batches with a small delay inserted between + * each batch in an attempt to avoid Javascript timeout errors. Set this property to the + * number of markers to be processed in a single batch; select as high a number as you can + * without causing a timeout error in the browser. This number might need to be as low as 100 + * if 15,000 markers are being managed, for example. + * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH] + * The full URL of the root name of the group of image files to use for cluster icons. + * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code> + * where n is the image file number (1, 2, etc.). + * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION] + * The extension name for the cluster icon image files (e.g., <code>"png"</code> or + * <code>"jpg"</code>). + * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES] + * An array of numbers containing the widths of the group of + * <code>imagePath</code>n.<code>imageExtension</code> image files. + * (The images are assumed to be square.) + */ +/** + * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}. + * @constructor + * @extends google.maps.OverlayView + * @param {google.maps.Map} map The Google map to attach to. + * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster. + * @param {MarkerClustererOptions} [opt_options] The optional parameters. + */ +function MarkerClusterer(map, opt_markers, opt_options) { + // MarkerClusterer implements google.maps.OverlayView interface. We use the + // extend function to extend MarkerClusterer with google.maps.OverlayView + // because it might not always be available when the code is defined so we + // look for it at the last possible moment. If it doesn't exist now then + // there is no point going ahead :) + this.extend(MarkerClusterer, google.maps.OverlayView); + + opt_markers = opt_markers || []; + opt_options = opt_options || {}; + + this.markers_ = []; + this.clusters_ = []; + this.listeners_ = []; + this.activeMap_ = null; + this.ready_ = false; + + this.gridSize_ = opt_options.gridSize || 60; + this.minClusterSize_ = opt_options.minimumClusterSize || 2; + this.maxZoom_ = opt_options.maxZoom || null; + this.styles_ = opt_options.styles || []; + this.title_ = opt_options.title || ""; + this.zoomOnClick_ = true; + if (opt_options.zoomOnClick !== undefined) { + this.zoomOnClick_ = opt_options.zoomOnClick; + } + this.averageCenter_ = false; + if (opt_options.averageCenter !== undefined) { + this.averageCenter_ = opt_options.averageCenter; + } + this.ignoreHidden_ = false; + if (opt_options.ignoreHidden !== undefined) { + this.ignoreHidden_ = opt_options.ignoreHidden; + } + this.enableRetinaIcons_ = false; + if (opt_options.enableRetinaIcons !== undefined) { + this.enableRetinaIcons_ = opt_options.enableRetinaIcons; + } + this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH; + this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION; + this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES; + this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR; + this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE; + this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE; + this.clusterClass_ = opt_options.clusterClass || "cluster"; + + if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) { + // Try to avoid IE timeout when processing a huge number of markers: + this.batchSize_ = this.batchSizeIE_; + } + + this.setupStyles_(); + + this.addMarkers(opt_markers, true); + this.setMap(map); // Note: this causes onAdd to be called +} + + +/** + * Implementation of the onAdd interface method. + * @ignore + */ +MarkerClusterer.prototype.onAdd = function () { + var cMarkerClusterer = this; + + this.activeMap_ = this.getMap(); + this.ready_ = true; + + this.repaint(); + + // Add the map event listeners + this.listeners_ = [ + google.maps.event.addListener(this.getMap(), "zoom_changed", function () { + cMarkerClusterer.resetViewport_(false); + // Workaround for this Google bug: when map is at level 0 and "-" of + // zoom slider is clicked, a "zoom_changed" event is fired even though + // the map doesn't zoom out any further. In this situation, no "idle" + // event is triggered so the cluster markers that have been removed + // do not get redrawn. Same goes for a zoom in at maxZoom. + if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) { + google.maps.event.trigger(this, "idle"); + } + }), + google.maps.event.addListener(this.getMap(), "idle", function () { + cMarkerClusterer.redraw_(); + }) + ]; +}; + + +/** + * Implementation of the onRemove interface method. + * Removes map event listeners and all cluster icons from the DOM. + * All managed markers are also put back on the map. + * @ignore + */ +MarkerClusterer.prototype.onRemove = function () { + var i; + + // Put all the managed markers back on the map: + for (i = 0; i < this.markers_.length; i++) { + if (this.markers_[i].getMap() !== this.activeMap_) { + this.markers_[i].setMap(this.activeMap_); + } + } + + // Remove all clusters: + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Remove map event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } + this.listeners_ = []; + + this.activeMap_ = null; + this.ready_ = false; +}; + + +/** + * Implementation of the draw interface method. + * @ignore + */ +MarkerClusterer.prototype.draw = function () {}; + + +/** + * Sets up the styles object. + */ +MarkerClusterer.prototype.setupStyles_ = function () { + var i, size; + if (this.styles_.length > 0) { + return; + } + + for (i = 0; i < this.imageSizes_.length; i++) { + size = this.imageSizes_[i]; + this.styles_.push({ + url: this.imagePath_ + (i + 1) + "." + this.imageExtension_, + height: size, + width: size + }); + } +}; + + +/** + * Fits the map to the bounds of the markers managed by the clusterer. + */ +MarkerClusterer.prototype.fitMapToMarkers = function () { + var i; + var markers = this.getMarkers(); + var bounds = new google.maps.LatLngBounds(); + for (i = 0; i < markers.length; i++) { + bounds.extend(markers[i].getPosition()); + } + + this.getMap().fitBounds(bounds); +}; + + +/** + * Returns the value of the <code>gridSize</code> property. + * + * @return {number} The grid size. + */ +MarkerClusterer.prototype.getGridSize = function () { + return this.gridSize_; +}; + + +/** + * Sets the value of the <code>gridSize</code> property. + * + * @param {number} gridSize The grid size. + */ +MarkerClusterer.prototype.setGridSize = function (gridSize) { + this.gridSize_ = gridSize; +}; + + +/** + * Returns the value of the <code>minimumClusterSize</code> property. + * + * @return {number} The minimum cluster size. + */ +MarkerClusterer.prototype.getMinimumClusterSize = function () { + return this.minClusterSize_; +}; + +/** + * Sets the value of the <code>minimumClusterSize</code> property. + * + * @param {number} minimumClusterSize The minimum cluster size. + */ +MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) { + this.minClusterSize_ = minimumClusterSize; +}; + + +/** + * Returns the value of the <code>maxZoom</code> property. + * + * @return {number} The maximum zoom level. + */ +MarkerClusterer.prototype.getMaxZoom = function () { + return this.maxZoom_; +}; + + +/** + * Sets the value of the <code>maxZoom</code> property. + * + * @param {number} maxZoom The maximum zoom level. + */ +MarkerClusterer.prototype.setMaxZoom = function (maxZoom) { + this.maxZoom_ = maxZoom; +}; + + +/** + * Returns the value of the <code>styles</code> property. + * + * @return {Array} The array of styles defining the cluster markers to be used. + */ +MarkerClusterer.prototype.getStyles = function () { + return this.styles_; +}; + + +/** + * Sets the value of the <code>styles</code> property. + * + * @param {Array.<ClusterIconStyle>} styles The array of styles to use. + */ +MarkerClusterer.prototype.setStyles = function (styles) { + this.styles_ = styles; +}; + + +/** + * Returns the value of the <code>title</code> property. + * + * @return {string} The content of the title text. + */ +MarkerClusterer.prototype.getTitle = function () { + return this.title_; +}; + + +/** + * Sets the value of the <code>title</code> property. + * + * @param {string} title The value of the title property. + */ +MarkerClusterer.prototype.setTitle = function (title) { + this.title_ = title; +}; + + +/** + * Returns the value of the <code>zoomOnClick</code> property. + * + * @return {boolean} True if zoomOnClick property is set. + */ +MarkerClusterer.prototype.getZoomOnClick = function () { + return this.zoomOnClick_; +}; + + +/** + * Sets the value of the <code>zoomOnClick</code> property. + * + * @param {boolean} zoomOnClick The value of the zoomOnClick property. + */ +MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) { + this.zoomOnClick_ = zoomOnClick; +}; + + +/** + * Returns the value of the <code>averageCenter</code> property. + * + * @return {boolean} True if averageCenter property is set. + */ +MarkerClusterer.prototype.getAverageCenter = function () { + return this.averageCenter_; +}; + + +/** + * Sets the value of the <code>averageCenter</code> property. + * + * @param {boolean} averageCenter The value of the averageCenter property. + */ +MarkerClusterer.prototype.setAverageCenter = function (averageCenter) { + this.averageCenter_ = averageCenter; +}; + + +/** + * Returns the value of the <code>ignoreHidden</code> property. + * + * @return {boolean} True if ignoreHidden property is set. + */ +MarkerClusterer.prototype.getIgnoreHidden = function () { + return this.ignoreHidden_; +}; + + +/** + * Sets the value of the <code>ignoreHidden</code> property. + * + * @param {boolean} ignoreHidden The value of the ignoreHidden property. + */ +MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) { + this.ignoreHidden_ = ignoreHidden; +}; + + +/** + * Returns the value of the <code>enableRetinaIcons</code> property. + * + * @return {boolean} True if enableRetinaIcons property is set. + */ +MarkerClusterer.prototype.getEnableRetinaIcons = function () { + return this.enableRetinaIcons_; +}; + + +/** + * Sets the value of the <code>enableRetinaIcons</code> property. + * + * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property. + */ +MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) { + this.enableRetinaIcons_ = enableRetinaIcons; +}; + + +/** + * Returns the value of the <code>imageExtension</code> property. + * + * @return {string} The value of the imageExtension property. + */ +MarkerClusterer.prototype.getImageExtension = function () { + return this.imageExtension_; +}; + + +/** + * Sets the value of the <code>imageExtension</code> property. + * + * @param {string} imageExtension The value of the imageExtension property. + */ +MarkerClusterer.prototype.setImageExtension = function (imageExtension) { + this.imageExtension_ = imageExtension; +}; + + +/** + * Returns the value of the <code>imagePath</code> property. + * + * @return {string} The value of the imagePath property. + */ +MarkerClusterer.prototype.getImagePath = function () { + return this.imagePath_; +}; + + +/** + * Sets the value of the <code>imagePath</code> property. + * + * @param {string} imagePath The value of the imagePath property. + */ +MarkerClusterer.prototype.setImagePath = function (imagePath) { + this.imagePath_ = imagePath; +}; + + +/** + * Returns the value of the <code>imageSizes</code> property. + * + * @return {Array} The value of the imageSizes property. + */ +MarkerClusterer.prototype.getImageSizes = function () { + return this.imageSizes_; +}; + + +/** + * Sets the value of the <code>imageSizes</code> property. + * + * @param {Array} imageSizes The value of the imageSizes property. + */ +MarkerClusterer.prototype.setImageSizes = function (imageSizes) { + this.imageSizes_ = imageSizes; +}; + + +/** + * Returns the value of the <code>calculator</code> property. + * + * @return {function} the value of the calculator property. + */ +MarkerClusterer.prototype.getCalculator = function () { + return this.calculator_; +}; + + +/** + * Sets the value of the <code>calculator</code> property. + * + * @param {function(Array.<google.maps.Marker>, number)} calculator The value + * of the calculator property. + */ +MarkerClusterer.prototype.setCalculator = function (calculator) { + this.calculator_ = calculator; +}; + + +/** + * Returns the value of the <code>batchSizeIE</code> property. + * + * @return {number} the value of the batchSizeIE property. + */ +MarkerClusterer.prototype.getBatchSizeIE = function () { + return this.batchSizeIE_; +}; + + +/** + * Sets the value of the <code>batchSizeIE</code> property. + * + * @param {number} batchSizeIE The value of the batchSizeIE property. + */ +MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) { + this.batchSizeIE_ = batchSizeIE; +}; + + +/** + * Returns the value of the <code>clusterClass</code> property. + * + * @return {string} the value of the clusterClass property. + */ +MarkerClusterer.prototype.getClusterClass = function () { + return this.clusterClass_; +}; + + +/** + * Sets the value of the <code>clusterClass</code> property. + * + * @param {string} clusterClass The value of the clusterClass property. + */ +MarkerClusterer.prototype.setClusterClass = function (clusterClass) { + this.clusterClass_ = clusterClass; +}; + + +/** + * Returns the array of markers managed by the clusterer. + * + * @return {Array} The array of markers managed by the clusterer. + */ +MarkerClusterer.prototype.getMarkers = function () { + return this.markers_; +}; + + +/** + * Returns the number of markers managed by the clusterer. + * + * @return {number} The number of markers. + */ +MarkerClusterer.prototype.getTotalMarkers = function () { + return this.markers_.length; +}; + + +/** + * Returns the current array of clusters formed by the clusterer. + * + * @return {Array} The array of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getClusters = function () { + return this.clusters_; +}; + + +/** + * Returns the number of clusters formed by the clusterer. + * + * @return {number} The number of clusters formed by the clusterer. + */ +MarkerClusterer.prototype.getTotalClusters = function () { + return this.clusters_.length; +}; + + +/** + * Adds a marker to the clusterer. The clusters are redrawn unless + * <code>opt_nodraw</code> is set to <code>true</code>. + * + * @param {google.maps.Marker} marker The marker to add. + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. + */ +MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) { + this.pushMarkerTo_(marker); + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Adds an array of markers to the clusterer. The clusters are redrawn unless + * <code>opt_nodraw</code> is set to <code>true</code>. + * + * @param {Array.<google.maps.Marker>} markers The markers to add. + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. + */ +MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) { + var key; + for (key in markers) { + if (markers.hasOwnProperty(key)) { + this.pushMarkerTo_(markers[key]); + } + } + if (!opt_nodraw) { + this.redraw_(); + } +}; + + +/** + * Pushes a marker to the clusterer. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.pushMarkerTo_ = function (marker) { + // If the marker is draggable add a listener so we can update the clusters on the dragend: + if (marker.getDraggable()) { + var cMarkerClusterer = this; + google.maps.event.addListener(marker, "dragend", function () { + if (cMarkerClusterer.ready_) { + this.isAdded = false; + cMarkerClusterer.repaint(); + } + }); + } + marker.isAdded = false; + this.markers_.push(marker); +}; + + +/** + * Removes a marker from the cluster. The clusters are redrawn unless + * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the + * marker was removed from the clusterer. + * + * @param {google.maps.Marker} marker The marker to remove. + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. + * @return {boolean} True if the marker was removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) { + var removed = this.removeMarker_(marker); + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes an array of markers from the cluster. The clusters are redrawn unless + * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers + * were removed from the clusterer. + * + * @param {Array.<google.maps.Marker>} markers The markers to remove. + * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing. + * @return {boolean} True if markers were removed from the clusterer. + */ +MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) { + var i, r; + var removed = false; + + for (i = 0; i < markers.length; i++) { + r = this.removeMarker_(markers[i]); + removed = removed || r; + } + + if (!opt_nodraw && removed) { + this.repaint(); + } + + return removed; +}; + + +/** + * Removes a marker and returns true if removed, false if not. + * + * @param {google.maps.Marker} marker The marker to remove + * @return {boolean} Whether the marker was removed or not + */ +MarkerClusterer.prototype.removeMarker_ = function (marker) { + var i; + var index = -1; + if (this.markers_.indexOf) { + index = this.markers_.indexOf(marker); + } else { + for (i = 0; i < this.markers_.length; i++) { + if (marker === this.markers_[i]) { + index = i; + break; + } + } + } + + if (index === -1) { + // Marker is not in our list of markers, so do nothing: + return false; + } + + marker.setMap(null); + this.markers_.splice(index, 1); // Remove the marker from the list of managed markers + return true; +}; + + +/** + * Removes all clusters and markers from the map and also removes all markers + * managed by the clusterer. + */ +MarkerClusterer.prototype.clearMarkers = function () { + this.resetViewport_(true); + this.markers_ = []; +}; + + +/** + * Recalculates and redraws all the marker clusters from scratch. + * Call this after changing any properties. + */ +MarkerClusterer.prototype.repaint = function () { + var oldClusters = this.clusters_.slice(); + this.clusters_ = []; + this.resetViewport_(false); + this.redraw_(); + + // Remove the old clusters. + // Do it in a timeout to prevent blinking effect. + setTimeout(function () { + var i; + for (i = 0; i < oldClusters.length; i++) { + oldClusters[i].remove(); + } + }, 0); +}; + + +/** + * Returns the current bounds extended by the grid size. + * + * @param {google.maps.LatLngBounds} bounds The bounds to extend. + * @return {google.maps.LatLngBounds} The extended bounds. + * @ignore + */ +MarkerClusterer.prototype.getExtendedBounds = function (bounds) { + var projection = this.getProjection(); + + // Turn the bounds into latlng. + var tr = new google.maps.LatLng(bounds.getNorthEast().lat(), + bounds.getNorthEast().lng()); + var bl = new google.maps.LatLng(bounds.getSouthWest().lat(), + bounds.getSouthWest().lng()); + + // Convert the points to pixels and the extend out by the grid size. + var trPix = projection.fromLatLngToDivPixel(tr); + trPix.x += this.gridSize_; + trPix.y -= this.gridSize_; + + var blPix = projection.fromLatLngToDivPixel(bl); + blPix.x -= this.gridSize_; + blPix.y += this.gridSize_; + + // Convert the pixel points back to LatLng + var ne = projection.fromDivPixelToLatLng(trPix); + var sw = projection.fromDivPixelToLatLng(blPix); + + // Extend the bounds to contain the new bounds. + bounds.extend(ne); + bounds.extend(sw); + + return bounds; +}; + + +/** + * Redraws all the clusters. + */ +MarkerClusterer.prototype.redraw_ = function () { + this.createClusters_(0); +}; + + +/** + * Removes all clusters from the map. The markers are also removed from the map + * if <code>opt_hide</code> is set to <code>true</code>. + * + * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers + * from the map. + */ +MarkerClusterer.prototype.resetViewport_ = function (opt_hide) { + var i, marker; + // Remove all the clusters + for (i = 0; i < this.clusters_.length; i++) { + this.clusters_[i].remove(); + } + this.clusters_ = []; + + // Reset the markers to not be added and to be removed from the map. + for (i = 0; i < this.markers_.length; i++) { + marker = this.markers_[i]; + marker.isAdded = false; + if (opt_hide) { + marker.setMap(null); + } + } +}; + + +/** + * Calculates the distance between two latlng locations in km. + * + * @param {google.maps.LatLng} p1 The first lat lng point. + * @param {google.maps.LatLng} p2 The second lat lng point. + * @return {number} The distance between the two points in km. + * @see http://www.movable-type.co.uk/scripts/latlong.html + */ +MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) { + var R = 6371; // Radius of the Earth in km + var dLat = (p2.lat() - p1.lat()) * Math.PI / 180; + var dLon = (p2.lng() - p1.lng()) * Math.PI / 180; + var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) * + Math.sin(dLon / 2) * Math.sin(dLon / 2); + var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + var d = R * c; + return d; +}; + + +/** + * Determines if a marker is contained in a bounds. + * + * @param {google.maps.Marker} marker The marker to check. + * @param {google.maps.LatLngBounds} bounds The bounds to check against. + * @return {boolean} True if the marker is in the bounds. + */ +MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) { + return bounds.contains(marker.getPosition()); +}; + + +/** + * Adds a marker to a cluster, or creates a new cluster. + * + * @param {google.maps.Marker} marker The marker to add. + */ +MarkerClusterer.prototype.addToClosestCluster_ = function (marker) { + var i, d, cluster, center; + var distance = 40000; // Some large number + var clusterToAddTo = null; + for (i = 0; i < this.clusters_.length; i++) { + cluster = this.clusters_[i]; + center = cluster.getCenter(); + if (center) { + d = this.distanceBetweenPoints_(center, marker.getPosition()); + if (d < distance) { + distance = d; + clusterToAddTo = cluster; + } + } + } + + if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) { + clusterToAddTo.addMarker(marker); + } else { + cluster = new Cluster(this); + cluster.addMarker(marker); + this.clusters_.push(cluster); + } +}; + + +/** + * Creates the clusters. This is done in batches to avoid timeout errors + * in some browsers when there is a huge number of markers. + * + * @param {number} iFirst The index of the first marker in the batch of + * markers to be added to clusters. + */ +MarkerClusterer.prototype.createClusters_ = function (iFirst) { + var i, marker; + var mapBounds; + var cMarkerClusterer = this; + if (!this.ready_) { + return; + } + + // Cancel previous batch processing if we're working on the first batch: + if (iFirst === 0) { + /** + * This event is fired when the <code>MarkerClusterer</code> begins + * clustering markers. + * @name MarkerClusterer#clusteringbegin + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringbegin", this); + + if (typeof this.timerRefStatic !== "undefined") { + clearTimeout(this.timerRefStatic); + delete this.timerRefStatic; + } + } + + // Get our current map view bounds. + // Create a new bounds object so we don't affect the map. + // + // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug: + if (this.getMap().getZoom() > 3) { + mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(), + this.getMap().getBounds().getNorthEast()); + } else { + mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625)); + } + var bounds = this.getExtendedBounds(mapBounds); + + var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length); + + for (i = iFirst; i < iLast; i++) { + marker = this.markers_[i]; + if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) { + if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) { + this.addToClosestCluster_(marker); + } + } + } + + if (iLast < this.markers_.length) { + this.timerRefStatic = setTimeout(function () { + cMarkerClusterer.createClusters_(iLast); + }, 0); + } else { + delete this.timerRefStatic; + + /** + * This event is fired when the <code>MarkerClusterer</code> stops + * clustering markers. + * @name MarkerClusterer#clusteringend + * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered. + * @event + */ + google.maps.event.trigger(this, "clusteringend", this); + } +}; + + +/** + * Extends an object's prototype by another's. + * + * @param {Object} obj1 The object to be extended. + * @param {Object} obj2 The object to extend with. + * @return {Object} The new extended object. + * @ignore + */ +MarkerClusterer.prototype.extend = function (obj1, obj2) { + return (function (object) { + var property; + for (property in object.prototype) { + this.prototype[property] = object.prototype[property]; + } + return this; + }).apply(obj1, [obj2]); +}; + + +/** + * The default function for determining the label text and style + * for a cluster icon. + * + * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster. + * @param {number} numStyles The number of marker styles available. + * @return {ClusterIconInfo} The information resource for the cluster. + * @constant + * @ignore + */ +MarkerClusterer.CALCULATOR = function (markers, numStyles) { + var index = 0; + var title = ""; + var count = markers.length.toString(); + + var dv = count; + while (dv !== 0) { + dv = parseInt(dv / 10, 10); + index++; + } + + index = Math.min(index, numStyles); + return { + text: count, + index: index, + title: title + }; +}; + + +/** + * The number of markers to process in one batch. + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE = 2000; + + +/** + * The number of markers to process in one batch (IE only). + * + * @type {number} + * @constant + */ +MarkerClusterer.BATCH_SIZE_IE = 500; + + +/** + * The default root name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_PATH = "http://google-maps-utility-library-v3.googlecode.com/svn/trunk/markerclustererplus/images/m"; + + +/** + * The default extension name for the marker cluster images. + * + * @type {string} + * @constant + */ +MarkerClusterer.IMAGE_EXTENSION = "png"; + + +/** + * The default array of sizes for the marker cluster images. + * + * @type {Array.<number>} + * @constant + */ +MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90]; + +if (typeof String.prototype.trim !== 'function') { + /** + * IE hack since trim() doesn't exist in all browsers + * @return {string} The string with removed whitespace + */ + String.prototype.trim = function() { + return this.replace(/^\s+|\s+$/g, ''); + } +} + +;/** + * 1.1.9-patched + * @name MarkerWithLabel for V3 + * @version 1.1.8 [February 26, 2013] + * @author Gary Little (inspired by code from Marc Ridey of Google). + * @copyright Copyright 2012 Gary Little [gary at luxcentral.com] + * @fileoverview MarkerWithLabel extends the Google Maps JavaScript API V3 + * <code>google.maps.Marker</code> class. + * <p> + * MarkerWithLabel allows you to define markers with associated labels. As you would expect, + * if the marker is draggable, so too will be the label. In addition, a marker with a label + * responds to all mouse events in the same manner as a regular marker. It also fires mouse + * events and "property changed" events just as a regular marker would. Version 1.1 adds + * support for the raiseOnDrag feature introduced in API V3.3. + * <p> + * If you drag a marker by its label, you can cancel the drag and return the marker to its + * original position by pressing the <code>Esc</code> key. This doesn't work if you drag the marker + * itself because this feature is not (yet) supported in the <code>google.maps.Marker</code> class. + */ + +/*! + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*jslint browser:true */ +/*global document,google */ + +/** + * @param {Function} childCtor Child class. + * @param {Function} parentCtor Parent class. + */ +function inherits(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + /** @override */ + childCtor.prototype.constructor = childCtor; +} + +/** + * This constructor creates a label and associates it with a marker. + * It is for the private use of the MarkerWithLabel class. + * @constructor + * @param {Marker} marker The marker with which the label is to be associated. + * @param {string} crossURL The URL of the cross image =. + * @param {string} handCursor The URL of the hand cursor. + * @private + */ +function MarkerLabel_(marker, crossURL, handCursorURL) { + this.marker_ = marker; + this.handCursorURL_ = marker.handCursorURL; + + this.labelDiv_ = document.createElement("div"); + this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;"; + + // Set up the DIV for handling mouse events in the label. This DIV forms a transparent veil + // in the "overlayMouseTarget" pane, a veil that covers just the label. This is done so that + // events can be captured even if the label is in the shadow of a google.maps.InfoWindow. + // Code is included here to ensure the veil is always exactly the same size as the label. + this.eventDiv_ = document.createElement("div"); + this.eventDiv_.style.cssText = this.labelDiv_.style.cssText; + + // This is needed for proper behavior on MSIE: + this.eventDiv_.setAttribute("onselectstart", "return false;"); + this.eventDiv_.setAttribute("ondragstart", "return false;"); + + // Get the DIV for the "X" to be displayed when the marker is raised. + this.crossDiv_ = MarkerLabel_.getSharedCross(crossURL); +} +inherits(MarkerLabel_, google.maps.OverlayView); + +/** + * Returns the DIV for the cross used when dragging a marker when the + * raiseOnDrag parameter set to true. One cross is shared with all markers. + * @param {string} crossURL The URL of the cross image =. + * @private + */ +MarkerLabel_.getSharedCross = function (crossURL) { + var div; + if (typeof MarkerLabel_.getSharedCross.crossDiv === "undefined") { + div = document.createElement("img"); + div.style.cssText = "position: absolute; z-index: 1000002; display: none;"; + // Hopefully Google never changes the standard "X" attributes: + div.style.marginLeft = "-8px"; + div.style.marginTop = "-9px"; + div.src = crossURL; + MarkerLabel_.getSharedCross.crossDiv = div; + } + return MarkerLabel_.getSharedCross.crossDiv; +}; + +/** + * Adds the DIV representing the label to the DOM. This method is called + * automatically when the marker's <code>setMap</code> method is called. + * @private + */ +MarkerLabel_.prototype.onAdd = function () { + var me = this; + var cMouseIsDown = false; + var cDraggingLabel = false; + var cSavedZIndex; + var cLatOffset, cLngOffset; + var cIgnoreClick; + var cRaiseEnabled; + var cStartPosition; + var cStartCenter; + // Constants: + var cRaiseOffset = 20; + var cDraggingCursor = "url(" + this.handCursorURL_ + ")"; + + // Stops all processing of an event. + // + var cAbortEvent = function (e) { + if (e.preventDefault) { + e.preventDefault(); + } + e.cancelBubble = true; + if (e.stopPropagation) { + e.stopPropagation(); + } + }; + + var cStopBounce = function () { + me.marker_.setAnimation(null); + }; + + this.getPanes().overlayImage.appendChild(this.labelDiv_); + this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_); + // One cross is shared with all markers, so only add it once: + if (typeof MarkerLabel_.getSharedCross.processed === "undefined") { + this.getPanes().overlayImage.appendChild(this.crossDiv_); + MarkerLabel_.getSharedCross.processed = true; + } + + this.listeners_ = [ + google.maps.event.addDomListener(this.eventDiv_, "mouseover", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + this.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseover", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mouseout", function (e) { + if ((me.marker_.getDraggable() || me.marker_.getClickable()) && !cDraggingLabel) { + this.style.cursor = me.marker_.getCursor(); + google.maps.event.trigger(me.marker_, "mouseout", e); + } + }), + google.maps.event.addDomListener(this.eventDiv_, "mousedown", function (e) { + cDraggingLabel = false; + if (me.marker_.getDraggable()) { + cMouseIsDown = true; + this.style.cursor = cDraggingCursor; + } + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "mousedown", e); + cAbortEvent(e); // Prevent map pan when starting a drag on a label + } + }), + google.maps.event.addDomListener(document, "mouseup", function (mEvent) { + var position; + if (cMouseIsDown) { + cMouseIsDown = false; + me.eventDiv_.style.cursor = "pointer"; + google.maps.event.trigger(me.marker_, "mouseup", mEvent); + } + if (cDraggingLabel) { + if (cRaiseEnabled) { // Lower the marker & label + position = me.getProjection().fromLatLngToDivPixel(me.marker_.getPosition()); + position.y += cRaiseOffset; + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + // This is not the same bouncing style as when the marker portion is dragged, + // but it will have to do: + try { // Will fail if running Google Maps API earlier than V3.3 + me.marker_.setAnimation(google.maps.Animation.BOUNCE); + setTimeout(cStopBounce, 1406); + } catch (e) {} + } + me.crossDiv_.style.display = "none"; + me.marker_.setZIndex(cSavedZIndex); + cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag + cDraggingLabel = false; + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragend", mEvent); + } + }), + google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) { + var position; + if (cMouseIsDown) { + if (cDraggingLabel) { + // Change the reported location from the mouse position to the marker position: + mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset); + position = me.getProjection().fromLatLngToDivPixel(mEvent.latLng); + if (cRaiseEnabled) { + me.crossDiv_.style.left = position.x + "px"; + me.crossDiv_.style.top = position.y + "px"; + me.crossDiv_.style.display = ""; + position.y -= cRaiseOffset; + } + me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position)); + if (cRaiseEnabled) { // Don't raise the veil; this hack needed to make MSIE act properly + me.eventDiv_.style.top = (position.y + cRaiseOffset) + "px"; + } + google.maps.event.trigger(me.marker_, "drag", mEvent); + } else { + // Calculate offsets from the click point to the marker position: + cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat(); + cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng(); + cSavedZIndex = me.marker_.getZIndex(); + cStartPosition = me.marker_.getPosition(); + cStartCenter = me.marker_.getMap().getCenter(); + cRaiseEnabled = me.marker_.get("raiseOnDrag"); + cDraggingLabel = true; + me.marker_.setZIndex(1000000); // Moves the marker & label to the foreground during a drag + mEvent.latLng = me.marker_.getPosition(); + google.maps.event.trigger(me.marker_, "dragstart", mEvent); + } + } + }), + google.maps.event.addDomListener(document, "keydown", function (e) { + if (cDraggingLabel) { + if (e.keyCode === 27) { // Esc key + cRaiseEnabled = false; + me.marker_.setPosition(cStartPosition); + me.marker_.getMap().setCenter(cStartCenter); + google.maps.event.trigger(document, "mouseup", e); + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "click", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + if (cIgnoreClick) { // Ignore the click reported when a label drag ends + cIgnoreClick = false; + } else { + google.maps.event.trigger(me.marker_, "click", e); + cAbortEvent(e); // Prevent click from being passed on to map + } + } + }), + google.maps.event.addDomListener(this.eventDiv_, "dblclick", function (e) { + if (me.marker_.getDraggable() || me.marker_.getClickable()) { + google.maps.event.trigger(me.marker_, "dblclick", e); + cAbortEvent(e); // Prevent map zoom when double-clicking on a label + } + }), + google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) { + if (!cDraggingLabel) { + cRaiseEnabled = this.get("raiseOnDrag"); + } + }), + google.maps.event.addListener(this.marker_, "drag", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(cRaiseOffset); + // During a drag, the marker's z-index is temporarily set to 1000000 to + // ensure it appears above all other markers. Also set the label's z-index + // to 1000000 (plus or minus 1 depending on whether the label is supposed + // to be above or below the marker). + me.labelDiv_.style.zIndex = 1000000 + (this.get("labelInBackground") ? -1 : +1); + } + } + }), + google.maps.event.addListener(this.marker_, "dragend", function (mEvent) { + if (!cDraggingLabel) { + if (cRaiseEnabled) { + me.setPosition(0); // Also restores z-index of label + } + } + }), + google.maps.event.addListener(this.marker_, "position_changed", function () { + me.setPosition(); + }), + google.maps.event.addListener(this.marker_, "zindex_changed", function () { + me.setZIndex(); + }), + google.maps.event.addListener(this.marker_, "visible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "labelvisible_changed", function () { + me.setVisible(); + }), + google.maps.event.addListener(this.marker_, "title_changed", function () { + me.setTitle(); + }), + google.maps.event.addListener(this.marker_, "labelcontent_changed", function () { + me.setContent(); + }), + google.maps.event.addListener(this.marker_, "labelanchor_changed", function () { + me.setAnchor(); + }), + google.maps.event.addListener(this.marker_, "labelclass_changed", function () { + me.setStyles(); + }), + google.maps.event.addListener(this.marker_, "labelstyle_changed", function () { + me.setStyles(); + }) + ]; +}; + +/** + * Removes the DIV for the label from the DOM. It also removes all event handlers. + * This method is called automatically when the marker's <code>setMap(null)</code> + * method is called. + * @private + */ +MarkerLabel_.prototype.onRemove = function () { + var i; + if (this.labelDiv_.parentNode !== null) + this.labelDiv_.parentNode.removeChild(this.labelDiv_); + if (this.eventDiv_.parentNode !== null) + this.eventDiv_.parentNode.removeChild(this.eventDiv_); + + // Remove event listeners: + for (i = 0; i < this.listeners_.length; i++) { + google.maps.event.removeListener(this.listeners_[i]); + } +}; + +/** + * Draws the label on the map. + * @private + */ +MarkerLabel_.prototype.draw = function () { + this.setContent(); + this.setTitle(); + this.setStyles(); +}; + +/** + * Sets the content of the label. + * The content can be plain text or an HTML DOM node. + * @private + */ +MarkerLabel_.prototype.setContent = function () { + var content = this.marker_.get("labelContent"); + if (typeof content.nodeType === "undefined") { + this.labelDiv_.innerHTML = content; + this.eventDiv_.innerHTML = this.labelDiv_.innerHTML; + } else { + this.labelDiv_.innerHTML = ""; // Remove current content + this.labelDiv_.appendChild(content); + content = content.cloneNode(true); + this.eventDiv_.appendChild(content); + } +}; + +/** + * Sets the content of the tool tip for the label. It is + * always set to be the same as for the marker itself. + * @private + */ +MarkerLabel_.prototype.setTitle = function () { + this.eventDiv_.title = this.marker_.getTitle() || ""; +}; + +/** + * Sets the style of the label by setting the style sheet and applying + * other specific styles requested. + * @private + */ +MarkerLabel_.prototype.setStyles = function () { + var i, labelStyle; + + // Apply style values from the style sheet defined in the labelClass parameter: + this.labelDiv_.className = this.marker_.get("labelClass"); + this.eventDiv_.className = this.labelDiv_.className; + + // Clear existing inline style values: + this.labelDiv_.style.cssText = ""; + this.eventDiv_.style.cssText = ""; + // Apply style values defined in the labelStyle parameter: + labelStyle = this.marker_.get("labelStyle"); + for (i in labelStyle) { + if (labelStyle.hasOwnProperty(i)) { + this.labelDiv_.style[i] = labelStyle[i]; + this.eventDiv_.style[i] = labelStyle[i]; + } + } + this.setMandatoryStyles(); +}; + +/** + * Sets the mandatory styles to the DIV representing the label as well as to the + * associated event DIV. This includes setting the DIV position, z-index, and visibility. + * @private + */ +MarkerLabel_.prototype.setMandatoryStyles = function () { + this.labelDiv_.style.position = "absolute"; + this.labelDiv_.style.overflow = "hidden"; + // Make sure the opacity setting causes the desired effect on MSIE: + if (typeof this.labelDiv_.style.opacity !== "undefined" && this.labelDiv_.style.opacity !== "") { + this.labelDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")\""; + this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")"; + } + + this.eventDiv_.style.position = this.labelDiv_.style.position; + this.eventDiv_.style.overflow = this.labelDiv_.style.overflow; + this.eventDiv_.style.opacity = 0.01; // Don't use 0; DIV won't be clickable on MSIE + this.eventDiv_.style.MsFilter = "\"progid:DXImageTransform.Microsoft.Alpha(opacity=1)\""; + this.eventDiv_.style.filter = "alpha(opacity=1)"; // For MSIE + + this.setAnchor(); + this.setPosition(); // This also updates z-index, if necessary. + this.setVisible(); +}; + +/** + * Sets the anchor point of the label. + * @private + */ +MarkerLabel_.prototype.setAnchor = function () { + var anchor = this.marker_.get("labelAnchor"); + this.labelDiv_.style.marginLeft = -anchor.x + "px"; + this.labelDiv_.style.marginTop = -anchor.y + "px"; + this.eventDiv_.style.marginLeft = -anchor.x + "px"; + this.eventDiv_.style.marginTop = -anchor.y + "px"; +}; + +/** + * Sets the position of the label. The z-index is also updated, if necessary. + * @private + */ +MarkerLabel_.prototype.setPosition = function (yOffset) { + var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition()); + if (typeof yOffset === "undefined") { + yOffset = 0; + } + this.labelDiv_.style.left = Math.round(position.x) + "px"; + this.labelDiv_.style.top = Math.round(position.y - yOffset) + "px"; + this.eventDiv_.style.left = this.labelDiv_.style.left; + this.eventDiv_.style.top = this.labelDiv_.style.top; + + this.setZIndex(); +}; + +/** + * Sets the z-index of the label. If the marker's z-index property has not been defined, the z-index + * of the label is set to the vertical coordinate of the label. This is in keeping with the default + * stacking order for Google Maps: markers to the south are in front of markers to the north. + * @private + */ +MarkerLabel_.prototype.setZIndex = function () { + var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1); + if (typeof this.marker_.getZIndex() === "undefined") { + this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } else { + this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust; + this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex; + } +}; + +/** + * Sets the visibility of the label. The label is visible only if the marker itself is + * visible (i.e., its visible property is true) and the labelVisible property is true. + * @private + */ +MarkerLabel_.prototype.setVisible = function () { + if (this.marker_.get("labelVisible")) { + this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none"; + } else { + this.labelDiv_.style.display = "none"; + } + this.eventDiv_.style.display = this.labelDiv_.style.display; +}; + +/** + * @name MarkerWithLabelOptions + * @class This class represents the optional parameter passed to the {@link MarkerWithLabel} constructor. + * The properties available are the same as for <code>google.maps.Marker</code> with the addition + * of the properties listed below. To change any of these additional properties after the labeled + * marker has been created, call <code>google.maps.Marker.set(propertyName, propertyValue)</code>. + * <p> + * When any of these properties changes, a property changed event is fired. The names of these + * events are derived from the name of the property and are of the form <code>propertyname_changed</code>. + * For example, if the content of the label changes, a <code>labelcontent_changed</code> event + * is fired. + * <p> + * @property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node). + * @property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so + * that its top left corner is positioned at the anchor point of the associated marker. Use this + * property to change the anchor point of the label. For example, to center a 50px-wide label + * beneath a marker, specify a <code>labelAnchor</code> of <code>google.maps.Point(25, 0)</code>. + * (Note: x-values increase to the right and y-values increase to the top.) + * @property {string} [labelClass] The name of the CSS class defining the styles for the label. + * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>, + * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and + * <code>marginTop</code> are ignored; these styles are for internal use only. + * @property {Object} [labelStyle] An object literal whose properties define specific CSS + * style values to be applied to the label. Style values defined here override those that may + * be defined in the <code>labelClass</code> style sheet. If this property is changed after the + * label has been created, all previously set styles (except those defined in the style sheet) + * are removed from the label before the new style values are applied. + * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>, + * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and + * <code>marginTop</code> are ignored; these styles are for internal use only. + * @property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its + * associated marker should appear in the background (i.e., in a plane below the marker). + * The default is <code>false</code>, which causes the label to appear in the foreground. + * @property {boolean} [labelVisible] A flag indicating whether the label is to be visible. + * The default is <code>true</code>. Note that even if <code>labelVisible</code> is + * <code>true</code>, the label will <i>not</i> be visible unless the associated marker is also + * visible (i.e., unless the marker's <code>visible</code> property is <code>true</code>). + * @property {boolean} [raiseOnDrag] A flag indicating whether the label and marker are to be + * raised when the marker is dragged. The default is <code>true</code>. If a draggable marker is + * being created and a version of Google Maps API earlier than V3.3 is being used, this property + * must be set to <code>false</code>. + * @property {boolean} [optimized] A flag indicating whether rendering is to be optimized for the + * marker. <b>Important: The optimized rendering technique is not supported by MarkerWithLabel, + * so the value of this parameter is always forced to <code>false</code>. + * @property {string} [crossImage="http://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"] + * The URL of the cross image to be displayed while dragging a marker. + * @property {string} [handCursor="http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"] + * The URL of the cursor to be displayed while dragging a marker. + */ +/** + * Creates a MarkerWithLabel with the options specified in {@link MarkerWithLabelOptions}. + * @constructor + * @param {MarkerWithLabelOptions} [opt_options] The optional parameters. + */ +function MarkerWithLabel(opt_options) { + opt_options = opt_options || {}; + opt_options.labelContent = opt_options.labelContent || ""; + opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0); + opt_options.labelClass = opt_options.labelClass || "markerLabels"; + opt_options.labelStyle = opt_options.labelStyle || {}; + opt_options.labelInBackground = opt_options.labelInBackground || false; + if (typeof opt_options.labelVisible === "undefined") { + opt_options.labelVisible = true; + } + if (typeof opt_options.raiseOnDrag === "undefined") { + opt_options.raiseOnDrag = true; + } + if (typeof opt_options.clickable === "undefined") { + opt_options.clickable = true; + } + if (typeof opt_options.draggable === "undefined") { + opt_options.draggable = false; + } + if (typeof opt_options.optimized === "undefined") { + opt_options.optimized = false; + } + opt_options.crossImage = opt_options.crossImage || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"; + opt_options.handCursor = opt_options.handCursor || "http" + (document.location.protocol === "https:" ? "s" : "") + "://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"; + opt_options.optimized = false; // Optimized rendering is not supported + + this.label = new MarkerLabel_(this, opt_options.crossImage, opt_options.handCursor); // Bind the label to the marker + + // Call the parent constructor. It calls Marker.setValues to initialize, so all + // the new parameters are conveniently saved and can be accessed with get/set. + // Marker.set triggers a property changed event (called "propertyname_changed") + // that the marker label listens for in order to react to state changes. + google.maps.Marker.apply(this, arguments); +} +inherits(MarkerWithLabel, google.maps.Marker); + +/** + * Overrides the standard Marker setMap function. + * @param {Map} theMap The map to which the marker is to be added. + * @private + */ +MarkerWithLabel.prototype.setMap = function (theMap) { + + // Call the inherited function... + google.maps.Marker.prototype.setMap.apply(this, arguments); + + // ... then deal with the label: + this.label.setMap(theMap); +}; +\ No newline at end of file