} The cluster center.\r\n */\r\nCluster.prototype.getMarkers = function() {\r\n return this.markers_;\r\n};\r\n\r\n/**\r\n * Returns the center of the cluster.\r\n *\r\n * @return {google.maps.LatLng} The cluster center.\r\n */\r\nCluster.prototype.getCenter = function() {\r\n return this.center_;\r\n};\r\n\r\n/**\r\n * Calculated the extended bounds of the cluster with the grid.\r\n *\r\n * @private\r\n */\r\nCluster.prototype.calculateBounds_ = function() {\r\n var bounds = new google.maps.LatLngBounds(this.center_, this.center_);\r\n this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);\r\n};\r\n\r\n/**\r\n * Determines if a marker lies in the clusters bounds.\r\n *\r\n * @param {google.maps.Marker} marker The marker to check.\r\n * @return {boolean} True if the marker lies in the bounds.\r\n */\r\nCluster.prototype.isMarkerInClusterBounds = function(marker) {\r\n return this.bounds_.contains(marker.getPosition());\r\n};\r\n\r\n/**\r\n * Returns the map that the cluster is associated with.\r\n *\r\n * @return {google.maps.Map} The map.\r\n */\r\nCluster.prototype.getMap = function() {\r\n return this.map_;\r\n};\r\n\r\n/**\r\n * Updates the cluster icon\r\n */\r\nCluster.prototype.updateIcon = function() {\r\n var zoom = this.map_.getZoom();\r\n var mz = this.markerClusterer_.getMaxZoom();\r\n\r\n if (mz && zoom > mz) {\r\n // The zoom is greater than our max zoom so show all the markers in cluster.\r\n for (var i = 0, marker; (marker = this.markers_[i]); i++) {\r\n marker.setMap(this.map_);\r\n }\r\n return;\r\n }\r\n\r\n if (this.markers_.length < this.minClusterSize_) {\r\n // Min cluster size not yet reached.\r\n this.clusterIcon_.hide();\r\n return;\r\n }\r\n\r\n var numStyles = this.markerClusterer_.getStyles().length;\r\n var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);\r\n this.clusterIcon_.setCenter(this.center_);\r\n this.clusterIcon_.setSums(sums);\r\n this.clusterIcon_.show();\r\n};\r\n\r\n/**\r\n * A cluster icon\r\n *\r\n * @param {Cluster} cluster The cluster to be associated with.\r\n * @param {Object} styles An object that has style properties:\r\n * 'url': (string) The image url.\r\n * 'height': (number) The image height.\r\n * 'width': (number) The image width.\r\n * 'anchor': (Array) The anchor position of the label text.\r\n * 'textColor': (string) The text color.\r\n * 'textSize': (number) The text size.\r\n * 'backgroundPosition: (string) The background postition x, y.\r\n * @param {number=} opt_padding Optional padding to apply to the cluster icon.\r\n * @constructor\r\n * @extends google.maps.OverlayView\r\n * @ignore\r\n */\r\nfunction ClusterIcon(cluster, styles, opt_padding) {\r\n cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);\r\n\r\n this.styles_ = styles;\r\n this.padding_ = opt_padding || 0;\r\n this.cluster_ = cluster;\r\n this.center_ = null;\r\n this.map_ = cluster.getMap();\r\n this.div_ = null;\r\n this.sums_ = null;\r\n this.visible_ = false;\r\n\r\n this.setMap(this.map_);\r\n}\r\n\r\n/**\r\n * Triggers the clusterclick event and zoom's if the option is set.\r\n *\r\n * @param {google.maps.MouseEvent} event The event to propagate\r\n */\r\nClusterIcon.prototype.triggerClusterClick = function(event) {\r\n var markerClusterer = this.cluster_.getMarkerClusterer();\r\n\r\n // Trigger the clusterclick event.\r\n google.maps.event.trigger(\r\n markerClusterer,\r\n \"clusterclick\",\r\n this.cluster_,\r\n event\r\n );\r\n\r\n if (markerClusterer.isZoomOnClick()) {\r\n // Zoom into the cluster.\r\n this.map_.fitBounds(this.cluster_.getBounds());\r\n }\r\n};\r\n\r\n/**\r\n * Adding the cluster icon to the dom.\r\n * @ignore\r\n */\r\nClusterIcon.prototype.onAdd = function() {\r\n this.div_ = document.createElement(\"DIV\");\r\n if (this.visible_) {\r\n var pos = this.getPosFromLatLng_(this.center_);\r\n this.div_.style.cssText = this.createCss(pos);\r\n this.div_.innerHTML = this.sums_.text;\r\n }\r\n\r\n var panes = this.getPanes();\r\n panes.overlayMouseTarget.appendChild(this.div_);\r\n\r\n var that = this;\r\n var isDragging = false;\r\n google.maps.event.addDomListener(this.div_, \"click\", function(event) {\r\n // Only perform click when not preceded by a drag\r\n if (!isDragging) {\r\n that.triggerClusterClick(event);\r\n }\r\n });\r\n google.maps.event.addDomListener(this.div_, \"mousedown\", function() {\r\n isDragging = false;\r\n });\r\n google.maps.event.addDomListener(this.div_, \"mousemove\", function() {\r\n isDragging = true;\r\n });\r\n};\r\n\r\n/**\r\n * Returns the position to place the div dending on the latlng.\r\n *\r\n * @param {google.maps.LatLng} latlng The position in latlng.\r\n * @return {google.maps.Point} The position in pixels.\r\n * @private\r\n */\r\nClusterIcon.prototype.getPosFromLatLng_ = function(latlng) {\r\n var pos = this.getProjection().fromLatLngToDivPixel(latlng);\r\n\r\n if (typeof this.iconAnchor_ === \"object\" && this.iconAnchor_.length === 2) {\r\n pos.x -= this.iconAnchor_[0];\r\n pos.y -= this.iconAnchor_[1];\r\n }\r\n else {\r\n pos.x -= parseInt(this.width_ / 2, 10);\r\n pos.y -= parseInt(this.height_ / 2, 10);\r\n }\r\n return pos;\r\n};\r\n\r\n/**\r\n * Draw the icon.\r\n * @ignore\r\n */\r\nClusterIcon.prototype.draw = function() {\r\n if (this.visible_) {\r\n var pos = this.getPosFromLatLng_(this.center_);\r\n this.div_.style.top = pos.y + \"px\";\r\n this.div_.style.left = pos.x + \"px\";\r\n }\r\n};\r\n\r\n/**\r\n * Hide the icon.\r\n */\r\nClusterIcon.prototype.hide = function() {\r\n if (this.div_) {\r\n this.div_.style.display = \"none\";\r\n }\r\n this.visible_ = false;\r\n};\r\n\r\n/**\r\n * Position and show the icon.\r\n */\r\nClusterIcon.prototype.show = function() {\r\n if (this.div_) {\r\n var pos = this.getPosFromLatLng_(this.center_);\r\n this.div_.style.cssText = this.createCss(pos);\r\n this.div_.style.display = \"\";\r\n }\r\n this.visible_ = true;\r\n};\r\n\r\n/**\r\n * Remove the icon from the map\r\n */\r\nClusterIcon.prototype.remove = function() {\r\n this.setMap(null);\r\n};\r\n\r\n/**\r\n * Implementation of the onRemove interface.\r\n * @ignore\r\n */\r\nClusterIcon.prototype.onRemove = function() {\r\n if (this.div_ && this.div_.parentNode) {\r\n this.hide();\r\n this.div_.parentNode.removeChild(this.div_);\r\n this.div_ = null;\r\n }\r\n};\r\n\r\n/**\r\n * Set the sums of the icon.\r\n *\r\n * @param {Object} sums The sums containing:\r\n * 'text': (string) The text to display in the icon.\r\n * 'index': (number) The style index of the icon.\r\n */\r\nClusterIcon.prototype.setSums = function(sums) {\r\n this.sums_ = sums;\r\n this.text_ = sums.text;\r\n this.index_ = sums.index;\r\n if (this.div_) {\r\n this.div_.innerHTML = sums.text;\r\n }\r\n\r\n this.useStyle();\r\n};\r\n\r\n/**\r\n * Sets the icon to the the styles.\r\n */\r\nClusterIcon.prototype.useStyle = function() {\r\n var index = Math.max(0, this.sums_.index - 1);\r\n index = Math.min(this.styles_.length - 1, index);\r\n var style = this.styles_[index];\r\n this.url_ = style[\"url\"];\r\n this.height_ = style[\"height\"];\r\n this.width_ = style[\"width\"];\r\n this.textColor_ = style[\"textColor\"];\r\n this.anchor_ = style[\"anchor\"];\r\n this.fontSize_ = style[\"fontSize\"];\r\n this.fontFamily_ = style[\"fontFamily\"];\r\n this.fontWeight_ = style[\"fontWeight\"];\r\n this.lineHeight_ = style[\"lineHeight\"];\r\n this.backgroundPosition_ = style[\"backgroundPosition\"];\r\n this.backgroundColor_ = style[\"backgroundColor\"];\r\n this.border_ = style[\"border\"];\r\n this.borderRadius_ = style[\"borderRadius\"];\r\n this.iconAnchor_ = style[\"iconAnchor\"];\r\n};\r\n\r\n/**\r\n * Sets the center of the icon.\r\n *\r\n * @param {google.maps.LatLng} center The latlng to set as the center.\r\n */\r\nClusterIcon.prototype.setCenter = function(center) {\r\n this.center_ = center;\r\n};\r\n\r\n/**\r\n * Create the css text based on the position of the icon.\r\n *\r\n * @param {google.maps.Point} pos The position.\r\n * @return {string} The css style text.\r\n */\r\nClusterIcon.prototype.createCss = function(pos) {\r\n var style = [];\r\n style.push(\"background-image:url(\" + this.url_ + \");\");\r\n var backgroundPosition = this.backgroundPosition_\r\n ? this.backgroundPosition_\r\n : \"0 0\";\r\n style.push(\"background-position:\" + backgroundPosition + \";\");\r\n\r\n if (typeof this.anchor_ === \"object\") {\r\n if (\r\n typeof this.anchor_[0] === \"number\" &&\r\n this.anchor_[0] > 0 &&\r\n this.anchor_[0] < this.height_\r\n ) {\r\n style.push(\r\n \"height:\" +\r\n (this.height_ - this.anchor_[0]) +\r\n \"px; padding-top:\" +\r\n this.anchor_[0] +\r\n \"px;\"\r\n );\r\n }\r\n else if (\r\n typeof this.anchor_[0] === \"number\" &&\r\n this.anchor_[0] < 0 &&\r\n -this.anchor_[0] < this.height_\r\n ) {\r\n style.push(\r\n \"height:\" +\r\n this.height_ +\r\n \"px; line-height:\" +\r\n (this.height_ + this.anchor_[0]) +\r\n \"px;\"\r\n );\r\n }\r\n else {\r\n style.push(\r\n \"height:\" + this.height_ + \"px; line-height:\" + this.height_ + \"px;\"\r\n );\r\n }\r\n if (\r\n typeof this.anchor_[1] === \"number\" &&\r\n this.anchor_[1] > 0 &&\r\n this.anchor_[1] < this.width_\r\n ) {\r\n style.push(\r\n \"width:\" +\r\n (this.width_ - this.anchor_[1]) +\r\n \"px; padding-left:\" +\r\n this.anchor_[1] +\r\n \"px;\"\r\n );\r\n }\r\n else {\r\n style.push(\"width:\" + this.width_ + \"px; text-align:center;\");\r\n }\r\n }\r\n else {\r\n style.push(\r\n \"height:\" +\r\n this.height_ +\r\n \"px; line-height:\" +\r\n this.height_ +\r\n \"px; width:\" +\r\n this.width_ +\r\n \"px; text-align:center;\"\r\n );\r\n }\r\n\r\n var txtColor = this.textColor_ ? this.textColor_ : \"black\";\r\n var fontSize = this.fontSize_ ? this.fontSize_ : 11;\r\n var fontFamily = this.fontFamily_\r\n ? this.fontFamily_\r\n : \"Helvetica, Arial, sans-serif\";\r\n var fontWeight = this.fontWeight_ ? this.fontWeight_ : \"500\";\r\n var lineHeight = this.lineHeight_ ? this.lineHeight_ : \"auto\";\r\n var backgroundColor = this.backgroundColor_\r\n ? this.backgroundColor_\r\n : \"transparent\";\r\n var border = this.border_ ? this.border_ : \"none\";\r\n var borderRadius = this.borderRadius_ ? this.borderRadius_ : \"0\";\r\n style.push(\r\n \"cursor:pointer; top:\" +\r\n pos.y +\r\n \"px; left:\" +\r\n pos.x +\r\n \"px; color:\" +\r\n txtColor +\r\n \"; position:absolute; font-size:\" +\r\n fontSize +\r\n \"; line-height:\" +\r\n lineHeight +\r\n \"; font-family:\" +\r\n fontFamily +\r\n \"; font-weight: \" +\r\n fontWeight +\r\n \"; background-color: \" +\r\n backgroundColor +\r\n \"; border: \" +\r\n border +\r\n \"; border-radius: \" +\r\n borderRadius +\r\n \";\"\r\n );\r\n return style.join(\"\");\r\n};\r\n\r\n// Export Symbols for Closure\r\n// If you are not going to compile with closure then you can remove the\r\n// code below.\r\nwindow[\"MarkerClusterer\"] = MarkerClusterer;\r\nMarkerClusterer.prototype[\"addMarker\"] = MarkerClusterer.prototype.addMarker;\r\nMarkerClusterer.prototype[\"addMarkers\"] = MarkerClusterer.prototype.addMarkers;\r\nMarkerClusterer.prototype[\"clearMarkers\"] =\r\n MarkerClusterer.prototype.clearMarkers;\r\nMarkerClusterer.prototype[\"fitMapToMarkers\"] =\r\n MarkerClusterer.prototype.fitMapToMarkers;\r\nMarkerClusterer.prototype[\"getCalculator\"] =\r\n MarkerClusterer.prototype.getCalculator;\r\nMarkerClusterer.prototype[\"getGridSize\"] =\r\n MarkerClusterer.prototype.getGridSize;\r\nMarkerClusterer.prototype[\"getExtendedBounds\"] =\r\n MarkerClusterer.prototype.getExtendedBounds;\r\nMarkerClusterer.prototype[\"getMap\"] = MarkerClusterer.prototype.getMap;\r\nMarkerClusterer.prototype[\"getMarkers\"] = MarkerClusterer.prototype.getMarkers;\r\nMarkerClusterer.prototype[\"getMaxZoom\"] = MarkerClusterer.prototype.getMaxZoom;\r\nMarkerClusterer.prototype[\"getStyles\"] = MarkerClusterer.prototype.getStyles;\r\nMarkerClusterer.prototype[\"getTotalClusters\"] =\r\n MarkerClusterer.prototype.getTotalClusters;\r\nMarkerClusterer.prototype[\"getTotalMarkers\"] =\r\n MarkerClusterer.prototype.getTotalMarkers;\r\nMarkerClusterer.prototype[\"redraw\"] = MarkerClusterer.prototype.redraw;\r\nMarkerClusterer.prototype[\"removeMarker\"] =\r\n MarkerClusterer.prototype.removeMarker;\r\nMarkerClusterer.prototype[\"removeMarkers\"] =\r\n MarkerClusterer.prototype.removeMarkers;\r\nMarkerClusterer.prototype[\"resetViewport\"] =\r\n MarkerClusterer.prototype.resetViewport;\r\nMarkerClusterer.prototype[\"repaint\"] = MarkerClusterer.prototype.repaint;\r\nMarkerClusterer.prototype[\"setCalculator\"] =\r\n MarkerClusterer.prototype.setCalculator;\r\nMarkerClusterer.prototype[\"setGridSize\"] =\r\n MarkerClusterer.prototype.setGridSize;\r\nMarkerClusterer.prototype[\"setMaxZoom\"] = MarkerClusterer.prototype.setMaxZoom;\r\nMarkerClusterer.prototype[\"onAdd\"] = MarkerClusterer.prototype.onAdd;\r\nMarkerClusterer.prototype[\"draw\"] = MarkerClusterer.prototype.draw;\r\n\r\nCluster.prototype[\"getCenter\"] = Cluster.prototype.getCenter;\r\nCluster.prototype[\"getSize\"] = Cluster.prototype.getSize;\r\nCluster.prototype[\"getMarkers\"] = Cluster.prototype.getMarkers;\r\n\r\nClusterIcon.prototype[\"onAdd\"] = ClusterIcon.prototype.onAdd;\r\nClusterIcon.prototype[\"draw\"] = ClusterIcon.prototype.draw;\r\nClusterIcon.prototype[\"onRemove\"] = ClusterIcon.prototype.onRemove;\r\n","(function() {\r\n angular.module(\"app\", [\"ngMap\", \"ngSanitize\"]);\r\n})();\r\n","(function() {\r\n /*\r\n OverlappingMarkerSpiderfier\r\nhttps://github.com/jawj/OverlappingMarkerSpiderfier\r\nCopyright (c) 2011 - 2012 George MacKerron\r\nReleased under the MIT licence: http://opensource.org/licenses/mit-license\r\nNote: The Google Maps API v3 must be included *before* this code\r\n*/\r\n var h = !0,\r\n u = null,\r\n v = !1;\r\n (function() {\r\n var A,\r\n B = {}.hasOwnProperty,\r\n C = [].slice;\r\n if (((A = this.google) != u ? A.maps : void 0) != u)\r\n this.OverlappingMarkerSpiderfier = (function() {\r\n function w(b, d) {\r\n var a,\r\n g,\r\n f,\r\n e,\r\n c = this;\r\n this.map = b;\r\n d == u && (d = {});\r\n for (a in d) B.call(d, a) && ((g = d[a]), (this[a] = g));\r\n this.e = new this.constructor.g(this.map);\r\n this.n();\r\n this.b = {};\r\n e = [\"click\", \"zoom_changed\", \"maptypeid_changed\"];\r\n g = 0;\r\n for (f = e.length; g < f; g++)\r\n (a = e[g]),\r\n p.addListener(this.map, a, function() {\r\n return c.unspiderfy();\r\n });\r\n }\r\n var p, s, t, q, k, c, y, z;\r\n c = w.prototype;\r\n z = [w, c];\r\n q = 0;\r\n for (k = z.length; q < k; q++) (t = z[q]), (t.VERSION = \"0.3.3\");\r\n s = google.maps;\r\n p = s.event;\r\n k = s.MapTypeId;\r\n y = 2 * Math.PI;\r\n c.keepSpiderfied = v;\r\n c.markersWontHide = v;\r\n c.markersWontMove = v;\r\n c.nearbyDistance = 20;\r\n c.circleSpiralSwitchover = 9;\r\n c.circleFootSeparation = 23;\r\n c.circleStartAngle = y / 12;\r\n c.spiralFootSeparation = 26;\r\n c.spiralLengthStart = 11;\r\n c.spiralLengthFactor = 4;\r\n c.spiderfiedZIndex = 1e3;\r\n c.usualLegZIndex = 10;\r\n c.highlightedLegZIndex = 20;\r\n c.legWeight = 1.5;\r\n c.legColors = { usual: {}, highlighted: {} };\r\n q = c.legColors.usual;\r\n t = c.legColors.highlighted;\r\n q[k.HYBRID] = q[k.SATELLITE] = \"#fff\";\r\n t[k.HYBRID] = t[k.SATELLITE] = \"#f00\";\r\n q[k.TERRAIN] = q[k.ROADMAP] = \"#444\";\r\n t[k.TERRAIN] = t[k.ROADMAP] = \"#f00\";\r\n c.n = function() {\r\n this.a = [];\r\n this.j = [];\r\n };\r\n c.addMarker = function(b) {\r\n var d,\r\n a = this;\r\n if (b._oms != u) return this;\r\n b._oms = h;\r\n d = [\r\n p.addListener(b, \"click\", function(d) {\r\n return a.F(b, d);\r\n })\r\n ];\r\n this.markersWontHide ||\r\n d.push(\r\n p.addListener(b, \"visible_changed\", function() {\r\n return a.o(b, v);\r\n })\r\n );\r\n this.markersWontMove ||\r\n d.push(\r\n p.addListener(b, \"position_changed\", function() {\r\n return a.o(b, h);\r\n })\r\n );\r\n this.j.push(d);\r\n this.a.push(b);\r\n return this;\r\n };\r\n c.o = function(b, d) {\r\n if (\r\n b._omsData != u &&\r\n (d || !b.getVisible()) &&\r\n !(this.s != u || this.t != u)\r\n )\r\n return this.unspiderfy(d ? b : u);\r\n };\r\n c.getMarkers = function() {\r\n return this.a.slice(0);\r\n };\r\n c.removeMarker = function(b) {\r\n var d, a, g, f, e;\r\n b._omsData != u && this.unspiderfy();\r\n d = this.m(this.a, b);\r\n if (d < 0) return this;\r\n g = this.j.splice(d, 1)[0];\r\n f = 0;\r\n for (e = g.length; f < e; f++) (a = g[f]), p.removeListener(a);\r\n delete b._oms;\r\n this.a.splice(d, 1);\r\n return this;\r\n };\r\n c.clearMarkers = function() {\r\n var b, d, a, g, f, e, c, m;\r\n this.unspiderfy();\r\n m = this.a;\r\n b = g = 0;\r\n for (e = m.length; g < e; b = ++g) {\r\n a = m[b];\r\n d = this.j[b];\r\n f = 0;\r\n for (c = d.length; f < c; f++) (b = d[f]), p.removeListener(b);\r\n delete a._oms;\r\n }\r\n this.n();\r\n return this;\r\n };\r\n c.addListener = function(b, d) {\r\n var a, g;\r\n ((g = (a = this.b)[b]) != u ? g : (a[b] = [])).push(d);\r\n return this;\r\n };\r\n c.removeListener = function(b, d) {\r\n var a;\r\n a = this.m(this.b[b], d);\r\n a < 0 || this.b[b].splice(a, 1);\r\n return this;\r\n };\r\n c.clearListeners = function(b) {\r\n this.b[b] = [];\r\n return this;\r\n };\r\n c.trigger = function() {\r\n var b, d, a, g, f, e;\r\n d = arguments[0];\r\n b = arguments.length >= 2 ? C.call(arguments, 1) : [];\r\n d = (a = this.b[d]) != u ? a : [];\r\n e = [];\r\n g = 0;\r\n for (f = d.length; g < f; g++) (a = d[g]), e.push(a.apply(u, b));\r\n return e;\r\n };\r\n c.u = function(b, d) {\r\n var a, g, f, e, c;\r\n f = (this.circleFootSeparation * (2 + b)) / y;\r\n g = y / b;\r\n c = [];\r\n for (a = e = 0; b >= 0 ? e < b : e > b; a = b >= 0 ? ++e : --e)\r\n (a = this.circleStartAngle + a * g),\r\n c.push(new s.Point(d.x + f * Math.cos(a), d.y + f * Math.sin(a)));\r\n return c;\r\n };\r\n c.v = function(b, d) {\r\n var a, g, f, e, c;\r\n f = this.spiralLengthStart;\r\n a = 0;\r\n c = [];\r\n for (g = e = 0; b >= 0 ? e < b : e > b; g = b >= 0 ? ++e : --e)\r\n (a += this.spiralFootSeparation / f + 5e-4 * g),\r\n (g = new s.Point(d.x + f * Math.cos(a), d.y + f * Math.sin(a))),\r\n (f += (y * this.spiralLengthFactor) / a),\r\n c.push(g);\r\n return c;\r\n };\r\n c.F = function(b, d) {\r\n var a, g, f, e, c, m, l, x, n;\r\n e = b._omsData != u;\r\n (!e || !this.keepSpiderfied) && this.unspiderfy();\r\n if (\r\n e ||\r\n this.map.getStreetView().getVisible() ||\r\n this.map.getMapTypeId() === \"GoogleEarthAPI\"\r\n )\r\n return this.trigger(\"click\", b, d);\r\n e = [];\r\n c = [];\r\n a = this.nearbyDistance;\r\n m = a * a;\r\n f = this.c(b.position);\r\n n = this.a;\r\n l = 0;\r\n for (x = n.length; l < x; l++)\r\n (a = n[l]),\r\n a.map != u &&\r\n a.getVisible() &&\r\n ((g = this.c(a.position)),\r\n this.f(g, f) < m ? e.push({ A: a, p: g }) : c.push(a));\r\n return e.length === 1 ? this.trigger(\"click\", b, d) : this.G(e, c);\r\n };\r\n c.markersNearMarker = function(b, d) {\r\n var a, g, f, e, c, m, l, x, n, p;\r\n d == u && (d = v);\r\n if (this.e.getProjection() == u)\r\n throw \"Must wait for 'idle' event on map before calling markersNearMarker\";\r\n a = this.nearbyDistance;\r\n c = a * a;\r\n f = this.c(b.position);\r\n e = [];\r\n x = this.a;\r\n m = 0;\r\n for (l = x.length; m < l; m++)\r\n if (((a = x[m]), !(a === b || a.map == u || !a.getVisible())))\r\n if (\r\n ((g = this.c(\r\n (n = (p = a._omsData) != u ? p.l : void 0) != u\r\n ? n\r\n : a.position\r\n )),\r\n this.f(g, f) < c && (e.push(a), d))\r\n )\r\n break;\r\n return e;\r\n };\r\n c.markersNearAnyOtherMarker = function() {\r\n var b, d, a, g, c, e, r, m, l, p, n, k;\r\n if (this.e.getProjection() == u)\r\n throw \"Must wait for 'idle' event on map before calling markersNearAnyOtherMarker\";\r\n e = this.nearbyDistance;\r\n b = e * e;\r\n g = this.a;\r\n e = [];\r\n n = 0;\r\n for (a = g.length; n < a; n++)\r\n (d = g[n]),\r\n e.push({\r\n q: this.c(\r\n (r = (l = d._omsData) != u ? l.l : void 0) != u\r\n ? r\r\n : d.position\r\n ),\r\n d: v\r\n });\r\n n = this.a;\r\n d = r = 0;\r\n for (l = n.length; r < l; d = ++r)\r\n if (\r\n ((a = n[d]), a.map != u && a.getVisible() && ((g = e[d]), !g.d))\r\n ) {\r\n k = this.a;\r\n a = m = 0;\r\n for (p = k.length; m < p; a = ++m)\r\n if (\r\n ((c = k[a]),\r\n a !== d &&\r\n (c.map != u && c.getVisible()) &&\r\n ((c = e[a]), (!(a < d) || c.d) && this.f(g.q, c.q) < b))\r\n ) {\r\n g.d = c.d = h;\r\n break;\r\n }\r\n }\r\n n = this.a;\r\n a = [];\r\n b = r = 0;\r\n for (l = n.length; r < l; b = ++r) (d = n[b]), e[b].d && a.push(d);\r\n return a;\r\n };\r\n c.z = function(b) {\r\n var d = this;\r\n return {\r\n h: function() {\r\n return b._omsData.i.setOptions({\r\n strokeColor: d.legColors.highlighted[d.map.mapTypeId],\r\n zIndex: d.highlightedLegZIndex\r\n });\r\n },\r\n k: function() {\r\n return b._omsData.i.setOptions({\r\n strokeColor: d.legColors.usual[d.map.mapTypeId],\r\n zIndex: d.usualLegZIndex\r\n });\r\n }\r\n };\r\n };\r\n c.G = function(b, d) {\r\n var a, c, f, e, r, m, l, k, n, q;\r\n this.s = h;\r\n q = b.length;\r\n a = this.C(\r\n (function() {\r\n var a, d, c;\r\n c = [];\r\n a = 0;\r\n for (d = b.length; a < d; a++) (k = b[a]), c.push(k.p);\r\n return c;\r\n })()\r\n );\r\n e =\r\n q >= this.circleSpiralSwitchover\r\n ? this.v(q, a).reverse()\r\n : this.u(q, a);\r\n a = function() {\r\n var a,\r\n d,\r\n k,\r\n q = this;\r\n k = [];\r\n a = 0;\r\n for (d = e.length; a < d; a++)\r\n (f = e[a]),\r\n (c = this.D(f)),\r\n (n = this.B(b, function(a) {\r\n return q.f(a.p, f);\r\n })),\r\n (l = n.A),\r\n (m = new s.Polyline({\r\n map: this.map,\r\n path: [l.position, c],\r\n strokeColor: this.legColors.usual[this.map.mapTypeId],\r\n strokeWeight: this.legWeight,\r\n zIndex: this.usualLegZIndex\r\n })),\r\n (l._omsData = { l: l.position, i: m }),\r\n this.legColors.highlighted[this.map.mapTypeId] !==\r\n this.legColors.usual[this.map.mapTypeId] &&\r\n ((r = this.z(l)),\r\n (l._omsData.w = {\r\n h: p.addListener(l, \"mouseover\", r.h),\r\n k: p.addListener(l, \"mouseout\", r.k)\r\n })),\r\n l.setPosition(c),\r\n l.setZIndex(Math.round(this.spiderfiedZIndex + f.y)),\r\n k.push(l);\r\n return k;\r\n }.call(this);\r\n delete this.s;\r\n this.r = h;\r\n return this.trigger(\"spiderfy\", a, d);\r\n };\r\n c.unspiderfy = function(b) {\r\n var d, a, c, f, e, k, m;\r\n b == u && (b = u);\r\n if (this.r == u) return this;\r\n this.t = h;\r\n f = [];\r\n c = [];\r\n m = this.a;\r\n e = 0;\r\n for (k = m.length; e < k; e++)\r\n (a = m[e]),\r\n a._omsData != u\r\n ? (a._omsData.i.setMap(u),\r\n a !== b && a.setPosition(a._omsData.l),\r\n a.setZIndex(u),\r\n (d = a._omsData.w),\r\n d != u && (p.removeListener(d.h), p.removeListener(d.k)),\r\n delete a._omsData,\r\n f.push(a))\r\n : c.push(a);\r\n delete this.t;\r\n delete this.r;\r\n this.trigger(\"unspiderfy\", f, c);\r\n return this;\r\n };\r\n c.f = function(b, d) {\r\n var a, c;\r\n a = b.x - d.x;\r\n c = b.y - d.y;\r\n return a * a + c * c;\r\n };\r\n c.C = function(b) {\r\n var d, a, c, f, e;\r\n f = a = c = 0;\r\n for (e = b.length; f < e; f++) (d = b[f]), (a += d.x), (c += d.y);\r\n b = b.length;\r\n return new s.Point(a / b, c / b);\r\n };\r\n c.c = function(b) {\r\n return this.e.getProjection().fromLatLngToDivPixel(b);\r\n };\r\n c.D = function(b) {\r\n return this.e.getProjection().fromDivPixelToLatLng(b);\r\n };\r\n c.B = function(b, c) {\r\n var a, g, f, e, k, m;\r\n f = k = 0;\r\n for (m = b.length; k < m; f = ++k)\r\n if (\r\n ((e = b[f]),\r\n (e = c(e)),\r\n typeof a === \"undefined\" || a === u || e < g)\r\n )\r\n (g = e), (a = f);\r\n return b.splice(a, 1)[0];\r\n };\r\n c.m = function(b, c) {\r\n var a, g, f, e;\r\n if (b.indexOf != u) return b.indexOf(c);\r\n a = f = 0;\r\n for (e = b.length; f < e; a = ++f)\r\n if (((g = b[a]), g === c)) return a;\r\n return -1;\r\n };\r\n w.g = function(b) {\r\n return this.setMap(b);\r\n };\r\n w.g.prototype = new s.OverlayView();\r\n w.g.prototype.draw = function() {};\r\n return w;\r\n })();\r\n }.call(this));\r\n}.call(window));\r\n/* Tue 7 May 2013 14:56:02 BST */\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"ActionsBarController\", [\r\n \"$scope\",\r\n \"$location\",\r\n \"$http\",\r\n ActionsBarController\r\n ]);\r\n\r\n function ActionsBarController($scope, $location, $http) {\r\n $scope.currentURL = $location;\r\n $scope.imageDownload = {\r\n inProgress: false,\r\n error: false\r\n };\r\n\r\n function getEncodedUrl() {\r\n return encodeURIComponent($scope.currentURL.absUrl());\r\n }\r\n\r\n $scope.getEncodedURL = getEncodedUrl;\r\n\r\n var selectedAssetsString = function() {\r\n if ($scope.state && $scope.state.selectedAssets) {\r\n var assetsByName = $scope.state.selectedAssets\r\n .map(function(assetId) {\r\n var assetParents = $scope.findAssetParent(assetId);\r\n if (assetParents.length > 1) {\r\n return (\r\n $scope.state.assetsById[assetParents[1]].name.trim() +\r\n \"/\" +\r\n $scope.state.assetsById[assetId].name.trim()\r\n );\r\n }\r\n else {\r\n return $scope.state.assetsById[assetId].name.trim();\r\n }\r\n })\r\n .join(\"|\");\r\n\r\n return assetsByName;\r\n }\r\n else {\r\n return \"\";\r\n }\r\n };\r\n\r\n $scope.assetRawDataUrl = function() {\r\n return $scope.state && $scope.state.selectedIndicator\r\n ? \"/AssetRawData.axd?ind=\" +\r\n $scope.state.selectedIndicator.id +\r\n \"&assets=\" +\r\n encodeURIComponent(selectedAssetsString())\r\n : \"\";\r\n };\r\n\r\n $scope.$watch(\r\n \"state.selectedCommunities\",\r\n function(oldVal, newVal) {\r\n if (oldVal === newVal) {\r\n return false;\r\n }\r\n $location.search(\r\n \"communities\",\r\n $scope.state.selectedCommunities.communities.join(\"|\")\r\n );\r\n },\r\n true\r\n );\r\n\r\n $scope.retryCount = 0;\r\n $scope.downloadImage = function() {\r\n var url =\r\n \"/image/generate?width=1200&wait=true&url=\" +\r\n getEncodedUrl() +\r\n \"%26cap%3Dtrue\";\r\n $scope.imageDownload.inProgress = true;\r\n $scope.imageDownload.error = false;\r\n\r\n $http({\r\n url: url,\r\n method: \"GET\",\r\n responseType: \"arraybuffer\"\r\n })\r\n .success(function(data, status, headers, config) {\r\n // TODO when WS success\r\n var file = new Blob([data], {\r\n type: \"image/png\"\r\n });\r\n\r\n var fileName = \"CCC-Image.png\";\r\n\r\n if (window.navigator.msSaveOrOpenBlob) {\r\n window.navigator.msSaveBlob(file, fileName);\r\n }\r\n else {\r\n //trick to download store a file having its URL\r\n var fileURL = URL.createObjectURL(file);\r\n var tempLink = document.createElement(\"a\");\r\n tempLink.href = fileURL;\r\n tempLink.target = \"_blank\";\r\n tempLink.download = fileName;\r\n document.body.appendChild(tempLink);\r\n tempLink.click();\r\n document.body.removeChild(tempLink);\r\n }\r\n $scope.imageDownload.inProgress = false;\r\n })\r\n .error(function(data, status, headers, config) {\r\n if ($scope.retryCount >= 3) {\r\n $scope.retryCount = 0;\r\n $scope.imageDownload.inProgress = false;\r\n $scope.imageDownload.error = true;\r\n console.log(data, status, headers, config);\r\n }\r\n else {\r\n $scope.retryCount += 1;\r\n $scope.downloadImage();\r\n }\r\n });\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccActionsBar\", cccActionsBar);\r\n\r\n function cccActionsBar() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"ActionsBarController\"\r\n };\r\n }\r\n})();\r\n","/* global google, MarkerClusterer */\r\n\r\n(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"AssetMapController\", [\r\n \"$scope\",\r\n \"$q\",\r\n \"$http\",\r\n \"$timeout\",\r\n \"$location\",\r\n \"$attrs\",\r\n \"NgMap\",\r\n \"AssetsService\",\r\n \"LoadingService\",\r\n AssetMapController\r\n ]);\r\n\r\n // This controller exists alongside the RiskRankingMapController, adding additional functionality\r\n // to handle assets and map marker interaction\r\n function AssetMapController(\r\n $scope,\r\n $q,\r\n $http,\r\n $timeout,\r\n $location,\r\n $attrs,\r\n NgMap,\r\n AssetsService,\r\n LoadingService\r\n ) {\r\n var pinPath =\r\n \"M0-31c-6.6,0-12,5.3-12,11.8C-12-14-4.6-4.5-1.4-0.7C-1.1-0.3-0.5,0,0,0s1.1-0.2,1.4-0.7C4.6-4.5,12-14,12-19.2C12-25.7,6.6-31,0-31zM0-14.9c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S2.2-14.9,0-14.9z\",\r\n pinPathSolid =\r\n \"M0-31c-6.6,0-12,5.3-12,11.8C-12-14-4.6-4.5-1.4-0.7C-1.1-0.3-0.5,0,0,0s1.1-0.2,1.4-0.7C4.6-4.5,12-14,12-19.2C12-25.7,6.6-31,0-31z\",\r\n overlapDistance = 3,\r\n maxClusterZoom = 13;\r\n\r\n $scope.markerClusters = {};\r\n $scope.markerOverlappers = {};\r\n $scope.markerStash = {};\r\n $scope.markerReference = {};\r\n\r\n function standardIcon(color) {\r\n return {\r\n path: pinPath,\r\n labelOrigin: new google.maps.Point(0, -18),\r\n fillColor: color,\r\n fillOpacity: 1,\r\n scale: 1,\r\n strokeColor: \"#e1e9eb\",\r\n strokeWeight: 1\r\n };\r\n }\r\n\r\n function solidIcon(color) {\r\n var icon = standardIcon(color);\r\n icon.path = pinPathSolid;\r\n return icon;\r\n }\r\n\r\n // Change the marker style back to normal\r\n function standardizeMarkerStyle(m) {\r\n m.setIcon(standardIcon(m.icon.fillColor));\r\n m.setLabel({\r\n text: \" \",\r\n color: \"white\"\r\n });\r\n }\r\n\r\n // Add a count to the marker pin\r\n function countifyMarkerStyle(m, count) {\r\n m.setIcon(solidIcon(m.icon.fillColor));\r\n m.setLabel({\r\n text: count.toString(),\r\n color: \"white\"\r\n });\r\n }\r\n\r\n var markerClusterer = {\r\n // Create a new marker cluster for a domain id, if one doesn't exist already\r\n // This gives us a reference for adding/removing individual markers per domain later on\r\n create: function(id) {\r\n // Add an overlapping marker spiderfier\r\n if (!$scope.markerOverlappers[id]) {\r\n $scope.markerOverlappers[id] = new OverlappingMarkerSpiderfier(\r\n $scope.map,\r\n {\r\n nearbyDistance: overlapDistance\r\n }\r\n );\r\n\r\n // On spiderfy, remove the special number style\r\n $scope.markerOverlappers[id].addListener(\"spiderfy\", function(m) {\r\n m.forEach(function(m) {\r\n m.color = \"black\";\r\n m.data.countLabel = m.label.text;\r\n standardizeMarkerStyle(m);\r\n });\r\n });\r\n\r\n // On Unspiderfy, change back to special number style\r\n $scope.markerOverlappers[id].addListener(\"unspiderfy\", function(m) {\r\n m.forEach(function(m) {\r\n countifyMarkerStyle(m, m.data.countLabel);\r\n m.data.countLabel = undefined;\r\n });\r\n });\r\n }\r\n\r\n $scope.markerClusters[id] =\r\n $scope.markerClusters[id] ||\r\n new MarkerClusterer(\r\n $scope.map,\r\n null, // This param is usually new markers; we'll add our own later\r\n {\r\n gridSize: 90,\r\n maxZoom: maxClusterZoom,\r\n groupPredicate: function(m1, m2) {\r\n var currentZoom = this.map.zoom;\r\n // Above this zoom level, only group within CD boundaries\r\n if (currentZoom >= 12) {\r\n return (\r\n m1.data.communityDistrictId === m2.data.communityDistrictId\r\n );\r\n }\r\n return true;\r\n },\r\n styles: [\r\n {\r\n fontFamily: \"GestaCond-Bold\",\r\n url: \"\",\r\n height: 36,\r\n width: 36,\r\n fontSize: \"16px\",\r\n textColor: \"#e1e9eb\",\r\n lineHeight: \"32px\",\r\n backgroundColor: \"#\" + $scope.state.domainsByName[id].color,\r\n border: \"3px solid #e1e9eb\",\r\n borderRadius: \"40px\"\r\n }\r\n ]\r\n }\r\n );\r\n },\r\n\r\n // Adds markers by array for more efficient processing\r\n addMarkers: function(domain, arr) {\r\n var markerArr = [];\r\n\r\n arr.forEach(function(marker) {\r\n if (marker.data.isArea) {\r\n marker.polygon.data = marker.data;\r\n $scope.markerReference[marker.id] = marker.polygon;\r\n }\r\n else {\r\n marker.mapMarker.data = marker.data;\r\n markerArr.push(marker.mapMarker);\r\n $scope.markerReference[marker.id] = marker.mapMarker;\r\n }\r\n });\r\n\r\n $scope.markerClusters[domain].addMarkers(markerArr);\r\n markerArr.forEach(function(m) {\r\n $scope.markerOverlappers[domain].addMarker(m);\r\n });\r\n },\r\n\r\n // Removes markers by array for more efficient processing\r\n removeMarkers: function(domain, arr) {\r\n var markerArr = [];\r\n\r\n arr.forEach(function(marker) {\r\n if ($scope.markerReference[marker].data.isArea) {\r\n $scope.markerReference[marker].setMap(null);\r\n }\r\n\r\n // Add each marker to the removal array, then remove it from our reference arrays\r\n markerArr.push($scope.markerReference[marker]);\r\n\r\n var ind = $scope.markerStash[domain].indexOf(marker);\r\n $scope.markerStash[domain].splice(ind, 1);\r\n delete $scope.markerReference[marker];\r\n });\r\n\r\n if ($scope.markerClusters[domain]) {\r\n $scope.markerClusters[domain].removeMarkers(markerArr);\r\n }\r\n }\r\n };\r\n\r\n // Calculate pixel position of any point on the map\r\n function fromLatLngToPixel(position) {\r\n var scale = Math.pow(2, $scope.map.getZoom());\r\n var proj = $scope.map.getProjection();\r\n var bounds = $scope.map.getBounds();\r\n\r\n var nw = proj.fromLatLngToPoint(\r\n new google.maps.LatLng(\r\n bounds.getNorthEast().lat(),\r\n bounds.getSouthWest().lng()\r\n )\r\n );\r\n var point = proj.fromLatLngToPoint(position);\r\n\r\n return new google.maps.Point(\r\n Math.floor((point.x - nw.x) * scale),\r\n Math.floor((point.y - nw.y) * scale)\r\n );\r\n }\r\n\r\n // Square of distance between 2 points\r\n function ptDistanceSq(pt1, pt2) {\r\n var dx, dy;\r\n\r\n dx = pt1.x - pt2.x;\r\n dy = pt1.y - pt2.y;\r\n return dx * dx + dy * dy;\r\n }\r\n\r\n // Find markers that are overlapping\r\n function markerIndexesNearMarker(markers, marker) {\r\n var pxSq = overlapDistance * overlapDistance,\r\n markerPt = fromLatLngToPixel(marker.position);\r\n\r\n return markers.reduce(function(accum, m, i) {\r\n if (m === marker || !m.getVisible()) {\r\n return accum;\r\n }\r\n var mPt = fromLatLngToPixel(\r\n m._omsData ? m._omsData.usualPosition : m.position\r\n );\r\n return ptDistanceSq(mPt, markerPt) < pxSq ? accum.concat(i) : accum;\r\n }, []);\r\n }\r\n\r\n // Group all markers that overlap each other\r\n function groupOverlappingMarkers(markers) {\r\n var skip = markers.map(function() {\r\n return false;\r\n }),\r\n groups = [];\r\n\r\n for (var i = 0; i < markers.length; i++) {\r\n if (skip[i]) continue;\r\n var overlaps = markerIndexesNearMarker(markers.slice(i), markers[i]);\r\n\r\n if (overlaps.length > 0) {\r\n var group = [markers[i]].concat(\r\n overlaps.map(function(mi) {\r\n return markers[i + mi];\r\n })\r\n );\r\n overlaps.forEach(function(mi) {\r\n skip[i + mi] = true;\r\n });\r\n groups.push(group);\r\n }\r\n }\r\n return groups;\r\n }\r\n\r\n // Find and style any overlapping markers\r\n var findAndStyleOverlappingMarkers = _.debounce(function() {\r\n // Unspiderfy any spiderfied markers to avoid errors\r\n Object.keys($scope.markerOverlappers).forEach(function(key) {\r\n $scope.markerOverlappers[key].unspiderfy();\r\n });\r\n\r\n // Don't do anything if we're in a clustered zoom level\r\n\t\t\tif ($scope.map && $scope.map.getZoom() <= maxClusterZoom) {\r\n return;\r\n }\r\n\r\n var mapBounds = $scope.map.getBounds();\r\n\r\n // For every marker clusterer\r\n Object.keys($scope.markerClusters).forEach(function(key) {\r\n // Filter down to just the pins that are in view, for performance\r\n var pinsInView = $scope.markerClusters[key]\r\n .getMarkers()\r\n .filter(function(m) {\r\n return mapBounds.contains(m.position);\r\n });\r\n\r\n // Group these pins by overlapping\r\n var groupedPins = groupOverlappingMarkers(pinsInView);\r\n\r\n // Style each pin with the count in the group\r\n groupedPins.forEach(function(g) {\r\n g.forEach(function(m) {\r\n countifyMarkerStyle(m, g.length);\r\n });\r\n });\r\n });\r\n }, 100);\r\n\r\n // Find an asset's direct parent\r\n // This will either return a parent asset array, or nothing if the asset is a parent or has no children\r\n $scope.findAssetParent = function(assetId) {\r\n return Object.keys($scope.state.assetsByGroup).filter(function(group) {\r\n return $scope.state.assetsByGroup[group].indexOf(assetId) > -1;\r\n });\r\n };\r\n\r\n // When the selected assets change, make an API request to load asset + marker data\r\n $scope.$watch(\r\n \"state.selectedAssets\",\r\n function(newValue, oldValue) {\r\n // Quick exit on initialization, not everything will be ready yet.\r\n // https://docs.angularjs.org/api/ng/type/$rootScope.Scope\r\n // If `newValue` has length, that means `state.selectedAssets` was created from URL parameters,\r\n // so it's safe to execute the rest of the function\r\n if (!newValue.length && newValue === oldValue) {\r\n return false;\r\n }\r\n\r\n // Concatenate all selected assets together in the right format for making the API request\r\n var assetsByName = $scope.state.selectedAssets\r\n .map(function(assetId) {\r\n var assetParents = $scope.findAssetParent(assetId);\r\n if (assetParents.length > 1) {\r\n return (\r\n $scope.state.assetsById[assetParents[1]].name.trim() +\r\n \"/\" +\r\n $scope.state.assetsById[assetId].name.trim()\r\n );\r\n }\r\n else {\r\n return $scope.state.assetsById[assetId].name.trim();\r\n }\r\n })\r\n .join(\"|\");\r\n\r\n // Update the URL with the new selected assets string\r\n $location.search(\"assets\", assetsByName);\r\n\r\n var assetsLoadingId = LoadingService.taskStart(\r\n $scope,\r\n \"Loading Assets\"\r\n );\r\n\r\n AssetsService.getAssets(assetsByName).then(\r\n function(response) {\r\n LoadingService.taskEnd($scope, assetsLoadingId);\r\n\r\n var assetDrawingId = LoadingService.taskStart(\r\n $scope,\r\n \"Drawing Asset Pins\"\r\n );\r\n\r\n var newMarkers = {};\r\n var selectedAssetMarkers = [];\r\n var assetNumStash = {}; // so we don't bother $scope over and over\r\n $scope.state.numberOfAssets = {}; // reset the assets counter\r\n\r\n function processMarker(marker) {\r\n /* For sub-assets, an array of categories will be returned by the API, including any parent assets. The actual category is the last item in the array. Example:\r\n category: [\r\n 'Parent Asset',\r\n 'Child Asset'\r\n ]\r\n\r\n Parent assets will not have assets of their own, but their total count should be\r\n the sum of all their child assets. */\r\n\r\n if (!assetNumStash[marker.communityDistrictId]) {\r\n assetNumStash[marker.communityDistrictId] = {};\r\n }\r\n\r\n marker.category.forEach(function(category, ind, arr) {\r\n if (assetNumStash[marker.communityDistrictId][category]) {\r\n assetNumStash[marker.communityDistrictId][category]++;\r\n }\r\n else {\r\n assetNumStash[marker.communityDistrictId][category] = 1;\r\n }\r\n });\r\n\r\n // Sugar to make things a little more understandable\r\n var asset = $scope.state.assetsByName[marker.category[0]];\r\n var domain = $scope.state.domainsByAsset[asset.id];\r\n\r\n // Markers don't have IDs, so let's build our own from three unique data points:\r\n // - Marker description\r\n // - Latitude\r\n // - Longitude\r\n var markerID =\r\n marker.description + \"-\" + marker.lat + \"-\" + marker.long;\r\n selectedAssetMarkers.push(markerID);\r\n\r\n $scope.markerStash[domain] = $scope.markerStash[domain] || [];\r\n newMarkers[domain] = newMarkers[domain] || [];\r\n\r\n // If the marker doesn't already exist, add it to the stash and create it on the map\r\n if ($scope.markerStash[domain].indexOf(markerID) === -1) {\r\n $scope.markerStash[domain].push(markerID);\r\n\r\n var newMarker;\r\n var isArea =\r\n marker.isArea &&\r\n marker.assetId != null &&\r\n marker.assetId != 0;\r\n if (!isArea && marker.lat && marker.long) {\r\n newMarker = {\r\n data: marker,\r\n id: markerID,\r\n asset: asset,\r\n mapMarker: new google.maps.Marker({\r\n position: {\r\n lat: marker.lat,\r\n lng: marker.long\r\n },\r\n label: {\r\n text: \" \",\r\n color: \"white\"\r\n },\r\n icon: standardIcon(\r\n \"#\" + $scope.state.domainsByName[domain].color\r\n )\r\n })\r\n };\r\n\r\n // On mouseover, we need to populate and display the marker info box\r\n newMarker.mapMarker.addListener(\"mouseover\", function() {\r\n assetMouseOver(marker, $scope.map);\r\n });\r\n newMarker.mapMarker.addListener(\"mouseout\", function() {\r\n assetMouseOut();\r\n });\r\n newMarkers[domain].push(newMarker);\r\n }\r\n else if (isArea) {\r\n var wkt = marker.shapeData;\r\n var polygon = ccc.viz.map.util.MapUtil.wktToGooglePolygon(\r\n wkt\r\n );\r\n\r\n polygon.setOptions({\r\n fillColor: \"#\" + $scope.state.domainsByName[domain].color,\r\n\r\n zIndex: 1000\r\n });\r\n\r\n polygon.addListener(\"mouseover\", function() {\r\n assetMouseOver(marker, $scope.map);\r\n\r\n this.setOptions({\r\n strokeColor: \"#dea003\",\r\n strokeWeight: 3\r\n });\r\n });\r\n polygon.addListener(\"mouseout\", function() {\r\n assetMouseOut();\r\n this.setOptions({\r\n strokeColor: \"#e2ecec\",\r\n strokeWeight: 1\r\n });\r\n });\r\n\r\n newMarker = {\r\n data: marker,\r\n id: markerID,\r\n asset: asset,\r\n polygon: polygon\r\n };\r\n\r\n polygon.setMap($scope.map);\r\n newMarkers[domain].push(newMarker);\r\n }\r\n }\r\n }\r\n\r\n function assetMouseOver(marker, map) {\r\n $scope.map = map;\r\n var markerLatLng = new google.maps.LatLng(\r\n marker.lat,\r\n marker.long\r\n );\r\n var eventLoc = $scope.latLng2Point(markerLatLng, $scope.map);\r\n var domain =\r\n $scope.state.domainsByAsset[\r\n $scope.state.assetsByName[marker.category[0]].id\r\n ];\r\n\r\n // Magic numbers to offset the box position for better UI\r\n $scope.mapHoverBox.posLeft = eventLoc.x + 40;\r\n $scope.mapHoverBox.posTop = eventLoc.y - 70;\r\n\r\n $scope.mapHoverBox.domainName = domain;\r\n $scope.mapHoverBox.domainIcon =\r\n $scope.state.domainsByName[domain].icon;\r\n\r\n // For all categories an asset has, create a pretty slash-separated string for the display name when a user hovers on the map marker.\r\n $scope.mapHoverBox.assetCategory = \"\";\r\n marker.category.forEach(function(category, ind, arr) {\r\n $scope.mapHoverBox.assetCategory += marker.category[ind];\r\n // Separate every category except for the last one, so no trailing slash\r\n if (ind !== marker.category.length - 1) {\r\n $scope.mapHoverBox.assetCategory += \" / \";\r\n }\r\n });\r\n\r\n $scope.mapHoverBox.assetDescription = marker.description;\r\n $scope.mapHoverBox.hoveredCommunity =\r\n $scope.state.communitiesById[marker.communityDistrictId];\r\n\r\n // The `isAsset` flag determines whether the hover pop-out displays community\r\n // info, or asset info. `isAsset` trumps community info\r\n $scope.mapHoverBox.isAsset = true;\r\n\r\n $scope.$apply();\r\n }\r\n\r\n function assetMouseOut() {\r\n $scope.mapHoverBox.isAsset = false;\r\n $scope.$apply();\r\n }\r\n\r\n // Batch the markers up in to groups and process them one group at a time, sequentially\r\n var batchSize = 75;\r\n function batchMarkers(accum, rest) {\r\n if (rest.length <= batchSize) {\r\n accum.push(rest);\r\n return accum;\r\n }\r\n\r\n accum.push(rest.slice(0, batchSize));\r\n return batchMarkers(accum, rest.slice(batchSize));\r\n }\r\n\r\n var batchedMarkers = batchMarkers([], response);\r\n\r\n // Process each batch at a time sequentially, with a settimeout on each batch process\r\n // This has the benefit of returning control to the browser after each batch is processed\r\n batchedMarkers\r\n .reduce(function(promise, batch) {\r\n return promise.then(function() {\r\n return $timeout(function() {\r\n batch.forEach(processMarker);\r\n }, 0);\r\n });\r\n }, $q.when(true))\r\n .then(function() {\r\n $scope.state.numberOfAssets = assetNumStash;\r\n\r\n // Helper function to prevent race condition caused by trying to add markers\r\n // before the map promise resolves\r\n var buildNewMarkers = function(domain) {\r\n var newMarkersArr = [];\r\n\r\n for (var marker in newMarkers[domain]) {\r\n newMarkersArr.push(newMarkers[domain][marker]);\r\n }\r\n\r\n markerClusterer.addMarkers(domain, newMarkersArr);\r\n };\r\n\r\n Object.keys(newMarkers).forEach(function(domain, ind, arr) {\r\n // Create a new cluster of markers for this specific domain\r\n // Sometimes this fires before the map has made it into scope; check for that\r\n if (!$scope.map) {\r\n NgMap.getMap().then(function(map) {\r\n $scope.map = map;\r\n markerClusterer.create(domain, map);\r\n\r\n buildNewMarkers(domain);\r\n });\r\n }\r\n else {\r\n markerClusterer.create(domain, $scope.map);\r\n buildNewMarkers(domain);\r\n }\r\n });\r\n\r\n // The `markerStash` lets us know all the markers we just added. Since `state.selectedAssets`\r\n // changed, and we might need to remove assets too, compare `markerStash` to the\r\n // `selectedAssetMarkers` array. Any markers that aren't found can be presumed removed.\r\n Object.keys($scope.markerStash).forEach(function(\r\n domain,\r\n ind,\r\n arr\r\n ) {\r\n var removeThese = [];\r\n\r\n $scope.markerStash[domain].forEach(function(marker) {\r\n if (selectedAssetMarkers.indexOf(marker) === -1) {\r\n removeThese.push(marker);\r\n }\r\n });\r\n\r\n markerClusterer.removeMarkers(domain, removeThese);\r\n });\r\n\r\n LoadingService.taskEnd($scope, assetDrawingId);\r\n\r\n findAndStyleOverlappingMarkers();\r\n\r\n if ($attrs.assetMap) {\r\n\t\t\t\t\t\t// At this point, the map should be all done rendering\r\n\t\t\t\t\t\t$timeout(function () {\r\n if ($scope.currentURL.search().cap) {\r\n window.triggerScreenshot();\r\n }\r\n }, 1000);\r\n }\r\n });\r\n },\r\n function(response) {\r\n console.warn(\"getAssets failed!\");\r\n }\r\n );\r\n },\r\n true\r\n ); // compares equality, not reference\r\n\r\n // Attach map listeners to process any overlapping markers\r\n var attachMapListeners = _.once(function() {\r\n var lastZoom = $scope.map.getZoom();\r\n var zoomedInCount = 0;\r\n google.maps.event.addListener(\r\n $scope.map,\r\n \"zoom_changed\",\r\n function trackMapZoom() {\r\n // Tracks when a user clicks on zoom on a map 3 or more times on the \"Asset Mapping\" page.\r\n var curZoom = $scope.map.getZoom();\r\n\r\n if (curZoom > lastZoom) {\r\n zoomedInCount += 1;\r\n\r\n if (zoomedInCount >= 3) {\r\n dataLayer.push({\r\n event: \"3 or More Map Zoom\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"3 or More Map Zoom\",\r\n eventLabel: window.location.pathname\r\n });\r\n }\r\n }\r\n else if (curZoom < lastZoom) {\r\n zoomedInCount -= 1;\r\n }\r\n\r\n lastZoom = curZoom;\r\n }\r\n );\r\n\r\n google.maps.event.addListener($scope.map, \"zoom_changed\", function() {\r\n findAndStyleOverlappingMarkers();\r\n });\r\n\r\n google.maps.event.addListener($scope.map, \"center_changed\", function() {\r\n findAndStyleOverlappingMarkers();\r\n });\r\n });\r\n\r\n $scope.$watch(\"map\", function() {\r\n if ($scope.map) {\r\n attachMapListeners();\r\n }\r\n });\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccAssetMap\", cccAssetMap);\r\n\r\n function cccAssetMap() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"AssetMapController\"\r\n };\r\n }\r\n})();\r\n","/* global Macy */\r\n\r\n(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"AssetMapTableController\", [\r\n \"$scope\",\r\n \"$sanitize\",\r\n \"$timeout\",\r\n AssetMapTableController\r\n ]);\r\n\r\n function AssetMapTableController($scope, $sanitize, $timeout) {\r\n // A custom filter that returns all groups (domains) with selected assets\r\n $scope.groupsWithSelectedAssets = function(groups) {\r\n var groupsArr = [];\r\n var groupIdsArr = [];\r\n\r\n var addAssetsToTable = function(domain, parentGroup) {\r\n if (domain.children) {\r\n domain.children.forEach(function(asset, ind, arr) {\r\n // We only want to add each domain/group once\r\n // If there's a parent group, we'll want to use that ID\r\n var groupID;\r\n if (parentGroup) {\r\n groupID = parentGroup.id;\r\n }\r\n else {\r\n groupID = domain.id;\r\n }\r\n\r\n if (\r\n $scope.state.selectedAssets.indexOf(asset.id) >= 0 &&\r\n groupIdsArr.indexOf(groupID) === -1\r\n ) {\r\n groupsArr.push(parentGroup ? parentGroup : domain);\r\n groupIdsArr.push(groupID);\r\n }\r\n\r\n // If this specific asset has child assets, capture those too\r\n // A child asset might be selected even if a parent isn't, so we want to make sure we\r\n // catch that and show the group as expected\r\n if (asset.children) {\r\n addAssetsToTable(asset, domain);\r\n }\r\n });\r\n }\r\n };\r\n\r\n groups.forEach(function(group, ind, arr) {\r\n addAssetsToTable(group);\r\n });\r\n\r\n return groupsArr;\r\n };\r\n\r\n // Check if an asset is, or any children of the asset are selected\r\n // Per the feature spec, parent/sibling assets need to be visible if any child is selected\r\n $scope.assetOrChildIsSelected = function(asset) {\r\n var assetIsGroup = Object.keys($scope.state.assetsByGroup).indexOf(\r\n asset.toString()\r\n );\r\n\r\n if (assetIsGroup >= 0) {\r\n var shouldReturn = false;\r\n $scope.state.assetsByGroup[asset].forEach(function(child, ind, arr) {\r\n if ($scope.state.selectedAssets.indexOf(child) >= 0) {\r\n shouldReturn = true;\r\n return;\r\n }\r\n });\r\n return shouldReturn;\r\n }\r\n else {\r\n return $scope.state.selectedAssets.indexOf(asset) >= 0;\r\n }\r\n };\r\n\r\n var refreshMasonry = function() {\r\n $scope.state.selectedCommunities.communities.forEach(function(\r\n elm,\r\n ind,\r\n arr\r\n ) {\r\n // Macy doesn't like the idea of multiple masonry containers, so we get around that by\r\n // re-initializing each container every time. Definitely not ideal or optimal, but it works.\r\n Macy.init({\r\n container: \".macy-wrapper-\" + ind,\r\n margin: 24,\r\n columns: 3\r\n });\r\n });\r\n };\r\n\r\n var assetsTable = {};\r\n\r\n $scope.$watch(\r\n \"state.selectedAssets\",\r\n function(oldVal, newVal) {\r\n if (oldVal === newVal) {\r\n return false;\r\n }\r\n\r\n $scope.state.selectedAssets.forEach(function(asset, ind, arr) {\r\n for (var group in $scope.state.assetsByGroup) {\r\n assetsTable[group] = assetsTable[group] || [];\r\n if (\r\n $scope.state.assetsByGroup[group].indexOf(asset) >= 0 &&\r\n assetsTable[group].indexOf(asset) === -1\r\n ) {\r\n assetsTable[group].push(asset);\r\n }\r\n }\r\n });\r\n\r\n // Since Macy isn't tied to Angular, this timeout prevents a race condition if Angular\r\n // takes longer to update than Macy does to update\r\n $timeout(refreshMasonry, 175);\r\n },\r\n true\r\n );\r\n\r\n $scope.$watch(\r\n \"state.selectedCommunities\",\r\n function(oldVal, newVal) {\r\n if (oldVal === newVal) {\r\n return false;\r\n }\r\n\r\n // Since Macy isn't tied to Angular, this timeout prevents a race condition if Angular\r\n // takes longer to update than Macy does to update\r\n $timeout(refreshMasonry, 175);\r\n },\r\n true\r\n );\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccAssetMapTable\", cccAssetMapTable);\r\n\r\n function cccAssetMapTable() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"AssetMapTableController\"\r\n };\r\n }\r\n})();\r\n","(function(root, factory) {\r\n if (typeof define === \"function\" && define.amd) {\r\n define([], function() {\r\n return factory();\r\n });\r\n }\r\n else if (typeof exports === \"object\") {\r\n module.exports = factory();\r\n }\r\n else {\r\n root.Macy = factory();\r\n }\r\n})(window, function () {\r\n \"use strict\";\r\n\r\n /**\r\n * Extend objects.\r\n * @param {Object} objects - Objects to merge\r\n * @return {Object} - New object\r\n */\r\n var extend = function(objects) {\r\n var extended = {};\r\n var i = 1;\r\n var prop;\r\n\r\n var merge = function(obj) {\r\n for (prop in obj) {\r\n if (Object.prototype.hasOwnProperty.call(obj, prop)) {\r\n if (Object.prototype.toString.call(obj[prop]) === \"[object Object]\") {\r\n extended[prop] = extend(extended[prop], obj[prop]);\r\n }\r\n else {\r\n extended[prop] = obj[prop];\r\n }\r\n }\r\n }\r\n };\r\n\r\n merge(arguments[0]);\r\n\r\n for (i = 1; i < arguments.length; i++) {\r\n var obj = arguments[i];\r\n merge(obj);\r\n }\r\n\r\n return extended;\r\n };\r\n\r\n /**\r\n * Public API.\r\n * @type {Object}\r\n */\r\n var Macy = {};\r\n\r\n /**\r\n * Current version.\r\n * @type {String}\r\n */\r\n Macy.VERSION = \"1.1.2\";\r\n\r\n /**\r\n * Expose settings.\r\n * @type {Object}\r\n */\r\n Macy.settings = {};\r\n\r\n /**\r\n * Default options.\r\n * @type {Object}\r\n */\r\n var defaults = {\r\n columns: 3,\r\n margin: 2,\r\n trueOrder: true,\r\n waitForImages: false\r\n };\r\n\r\n var cache = {\r\n options: {}\r\n };\r\n\r\n var imgsRequired, currentlyLoaded;\r\n\r\n /**\r\n * Works out how many columns should be used at the current width.\r\n * @return {Number} - The number of colummns\r\n */\r\n var getCurrentColumns = function() {\r\n var docWidth = document.body.clientWidth;\r\n var noOfColumns;\r\n\r\n for (var widths in cache.options.breakAt) {\r\n if (docWidth < widths) {\r\n noOfColumns = cache.options.breakAt[widths];\r\n\r\n break;\r\n }\r\n }\r\n\r\n if (!noOfColumns) {\r\n noOfColumns = cache.options.columns;\r\n }\r\n\r\n return noOfColumns;\r\n };\r\n\r\n /**\r\n * Gets the column column width with or without margin calculated\r\n * @param Boolean marginIncluded - Value that decided if margine should be included\r\n * @return {String|Number} - Returns either a string or number\r\n */\r\n var getColumnWidths = function(marginIncluded) {\r\n marginIncluded =\r\n typeof marginIncluded !== \"undefined\" ? marginIncluded : true;\r\n var noOfColumns = getCurrentColumns();\r\n var margins;\r\n\r\n if (!marginIncluded) {\r\n return 100 / noOfColumns;\r\n }\r\n\r\n if (noOfColumns === 1) {\r\n return 100 + \"%\";\r\n }\r\n\r\n margins = ((noOfColumns - 1) * cache.options.margin) / noOfColumns;\r\n\r\n return \"calc(\" + 100 / noOfColumns + \"% - \" + margins + \"px)\";\r\n };\r\n\r\n /**\r\n * Sets the element widths based on the amount of columns.\r\n */\r\n var setWidths = function() {\r\n var percentageWidth = getColumnWidths();\r\n\r\n each(cache.elements, function(index, val) {\r\n val.style.width = percentageWidth;\r\n val.style.position = \"absolute\";\r\n });\r\n };\r\n\r\n /**\r\n * Gets the calculated left value for an element.\r\n * @param {Number} col - Current Column Number\r\n * @return {String} - Returns a calculated string\r\n */\r\n var getLeftValue = function(col) {\r\n var noOfColumns = getCurrentColumns();\r\n var totalLeft = 0;\r\n var margin, str;\r\n\r\n col++;\r\n\r\n if (col === 1) {\r\n return 0;\r\n }\r\n\r\n margin =\r\n (cache.options.margin -\r\n ((noOfColumns - 1) * cache.options.margin) / noOfColumns) *\r\n (col - 1);\r\n totalLeft += getColumnWidths(false) * (col - 1);\r\n str = \"calc(\" + totalLeft + \"% + \" + margin + \"px)\";\r\n\r\n return str;\r\n };\r\n\r\n /**\r\n * Gets the calculated top value for an element.\r\n * @param {Number} row - The elements row position\r\n * @param {Number} col - The elements columns position\r\n * @param {Array} eles - The elements\r\n * @return {Number} - The Position that should be set on the element\r\n */\r\n var getTopValue = function(row, col, eles) {\r\n var totalHeight = 0;\r\n var tempHeight;\r\n\r\n if (row === 0) {\r\n return 0;\r\n }\r\n\r\n for (var i = 0; i < row; i++) {\r\n tempHeight = parseInt(\r\n getProperty(cache.elements[eles[i]], \"height\").replace(\"px\", \"\"),\r\n 10\r\n );\r\n totalHeight += isNaN(tempHeight) ? 0 : tempHeight + cache.options.margin;\r\n }\r\n\r\n return totalHeight;\r\n };\r\n\r\n /**\r\n * Sorts the elements in the ordering method by placing each element in the next column.\r\n * @param {Number} columns - Number of columns\r\n */\r\n var reOrder = function(columns) {\r\n var col = 0;\r\n var elements2d = [];\r\n var tempIndexs = [];\r\n var indexArray = [];\r\n\r\n each(cache.elements, function(index) {\r\n col++;\r\n\r\n if (col > columns) {\r\n col = 1;\r\n elements2d.push(tempIndexs);\r\n tempIndexs = [];\r\n }\r\n\r\n tempIndexs.push(index);\r\n });\r\n\r\n elements2d.push(tempIndexs);\r\n\r\n for (var i = 0, elements2dLen = elements2d.length; i < elements2dLen; i++) {\r\n var eleIndexs = elements2d[i];\r\n\r\n for (var j = 0, eleIndexsLen = eleIndexs.length; j < eleIndexsLen; j++) {\r\n indexArray[j] =\r\n typeof indexArray[j] === \"undefined\" ? [] : indexArray[j];\r\n indexArray[j].push(eleIndexs[j]);\r\n }\r\n }\r\n\r\n cache.rows = indexArray;\r\n setPosition(false);\r\n };\r\n\r\n /**\r\n * Sorts the elements in the shuffle method by placing each element in the shortest column.\r\n * @param {Number} columns - Number of columns\r\n */\r\n var shuffleOrder = function(columns) {\r\n var eles = cache.elements;\r\n var element2dArray = [];\r\n var overflowEles = [];\r\n\r\n for (var i = 0; i < columns; i++) {\r\n element2dArray[i] = [];\r\n }\r\n\r\n for (var k = 0; k < eles.length; k++) {\r\n overflowEles.push(k);\r\n }\r\n\r\n for (\r\n var v = 0, overflowElesLen = overflowEles.length;\r\n v < overflowElesLen;\r\n v++\r\n ) {\r\n var index = findIndexOfSmallestTotal(element2dArray);\r\n\r\n element2dArray[index] =\r\n typeof element2dArray[index] === \"undefined\"\r\n ? []\r\n : element2dArray[index];\r\n element2dArray[index].push(overflowEles[v]);\r\n }\r\n\r\n cache.rows = element2dArray;\r\n setPosition(true);\r\n };\r\n\r\n /**\r\n * Sets the position of each element in array.\r\n * @param {Bool} alternate=false - The sort method used.\r\n */\r\n var setPosition = function(alternate) {\r\n alternate = alternate || false;\r\n var eles = cache.elements;\r\n var element2dArray = cache.rows;\r\n\r\n for (\r\n var i = 0, element2dArrayLen = element2dArray.length;\r\n i < element2dArrayLen;\r\n i++\r\n ) {\r\n var rowArray = alternate\r\n ? bubbleSort(element2dArray[i])\r\n : element2dArray[i];\r\n\r\n for (var j = 0, rowArrayLen = rowArray.length; j < rowArrayLen; j++) {\r\n var left, top;\r\n\r\n left = getLeftValue(i);\r\n top = getTopValue(j, i, rowArray, alternate);\r\n eles[rowArray[j]].style.top = top + \"px\";\r\n eles[rowArray[j]].style.left = left;\r\n }\r\n }\r\n };\r\n\r\n /**\r\n * Finds the shortest row and return the index in the 2-dimentional array.\r\n * @param {Array} arr - Array to find the shortest column from\r\n * @return {Number} - Index for the shortest row\r\n */\r\n var findIndexOfSmallestTotal = function(arr) {\r\n var runningTotal = 0;\r\n var smallestIndex, smallest, lastSmall, tempHeight;\r\n\r\n for (var i = 0, arrLen = arr.length; i < arrLen; i++) {\r\n for (var j = 0; j < arr[i].length; j++) {\r\n tempHeight = parseInt(\r\n getProperty(cache.elements[arr[i][j]], \"height\").replace(\"px\", \"\"),\r\n 10\r\n );\r\n runningTotal += isNaN(tempHeight) ? 0 : tempHeight;\r\n }\r\n\r\n lastSmall = smallest;\r\n smallest =\r\n smallest === undefined\r\n ? runningTotal\r\n : smallest > runningTotal\r\n ? runningTotal\r\n : smallest;\r\n\r\n if (lastSmall === undefined || lastSmall > smallest) {\r\n smallestIndex = i;\r\n }\r\n runningTotal = 0;\r\n }\r\n\r\n return smallestIndex;\r\n };\r\n\r\n /**\r\n * Shortcut for window.getComputedStyle(element, null).getPropertyValue(value).\r\n * @param {Object} element - Element to get property from\r\n * @param {String} value - Value to get from element\r\n * @return {String} - The returned property\r\n */\r\n var getProperty = function(element, value) {\r\n return window.getComputedStyle(element, null).getPropertyValue(value);\r\n };\r\n\r\n /**\r\n * Finds the largest column and returns its height value.\r\n * @return {Number} - The height value of the highest column\r\n */\r\n var findLargestColumn = function() {\r\n var arr = cache.rows;\r\n var highest = 0,\r\n runningTotal = 0;\r\n\r\n for (var i = 0, arrLen = arr.length; i < arrLen; i++) {\r\n for (var j = 0; j < arr[i].length; j++) {\r\n runningTotal += parseInt(\r\n getProperty(cache.elements[arr[i][j]], \"height\").replace(\"px\", \"\"),\r\n 10\r\n );\r\n runningTotal += j !== 0 ? cache.options.margin : 0;\r\n }\r\n\r\n highest = highest < runningTotal ? runningTotal : highest;\r\n runningTotal = 0;\r\n }\r\n\r\n return highest;\r\n };\r\n\r\n /**\r\n * Recalculates element positions.\r\n */\r\n var recalculate = function() {\r\n var columns = getCurrentColumns();\r\n\r\n if (columns === 1) {\r\n cache.container.style.height = \"auto\";\r\n each(cache.elements, function(index, ele) {\r\n ele.removeAttribute(\"style\");\r\n });\r\n\r\n return;\r\n }\r\n\r\n setWidths();\r\n cache.elements = cache.container.children;\r\n\r\n if (!cache.options.trueOrder) {\r\n shuffleOrder(columns);\r\n setContainerHeight();\r\n\r\n return;\r\n }\r\n\r\n reOrder(columns);\r\n setContainerHeight();\r\n };\r\n\r\n /**\r\n * Sets the container height to the largest row.\r\n */\r\n var setContainerHeight = function() {\r\n cache.container.style.height = findLargestColumn() + \"px\";\r\n };\r\n\r\n /**\r\n * Sorts an array of integers in accending order.\r\n * @param {Array} list - Array to be sorted\r\n * @return {Array} - Sorted Array\r\n */\r\n var bubbleSort = function(list) {\r\n var arr = list;\r\n var n = arr.length - 1;\r\n\r\n for (var i = 0; i < n; i++) {\r\n for (var j = 0; j < n; j++) {\r\n if (arr[j] > arr[j + 1]) {\r\n var temp = arr[j];\r\n arr[j] = arr[j + 1];\r\n arr[j + 1] = temp;\r\n }\r\n }\r\n }\r\n\r\n return arr;\r\n };\r\n\r\n /**\r\n * Returns an element with the selector.\r\n * @param {String} selector - Element selector String\r\n * @return {Object} - An Element with desired selector\r\n */\r\n var ele = function(selector) {\r\n return document.querySelector(selector);\r\n };\r\n\r\n /**\r\n * Returns all elements with the selector that is visible.\r\n * @param {String} selector - Element selector String\r\n * @return {Array} - An array of all visible elements with that selector\r\n */\r\n var eles = function(selector) {\r\n var nl = document.querySelectorAll(selector);\r\n var arr = [];\r\n\r\n for (var i = nl.length - 1; i >= 0; i--) {\r\n if (nl[i].offsetParent !== null) {\r\n arr.unshift(nl[i]);\r\n }\r\n }\r\n\r\n return arr;\r\n };\r\n\r\n /**\r\n * Loops through each item in an array calling a function.\r\n * @param {Array} arr - Array to be cycled through\r\n * @param {Function} func - Function to be called\r\n */\r\n var each = function(arr, func) {\r\n for (var i = 0, arrLen = arr.length; i < arrLen; i++) {\r\n func(i, arr[i]);\r\n }\r\n };\r\n\r\n /**\r\n * Fires recalculate if images are loaded.\r\n * @param {Function} during\r\n * @param {Function} after\r\n */\r\n var imagesLoaded = function(during, after) {\r\n during = during || false;\r\n after = after || false;\r\n\r\n if (typeof during === \"function\") {\r\n during();\r\n }\r\n\r\n if (currentlyLoaded >= imgsRequired && typeof after === \"function\") {\r\n after();\r\n }\r\n };\r\n\r\n /**\r\n * Removes Macy instance.\r\n */\r\n var remove = function() {\r\n each(cache.container.children, function(index, ele) {\r\n ele.removeAttribute(\"style\");\r\n });\r\n\r\n cache.container.removeAttribute(\"style\");\r\n\r\n window.removeEventListener(\"resize\", recalculate);\r\n };\r\n\r\n /**\r\n * Fires calculate on image load.\r\n * @param {Function} during\r\n * @param {Function} after\r\n */\r\n var calculateOnImageLoad = function(during, after) {\r\n var imgs = eles(\"img\");\r\n\r\n imgsRequired = imgs.length - 1;\r\n currentlyLoaded = 0;\r\n\r\n each(imgs, function(i, img) {\r\n if (img.complete) {\r\n currentlyLoaded++;\r\n imagesLoaded(during, after);\r\n\r\n return;\r\n }\r\n\r\n img.addEventListener(\"load\", function() {\r\n currentlyLoaded++;\r\n imagesLoaded(during, after);\r\n });\r\n\r\n img.addEventListener(\"error\", function() {\r\n imgsRequired--;\r\n imagesLoaded(during, after);\r\n });\r\n });\r\n };\r\n\r\n /**\r\n * Initialising function.\r\n * @param {Object} options - The configuartion object for griddly\r\n */\r\n Macy.init = function(options) {\r\n if (!options.container) {\r\n return;\r\n }\r\n\r\n cache.container = ele(options.container);\r\n\r\n if (!cache.container) {\r\n return;\r\n }\r\n\r\n delete options.container;\r\n\r\n cache.options = extend(defaults, options);\r\n\r\n window.addEventListener(\"resize\", recalculate);\r\n\r\n cache.container.style.position = \"relative\";\r\n cache.elements = cache.container.children;\r\n\r\n if (!cache.options.waitForImages) {\r\n recalculate();\r\n calculateOnImageLoad(function() {\r\n recalculate();\r\n });\r\n\r\n return;\r\n }\r\n\r\n calculateOnImageLoad(null, function() {\r\n recalculate();\r\n });\r\n };\r\n\r\n /**\r\n * Set up public methods.\r\n */\r\n Macy.recalculate = recalculate;\r\n Macy.onImageLoad = calculateOnImageLoad;\r\n Macy.remove = remove;\r\n\r\n /**\r\n * Return public API.\r\n */\r\n return Macy;\r\n});\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"AssetsListController\", [\r\n \"$scope\",\r\n \"$location\",\r\n \"$timeout\",\r\n \"$sanitize\",\r\n AssetsListController,\r\n ]);\r\n\r\n function AssetsListController($scope, $location, $timeout, $sanitize) {\r\n // Assets don't come from the server with any sort of unique identifier\r\n // For easier tracking, let's build a unique ID for each asset\r\n var assetIdCounter = 0;\r\n var generateAssetId = function() {\r\n assetIdCounter += 1;\r\n return assetIdCounter;\r\n };\r\n\r\n var processAndGroupAssets = function(obj, opts) {\r\n opts = opts || {};\r\n\r\n for (var i = 0; i < obj.length; i++) {\r\n var generatedId = generateAssetId();\r\n\r\n obj[i].id = generatedId;\r\n\r\n $scope.state.assetsById[generatedId] = {\r\n name: obj[i].name,\r\n isTopLevel: !opts.isChildOfGroupId,\r\n };\r\n\r\n $scope.state.assetsByName[obj[i].name] = {\r\n id: generatedId,\r\n isTopLevel: !opts.isChildOfGroupId,\r\n };\r\n\r\n if (opts.isChildOfGroupId) {\r\n $scope.state.assetsByGroup[opts.isChildOfGroupId].push(obj[i].id);\r\n }\r\n\r\n if (\r\n opts.topLevelGroupId &&\r\n opts.topLevelGroupId !== opts.isChildOfGroupId\r\n ) {\r\n $scope.state.assetsByGroup[opts.topLevelGroupId].push(obj[i].id);\r\n // console.log(opts.domainName);\r\n }\r\n\r\n if (opts.domainName) {\r\n $scope.state.domainsByAsset[obj[i].id] = opts.domainName;\r\n }\r\n\r\n // If this asset is a group with children, need to assign IDs for those too\r\n // Since this isn't the first run, we also need to pass the new group ID\r\n if (obj[i].children.length) {\r\n // Since this asset has children, create a group for it if it doesn't already exist\r\n $scope.state.assetsByGroup[obj[i].id] =\r\n $scope.state.assetsByGroup[obj[i].id] || [];\r\n processAndGroupAssets(obj[i].children, {\r\n isChildOfGroupId: obj[i].id,\r\n topLevelGroupId: opts.topLevelGroupId || obj[i].id,\r\n domainName: opts.domainName || obj[i].name,\r\n });\r\n }\r\n }\r\n };\r\n\r\n // First run is top-level, defines groups\r\n processAndGroupAssets($scope.state.assets);\r\n\r\n $scope.state.findGroupDomain = function(groupName) {\r\n var output;\r\n $scope.domains.forEach(function(elm, ind, arr) {\r\n if (groupName === elm.name) {\r\n output = elm;\r\n }\r\n });\r\n return output;\r\n };\r\n\r\n $scope.state.selectAsset = function(assetId) {\r\n if ($scope.state.selectedAssets.indexOf(assetId) < 0) {\r\n $scope.state.selectedAssets.push(assetId);\r\n // $scope.rrMap.styleShape($scope.state.assetsById[assetId].fips);\r\n }\r\n };\r\n $scope.state.selectParent = function(assetId) {\r\n if ($scope.state.selectedParent.indexOf(assetId) < 0) {\r\n $scope.state.selectedParent.push(assetId);\r\n // $scope.rrMap.styleShape($scope.state.assetsById[assetId].fips);\r\n }\r\n };\r\n if (!$scope.state.selectedAssets) {\r\n if ($location.search().assets) {\r\n var assetIds = [];\r\n $location\r\n .search()\r\n .assets.split(\"|\")\r\n .forEach(function(asset, ind, arr) {\r\n // Child assets are prefixed by their parent and a slash. Break that out.\r\n asset = asset.split(\"/\");\r\n asset = asset[asset.length - 1];\r\n assetIds.push($scope.state.assetsByName[asset].id);\r\n });\r\n $scope.state.selectedAssets = assetIds;\r\n }\r\n else {\r\n $scope.state.selectedAssets = [];\r\n }\r\n }\r\n \r\n $scope.state.hasChildAssets = function(assetId) { \r\n var children = $scope.state.assetsByGroup[assetId];\r\n return (children && children.length > 0);\r\n };\r\n\r\n if (!$scope.state.selectedParent) {\r\n if ($location.search().assets) {\r\n var parentAssetIds = [];\r\n $location\r\n .search()\r\n .assets.split(\"|\")\r\n .forEach(function(asset, ind, arr) {\r\n // Child assets are prefixed by their parent and a slash. Break that out.\r\n asset = asset.split(\"/\");\r\n asset = asset[0];\r\n var candidateId = $scope.state.assetsByName[asset].id;\r\n if ($scope.state.hasChildAssets(candidateId)) {\r\n parentAssetIds.push(candidateId);\r\n }\r\n });\r\n $scope.state.selectedParent = parentAssetIds;\r\n }\r\n else {\r\n $scope.state.selectedParent = [];\r\n }\r\n }\r\n\r\n $scope.state.removeAsset = function(assetId) {\r\n var index = $scope.state.selectedAssets.indexOf(assetId);\r\n if (index > -1) {\r\n $scope.state.selectedAssets.splice(index, 1);\r\n // $scope.rrMap.styleShape($scope.state.communitiesById[assetId].fips);\r\n }\r\n };\r\n $scope.state.removeParent = function(assetId) {\r\n var index = $scope.state.selectedParent.indexOf(assetId);\r\n\r\n if (index > -1) {\r\n $scope.state.selectedParent.splice(index, 1);\r\n // $scope.rrMap.styleShape($scope.state.communitiesById[assetId].fips);\r\n }\r\n };\r\n $scope.state.triggerLabelClick = function(assetID, $event) {\r\n var element = $($event.currentTarget);\r\n if (\r\n !$(element)\r\n .attr(\"class\")\r\n .split(\" \")\r\n .includes(\"is-checked\")\r\n ) {\r\n $scope.state.openAccordion(element);\r\n }\r\n\r\n $scope.state.toggleAsset(assetID);\r\n $scope.state.toggleParent(assetID);\r\n };\r\n\r\n $scope.state.toggleParent = function(assetId) {\r\n var children = $scope.state.assetsByGroup[assetId];\r\n var selectedAssets = $scope.state.selectedAssets;\r\n var hasSelectedChildren = selectedAssets.filter(function(e) {\r\n return children && children.indexOf(e) > -1;\r\n });\r\n if (\r\n $scope.state.assetChildIsSelected(assetId) &&\r\n hasSelectedChildren.length == 0\r\n ) {\r\n $scope.state.removeParent(assetId);\r\n return false;\r\n }\r\n else if ($scope.state.hasChildAssets(assetId)) {\r\n $scope.state.selectParent(assetId);\r\n return true;\r\n }\r\n };\r\n $scope.state.toggleAsset = function(assetId) {\r\n // Tracks when a user clicks on a resource checkbox button on the \"Map Community Resources\" page\r\n var resourceName = $scope.state.assetsById[assetId].name;\r\n dataLayer.push({\r\n event: \"Resource Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Resource Click|\" + resourceName,\r\n eventLabel: window.location.pathname,\r\n });\r\n\r\n if ($scope.state.assetsByGroup[assetId]) {\r\n return $scope.state.toggleAssetsByParent(assetId);\r\n }\r\n else {\r\n if ($scope.state.assetIsSelected(assetId)) {\r\n $scope.state.removeAsset(assetId);\r\n return false;\r\n }\r\n else {\r\n $scope.state.selectAsset(assetId);\r\n return true;\r\n }\r\n }\r\n };\r\n\r\n $scope.state.toggleAccordion = function($event) {\r\n var element = $event.currentTarget;\r\n var parent = $(element).parent();\r\n if (\r\n parent\r\n .attr(\"class\")\r\n .split(\" \")\r\n .includes(\"open\")\r\n ) {\r\n $(parent).removeClass(\"open\");\r\n }\r\n else {\r\n $(parent).addClass(\"open\");\r\n }\r\n };\r\n\r\n $scope.state.openAccordion = function(element) {\r\n var parent = element.parent().parent();\r\n if (\r\n !parent\r\n .attr(\"class\")\r\n .split(\" \")\r\n .includes(\"open\")\r\n ) {\r\n $(parent).addClass(\"open\");\r\n }\r\n };\r\n\r\n $scope.state.toggleAssetsByParent = function(groupId, forceSelect) {\r\n if ($scope.state.assetGroupIsSelected(groupId) && !forceSelect) {\r\n $scope.state.assetsByGroup[groupId].forEach(function(val, ind, arr) {\r\n if ($scope.state.assetsByGroup[val]) {\r\n return $scope.state.toggleAssetsByParent(val, true);\r\n }\r\n else {\r\n $scope.state.removeAsset(val);\r\n }\r\n });\r\n $scope.state.removeAsset(groupId);\r\n }\r\n else {\r\n $scope.state.assetsByGroup[groupId].forEach(function(val, ind, arr) {\r\n if ($scope.state.assetsByGroup[val]) {\r\n return $scope.state.toggleAssetsByParent(val, true);\r\n }\r\n else {\r\n $scope.state.selectAsset(val);\r\n }\r\n });\r\n $scope.state.selectAsset(groupId);\r\n }\r\n };\r\n\r\n $scope.state.allAssetsAreSelected = function() {\r\n return (\r\n $scope.state.selectedAssets.length ===\r\n Object.keys($scope.state.assetsById).length\r\n );\r\n };\r\n\r\n $scope.state.toggleAllAssets = function() {\r\n var allSelected = $scope.state.allAssetsAreSelected();\r\n\r\n for (var asset in $scope.state.assetsById) {\r\n if ($scope.state.assetsById.hasOwnProperty(asset)) {\r\n // Need to coerce `asset` to a Number\r\n if (allSelected) {\r\n $scope.state.removeAsset(+asset);\r\n }\r\n else {\r\n $scope.state.selectAsset(+asset);\r\n }\r\n }\r\n }\r\n };\r\n\r\n $scope.state.assetIsSelected = function(assetId) {\r\n if ($scope.state.assetsByGroup[assetId]) {\r\n return $scope.state.assetGroupIsSelected(assetId);\r\n }\r\n else {\r\n return $scope.state.selectedAssets.indexOf(assetId) >= 0;\r\n }\r\n };\r\n\r\n $scope.state.assetChildIsSelected = function(assetId) {\r\n var matchedArray = $scope.state.selectedParent.filter(function(e) {\r\n return e == assetId;\r\n });\r\n\r\n if (matchedArray.length > 0) {\r\n return true;\r\n }\r\n };\r\n $scope.state.assetGroupIsSelected = function(groupId) {\r\n // Quick exist if we haven't created groups yet\r\n if (!$scope.state.assetsByGroup[groupId]) {\r\n return false;\r\n }\r\n\r\n var matchedArray = $scope.state.selectedAssets.filter(function(elem) {\r\n return $scope.state.assetsByGroup[groupId].indexOf(elem) > -1;\r\n });\r\n if (matchedArray.length == $scope.state.assetsByGroup[groupId].length) {\r\n $scope.state.selectAsset(groupId);\r\n return true;\r\n }\r\n else {\r\n $scope.state.removeAsset(groupId);\r\n return false;\r\n }\r\n };\r\n\r\n $scope.state.assetGroupHasSelectedAssets = function(assetGroup) {\r\n for (var i = 0; i < assetGroup.children.length; i++) {\r\n var child = assetGroup.children[i];\r\n if (\r\n $scope.state.assetIsSelected(child.id) ||\r\n $scope.state.assetChildIsSelected(child.id)\r\n ) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n };\r\n\r\n // When the selected year changes, we need to default to the highest- and\r\n // lowest-ranked communities ONLY if the user hasn't made any selections themselves\r\n $scope.state.checkForSelectedAssets = function(before) {\r\n // before = communitiesByRank before `selectedYear` pulled in new data\r\n var after = $scope.state.communitiesByRank;\r\n\r\n var highestRankBefore = before ? before[0] : {};\r\n var lowestRankBefore = before ? before[before.length - 1] : {};\r\n\r\n var highestRankAfter = after[0];\r\n var lowestRankAfter = after[after.length - 1];\r\n\r\n // By default, or if the only two selected communities are the old highest and\r\n // lowest ranked, clear the selections and select the new highest- and\r\n // lowest-ranking communities\r\n if (\r\n $scope.state.selectedAssets.length === 0 ||\r\n ($scope.state.selectedAssets.length === 2 &&\r\n $scope.state.assetIsSelected(highestRankBefore.id) &&\r\n $scope.state.assetIsSelected(lowestRankBefore.id))\r\n ) {\r\n $scope.state.selectedAssets = [];\r\n $scope.state.selectAsset(highestRankAfter.id);\r\n $scope.state.selectAsset(lowestRankAfter.id);\r\n\r\n // Restyle the old districts on the map if we need to\r\n $scope.rrMap.styleShape(highestRankBefore.fips);\r\n $scope.rrMap.styleShape(lowestRankBefore.fips);\r\n }\r\n };\r\n\r\n $scope.$watch(\r\n \"state.selectedDomain\",\r\n function(newDomain, prevDomain) {\r\n // quick exit on the first run, or if there's no default asset\r\n if (\r\n prevDomain === newDomain ||\r\n !$scope.state.selectedDomain.defaultAsset\r\n ) {\r\n return false;\r\n }\r\n\r\n var domain = $scope.state.selectedDomain;\r\n\r\n var prevDefaultAsset;\r\n if (prevDomain && prevDomain.defaultAsset) {\r\n prevDefaultAsset = $scope.state.assetsByName[prevDomain.defaultAsset.name];\r\n }\r\n\r\n var nonTopLevelAssets = $scope.state.selectedAssets.filter(function(id) {\r\n return !$scope.state.assetsById[id].isTopLevel;\r\n });\r\n\r\n function isSelectedAssetThePreviousDefault() {\r\n return prevDefaultAsset &&\r\n (prevDefaultAsset.id === nonTopLevelAssets[0] ||\r\n ($scope.state.selectedParent && $scope.state.selectedParent.includes(prevDefaultAsset.id)));\r\n }\r\n\r\n if (nonTopLevelAssets.length === 0 || (nonTopLevelAssets.length === 1 && isSelectedAssetThePreviousDefault)) {\r\n // ...clear any selected assets and select the new domain's default asset\r\n $scope.state.selectedParent = [];\r\n $scope.state.selectedAssets = [];\r\n if (!domain.defaultAsset || domain.defaultAsset.name === \"0\") { \r\n console.warn(\"No default asset found for domain '\" + domain.name + \"'.\");\r\n return;\r\n }\r\n\r\n var targetAsset;\r\n var defaultAssetChildren = domain.defaultAsset.children;\r\n if (defaultAssetChildren && defaultAssetChildren.length) {\r\n var domainAsset = $scope.state.assets.find(a => a.name === domain.name);\r\n if (!domainAsset) {\r\n console.error(\"Unable to find domain asset '\" + domain.name + '\"');\r\n return;\r\n }\r\n\r\n var parentAsset = domainAsset.children.find(a => a.name == domain.defaultAsset.name);\r\n if (!parentAsset) {\r\n console.error(\"Unable to find parent default asset '\" + domain.defaultAsset.name + '\"');\r\n return;\r\n }\r\n\r\n $scope.state.selectParent(parentAsset.id);\r\n targetAsset = parentAsset.children.find(a => a.name === defaultAssetChildren[0].name);\r\n if (!targetAsset) {\r\n console.error(\"Unable to find default child asset '\" + defaultAssetChildren[0].name + \"'.\");\r\n return;\r\n }\r\n }\r\n else {\r\n targetAsset = $scope.state.assetsByName[domain.defaultAsset.name];\r\n if (!targetAsset) {\r\n console.error(\"Unable to find default asset '\" + domain.defaultAsset.name + \"'.\");\r\n return;\r\n }\r\n }\r\n \r\n $scope.state.selectAsset(targetAsset.id);\r\n }\r\n },\r\n true,\r\n );\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccAssetsList\", cccAssetsList);\r\n\r\n function cccAssetsList() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"AssetsListController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .factory(\"AssetsService\", [\"$http\", \"$q\", AssetsService]);\r\n\r\n function AssetsService($http, $q) {\r\n var service = {\r\n getAssets: getAssets\r\n };\r\n\r\n function getAssets(assetsArray) {\r\n var deferred = $q.defer();\r\n\r\n $http({\r\n method: \"POST\",\r\n url: \"api/AssetMapping/GetAssets\",\r\n data: '{assets: \"' + assetsArray + '\"}',\r\n cache: true // this data rarely updates, no need for an instant re-request\r\n })\r\n .success(function(data) {\r\n deferred.resolve(data);\r\n })\r\n .error(function(error) {\r\n deferred.reject();\r\n });\r\n\r\n return deferred.promise;\r\n }\r\n\r\n return service;\r\n }\r\n})();\r\n","(function() {\r\n angular.module(\"app\").directive(\"clickOutToClose\", [\r\n \"$document\",\r\n \"$timeout\",\r\n function($document, $timeout) {\r\n return {\r\n restrict: \"A\",\r\n link: function(scope, elem, attr, ctrl) {\r\n var elemClickHandler = function(e) {\r\n e.stopPropagation();\r\n };\r\n\r\n var docClickHandler = function(e) {\r\n $timeout(function() {\r\n scope.$apply(attr.clickOutToClose);\r\n });\r\n };\r\n\r\n elem.on(\"click\", elemClickHandler);\r\n $document.on(\"click\", docClickHandler);\r\n\r\n // teardown the event handlers when the scope is destroyed.\r\n scope.$on(\"$destroy\", function() {\r\n elem.off(\"click\", elemClickHandler);\r\n $document.off(\"click\", docClickHandler);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"CommunitiesController\", [\r\n \"$scope\",\r\n \"$location\",\r\n \"rankingsService\",\r\n CommunitiesController\r\n ]);\r\n\r\n function CommunitiesController($scope, $location, rankingsService) {\r\n // Create the app-wide selectedCommunities object\r\n // `communities` is the actual ID store. Accessed by getters/setters, basically\r\n $scope.state.selectedCommunities = {\r\n communities: []\r\n };\r\n\r\n $scope.state.assets = window.ccc.assetPicker;\r\n $scope.state.assetsById = {};\r\n $scope.state.assetsByName = {};\r\n $scope.state.assetsByGroup = {};\r\n $scope.state.domainsByAsset = {}; // Easily pull domain name/color for an asset\r\n\r\n $scope.state.selectedCommunities.addCommunity = function(communityId) {\r\n if (\r\n $scope.state.selectedCommunities.communities.indexOf(communityId) < 0\r\n ) {\r\n $scope.state.selectedCommunities.communities.push(communityId);\r\n $scope.rrMap.styleShape($scope.state.communitiesById[communityId].fips);\r\n }\r\n };\r\n\r\n $scope.state.selectedCommunities.removeCommunity = function(\r\n communityId,\r\n doPush\r\n ) {\r\n var index = $scope.state.selectedCommunities.communities.indexOf(\r\n communityId\r\n );\r\n if (index > -1) {\r\n $scope.state.selectedCommunities.communities.splice(index, 1);\r\n\r\n // `true` parameter forces community to style as unselected/unhovered\r\n // Offers better UX when a user un-selects a community from the map itself\r\n var community = $scope.state.communitiesById[communityId];\r\n $scope.rrMap.styleShape(community.fips, true);\r\n\r\n if (doPush) {\r\n // Tracks when a user removes a community\r\n dataLayer.push({\r\n event: \"Community Removed\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Community Removed|\" + community.name,\r\n eventLabel: window.location.pathname\r\n });\r\n }\r\n }\r\n };\r\n\r\n $scope.state.selectedCommunities.toggleCommunity = function(communityId) {\r\n if ($scope.state.selectedCommunities.communityIsSelected(communityId)) {\r\n $scope.state.selectedCommunities.removeCommunity(communityId);\r\n return false;\r\n }\r\n else {\r\n $scope.state.selectedCommunities.addCommunity(communityId);\r\n return true;\r\n }\r\n };\r\n\r\n $scope.state.selectedCommunities.toggleCommunitiesByGroup = function(\r\n groupName\r\n ) {\r\n if ($scope.state.selectedCommunities.groupIsSelected(groupName)) {\r\n $scope.state.communitiesByGroup[groupName].forEach(function(\r\n val,\r\n ind,\r\n arr\r\n ) {\r\n $scope.state.selectedCommunities.removeCommunity(val);\r\n });\r\n }\r\n else {\r\n $scope.state.communitiesByGroup[groupName].forEach(function(\r\n val,\r\n ind,\r\n arr\r\n ) {\r\n $scope.state.selectedCommunities.addCommunity(val);\r\n });\r\n }\r\n };\r\n\r\n $scope.state.selectedCommunities.communityIsSelected = function(\r\n communityId,\r\n filteredCommunitiesByGroup\r\n ) {\r\n var selectedCommunities = $scope.state.selectedCommunities.communities;\r\n if (selectedCommunities.indexOf(communityId) < 0) {\r\n return false;\r\n }\r\n if (\r\n $scope.currentURL.search().cap &&\r\n filteredCommunitiesByGroup &&\r\n selectedCommunities.length > 25\r\n ) {\r\n var communityIndex = filteredCommunitiesByGroup\r\n .filter(function(community) {\r\n return selectedCommunities.indexOf(community.id) > -1;\r\n })\r\n .map(function(community) {\r\n return community.id;\r\n })\r\n .indexOf(communityId);\r\n\r\n return communityIndex > -1 && communityIndex < 5;\r\n }\r\n return true;\r\n };\r\n\r\n $scope.state.selectedCommunities.groupIsSelected = function(groupName) {\r\n // Quick exit if we haven't built out the requisite datasets yet\r\n if (!$scope.state.communitiesByGroup[groupName]) {\r\n return false;\r\n }\r\n\r\n var matchedArray = $scope.state.selectedCommunities.communities.filter(\r\n function(elem) {\r\n return $scope.state.communitiesByGroup[groupName].indexOf(elem) > -1;\r\n }\r\n );\r\n\r\n return (\r\n matchedArray.length == $scope.state.communitiesByGroup[groupName].length\r\n );\r\n };\r\n\r\n $scope.state.selectedCommunities.communitiesByGroup = function(groupName) {\r\n var selectedCommunitiesByGroup = $scope.state.communitiesByGroup[\r\n groupName\r\n ].filter(function(communityId) {\r\n return $scope.state.selectedCommunities.communityIsSelected(\r\n communityId\r\n );\r\n });\r\n return selectedCommunitiesByGroup;\r\n };\r\n\r\n // When the selected year changes, we need to default to the highest- and\r\n // lowest-ranked communities ONLY if the user hasn't made any selections themselves\r\n $scope.state.selectedCommunities.checkForUserSelections = function(before) {\r\n // before = communitiesByRank before `selectedYear` pulled in new data\r\n var after = $scope.state.communitiesByRank;\r\n\r\n var highestRankBefore = rankingsService.highestRanked(before);\r\n var lowestRankBefore = rankingsService.lowestRanked(before);\r\n\r\n var highestRankAfter = rankingsService.highestRanked(after);\r\n var lowestRankAfter = rankingsService.lowestRanked(after);\r\n\r\n function id(x) {\r\n return x.id;\r\n }\r\n\r\n function oldDefaultsAreSelected() {\r\n var allSelectedBefore = highestRankBefore.concat(lowestRankBefore);\r\n return allSelectedBefore.reduce(function(accum, cd) {\r\n return (\r\n accum && $scope.state.selectedCommunities.communityIsSelected(cd.id)\r\n );\r\n }, allSelectedBefore.length ===\r\n $scope.state.selectedCommunities.communities.length);\r\n }\r\n\r\n // By default, or if the only two selected communities are the old highest and\r\n // lowest ranked, clear the selections and select the new highest- and\r\n // lowest-ranking communities\r\n if (\r\n $scope.state.selectedCommunities.communities.length === 0 ||\r\n oldDefaultsAreSelected()\r\n ) {\r\n $scope.state.selectedCommunities.communities = [];\r\n\r\n highestRankAfter.concat(lowestRankAfter).forEach(function(cd) {\r\n $scope.state.selectedCommunities.addCommunity(cd.id);\r\n });\r\n\r\n highestRankBefore.concat(lowestRankBefore).forEach(function(cd) {\r\n $scope.rrMap.styleShape(cd.fips);\r\n });\r\n }\r\n };\r\n\r\n $scope.state.selectedCommunities.build = function(\r\n response,\r\n oldCommunityRanks\r\n ) {\r\n $scope.state.communityData = response;\r\n\r\n // Depending on what data a method has available, we might need to find a community\r\n // by a variety of keys. Group (Lowest Risk, Medium Risk, etc), ID, Fips ID, etc.\r\n // Pre-building these objects makes it easier and faster than walking the main obj.\r\n var sortedByGroup = {};\r\n var sortedById = {};\r\n var sortedByFips = {};\r\n\r\n $scope.state.communityData.map(function(community) {\r\n sortedByGroup[community.group] = sortedByGroup[community.group] || [];\r\n sortedByGroup[community.group].push(community.id);\r\n sortedById[community.id] = community;\r\n sortedByFips[community.fips] = community;\r\n });\r\n\r\n $scope.state.communitiesByGroup = sortedByGroup;\r\n $scope.state.communitiesById = sortedById;\r\n $scope.state.communitiesByFips = sortedByFips;\r\n\r\n var sortedCommunities = $scope.state.communityData.sort(function(a, b) {\r\n return a.rank - b.rank;\r\n });\r\n\r\n $scope.state.communitiesByRank = sortedCommunities;\r\n\r\n if ($scope.state.selectedCommunities) {\r\n if (\r\n !$scope.state.selectedCommunities.communities.length &&\r\n $location.search().communities\r\n ) {\r\n $location\r\n .search()\r\n .communities.split(\"|\")\r\n .forEach(function(id, ind, arr) {\r\n $scope.state.selectedCommunities.addCommunity(+id); // coerse to int\r\n });\r\n }\r\n else {\r\n $scope.state.selectedCommunities.checkForUserSelections(\r\n oldCommunityRanks\r\n );\r\n }\r\n }\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccCommunities\", cccCommunities);\r\n\r\n function cccCommunities() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"CommunitiesController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .factory(\"CommunitiesService\", [\"$http\", \"$q\", CommunitiesService]);\r\n\r\n function CommunitiesService($http, $q) {\r\n var _communities = [];\r\n var service = {\r\n getCommunityData: getCommunityData\r\n };\r\n\r\n // Prevents unnecessary API calls\r\n var communitiesAreLoaded = false;\r\n\r\n function getCommunityData(domainId, yearId, includeOtherIndicators) {\r\n var deferred = $q.defer();\r\n\r\n $http({\r\n method: \"GET\",\r\n url:\r\n \"api/RiskRankingData/GetDomainLocation?id=\" +\r\n domainId +\r\n \"&timeFrame=\" +\r\n yearId +\r\n \"&includeOtherIndicators=\" +\r\n (includeOtherIndicators ? \"true\" : \"false\"),\r\n cache: true // this data rarely updates, no need for an instant re-request\r\n })\r\n .success(function(data) {\r\n processCommunityData(data);\r\n })\r\n .error(function(error) {\r\n deferred.reject();\r\n });\r\n\r\n function processCommunityData(data) {\r\n deferred.resolve(data);\r\n }\r\n\r\n return deferred.promise;\r\n }\r\n\r\n return service;\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"CommunitiesListController\", [\r\n \"$scope\",\r\n CommunitiesListController\r\n ]);\r\n\r\n function CommunitiesListController($scope) {\r\n $scope.sortList = function(indicator, isReverse) {\r\n $scope.sorting.indicatorId = indicator;\r\n $scope.sorting.indicator = \"indicators[\" + indicator + \"].value\";\r\n $scope.sorting.reverse = isReverse;\r\n };\r\n\r\n $scope.isActiveSortingDirection = function(indicatorId, isReverse) {\r\n return (\r\n $scope.sorting.indicatorId === indicatorId &&\r\n $scope.sorting.reverse === isReverse\r\n );\r\n };\r\n\r\n $scope.state.selectedCommunities.checkForUserSelections();\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccCommunitiesList\", cccCommunitiesList);\r\n\r\n function cccCommunitiesList() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"CommunitiesListController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"CommunityDataTableController\", [\r\n \"$scope\",\r\n CommunityDataTableController\r\n ]);\r\n\r\n function CommunityDataTableController($scope) {\r\n // By default, sort by the first indicator returned from the API\r\n // This will be \"Overall Risk Ranking\", if required\r\n $scope.sorting = {\r\n indicator:\r\n \"indicators[\" +\r\n $scope.state.selectedDomain.indicators[0].id +\r\n \"].value\",\r\n indicatorId: $scope.state.selectedDomain.indicators[0].id,\r\n reverse: false\r\n };\r\n\r\n // Middleware-ish for community data table ng-repeat\r\n // ng-repeat iterates through all communities, this filter checks if community is selected\r\n // That way, we have access to all the most recent community data without data duplication\r\n $scope.findSelectedCommunities = function(value, index, array) {\r\n if (!$scope.state.selectedCommunities) {\r\n return false;\r\n }\r\n return $scope.state.selectedCommunities.communityIsSelected(value.id);\r\n };\r\n\r\n $scope.sorting.sortTable = function(indicator) {\r\n if (indicator === $scope.sorting.indicatorId) {\r\n // User is re-sorting the currently selected indicator. Just flip the order.\r\n $scope.sorting.reverse = !$scope.sorting.reverse;\r\n }\r\n else {\r\n // User has selected to a new indicator. Update scope, default to top-down sorting.\r\n $scope.sorting.indicatorId = indicator;\r\n $scope.sorting.indicator = \"indicators[\" + indicator + \"].value\";\r\n $scope.sorting.reverse = false;\r\n }\r\n };\r\n\r\n $scope.isActiveSortingDirection = function(indicatorId, isReverse) {\r\n return (\r\n $scope.sorting.indicatorId === indicatorId &&\r\n $scope.sorting.reverse === isReverse\r\n );\r\n };\r\n\r\n $scope.domainHasIndicator = function(domain, indicator) {\r\n for (var prop in domain) {\r\n if (domain.hasOwnProperty(prop) && +prop === +indicator) {\r\n return true;\r\n }\r\n }\r\n\r\n return false;\r\n };\r\n\r\n // Basically, the \"Overall Risk Ranking\" indicator needs to render as \"Overall Rank\".\r\n // Because that indicator is arbitrary, we need to do a string match to check the indicator\r\n // and tweak the name if need be.\r\n $scope.parseIndicatorName = function(indicator) {\r\n return indicator === \"Overall Risk Ranking\" ? \"Overall Rank\" : indicator;\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .directive(\"cccCommunityDataTable\", cccCommunityDataTable);\r\n\r\n function cccCommunityDataTable() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"CommunityDataTableController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"DomainIndicatorPickerController\", [\r\n \"$scope\",\r\n \"$location\",\r\n \"LegendService\",\r\n DomainIndicatorPickerController\r\n ]);\r\n\r\n function DomainIndicatorPickerController($scope, $location, LegendService) {\r\n $scope.state.mapLegend = undefined;\r\n $scope.legend.colorByCommunity = {};\r\n\r\n $scope.state.refreshLegend = function() {\r\n LegendService.getLegend($scope.state.selectedIndicator.id).then(function(\r\n response\r\n ) {\r\n $scope.state.mapLegend = response;\r\n\r\n // When the indicator changes, the community data does too.\r\n // Re-style each community to display the proper ranking\r\n $scope.state.communityData.forEach(function(community, ind, arr) {\r\n var commIndValue =\r\n community.indicators[$scope.state.selectedIndicator.id];\r\n if (commIndValue && commIndValue.value) {\r\n $scope.state.mapLegend.forEach(function(legend, ind, arr) {\r\n if (\r\n commIndValue.value <= legend.highDataValue &&\r\n commIndValue.value >= legend.lowDataValue\r\n ) {\r\n community.indicatorGroup = ind;\r\n $scope.legend.colorByCommunity[community.id] =\r\n $scope.legend.colors[ind];\r\n }\r\n });\r\n }\r\n\r\n $scope.rrMap.styleShape(community.fips);\r\n });\r\n });\r\n };\r\n\r\n $scope.$watch(\"state.selectedIndicator\", function(oldVal, newVal) {\r\n if (oldVal === newVal) {\r\n return false;\r\n }\r\n\r\n $scope.state.indicatorPickerIsOpen = false;\r\n $scope.state.refreshLegend();\r\n $location.search(\"indicator\", $scope.state.selectedIndicator.id);\r\n\r\n // Tracks when users select different Risk Ranking indicators from a drop down under a domain (e.g. health) on the \"Asset Mapping\" page\r\n dataLayer.push({\r\n event: \"Risk Ranking Indicator Click on Asset Page\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Domain Indicator |\" + $scope.state.selectedIndicator.name,\r\n eventLabel: window.location.pathname\r\n });\r\n });\r\n\r\n if ($location.search().indicator) {\r\n $scope.state.selectedIndicator = $scope.state.selectedDomain.indicators.find(\r\n function(indicator) {\r\n if (indicator.id == $location.search().indicator) {\r\n return indicator;\r\n }\r\n }\r\n );\r\n }\r\n\r\n if (!$scope.state.selectedIndicator) {\r\n $scope.state.selectedIndicator = $scope.state.selectedDomain.indicators.find(\r\n function(indicator) {\r\n if (indicator.id == $scope.state.selectedDomain.id) {\r\n return indicator;\r\n }\r\n }\r\n );\r\n }\r\n\r\n if (!$scope.state.selectedIndicator) {\r\n $scope.state.selectedIndicator =\r\n $scope.state.selectedDomain.indicators[\r\n $scope.state.selectedDomain.indicators.length - 1\r\n ];\r\n }\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .directive(\"cccDomainIndicatorPicker\", cccDomainIndicatorPicker);\r\n\r\n function cccDomainIndicatorPicker() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"DomainIndicatorPickerController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"DomainSelectorController\", [\r\n \"$scope\",\r\n \"$attrs\",\r\n \"$location\",\r\n \"CommunitiesService\",\r\n \"DomainSelectorService\",\r\n \"LoadingService\",\r\n DomainSelectorController\r\n ]);\r\n\r\n function DomainSelectorController(\r\n $scope,\r\n $attrs,\r\n $location,\r\n CommunitiesService,\r\n DomainSelectorService,\r\n LoadingService\r\n ) {\r\n // We receive domain border color data from the API as a hex code\r\n // Need to convert that to RGBA for hover styling\r\n var hexToRgba = function(hex, opacity) {\r\n hex = hex.replace(\"#\", \"\");\r\n var r = parseInt(hex.substring(0, 2), 16);\r\n var g = parseInt(hex.substring(2, 4), 16);\r\n var b = parseInt(hex.substring(4, 6), 16);\r\n\r\n return \"rgba(\" + r + \",\" + g + \",\" + b + \",\" + opacity / 100 + \")\";\r\n };\r\n\r\n // Domain tab is clicked\r\n $scope.selectDomain = function(domain) {\r\n var domainLoadingId = LoadingService.taskStart(\r\n $scope,\r\n \"Loading Domain Data\"\r\n );\r\n\r\n DomainSelectorService.getDomainById(domain, $attrs.assetMap).then(\r\n function(response) {\r\n LoadingService.taskEnd($scope, domainLoadingId);\r\n $scope.state.selectedDomain = response.data;\r\n\r\n $location.search(\"domain\", $scope.state.selectedDomain.id);\r\n\r\n if ($attrs.assetMap) {\r\n // Domains should default to the Risk Ranking indicator\r\n // But that indicator needs to be generated ourselves\r\n $scope.state.selectedDomain.indicators.push({\r\n id: $scope.state.selectedDomain.id,\r\n name: $scope.state.selectedDomain.name.match(/^overall.*/i)\r\n ? $scope.state.selectedDomain.name\r\n : $scope.state.selectedDomain.name + \" Risk Ranking\"\r\n });\r\n }\r\n\r\n // If there's already a year selected, check if the new domain has data for that\r\n // year. If so, keep that year selected; otherwise, default to the most recent year\r\n\r\n if ($location.search().year) {\r\n $scope.state.selectedYear = {\r\n id: +$location.search().year // coerse to int\r\n };\r\n }\r\n\r\n if ($scope.state.selectedYear) {\r\n var hasSelectedYear;\r\n\r\n // Run through the response data `years` array of objects to find any match\r\n response.data.years.filter(function(year, index, array) {\r\n if (year.id === $scope.state.selectedYear.id) {\r\n hasSelectedYear = index;\r\n return true;\r\n }\r\n return false;\r\n });\r\n\r\n $scope.state.selectedYear = hasSelectedYear\r\n ? response.data.years[hasSelectedYear]\r\n : response.data.years[0];\r\n }\r\n else {\r\n $scope.state.selectedYear = response.data.years[0];\r\n }\r\n\r\n // Map the new domain's groups to our legend colors\r\n $scope.legend.colorByGroup = {};\r\n $scope.state.selectedDomain.groups.map(function(group, index) {\r\n $scope.legend.colorByGroup[group] = $scope.legend.colors[index];\r\n return true;\r\n });\r\n\r\n if ($attrs.assetMap) {\r\n var communityLoadingId = LoadingService.taskStart(\r\n $scope,\r\n \"Loading Community Data\"\r\n );\r\n CommunitiesService.getCommunityData(\r\n $scope.state.selectedDomain.id,\r\n $scope.state.selectedYear.id,\r\n true\r\n ).then(\r\n function(response) {\r\n LoadingService.taskEnd($scope, communityLoadingId);\r\n var oldCommunityRanks = $scope.state.communitiesByRank;\r\n $scope.state.selectedCommunities.build(\r\n response.communityDistrictData,\r\n oldCommunityRanks\r\n );\r\n\r\n // Domains should default to the Risk Ranking indicator\r\n // As a fall-back, default the default (ha) to the last indicator (usually Risk Ranking)\r\n var newIndicator =\r\n $scope.state.selectedDomain.indicators[\r\n $scope.state.selectedDomain.indicators.length - 1\r\n ];\r\n\r\n $scope.state.selectedDomain.indicators.forEach(function(\r\n indicator\r\n ) {\r\n // Indicator id for Risk Ranking is just the domain ID\r\n if (indicator.id === $scope.state.selectedDomain.id) {\r\n newIndicator = indicator;\r\n }\r\n });\r\n\r\n if ($scope.state.selectedIndicator) {\r\n // If there already a selected indicator, and the new domain also shares that indicator,\r\n // don't update the indicator. Otherwise, default to Risk Ranking.\r\n if (\r\n $scope.state.selectedDomain.indicators.indexOf(\r\n $scope.state.selectedIndicator\r\n ) === -1\r\n ) {\r\n $scope.state.selectedIndicator = newIndicator;\r\n }\r\n }\r\n else {\r\n $scope.state.selectedIndicator = newIndicator;\r\n }\r\n\r\n $scope.state.refreshLegend();\r\n\r\n // Now that we have all the community data we need, render that map\r\n $scope.rrMap.render();\r\n },\r\n function(response) {\r\n console.warn(\"getCommunityData failed!\");\r\n }\r\n );\r\n }\r\n },\r\n function(response) {\r\n console.warn(\"getDomainById failed!\");\r\n }\r\n );\r\n };\r\n\r\n $scope.setDomainHoverState = function(domainIndex, isHovered) {\r\n $scope.domains[domainIndex].isHovered = isHovered;\r\n };\r\n\r\n $scope.domainBorderStyle = function(domainIndex) {\r\n var thisDomain = $scope.domains[domainIndex]; // Sugar\r\n\r\n // If the domain is selected, give it the full color border, hovering or not\r\n if (\r\n $scope.state.selectedDomain &&\r\n $scope.state.selectedDomain.id === thisDomain.id\r\n ) {\r\n return \"#\" + thisDomain.color;\r\n }\r\n\r\n // If a domain isn't selected, but is hovered, give it a colored border at 40% opacity\r\n if (thisDomain.isHovered) {\r\n return hexToRgba(thisDomain.color, 40);\r\n }\r\n\r\n // Otherwise, just return nothing / no style changes\r\n return \"\";\r\n };\r\n\r\n var init = function() {\r\n // `window.ccc` is set in index.cshtml, populated from data provided by the view model\r\n // Prevents an unnecessary API request\r\n $scope.domains = window.ccc.domains;\r\n\r\n // Creating a `state` object on ``$scope` makes it easier for nested directives to\r\n // access our app-wide state via JS prototypal inheritance, without resorting to `$parent`.\r\n $scope.state = {};\r\n\r\n $scope.state.domainsByName = {};\r\n $scope.domains.map(function(domain) {\r\n $scope.state.domainsByName[domain.name] = domain;\r\n return true;\r\n });\r\n\r\n // If the user hasn't specified a domain, default to Overall Risk Ranking\r\n if ($location.search().domain) {\r\n var defaultDomainIndex = 0;\r\n var domainId = $location.search().domain;\r\n $scope.domains.forEach(function(domain, ind, arr) {\r\n if (domain.id == domainId) {\r\n defaultDomainIndex = ind;\r\n }\r\n });\r\n $scope.selectDomain($scope.domains[defaultDomainIndex]);\r\n }\r\n else {\r\n $scope.selectDomain($scope.domains[0]);\r\n }\r\n };\r\n\r\n // Ready, set, go!\r\n init();\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccDomainSelector\", cccDomainSelector);\r\n\r\n function cccDomainSelector() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"DomainSelectorController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .factory(\"DomainSelectorService\", [\"$http\", \"$q\", DomainSelectorService]);\r\n\r\n function DomainSelectorService($http, $q) {\r\n var service = {\r\n getDomainById: getDomainById,\r\n };\r\n\r\n function getDomainById(domain, includeOtherIndicators) {\r\n var deferred = $q.defer();\r\n\r\n $http({\r\n method: \"GET\",\r\n url:\r\n \"/api/RiskRankingData/GetDomainById?id=\" +\r\n domain.id +\r\n \"&includeOtherIndicators=\" +\r\n (includeOtherIndicators ? \"true\" : \"false\"),\r\n cache: true, // this data rarely updates, no need for an instant re-request\r\n })\r\n .success(function(data) {\r\n processDomainData(data);\r\n })\r\n .error(function(error) {\r\n deferred.reject();\r\n });\r\n\r\n var processDomainData = function(data) {\r\n if (domain.defaultAsset) {\r\n data.defaultAsset = domain.defaultAsset[0];\r\n }\r\n\r\n deferred.resolve({\r\n data: data,\r\n });\r\n };\r\n\r\n return deferred.promise;\r\n }\r\n\r\n return service;\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"DomainYearPickerController\", [\r\n \"$scope\",\r\n \"$location\",\r\n \"CommunitiesService\",\r\n \"LoadingService\",\r\n DomainYearPickerController\r\n ]);\r\n\r\n function DomainYearPickerController(\r\n $scope,\r\n $location,\r\n CommunitiesService,\r\n LoadingService\r\n ) {\r\n // This is a `$watch`er instead of a simple method so changes made by other controllers\r\n // will still trigger this API request. Primary use-case: when a user selects a domain\r\n // for the first time, `selectedYear` defaults to the most recent year, and we want to\r\n // immediately request and display the appropriate community data for that year.\r\n $scope.$watch(\"state.selectedYear\", function() {\r\n $scope.yearPickerIsOpen = false;\r\n\r\n $location.search(\"year\", $scope.state.selectedYear.id);\r\n // We pass this to `checkForUserSelections` to compare against the updated community\r\n // ranks, checking to see if we need to make any selection changes\r\n var oldCommunityRanks = $scope.state.communitiesByRank;\r\n\r\n var communityLoadingId = LoadingService.taskStart(\r\n $scope.$parent,\r\n \"Loading Community Data\"\r\n );\r\n\r\n CommunitiesService.getCommunityData(\r\n $scope.state.selectedDomain.id,\r\n $scope.state.selectedYear.id\r\n ).then(\r\n function(response) {\r\n LoadingService.taskEnd($scope.$parent, communityLoadingId);\r\n\r\n $scope.state.selectedCommunities.build(\r\n response.communityDistrictData,\r\n oldCommunityRanks\r\n );\r\n\r\n if (\r\n response.cityDemographics &&\r\n Object.keys(response.cityDemographics).length\r\n ) {\r\n $scope.state.citywideStats = response.cityDemographics;\r\n }\r\n\r\n // Now that we have all the community data we need, render that map\r\n $scope.rrMap.render();\r\n\r\n // When the indicator changes, the community data does too.\r\n // Re-style each community to display the proper ranking\r\n $scope.state.communityData.forEach(function(community, ind, arr) {\r\n $scope.rrMap.styleShape(community.fips);\r\n });\r\n },\r\n function(response) {\r\n console.warn(\"getCommunityData failed!\");\r\n }\r\n );\r\n });\r\n\r\n $scope.dataLayerPush = function() {\r\n // Tracks use of the domain year selector dropdown on the \"Risk Ranking\" page\r\n dataLayer.push({\r\n event: \"Domain Year Selector Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Domain Year Selector|\" + $scope.state.selectedYear.name,\r\n eventLabel: window.location.pathname\r\n });\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccDomainYearPicker\", cccDomainYearPicker);\r\n\r\n function cccDomainYearPicker() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"DomainYearPickerController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .factory(\"LegendService\", [\"$http\", \"$q\", LegendService]);\r\n\r\n function LegendService($http, $q) {\r\n var service = {\r\n getLegend: getLegend\r\n };\r\n\r\n function getLegend(indicatorID) {\r\n var deferred = $q.defer();\r\n\r\n $http({\r\n method: \"GET\",\r\n url: \"api/AssetMapping/GetLegend?id=\" + indicatorID,\r\n cache: true // this data rarely updates, no need for an instant re-request\r\n })\r\n .success(function(data) {\r\n deferred.resolve(data);\r\n })\r\n .error(function(error) {\r\n deferred.reject();\r\n });\r\n\r\n return deferred.promise;\r\n }\r\n\r\n return service;\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").factory(\"LoadingService\", [LoadingService]);\r\n\r\n // As noted in http://stackoverflow.com/a/2117523/302749\r\n function generateTaskId() {\r\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function(c) {\r\n var r = (Math.random() * 16) | 0,\r\n v = c == \"x\" ? r : (r & 0x3) | 0x8;\r\n return v.toString(16);\r\n });\r\n }\r\n\r\n function LoadingService() {\r\n return {\r\n taskStart: function(scope, description) {\r\n var tasks = scope.loadingTasks || [],\r\n taskId = generateTaskId();\r\n\r\n scope.loadingTasks = tasks.concat({\r\n id: taskId,\r\n description: description\r\n });\r\n return taskId;\r\n },\r\n taskEnd: function(scope, id) {\r\n var tasks = scope.loadingTasks || [];\r\n scope.loadingTasks = tasks.filter(function(t) {\r\n return t.id !== id;\r\n });\r\n }\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"RaceEthnoChartsController\", [\r\n \"$scope\",\r\n \"$sanitize\",\r\n \"$timeout\",\r\n \"$location\",\r\n \"rankingsService\",\r\n RaceEthnoChartsController,\r\n ]);\r\n\r\n function RaceEthnoChartsController(\r\n $scope,\r\n $sanitize,\r\n $timeout,\r\n $location,\r\n rankingsService,\r\n ) {\r\n $scope.showHoverStats = [];\r\n $scope.state.communityCharts = {};\r\n $scope.state.selectedChartCommunities = [];\r\n\r\n var madeChartSelection = false;\r\n\r\n $scope.getFirstDemographic = function(demographics) {\r\n var key = demographics ? Object.keys(demographics)[0] : null;\r\n\r\n return key ? demographics[key] : [];\r\n };\r\n\r\n $scope.rebuildCharts = function(forceReload) {\r\n if (!$scope.state.chartsToShow && !$scope.state.communitiesByRank) {\r\n return;\r\n }\r\n\r\n var rebuild = function(forceReload) {\r\n var demographicsIndicatorKey = $scope.state.citywideStats\r\n ? Object.keys($scope.state.citywideStats)[0]\r\n : undefined;\r\n\r\n if (demographicsIndicatorKey) {\r\n if (!$scope.state.communityCharts.all) {\r\n var thisChart = chartDefaults;\r\n thisChart.data.datasets[0].data = [];\r\n\r\n var remainder = 1;\r\n\r\n $scope.state.citywideStats[demographicsIndicatorKey].forEach(\r\n function(demo, ind, arr) {\r\n // data arrives as floating point\r\n thisChart.data.datasets[0].data.push(demo.percent);\r\n $scope.chartLegend[ind].label = demo.name;\r\n remainder = remainder - demo.percent;\r\n },\r\n );\r\n\r\n if (remainder > 0) {\r\n thisChart.data.datasets[0].data.push(remainder);\r\n $scope.chartLegend[$scope.chartLegend.length - 1].label = \"Other\";\r\n $scope.state.citywideStats[demographicsIndicatorKey].push({\r\n name: \"Other\",\r\n percent: remainder,\r\n display: Math.round(remainder * 1000) / 10 + \"%\",\r\n });\r\n }\r\n\r\n $scope.state.communityCharts.all = new Chart(\r\n document.getElementById(\"race-ethno-chart-all\").getContext(\"2d\"),\r\n thisChart,\r\n );\r\n }\r\n else {\r\n }\r\n\r\n $scope.state.chartsToShow.forEach(function(comm, ind, arr) {\r\n var thisChart = chartDefaults;\r\n thisChart.data.datasets[0].data = [];\r\n\r\n if (comm.demographics) {\r\n var remainder = 1;\r\n comm.demographics[demographicsIndicatorKey].forEach(function(\r\n demo,\r\n ind,\r\n arr,\r\n ) {\r\n // data arrives as floating point\r\n thisChart.data.datasets[0].data.push(demo.percent);\r\n $scope.chartLegend[ind].label = demo.name;\r\n remainder = remainder - demo.percent;\r\n });\r\n\r\n if (remainder > Number.EPSILON) {\r\n thisChart.data.datasets[0].data.push(remainder);\r\n $scope.chartLegend[$scope.chartLegend.length - 1].label =\r\n \"Other\";\r\n \r\n if(!comm.demographics[demographicsIndicatorKey].some((demographic) => demographic && demographic.name === \"Other\")) {\r\n // Only add a catch-all \"Other\" demographic if there isn't one with that name yet\r\n comm.demographics[demographicsIndicatorKey].push({\r\n name: \"Other\",\r\n percent: remainder,\r\n display: Math.round(remainder * 1000) / 10 + \"%\",\r\n });\r\n }\r\n }\r\n\r\n if (!$scope.state.communityCharts[comm.id]) {\r\n $scope.state.communityCharts[comm.id] = new Chart(\r\n document\r\n .getElementById(\"race-ethno-chart-\" + ind)\r\n .getContext(\"2d\"),\r\n thisChart,\r\n );\r\n }\r\n\r\n if (forceReload) {\r\n $scope.state.communityCharts[comm.id].update();\r\n }\r\n }\r\n });\r\n }\r\n };\r\n\r\n $timeout(function() {\r\n rebuild(forceReload);\r\n }, 150);\r\n };\r\n\r\n $scope.$watch(\r\n \"state.communitiesByRank\",\r\n function() {\r\n if (!$scope.state.communitiesByRank) return;\r\n\r\n if (!madeChartSelection) {\r\n $scope.state.communityCharts = {};\r\n\r\n if ($location.search()[\"charts\"]) {\r\n madeChartSelection = true;\r\n var commIds = $location\r\n .search()\r\n [\"charts\"].split(\",\")\r\n .map(function(id) {\r\n return +id;\r\n });\r\n $scope.state.selectedChartCommunities = commIds;\r\n $scope.state.chartsToShow = commIds.map(function(id) {\r\n return $scope.state.communitiesById[id];\r\n });\r\n }\r\n else {\r\n $scope.state.chartsToShow = rankingsService\r\n .lowestRanked($scope.state.communitiesByRank)\r\n .concat(\r\n rankingsService.highestRanked($scope.state.communitiesByRank),\r\n );\r\n }\r\n }\r\n },\r\n true,\r\n );\r\n\r\n $scope.$watch(\r\n \"state.chartsToShow\",\r\n function() {\r\n $scope.rebuildCharts(true);\r\n },\r\n true,\r\n );\r\n\r\n $scope.chartLegend = [\r\n {\r\n color: \"#516747\",\r\n },\r\n {\r\n color: \"#648c4e\",\r\n },\r\n {\r\n color: \"#64ab4d\",\r\n },\r\n {\r\n color: \"#b9d5aa\",\r\n },\r\n {\r\n color: \"#ededed\",\r\n },\r\n ];\r\n\r\n var chartDefaults = {\r\n type: \"pie\",\r\n data: {\r\n labels: [\"Red\", \"Blue\", \"Yellow\"],\r\n datasets: [\r\n {\r\n data: [300, 50, 100, 20],\r\n backgroundColor: [\"#516747\", \"#648c4e\", \"#64ab4d\", \"#b9d5aa\"],\r\n },\r\n ],\r\n },\r\n options: {\r\n tooltips: {\r\n enabled: false,\r\n },\r\n legend: {\r\n display: false,\r\n },\r\n responsive: false,\r\n },\r\n };\r\n\r\n // TODO - This is copied from the main Typeahead component\r\n // No time to refactor Typeahead, do that soon.\r\n\r\n $scope.state.toggleChartCommunity = function(id) {\r\n var selectedCharts = $scope.state.selectedChartCommunities;\r\n\r\n if (!madeChartSelection) {\r\n madeChartSelection = true;\r\n selectedCharts = rankingsService\r\n .lowestRanked($scope.state.communitiesByRank)\r\n .concat(rankingsService.highestRanked($scope.state.communitiesByRank))\r\n .map(function(x) {\r\n return x.id;\r\n });\r\n }\r\n\r\n var index = selectedCharts.indexOf(id);\r\n if (index >= 0) {\r\n selectedCharts.splice(index, 1);\r\n $scope.state.communityCharts[id] = undefined;\r\n\r\n // Tracks when a user removes a community\r\n dataLayer.push({\r\n event: \"Community Removed\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction:\r\n \"Community Removed|\" + $scope.state.communitiesById[id].name,\r\n eventLabel: window.location.pathname,\r\n });\r\n }\r\n else {\r\n selectedCharts.push(id);\r\n }\r\n\r\n $location.search(\"charts\", selectedCharts.join(\",\"));\r\n $scope.state.selectedChartCommunities = selectedCharts;\r\n $scope.state.chartsToShow = $scope.state.selectedChartCommunities.map(\r\n function(id) {\r\n return $scope.state.communitiesById[id];\r\n },\r\n );\r\n };\r\n\r\n $scope.chartTypeahead = {};\r\n\r\n $scope.chartResetTypeahead = function() {\r\n // Prevents first item from being selected automatically\r\n $scope.chartTypeahead.selectedIndex = -1;\r\n\r\n $scope.chartTypeahead.filteredResults = [];\r\n\r\n // Empty out typeahead input field\r\n $scope.chartQueryString = \"\";\r\n };\r\n\r\n $scope.chartResetTypeahead();\r\n\r\n $scope.chartUpdateTypeahead = function(needle) {\r\n $scope.chartTypeahead.filteredResults = $scope.state.communityData.filter(\r\n function(community) {\r\n return firstLettersFilter(community.name, needle);\r\n },\r\n );\r\n };\r\n\r\n // Typeahead filter\r\n // Requirements: find matches for community names where any word starts with a user-provided\r\n // string. Example: `b` matches `Battery Park` and `Midtown Business District`\r\n // TODO - This should probably be its own filter file\r\n function firstLettersFilter(haystack, needle) {\r\n // Split the community name by any non-word characters\r\n var parsedStr = haystack.toLowerCase().split(/\\W+/);\r\n\r\n // User input and strings to search are converted to lowercase\r\n needle = needle.toLowerCase();\r\n\r\n for (var i = 0; i < parsedStr.length; i++) {\r\n if (parsedStr[i].indexOf(needle) === 0) {\r\n // string match found!\r\n return true;\r\n }\r\n }\r\n\r\n // no string match found\r\n return false;\r\n }\r\n\r\n $scope.chartTypeaheadKeyPressed = function(event, communityId) {\r\n if (event.key === \"ArrowDown\") {\r\n $scope.chartTypeahead.selectedIndex += 1;\r\n }\r\n else if (event.key === \"ArrowUp\") {\r\n $scope.chartTypeahead.selectedIndex =\r\n $scope.chartTypeahead.selectedIndex >= -1\r\n ? $scope.chartTypeahead.selectedIndex - 1\r\n : -1;\r\n }\r\n else if (event.key === \"Enter\") {\r\n var selectedId =\r\n $scope.chartTypeahead.filteredResults[\r\n $scope.chartTypeahead.selectedIndex\r\n ].id;\r\n $scope.chartTypeaheadSelected(selectedId);\r\n }\r\n else {\r\n // If the keypress isn't an arrow, it's probably a letter or number\r\n // Reset the selected index\r\n $scope.chartTypeahead.selectedIndex = -1;\r\n }\r\n };\r\n\r\n $scope.chartTypeaheadSelected = function(id) {\r\n // Tracks when a user searches using \"Add community to compare\" which displays a race/ethnicity pie chart on the \"Risk Ranking\" page\r\n dataLayer.push({\r\n event: \"Compare Community for Race/Ethnicity\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction:\r\n \"RE Community Search|\" + $scope.state.communitiesById[id].name,\r\n eventLabel: window.location.pathname,\r\n });\r\n\r\n $scope.state.toggleChartCommunity(id);\r\n $scope.chartResetTypeahead();\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccRaceEthnoCharts\", cccRaceEthnoCharts);\r\n\r\n function cccRaceEthnoCharts() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"RaceEthnoChartsController\"\r\n };\r\n }\r\n})();\r\n","!(function(e, t) {\r\n typeof exports == \"object\"\r\n ? (module.exports = t(require(\"angular\")))\r\n : typeof define == \"function\" && define.amd\r\n ? define([\"angular\"], t)\r\n : t(e.angular);\r\n})(window, function(angular) {\r\n /**\r\n * AngularJS Google Maps Ver. 1.17.94\r\n *\r\n * The MIT License (MIT)\r\n *\r\n * Copyright (c) 2014, 2015, 1016 Allen Kim\r\n *\r\n * Permission is hereby granted, free of charge, to any person obtaining a copy of\r\n * this software and associated documentation files (the \"Software\"), to deal in\r\n * the Software without restriction, including without limitation the rights to\r\n * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\r\n * the Software, and to permit persons to whom the Software is furnished to do so,\r\n * subject to the following conditions:\r\n *\r\n * The above copyright notice and this permission notice shall be included in all\r\n * copies or substantial portions of the Software.\r\n *\r\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r\n * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\r\n * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r\n * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r\n * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n */\r\n return (\r\n angular.module(\"ngMap\", []),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t = function(t, n, o, i, a, r, s) {\r\n e = a;\r\n var p = this;\r\n p.mapOptions,\r\n p.mapEvents,\r\n p.eventListeners,\r\n (p.addObject = function(e, t) {\r\n if (p.map) {\r\n p.map[e] = p.map[e] || {};\r\n var n = Object.keys(p.map[e]).length;\r\n (p.map[e][t.id || n] = t),\r\n p.map instanceof google.maps.Map &&\r\n (e != \"infoWindows\" &&\r\n t.setMap &&\r\n t.setMap &&\r\n t.setMap(p.map),\r\n t.centered && t.position && p.map.setCenter(t.position),\r\n e == \"markers\" && p.objectChanged(\"markers\"),\r\n e == \"customMarkers\" && p.objectChanged(\"customMarkers\"));\r\n }\r\n }),\r\n (p.deleteObject = function(e, t) {\r\n if (t.map) {\r\n var n = t.map[e];\r\n for (var o in n)\r\n n[o] === t &&\r\n (google.maps.event.clearInstanceListeners(t), delete n[o]);\r\n t.map && t.setMap && t.setMap(null),\r\n e == \"markers\" && p.objectChanged(\"markers\"),\r\n e == \"customMarkers\" && p.objectChanged(\"customMarkers\");\r\n }\r\n }),\r\n (p.observeAttrSetObj = function(t, n, o) {\r\n if (n.noWatcher) return !1;\r\n for (var i = e.getAttrsToObserve(t), a = 0; a < i.length; a++) {\r\n var s = i[a];\r\n n.$observe(s, r.observeAndSet(s, o));\r\n }\r\n }),\r\n (p.zoomToIncludeMarkers = function() {\r\n if (\r\n (p.map.markers != null &&\r\n Object.keys(p.map.markers).length > 0) ||\r\n (p.map.customMarkers != null &&\r\n Object.keys(p.map.customMarkers).length > 0)\r\n ) {\r\n var e = new google.maps.LatLngBounds();\r\n for (var t in p.map.markers)\r\n e.extend(p.map.markers[t].getPosition());\r\n for (var n in p.map.customMarkers)\r\n e.extend(p.map.customMarkers[n].getPosition());\r\n p.mapOptions.maximumZoom && (p.enableMaximumZoomCheck = !0),\r\n p.map.fitBounds(e);\r\n }\r\n }),\r\n (p.objectChanged = function(e) {\r\n !p.map ||\r\n (e != \"markers\" && e != \"customMarkers\") ||\r\n p.map.zoomToIncludeMarkers != \"auto\" ||\r\n p.zoomToIncludeMarkers();\r\n }),\r\n (p.initializeMap = function() {\r\n var a = p.mapOptions,\r\n u = p.mapEvents,\r\n l = p.map;\r\n if (((p.map = s.getMapInstance(n[0])), r.setStyle(n[0]), l)) {\r\n var g = e.filter(o),\r\n d = e.getOptions(g),\r\n m = e.getControlOptions(g);\r\n a = angular.extend(d, m);\r\n for (var f in l) {\r\n var v = l[f];\r\n if (typeof v == \"object\")\r\n for (var y in v) p.addObject(f, v[y]);\r\n }\r\n (p.map.showInfoWindow = p.showInfoWindow),\r\n (p.map.hideInfoWindow = p.hideInfoWindow);\r\n }\r\n a.zoom = a.zoom || 15;\r\n var h = a.center;\r\n if (!a.center || (typeof h == \"string\" && h.match(/\\{\\{.*\\}\\}/)))\r\n a.center = new google.maps.LatLng(0, 0);\r\n else if (typeof h == \"string\" && h.match(/[0-9.-]*,[0-9.-]*/))\r\n a.center = new google.maps.LatLng(h);\r\n else if (!(h instanceof google.maps.LatLng)) {\r\n var M = a.center;\r\n delete a.center,\r\n r.getGeoLocation(M, a.geoLocationOptions).then(\r\n function(e) {\r\n p.map.setCenter(e);\r\n var n = a.geoCallback;\r\n n && i(n)(t);\r\n },\r\n function() {\r\n a.geoFallbackCenter &&\r\n p.map.setCenter(a.geoFallbackCenter);\r\n }\r\n );\r\n }\r\n p.map.setOptions(a);\r\n for (var b in u) {\r\n var O = u[b],\r\n w = google.maps.event.addListener(p.map, b, O);\r\n p.eventListeners[b] = w;\r\n }\r\n p.observeAttrSetObj(c, o, p.map),\r\n (p.singleInfoWindow = a.singleInfoWindow),\r\n google.maps.event.trigger(p.map, \"resize\"),\r\n google.maps.event.addListenerOnce(p.map, \"idle\", function() {\r\n r.addMap(p),\r\n a.zoomToIncludeMarkers && p.zoomToIncludeMarkers(),\r\n (t.map = p.map),\r\n t.$emit(\"mapInitialized\", p.map),\r\n o.mapInitialized && i(o.mapInitialized)(t, { map: p.map });\r\n }),\r\n a.zoomToIncludeMarkers &&\r\n a.maximumZoom &&\r\n google.maps.event.addListener(\r\n p.map,\r\n \"zoom_changed\",\r\n function() {\r\n p.enableMaximumZoomCheck == 1 &&\r\n ((p.enableMaximumZoomCheck = !1),\r\n google.maps.event.addListenerOnce(\r\n p.map,\r\n \"bounds_changed\",\r\n function() {\r\n p.map.setZoom(\r\n Math.min(a.maximumZoom, p.map.getZoom())\r\n );\r\n }\r\n ));\r\n }\r\n );\r\n }),\r\n (t.google = google);\r\n var c = e.orgAttributes(n),\r\n u = e.filter(o),\r\n l = e.getOptions(u, { scope: t }),\r\n g = e.getControlOptions(u),\r\n d = angular.extend(l, g),\r\n m = e.getEvents(t, u);\r\n if (\r\n (Object.keys(m).length && void 0,\r\n (p.mapOptions = d),\r\n (p.mapEvents = m),\r\n (p.eventListeners = {}),\r\n l.lazyInit)\r\n ) {\r\n if (\r\n o.id &&\r\n o.id.indexOf(\"{{\", 0) === 0 &&\r\n o.id.indexOf(\"}}\", o.id.length - \"}}\".length) !== -1\r\n )\r\n var f = o.id.slice(2, -2),\r\n v = i(f)(t);\r\n else var v = o.id;\r\n (p.map = { id: v }), r.addMap(p);\r\n }\r\n else p.initializeMap();\r\n l.triggerResize && google.maps.event.trigger(p.map, \"resize\"),\r\n n.bind(\"$destroy\", function() {\r\n s.returnMapInstance(p.map), r.deleteMap(p);\r\n });\r\n };\r\n (t.$inject = [\r\n \"$scope\",\r\n \"$element\",\r\n \"$attrs\",\r\n \"$parse\",\r\n \"Attr2MapOptions\",\r\n \"NgMap\",\r\n \"NgMapPool\"\r\n ]),\r\n angular.module(\"ngMap\").controller(\"__MapController\", t);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t = function(t, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = e.orgAttributes(o),\r\n s = e.filter(i),\r\n p = e.getOptions(s, { scope: t }),\r\n c = e.getEvents(t, s),\r\n u = n(p, c);\r\n a.addObject(\"bicyclingLayers\", u),\r\n a.observeAttrSetObj(r, i, u),\r\n o.bind(\"$destroy\", function() {\r\n a.deleteObject(\"bicyclingLayers\", u);\r\n });\r\n },\r\n n = function(e, t) {\r\n var n = new google.maps.BicyclingLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n },\r\n o = function(n) {\r\n return (\r\n (e = n), { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: t }\r\n );\r\n };\r\n (o.$inject = [\"Attr2MapOptions\"]),\r\n angular.module(\"ngMap\").directive(\"bicyclingLayer\", o);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o = function(n, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = e.filter(i),\r\n s = e.getOptions(r, { scope: n }),\r\n p = e.getEvents(n, r),\r\n c = o[0].parentElement.removeChild(o[0]);\r\n t(c.innerHTML.trim())(n);\r\n for (var u in p) google.maps.event.addDomListener(c, u, p[u]);\r\n a.addObject(\"customControls\", c);\r\n var l = s.position;\r\n a.map.controls[google.maps.ControlPosition[l]].push(c),\r\n o.bind(\"$destroy\", function() {\r\n a.deleteObject(\"customControls\", c);\r\n });\r\n },\r\n i = function(i, a, r) {\r\n return (\r\n (e = i),\r\n (t = a),\r\n (n = r),\r\n { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: o }\r\n );\r\n };\r\n (i.$inject = [\"Attr2MapOptions\", \"$compile\", \"NgMap\"]),\r\n angular.module(\"ngMap\").directive(\"customControl\", i);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o,\r\n i = function(e) {\r\n (e = e || {}),\r\n (this.el = document.createElement(\"div\")),\r\n (this.el.style.display = \"inline-block\"),\r\n (this.el.style.visibility = \"hidden\"),\r\n (this.visible = !0);\r\n for (var t in e) this[t] = e[t];\r\n },\r\n a = function() {\r\n (i.prototype = new google.maps.OverlayView()),\r\n (i.prototype.setContent = function(e, t) {\r\n (this.el.innerHTML = e),\r\n (this.el.style.position = \"absolute\"),\r\n t && n(angular.element(this.el).contents())(t);\r\n }),\r\n (i.prototype.getDraggable = function() {\r\n return this.draggable;\r\n }),\r\n (i.prototype.setDraggable = function(e) {\r\n this.draggable = e;\r\n }),\r\n (i.prototype.getPosition = function() {\r\n return this.position;\r\n }),\r\n (i.prototype.setPosition = function(e) {\r\n e && (this.position = e);\r\n var n = this;\r\n if (\r\n this.getProjection() &&\r\n typeof this.position.lng == \"function\"\r\n ) {\r\n var o = function() {\r\n var e = n.getProjection().fromLatLngToDivPixel(n.position),\r\n t = Math.round(e.x - n.el.offsetWidth / 2),\r\n o = Math.round(e.y - n.el.offsetHeight - 10);\r\n (n.el.style.left = t + \"px\"),\r\n (n.el.style.top = o + \"px\"),\r\n (n.el.style.visibility = \"visible\");\r\n };\r\n n.el.offsetWidth && n.el.offsetHeight ? o() : t(o, 300);\r\n }\r\n }),\r\n (i.prototype.setZIndex = function(e) {\r\n e && (this.zIndex = e), (this.el.style.zIndex = this.zIndex);\r\n }),\r\n (i.prototype.getVisible = function() {\r\n return this.visible;\r\n }),\r\n (i.prototype.setVisible = function(e) {\r\n (this.el.style.display = e ? \"inline-block\" : \"none\"),\r\n (this.visible = e);\r\n }),\r\n (i.prototype.addClass = function(e) {\r\n var t = this.el.className.trim().split(\" \");\r\n t.indexOf(e) == -1 && t.push(e),\r\n (this.el.className = t.join(\" \"));\r\n }),\r\n (i.prototype.removeClass = function(e) {\r\n var t = this.el.className.split(\" \"),\r\n n = t.indexOf(e);\r\n n > -1 && t.splice(n, 1), (this.el.className = t.join(\" \"));\r\n }),\r\n (i.prototype.onAdd = function() {\r\n this.getPanes().overlayMouseTarget.appendChild(this.el);\r\n }),\r\n (i.prototype.draw = function() {\r\n this.setPosition(),\r\n this.setZIndex(this.zIndex),\r\n this.setVisible(this.visible);\r\n }),\r\n (i.prototype.onRemove = function() {\r\n this.el.parentNode.removeChild(this.el);\r\n });\r\n },\r\n r = function(n, a) {\r\n return function(r, s, p, c) {\r\n c = c[0] || c[1];\r\n var u = e.orgAttributes(s),\r\n l = e.filter(p),\r\n g = e.getOptions(l, { scope: r }),\r\n d = e.getEvents(r, l);\r\n s[0].style.display = \"none\";\r\n var m = new i(g);\r\n t(function() {\r\n r.$watch(\r\n \"[\" + a.join(\",\") + \"]\",\r\n function() {\r\n m.setContent(n, r);\r\n },\r\n !0\r\n ),\r\n m.setContent(s[0].innerHTML, r);\r\n var e = s[0].firstElementChild.className;\r\n m.addClass(\"custom-marker\"),\r\n m.addClass(e),\r\n g.position instanceof google.maps.LatLng ||\r\n o.getGeoLocation(g.position).then(function(e) {\r\n m.setPosition(e);\r\n });\r\n });\r\n for (var f in d) google.maps.event.addDomListener(m.el, f, d[f]);\r\n c.addObject(\"customMarkers\", m),\r\n c.observeAttrSetObj(u, p, m),\r\n s.bind(\"$destroy\", function() {\r\n c.deleteObject(\"customMarkers\", m);\r\n });\r\n };\r\n },\r\n s = function(i, s, p, c) {\r\n return (\r\n (e = p),\r\n (t = i),\r\n (n = s),\r\n (o = c),\r\n {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n compile: function(e) {\r\n a(), (e[0].style.display = \"none\");\r\n var t = e.html(),\r\n n = t.match(/{{([^}]+)}}/g),\r\n o = [];\r\n return (\r\n (n || []).forEach(function(e) {\r\n var t = e.replace(\"{{\", \"\").replace(\"}}\", \"\");\r\n e.indexOf(\"::\") == -1 &&\r\n e.indexOf(\"this.\") == -1 &&\r\n o.indexOf(t) == -1 &&\r\n o.push(e.replace(\"{{\", \"\").replace(\"}}\", \"\"));\r\n }),\r\n r(t, o)\r\n );\r\n }\r\n }\r\n );\r\n };\r\n (s.$inject = [\"$timeout\", \"$compile\", \"Attr2MapOptions\", \"NgMap\"]),\r\n angular.module(\"ngMap\").directive(\"customMarker\", s);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o = function(e, t) {\r\n e.panel &&\r\n (e.panel =\r\n document.getElementById(e.panel) ||\r\n document.querySelector(e.panel));\r\n var n = new google.maps.DirectionsRenderer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n },\r\n i = function(e, o) {\r\n var i = new google.maps.DirectionsService(),\r\n a = o;\r\n a.travelMode = a.travelMode || \"DRIVING\";\r\n var r = [\r\n \"origin\",\r\n \"destination\",\r\n \"travelMode\",\r\n \"transitOptions\",\r\n \"unitSystem\",\r\n \"durationInTraffic\",\r\n \"waypoints\",\r\n \"optimizeWaypoints\",\r\n \"provideRouteAlternatives\",\r\n \"avoidHighways\",\r\n \"avoidTolls\",\r\n \"region\"\r\n ];\r\n for (var s in a) r.indexOf(s) === -1 && delete a[s];\r\n a.waypoints &&\r\n (a.waypoints == \"[]\" || a.waypoints === \"\") &&\r\n delete a.waypoints;\r\n var p = function(n) {\r\n i.route(n, function(n, o) {\r\n o == google.maps.DirectionsStatus.OK &&\r\n t(function() {\r\n e.setDirections(n);\r\n });\r\n });\r\n };\r\n a.origin &&\r\n a.destination &&\r\n (a.origin == \"current-location\"\r\n ? n.getCurrentPosition().then(function(e) {\r\n (a.origin = new google.maps.LatLng(\r\n e.coords.latitude,\r\n e.coords.longitude\r\n )),\r\n p(a);\r\n })\r\n : a.destination == \"current-location\"\r\n ? n.getCurrentPosition().then(function(e) {\r\n (a.destination = new google.maps.LatLng(\r\n e.coords.latitude,\r\n e.coords.longitude\r\n )),\r\n p(a);\r\n })\r\n : p(a));\r\n },\r\n a = function(a, r, s, p) {\r\n var c = a;\r\n (e = p), (t = r), (n = s);\r\n var u = function(n, a, r, s) {\r\n s = s[0] || s[1];\r\n var p = c.orgAttributes(a),\r\n u = c.filter(r),\r\n l = c.getOptions(u, { scope: n }),\r\n g = c.getEvents(n, u),\r\n d = c.getAttrsToObserve(p),\r\n m = o(l, g);\r\n s.addObject(\"directionsRenderers\", m),\r\n d.forEach(function(e) {\r\n !(function(e) {\r\n r.$observe(e, function(n) {\r\n if (e == \"panel\")\r\n t(function() {\r\n var e =\r\n document.getElementById(n) ||\r\n document.querySelector(n);\r\n e && m.setPanel(e);\r\n });\r\n else if (l[e] !== n) {\r\n var o = c.toOptionValue(n, { key: e });\r\n (l[e] = o), i(m, l);\r\n }\r\n });\r\n })(e);\r\n }),\r\n e.getMap().then(function() {\r\n i(m, l);\r\n }),\r\n a.bind(\"$destroy\", function() {\r\n s.deleteObject(\"directionsRenderers\", m);\r\n });\r\n };\r\n return { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: u };\r\n };\r\n (a.$inject = [\r\n \"Attr2MapOptions\",\r\n \"$timeout\",\r\n \"NavigatorGeolocation\",\r\n \"NgMap\"\r\n ]),\r\n angular.module(\"ngMap\").directive(\"directions\", a);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"drawingManager\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e;\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, n, o, i) {\r\n i = i[0] || i[1];\r\n var a = t.filter(o),\r\n r = t.getOptions(a, { scope: e }),\r\n s = t.getControlOptions(a),\r\n p = t.getEvents(e, a),\r\n c = new google.maps.drawing.DrawingManager({\r\n drawingMode: r.drawingmode,\r\n drawingControl: r.drawingcontrol,\r\n drawingControlOptions: s.drawingControlOptions,\r\n circleOptions: r.circleoptions,\r\n markerOptions: r.markeroptions,\r\n polygonOptions: r.polygonoptions,\r\n polylineOptions: r.polylineoptions,\r\n rectangleOptions: r.rectangleoptions\r\n });\r\n o.$observe(\"drawingControlOptions\", function(e) {\r\n (c.drawingControlOptions = t.getControlOptions({\r\n drawingControlOptions: e\r\n }).drawingControlOptions),\r\n c.setDrawingMode(null),\r\n c.setMap(i.map);\r\n });\r\n for (var u in p) google.maps.event.addListener(c, u, p[u]);\r\n i.addObject(\"mapDrawingManager\", c),\r\n n.bind(\"$destroy\", function() {\r\n i.deleteObject(\"mapDrawingManager\", c);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"dynamicMapsEngineLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.visualization.DynamicMapsEngineLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.filter(i),\r\n s = t.getOptions(r, { scope: e }),\r\n p = t.getEvents(e, r, p),\r\n c = n(s, p);\r\n a.addObject(\"mapsEngineLayers\", c);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"fusionTablesLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.FusionTablesLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.filter(i),\r\n s = t.getOptions(r, { scope: e }),\r\n p = t.getEvents(e, r, p),\r\n c = n(s, p);\r\n a.addObject(\"fusionTablesLayers\", c);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"heatmapLayer\", [\r\n \"Attr2MapOptions\",\r\n \"$window\",\r\n function(e, t) {\r\n var n = e;\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = n.filter(i),\r\n s = n.getOptions(r, { scope: e });\r\n if (\r\n ((s.data = t[i.data] || e[i.data]), !(s.data instanceof Array))\r\n )\r\n throw \"invalid heatmap data\";\r\n s.data = new google.maps.MVCArray(s.data);\r\n {\r\n var p = new google.maps.visualization.HeatmapLayer(s);\r\n n.getEvents(e, r);\r\n }\r\n a.addObject(\"heatmapLayers\", p);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function(e, t, n, o, i, a, r) {\r\n var s = e,\r\n p = function(e, a, r) {\r\n var s;\r\n !e.position ||\r\n e.position instanceof google.maps.LatLng ||\r\n delete e.position,\r\n (s = new google.maps.InfoWindow(e));\r\n for (var p in a) p && google.maps.event.addListener(s, p, a[p]);\r\n var c = n(function(e) {\r\n angular.isString(r)\r\n ? o(r).then(\r\n function(t) {\r\n e(\r\n angular\r\n .element(t)\r\n .wrap(\"\")\r\n .parent()\r\n );\r\n },\r\n function(e) {\r\n throw \"info-window template request failed: \" + e;\r\n }\r\n )\r\n : e(r);\r\n }).then(function(e) {\r\n var t = e.html().trim();\r\n if (angular.element(t).length != 1)\r\n throw \"info-window working as a template must have a container\";\r\n s.__template = t.replace(/\\s?ng-non-bindable[='\"]+/, \"\");\r\n });\r\n return (\r\n (s.__open = function(e, n, o) {\r\n c.then(function() {\r\n i(function() {\r\n o && (n.anchor = o);\r\n var i = t(s.__template)(n);\r\n s.setContent(i[0]),\r\n n.$apply(),\r\n o && o.getPosition\r\n ? s.open(e, o)\r\n : o && o instanceof google.maps.LatLng\r\n ? (s.open(e), s.setPosition(o))\r\n : s.open(e);\r\n var a = s.content.parentElement.parentElement.parentElement;\r\n a.className = \"ng-map-info-window\";\r\n });\r\n });\r\n }),\r\n s\r\n );\r\n },\r\n c = function(e, t, n, o) {\r\n (o = o[0] || o[1]), t.css(\"display\", \"none\");\r\n var i,\r\n c = s.orgAttributes(t),\r\n u = s.filter(n),\r\n l = s.getOptions(u, { scope: e }),\r\n g = s.getEvents(e, u),\r\n d = p(l, g, l.template || t);\r\n !l.position ||\r\n l.position instanceof google.maps.LatLng ||\r\n (i = l.position),\r\n i &&\r\n r.getGeoLocation(i).then(function(t) {\r\n d.setPosition(t), d.__open(o.map, e, t);\r\n var i = n.geoCallback;\r\n i && a(i)(e);\r\n }),\r\n o.addObject(\"infoWindows\", d),\r\n o.observeAttrSetObj(c, n, d),\r\n (o.showInfoWindow = o.map.showInfoWindow =\r\n o.showInfoWindow ||\r\n function(t, n, i) {\r\n var a = typeof t == \"string\" ? t : n,\r\n r = typeof t == \"string\" ? n : i;\r\n if (typeof r == \"string\")\r\n if (\r\n typeof o.map.markers != \"undefined\" &&\r\n typeof o.map.markers[r] != \"undefined\"\r\n )\r\n r = o.map.markers[r];\r\n else {\r\n if (typeof o.map.customMarkers[r] == \"undefined\")\r\n throw new Error(\r\n \"Cant open info window for id \" +\r\n r +\r\n \". Marker or CustomMarker is not defined\"\r\n );\r\n r = o.map.customMarkers[r];\r\n }\r\n var s = o.map.infoWindows[a],\r\n p = r ? r : this.getPosition ? this : null;\r\n s.__open(o.map, e, p),\r\n o.singleInfoWindow &&\r\n (o.lastInfoWindow && e.hideInfoWindow(o.lastInfoWindow),\r\n (o.lastInfoWindow = a));\r\n }),\r\n (o.hideInfoWindow = o.map.hideInfoWindow =\r\n o.hideInfoWindow ||\r\n function(e, t) {\r\n var n = typeof e == \"string\" ? e : t,\r\n i = o.map.infoWindows[n];\r\n i.close();\r\n }),\r\n (e.showInfoWindow = o.map.showInfoWindow),\r\n (e.hideInfoWindow = o.map.hideInfoWindow);\r\n var m = d.mapId ? { id: d.mapId } : 0;\r\n r.getMap(m).then(function(t) {\r\n if ((d.visible && d.__open(t, e), d.visibleOnMarker)) {\r\n var n = d.visibleOnMarker;\r\n d.__open(t, e, t.markers[n]);\r\n }\r\n });\r\n };\r\n return { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: c };\r\n };\r\n (e.$inject = [\r\n \"Attr2MapOptions\",\r\n \"$compile\",\r\n \"$q\",\r\n \"$templateRequest\",\r\n \"$timeout\",\r\n \"$parse\",\r\n \"NgMap\"\r\n ]),\r\n angular.module(\"ngMap\").directive(\"infoWindow\", e);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"kmlLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.KmlLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.orgAttributes(o),\r\n s = t.filter(i),\r\n p = t.getOptions(s, { scope: e }),\r\n c = t.getEvents(e, s),\r\n u = n(p, c);\r\n a.addObject(\"kmlLayers\", u),\r\n a.observeAttrSetObj(r, i, u),\r\n o.bind(\"$destroy\", function() {\r\n a.deleteObject(\"kmlLayers\", u);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"mapData\", [\r\n \"Attr2MapOptions\",\r\n \"NgMap\",\r\n function(e, t) {\r\n var n = e;\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i) {\r\n var a = n.filter(i),\r\n r = n.getOptions(a, { scope: e }),\r\n s = n.getEvents(e, a, s);\r\n t.getMap().then(function(t) {\r\n for (var n in r) {\r\n var o = r[n];\r\n typeof e[o] == \"function\" ? t.data[n](e[o]) : t.data[n](o);\r\n }\r\n for (var i in s) t.data.addListener(i, s[i]);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o = [],\r\n i = [],\r\n a = function(n, a, r) {\r\n var s = r.mapLazyLoadParams || r.mapLazyLoad;\r\n if (void 0 === window.google || void 0 === window.google.maps) {\r\n i.push({ scope: n, element: a, savedHtml: o[i.length] }),\r\n (window.lazyLoadCallback = function() {\r\n e(function() {\r\n i.forEach(function(e) {\r\n e.element.html(e.savedHtml),\r\n t(e.element.contents())(e.scope);\r\n });\r\n }, 100);\r\n });\r\n var p = document.createElement(\"script\");\r\n (p.src =\r\n s +\r\n (s.indexOf(\"?\") > -1 ? \"&\" : \"?\") +\r\n \"callback=lazyLoadCallback\"),\r\n document.querySelector('script[src=\"' + p.src + '\"]') ||\r\n document.body.appendChild(p);\r\n }\r\n else a.html(o), t(a.contents())(n);\r\n },\r\n r = function(e, t) {\r\n return (\r\n !t.mapLazyLoad && void 0,\r\n o.push(e.html()),\r\n (n = t.mapLazyLoad),\r\n void 0 !== window.google && void 0 !== window.google.maps\r\n ? !1\r\n : (e.html(\"\"), { pre: a })\r\n );\r\n },\r\n s = function(n, o) {\r\n return (t = n), (e = o), { compile: r };\r\n };\r\n (s.$inject = [\"$compile\", \"$timeout\"]),\r\n angular.module(\"ngMap\").directive(\"mapLazyLoad\", s);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"mapType\", [\r\n \"$parse\",\r\n \"NgMap\",\r\n function(e, t) {\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(n, o, i, a) {\r\n a = a[0] || a[1];\r\n var r,\r\n s = i.name;\r\n if (!s) throw \"invalid map-type name\";\r\n if (((r = e(i.object)(n)), !r)) throw \"invalid map-type object\";\r\n t.getMap().then(function(e) {\r\n e.mapTypes.set(s, r);\r\n }),\r\n a.addObject(\"mapTypes\", r);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function() {\r\n return {\r\n restrict: \"AE\",\r\n controller: \"__MapController\",\r\n controllerAs: \"ngmap\"\r\n };\r\n };\r\n angular.module(\"ngMap\").directive(\"map\", [e]),\r\n angular.module(\"ngMap\").directive(\"ngMap\", [e]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"mapsEngineLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.visualization.MapsEngineLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.filter(i),\r\n s = t.getOptions(r, { scope: e }),\r\n p = t.getEvents(e, r, p),\r\n c = n(s, p);\r\n a.addObject(\"mapsEngineLayers\", c);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o = function(e, t) {\r\n var o;\r\n if (n.defaultOptions.marker)\r\n for (var i in n.defaultOptions.marker)\r\n typeof e[i] == \"undefined\" && (e[i] = n.defaultOptions.marker[i]);\r\n e.position instanceof google.maps.LatLng ||\r\n (e.position = new google.maps.LatLng(0, 0)),\r\n (o = new google.maps.Marker(e)),\r\n Object.keys(t).length > 0;\r\n for (var a in t) a && google.maps.event.addListener(o, a, t[a]);\r\n return o;\r\n },\r\n i = function(i, a, r, s) {\r\n s = s[0] || s[1];\r\n var p,\r\n c = e.orgAttributes(a),\r\n u = e.filter(r),\r\n l = e.getOptions(u, i, { scope: i }),\r\n g = e.getEvents(i, u);\r\n l.position instanceof google.maps.LatLng || (p = l.position);\r\n var d = o(l, g);\r\n s.addObject(\"markers\", d),\r\n p &&\r\n n.getGeoLocation(p).then(function(e) {\r\n d.setPosition(e), l.centered && d.map.setCenter(e);\r\n var n = r.geoCallback;\r\n n && t(n)(i);\r\n }),\r\n s.observeAttrSetObj(c, r, d),\r\n a.bind(\"$destroy\", function() {\r\n s.deleteObject(\"markers\", d);\r\n });\r\n },\r\n a = function(o, a, r) {\r\n return (\r\n (e = o),\r\n (t = a),\r\n (n = r),\r\n { restrict: \"E\", require: [\"^?map\", \"?^ngMap\"], link: i }\r\n );\r\n };\r\n (a.$inject = [\"Attr2MapOptions\", \"$parse\", \"NgMap\"]),\r\n angular.module(\"ngMap\").directive(\"marker\", a);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"overlayMapType\", [\r\n \"NgMap\",\r\n function(e) {\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(t, n, o, i) {\r\n i = i[0] || i[1];\r\n var a = o.initMethod || \"insertAt\",\r\n r = t[o.object];\r\n e.getMap().then(function(e) {\r\n if (a == \"insertAt\") {\r\n var t = parseInt(o.index, 10);\r\n e.overlayMapTypes.insertAt(t, r);\r\n }\r\n else a == \"push\" && e.overlayMapTypes.push(r);\r\n }),\r\n i.addObject(\"overlayMapTypes\", r);\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function(e, t) {\r\n var n = e,\r\n o = function(e, o, i, a) {\r\n if (i.placesAutoComplete === \"false\") return !1;\r\n var r = n.filter(i),\r\n s = n.getOptions(r, { scope: e }),\r\n p = n.getEvents(e, r),\r\n c = new google.maps.places.Autocomplete(o[0], s);\r\n for (var u in p) google.maps.event.addListener(c, u, p[u]);\r\n var l = function() {\r\n t(function() {\r\n a && a.$setViewValue(o.val());\r\n }, 100);\r\n };\r\n google.maps.event.addListener(c, \"place_changed\", l),\r\n o[0].addEventListener(\"change\", l),\r\n i.$observe(\"types\", function(e) {\r\n if (e) {\r\n var t = n.toOptionValue(e, { key: \"types\" });\r\n c.setTypes(t);\r\n }\r\n }),\r\n i.$observe(\"componentRestrictions\", function(t) {\r\n t && c.setComponentRestrictions(e.$eval(t));\r\n });\r\n };\r\n return { restrict: \"A\", require: \"?ngModel\", link: o };\r\n };\r\n (e.$inject = [\"Attr2MapOptions\", \"$timeout\"]),\r\n angular.module(\"ngMap\").directive(\"placesAutoComplete\", e);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function(e, t) {\r\n var n,\r\n o = e.name;\r\n switch ((delete e.name, o)) {\r\n case \"circle\":\r\n e.center instanceof google.maps.LatLng ||\r\n (e.center = new google.maps.LatLng(0, 0)),\r\n (n = new google.maps.Circle(e));\r\n break;\r\n case \"polygon\":\r\n n = new google.maps.Polygon(e);\r\n break;\r\n case \"polyline\":\r\n n = new google.maps.Polyline(e);\r\n break;\r\n case \"rectangle\":\r\n n = new google.maps.Rectangle(e);\r\n break;\r\n case \"groundOverlay\":\r\n case \"image\":\r\n var i = e.url,\r\n a = { opacity: e.opacity, clickable: e.clickable, id: e.id };\r\n n = new google.maps.GroundOverlay(i, e.bounds, a);\r\n }\r\n for (var r in t) t[r] && google.maps.event.addListener(n, r, t[r]);\r\n return n;\r\n },\r\n t = function(t, n, o) {\r\n var i = t,\r\n a = function(t, a, r, s) {\r\n s = s[0] || s[1];\r\n var p,\r\n c,\r\n u = i.orgAttributes(a),\r\n l = i.filter(r),\r\n g = i.getOptions(l, { scope: t }),\r\n d = i.getEvents(t, l);\r\n (c = g.name),\r\n g.center instanceof google.maps.LatLng || (p = g.center);\r\n var m = e(g, d);\r\n s.addObject(\"shapes\", m),\r\n p &&\r\n c == \"circle\" &&\r\n o.getGeoLocation(p).then(function(e) {\r\n m.setCenter(e), m.centered && m.map.setCenter(e);\r\n var o = r.geoCallback;\r\n o && n(o)(t);\r\n }),\r\n s.observeAttrSetObj(u, r, m),\r\n a.bind(\"$destroy\", function() {\r\n s.deleteObject(\"shapes\", m);\r\n });\r\n };\r\n return { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: a };\r\n };\r\n (t.$inject = [\"Attr2MapOptions\", \"$parse\", \"NgMap\"]),\r\n angular.module(\"ngMap\").directive(\"shape\", t);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function(e, t) {\r\n var n = e,\r\n o = function(e, t, n) {\r\n var o, i;\r\n t.container &&\r\n ((i = document.getElementById(t.container)),\r\n (i = i || document.querySelector(t.container))),\r\n i\r\n ? (o = new google.maps.StreetViewPanorama(i, t))\r\n : ((o = e.getStreetView()), o.setOptions(t));\r\n for (var a in n) a && google.maps.event.addListener(o, a, n[a]);\r\n return o;\r\n },\r\n i = function(e, i, a) {\r\n var r = n.filter(a),\r\n s = n.getOptions(r, { scope: e }),\r\n p = n.getControlOptions(r),\r\n c = angular.extend(s, p),\r\n u = n.getEvents(e, r);\r\n t.getMap().then(function(e) {\r\n var t = o(e, c, u);\r\n e.setStreetView(t),\r\n !t.getPosition() && t.setPosition(e.getCenter()),\r\n google.maps.event.addListener(\r\n t,\r\n \"position_changed\",\r\n function() {\r\n t.getPosition() !== e.getCenter() &&\r\n e.setCenter(t.getPosition());\r\n }\r\n );\r\n var n = google.maps.event.addListener(\r\n e,\r\n \"center_changed\",\r\n function() {\r\n t.setPosition(e.getCenter()),\r\n google.maps.event.removeListener(n);\r\n }\r\n );\r\n });\r\n };\r\n return { restrict: \"E\", require: [\"?^map\", \"?^ngMap\"], link: i };\r\n };\r\n (e.$inject = [\"Attr2MapOptions\", \"NgMap\"]),\r\n angular.module(\"ngMap\").directive(\"streetViewPanorama\", e);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"trafficLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.TrafficLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.orgAttributes(o),\r\n s = t.filter(i),\r\n p = t.getOptions(s, { scope: e }),\r\n c = t.getEvents(e, s),\r\n u = n(p, c);\r\n a.addObject(\"trafficLayers\", u),\r\n a.observeAttrSetObj(r, i, u),\r\n o.bind(\"$destroy\", function() {\r\n a.deleteObject(\"trafficLayers\", u);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n angular.module(\"ngMap\").directive(\"transitLayer\", [\r\n \"Attr2MapOptions\",\r\n function(e) {\r\n var t = e,\r\n n = function(e, t) {\r\n var n = new google.maps.TransitLayer(e);\r\n for (var o in t) google.maps.event.addListener(n, o, t[o]);\r\n return n;\r\n };\r\n return {\r\n restrict: \"E\",\r\n require: [\"?^map\", \"?^ngMap\"],\r\n link: function(e, o, i, a) {\r\n a = a[0] || a[1];\r\n var r = t.orgAttributes(o),\r\n s = t.filter(i),\r\n p = t.getOptions(s, { scope: e }),\r\n c = t.getEvents(e, s),\r\n u = n(p, c);\r\n a.addObject(\"transitLayers\", u),\r\n a.observeAttrSetObj(r, i, u),\r\n o.bind(\"$destroy\", function() {\r\n a.deleteObject(\"transitLayers\", u);\r\n });\r\n }\r\n };\r\n }\r\n ]);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = /([\\:\\-\\_]+(.))/g,\r\n t = /^moz([A-Z])/,\r\n n = function() {\r\n return function(n) {\r\n return n\r\n .replace(e, function(e, t, n, o) {\r\n return o ? n.toUpperCase() : n;\r\n })\r\n .replace(t, \"Moz$1\");\r\n };\r\n };\r\n angular.module(\"ngMap\").filter(\"camelCase\", n);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e = function() {\r\n return function(e) {\r\n try {\r\n return JSON.parse(e), e;\r\n }\r\n catch (t) {\r\n return e\r\n .replace(/([\\$\\w]+)\\s*:/g, function(e, t) {\r\n return '\"' + t + '\":';\r\n })\r\n .replace(/'([^']+)'/g, function(e, t) {\r\n return '\"' + t + '\"';\r\n });\r\n }\r\n };\r\n };\r\n angular.module(\"ngMap\").filter(\"jsonize\", e);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var isoDateRE = /^(\\d{4}\\-\\d\\d\\-\\d\\d([tT][\\d:\\.]*)?)([zZ]|([+\\-])(\\d\\d):?(\\d\\d))?$/,\r\n Attr2MapOptions = function(\r\n $parse,\r\n $timeout,\r\n $log,\r\n NavigatorGeolocation,\r\n GeoCoder,\r\n camelCaseFilter,\r\n jsonizeFilter\r\n ) {\r\n var orgAttributes = function(e) {\r\n e.length > 0 && (e = e[0]);\r\n for (var t = {}, n = 0; n < e.attributes.length; n++) {\r\n var o = e.attributes[n];\r\n t[o.name] = o.value;\r\n }\r\n return t;\r\n },\r\n getJSON = function(e) {\r\n var t = /^[\\+\\-]?[0-9\\.]+,[ ]*\\ ?[\\+\\-]?[0-9\\.]+$/;\r\n return (\r\n e.match(t) && (e = \"[\" + e + \"]\"), JSON.parse(jsonizeFilter(e))\r\n );\r\n },\r\n getLatLng = function(e) {\r\n var t = e;\r\n return (\r\n e[0].constructor == Array\r\n ? (t = e.map(function(e) {\r\n return new google.maps.LatLng(e[0], e[1]);\r\n }))\r\n : !isNaN(parseFloat(e[0])) &&\r\n isFinite(e[0]) &&\r\n (t = new google.maps.LatLng(t[0], t[1])),\r\n t\r\n );\r\n },\r\n toOptionValue = function(input, options) {\r\n var output;\r\n try {\r\n output = getNumber(input);\r\n }\r\n catch (err) {\r\n try {\r\n var output = getJSON(input);\r\n if (output instanceof Array)\r\n output =\r\n output[0].constructor == Object\r\n ? output\r\n : getLatLng(output);\r\n else if (output === Object(output)) {\r\n var newOptions = options;\r\n (newOptions.doNotConverStringToNumber = !0),\r\n (output = getOptions(output, newOptions));\r\n }\r\n }\r\n catch (err2) {\r\n if (input.match(/^[A-Z][a-zA-Z0-9]+\\(.*\\)$/))\r\n try {\r\n var exp = \"new google.maps.\" + input;\r\n output = eval(exp);\r\n }\r\n catch (e) {\r\n output = input;\r\n }\r\n else if (input.match(/^([A-Z][a-zA-Z0-9]+)\\.([A-Z]+)$/))\r\n try {\r\n var matches = input.match(\r\n /^([A-Z][a-zA-Z0-9]+)\\.([A-Z]+)$/\r\n );\r\n output = google.maps[matches[1]][matches[2]];\r\n }\r\n catch (e) {\r\n output = input;\r\n }\r\n else if (input.match(/^[A-Z]+$/))\r\n try {\r\n var capitalizedKey =\r\n options.key.charAt(0).toUpperCase() +\r\n options.key.slice(1);\r\n options.key.match(\r\n /temperatureUnit|windSpeedUnit|labelColor/\r\n )\r\n ? ((capitalizedKey = capitalizedKey.replace(/s$/, \"\")),\r\n (output = google.maps.weather[capitalizedKey][input]))\r\n : (output = google.maps[capitalizedKey][input]);\r\n }\r\n catch (e) {\r\n output = input;\r\n }\r\n else if (input.match(isoDateRE))\r\n try {\r\n output = new Date(input);\r\n }\r\n catch (e) {\r\n output = input;\r\n }\r\n else if (input.match(/^{/) && options.scope)\r\n try {\r\n var expr = input.replace(/{{/, \"\").replace(/}}/g, \"\");\r\n output = options.scope.$eval(expr);\r\n }\r\n catch (err) {\r\n output = input;\r\n }\r\n else output = input;\r\n }\r\n }\r\n if (\r\n ((options.key == \"center\" || options.key == \"position\") &&\r\n output instanceof Array &&\r\n (output = new google.maps.LatLng(output[0], output[1])),\r\n options.key == \"bounds\" &&\r\n output instanceof Array &&\r\n (output = new google.maps.LatLngBounds(output[0], output[1])),\r\n options.key == \"icons\" && output instanceof Array)\r\n )\r\n for (var i = 0; i < output.length; i++) {\r\n var el = output[i];\r\n el.icon.path.match(/^[A-Z_]+$/) &&\r\n (el.icon.path = google.maps.SymbolPath[el.icon.path]);\r\n }\r\n if (options.key == \"icon\" && output instanceof Object) {\r\n (\"\" + output.path).match(/^[A-Z_]+$/) &&\r\n (output.path = google.maps.SymbolPath[output.path]);\r\n for (var key in output) {\r\n var arr = output[key];\r\n key == \"anchor\" || key == \"origin\" || key == \"labelOrigin\"\r\n ? (output[key] = new google.maps.Point(arr[0], arr[1]))\r\n : (key == \"size\" || key == \"scaledSize\") &&\r\n (output[key] = new google.maps.Size(arr[0], arr[1]));\r\n }\r\n }\r\n return output;\r\n },\r\n getAttrsToObserve = function(e) {\r\n var t = [];\r\n if (!e.noWatcher)\r\n for (var n in e) {\r\n var o = e[n];\r\n o && o.match(/\\{\\{.*\\}\\}/) && t.push(camelCaseFilter(n));\r\n }\r\n return t;\r\n },\r\n filter = function(e) {\r\n var t = {};\r\n for (var n in e)\r\n n.match(/^\\$/) || n.match(/^ng[A-Z]/) || (t[n] = e[n]);\r\n return t;\r\n },\r\n getOptions = function(e, t) {\r\n t = t || {};\r\n var n = {};\r\n for (var o in e)\r\n if (e[o] || e[o] === 0) {\r\n if (o.match(/^on[A-Z]/)) continue;\r\n if (o.match(/ControlOptions$/)) continue;\r\n n[o] =\r\n typeof e[o] != \"string\"\r\n ? e[o]\r\n : t.doNotConverStringToNumber && e[o].match(/^[0-9]+$/)\r\n ? e[o]\r\n : toOptionValue(e[o], { key: o, scope: t.scope });\r\n }\r\n return n;\r\n },\r\n getEvents = function(e, t) {\r\n var n = {},\r\n o = function(e) {\r\n return \"_\" + e.toLowerCase();\r\n },\r\n i = function(t) {\r\n var n = t.match(/([^\\(]+)\\(([^\\)]*)\\)/),\r\n o = n[1],\r\n i = n[2].replace(/event[ ,]*/, \"\"),\r\n a = $parse(\"[\" + i + \"]\");\r\n return function(t) {\r\n function n(e, t) {\r\n return e[t];\r\n }\r\n var i = a(e),\r\n r = o.split(\".\").reduce(n, e);\r\n r && r.apply(this, [t].concat(i)),\r\n $timeout(function() {\r\n e.$apply();\r\n });\r\n };\r\n };\r\n for (var a in t)\r\n if (t[a]) {\r\n if (!a.match(/^on[A-Z]/)) continue;\r\n var r = a.replace(/^on/, \"\");\r\n (r = r.charAt(0).toLowerCase() + r.slice(1)),\r\n (r = r.replace(/([A-Z])/g, o));\r\n var s = t[a];\r\n n[r] = new i(s);\r\n }\r\n return n;\r\n },\r\n getControlOptions = function(e) {\r\n var t = {};\r\n if (typeof e != \"object\") return !1;\r\n for (var n in e)\r\n if (e[n]) {\r\n if (!n.match(/(.*)ControlOptions$/)) continue;\r\n var o = e[n],\r\n i = o.replace(/'/g, '\"');\r\n i = i.replace(/([^\"]+)|(\"[^\"]+\")/g, function(e, t, n) {\r\n return t ? t.replace(/([a-zA-Z0-9]+?):/g, '\"$1\":') : n;\r\n });\r\n try {\r\n var a = JSON.parse(i);\r\n for (var r in a)\r\n if (a[r]) {\r\n var s = a[r];\r\n if (\r\n (typeof s == \"string\"\r\n ? (s = s.toUpperCase())\r\n : r === \"mapTypeIds\" &&\r\n (s = s.map(function(e) {\r\n return e.match(/^[A-Z]+$/)\r\n ? google.maps.MapTypeId[e.toUpperCase()]\r\n : e;\r\n })),\r\n r === \"style\")\r\n ) {\r\n var p = n.charAt(0).toUpperCase() + n.slice(1),\r\n c = p.replace(/Options$/, \"\") + \"Style\";\r\n a[r] = google.maps[c][s];\r\n }\r\n else\r\n a[r] =\r\n r === \"position\"\r\n ? google.maps.ControlPosition[s]\r\n : s;\r\n }\r\n t[n] = a;\r\n }\r\n catch (u) {}\r\n }\r\n return t;\r\n };\r\n return {\r\n filter: filter,\r\n getOptions: getOptions,\r\n getEvents: getEvents,\r\n getControlOptions: getControlOptions,\r\n toOptionValue: toOptionValue,\r\n getAttrsToObserve: getAttrsToObserve,\r\n orgAttributes: orgAttributes\r\n };\r\n };\r\n (Attr2MapOptions.$inject = [\r\n \"$parse\",\r\n \"$timeout\",\r\n \"$log\",\r\n \"NavigatorGeolocation\",\r\n \"GeoCoder\",\r\n \"camelCaseFilter\",\r\n \"jsonizeFilter\"\r\n ]),\r\n angular.module(\"ngMap\").service(\"Attr2MapOptions\", Attr2MapOptions);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t = function(t) {\r\n var n = e.defer(),\r\n o = new google.maps.Geocoder();\r\n return (\r\n o.geocode(t, function(e, t) {\r\n t == google.maps.GeocoderStatus.OK ? n.resolve(e) : n.reject(t);\r\n }),\r\n n.promise\r\n );\r\n },\r\n n = function(n) {\r\n return (e = n), { geocode: t };\r\n };\r\n (n.$inject = [\"$q\"]), angular.module(\"ngMap\").service(\"GeoCoder\", n);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t = function(t) {\r\n var n = e.defer();\r\n return (\r\n navigator.geolocation\r\n ? (void 0 === t\r\n ? (t = { timeout: 5e3 })\r\n : void 0 === t.timeout && (t.timeout = 5e3),\r\n navigator.geolocation.getCurrentPosition(\r\n function(e) {\r\n n.resolve(e);\r\n },\r\n function(e) {\r\n n.reject(e);\r\n },\r\n t\r\n ))\r\n : n.reject(\"Browser Geolocation service failed.\"),\r\n n.promise\r\n );\r\n },\r\n n = function(n) {\r\n return (e = n), { getCurrentPosition: t };\r\n };\r\n (n.$inject = [\"$q\"]),\r\n angular.module(\"ngMap\").service(\"NavigatorGeolocation\", n);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o = [],\r\n i = function(n) {\r\n var i = t.createElement(\"div\");\r\n (i.style.width = \"100%\"), (i.style.height = \"100%\"), n.appendChild(i);\r\n var a = new e.google.maps.Map(i, {});\r\n return o.push(a), a;\r\n },\r\n a = function(e, t) {\r\n for (var n, i = 0; i < o.length; i++) {\r\n var a = o[i];\r\n if (a.id == t && !a.inUse) {\r\n var r = a.getDiv();\r\n e.appendChild(r), (n = a);\r\n break;\r\n }\r\n }\r\n return n;\r\n },\r\n r = function(e) {\r\n for (var t, n = 0; n < o.length; n++) {\r\n var i = o[n];\r\n if (!i.id && !i.inUse) {\r\n var a = i.getDiv();\r\n e.appendChild(a), (t = i);\r\n break;\r\n }\r\n }\r\n return t;\r\n },\r\n s = function(e) {\r\n var t = a(e, e.id) || r(e);\r\n return (\r\n t\r\n ? n(function() {\r\n google.maps.event.trigger(t, \"idle\");\r\n }, 100)\r\n : (t = i(e)),\r\n (t.inUse = !0),\r\n t\r\n );\r\n },\r\n p = function(e) {\r\n e.inUse = !1;\r\n },\r\n c = function() {\r\n for (var e = 0; e < o.length; e++) o[e] = null;\r\n o = [];\r\n },\r\n u = function(i, a, r) {\r\n return (\r\n (t = i[0]),\r\n (e = a),\r\n (n = r),\r\n {\r\n mapInstances: o,\r\n resetMapInstances: c,\r\n getMapInstance: s,\r\n returnMapInstance: p\r\n }\r\n );\r\n };\r\n (u.$inject = [\"$document\", \"$window\", \"$timeout\"]),\r\n angular.module(\"ngMap\").factory(\"NgMapPool\", u);\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t,\r\n n,\r\n o,\r\n i,\r\n a,\r\n r,\r\n s = {},\r\n p = function(n, o) {\r\n var i;\r\n return (\r\n n.currentStyle\r\n ? (i = n.currentStyle[o])\r\n : e.getComputedStyle &&\r\n (i = t.defaultView\r\n .getComputedStyle(n, null)\r\n .getPropertyValue(o)),\r\n i\r\n );\r\n },\r\n c = function(e) {\r\n var t = s[e || 0];\r\n return t.map instanceof google.maps.Map\r\n ? void 0\r\n : (t.initializeMap(), t.map);\r\n },\r\n u = function(t) {\r\n function o(n) {\r\n s[t]\r\n ? i.resolve(s[t].map)\r\n : n > a\r\n ? i.reject(\"could not find map\")\r\n : e.setTimeout(function() {\r\n o(n + 100);\r\n }, 100);\r\n }\r\n (t = typeof t == \"object\" ? t.id : t), (t = t || 0);\r\n var i = n.defer(),\r\n a = 2e3;\r\n return o(0), i.promise;\r\n },\r\n l = function(e) {\r\n if (e.map) {\r\n var t = Object.keys(s).length;\r\n s[e.map.id || t] = e;\r\n }\r\n },\r\n g = function(e) {\r\n var t = Object.keys(s).length - 1,\r\n n = e.map.id || t;\r\n if (e.map) {\r\n for (var o in e.eventListeners) {\r\n var i = e.eventListeners[o];\r\n google.maps.event.removeListener(i);\r\n }\r\n e.map.controls &&\r\n e.map.controls.forEach(function(e) {\r\n e.clear();\r\n });\r\n }\r\n e.map.heatmapLayers &&\r\n Object.keys(e.map.heatmapLayers).forEach(function(t) {\r\n e.deleteObject(\"heatmapLayers\", e.map.heatmapLayers[t]);\r\n }),\r\n delete s[n];\r\n },\r\n d = function(e, t) {\r\n var i = n.defer();\r\n return (\r\n !e || e.match(/^current/i)\r\n ? o.getCurrentPosition(t).then(\r\n function(e) {\r\n var t = e.coords.latitude,\r\n n = e.coords.longitude,\r\n o = new google.maps.LatLng(t, n);\r\n i.resolve(o);\r\n },\r\n function(e) {\r\n i.reject(e);\r\n }\r\n )\r\n : a.geocode({ address: e }).then(\r\n function(e) {\r\n i.resolve(e[0].geometry.location);\r\n },\r\n function(e) {\r\n i.reject(e);\r\n }\r\n ),\r\n i.promise\r\n );\r\n },\r\n m = function(e, t) {\r\n return function(n) {\r\n if (n) {\r\n var o = r(\"set-\" + e),\r\n a = i.toOptionValue(n, { key: e });\r\n t[o] &&\r\n (e.match(/center|position/) && typeof a == \"string\"\r\n ? d(a).then(function(e) {\r\n t[o](e);\r\n })\r\n : t[o](a));\r\n }\r\n };\r\n },\r\n f = function(e) {\r\n var t = e.getAttribute(\"default-style\");\r\n t == \"true\"\r\n ? ((e.style.display = \"block\"), (e.style.height = \"300px\"))\r\n : (p(e, \"display\") != \"block\" && (e.style.display = \"block\"),\r\n p(e, \"height\").match(/^(0|auto)/) && (e.style.height = \"300px\"));\r\n };\r\n angular.module(\"ngMap\").provider(\"NgMap\", function() {\r\n var s = {};\r\n this.setDefaultOptions = function(e) {\r\n s = e;\r\n };\r\n var p = function(p, v, y, h, M, b, O) {\r\n return (\r\n (e = p),\r\n (t = v[0]),\r\n (n = y),\r\n (o = h),\r\n (i = M),\r\n (a = b),\r\n (r = O),\r\n {\r\n defaultOptions: s,\r\n addMap: l,\r\n deleteMap: g,\r\n getMap: u,\r\n initMap: c,\r\n setStyle: f,\r\n getGeoLocation: d,\r\n observeAndSet: m\r\n }\r\n );\r\n };\r\n (p.$inject = [\r\n \"$window\",\r\n \"$document\",\r\n \"$q\",\r\n \"NavigatorGeolocation\",\r\n \"Attr2MapOptions\",\r\n \"GeoCoder\",\r\n \"camelCaseFilter\"\r\n ]),\r\n (this.$get = p);\r\n });\r\n })(),\r\n (function() {\r\n \"use strict\";\r\n var e,\r\n t = function(t, n) {\r\n n = n || t.getCenter();\r\n var o = e.defer(),\r\n i = new google.maps.StreetViewService();\r\n return (\r\n i.getPanoramaByLocation(n || t.getCenter, 100, function(e, t) {\r\n t === google.maps.StreetViewStatus.OK\r\n ? o.resolve(e.location.pano)\r\n : o.resolve(!1);\r\n }),\r\n o.promise\r\n );\r\n },\r\n n = function(e, t) {\r\n var n = new google.maps.StreetViewPanorama(e.getDiv(), {\r\n enableCloseButton: !0\r\n });\r\n n.setPano(t);\r\n },\r\n o = function(o) {\r\n return (e = o), { getPanorama: t, setPanorama: n };\r\n };\r\n (o.$inject = [\"$q\"]), angular.module(\"ngMap\").service(\"StreetView\", o);\r\n })(),\r\n \"ngMap\"\r\n );\r\n});\r\n","/* global google */\r\n\r\n(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"RiskRankingMapController\", [\r\n \"$scope\",\r\n \"$http\",\r\n \"$timeout\",\r\n \"$location\",\r\n \"$attrs\",\r\n \"NgMap\",\r\n \"LoadingService\",\r\n RiskRankingMapController,\r\n ]);\r\n\r\n function RiskRankingMapController(\r\n $scope,\r\n $http,\r\n $timeout,\r\n $location,\r\n $attrs,\r\n NgMap,\r\n LoadingService,\r\n ) {\r\n /*\r\n Helper functions from the old site for making shape data into a format Google Maps likes.\r\n Haven't touched these, just ported from the original code.\r\n */\r\n\r\n var regExes = {\r\n typeStr: /^\\s*(\\w+)\\s*\\(\\s*(.*)\\s*\\)\\s*$/,\r\n parenComma: /\\)\\s*,\\s*\\(/,\r\n doubleParenComma: /\\)\\s*\\)\\s*,\\s*\\(\\s*\\(/,\r\n trimParens: /^\\s*\\(?(.*?)\\)?\\s*$/,\r\n };\r\n\r\n // Helper function to determine the pixel location of a map lat/long\r\n // Used for Google Maps events that don't pass the browser mouse event data (x/y coords)\r\n $scope.latLng2Point = function(latLng, map) {\r\n var topRight = map\r\n .getProjection()\r\n .fromLatLngToPoint(map.getBounds().getNorthEast());\r\n var bottomLeft = map\r\n .getProjection()\r\n .fromLatLngToPoint(map.getBounds().getSouthWest());\r\n var scale = Math.pow(2, map.getZoom());\r\n var worldPoint = map.getProjection().fromLatLngToPoint(latLng);\r\n return new google.maps.Point(\r\n (worldPoint.x - bottomLeft.x) * scale,\r\n (worldPoint.y - topRight.y) * scale,\r\n );\r\n };\r\n\t\t\r\n if (!google.maps.Polygon.prototype.getBounds) {\r\n google.maps.Polygon.prototype.getBounds = function () {\r\n var bounds = new google.maps.LatLngBounds();\r\n this.getPaths().forEach(function (element, index) {\r\n bounds.extend(element);\r\n });\r\n return bounds;\r\n };\r\n }\r\n\r\n // Per spec, legend colors are hard-coded, not provided by Datacenter\r\n $scope.legend = {\r\n colors: [\"#0b7ace\", \"#3895cb\", \"#6fb6cc\", \"#97cdd0\", \"#c4e4df\"],\r\n };\r\n\r\n $scope.mapHoverBox = {\r\n posLeft: 0,\r\n posTop: 0,\r\n hoveredCommunity: {},\r\n };\r\n\r\n $scope.doCommunityGroupToggle = function(groupName) {\r\n $scope.state.selectedCommunities.toggleCommunitiesByGroup(groupName);\r\n\r\n // Tracks when a user clicks on a community group checkbox button on the \"Risk Ranking\" page\r\n dataLayer.push({\r\n event: \"Community Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Community Click|\" + groupName,\r\n eventLabel: window.location.pathname,\r\n });\r\n };\r\n\r\n $scope.doCommunityToggle = function(communityId) {\r\n $scope.state.selectedCommunities.toggleCommunity(communityId);\r\n\r\n // Tracks when a user clicks on a community checkbox button on the \"Risk Ranking\" page\r\n var communityName = $scope.state.communitiesById[communityId].name;\r\n dataLayer.push({\r\n event: \"Community Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Community Click|\" + communityName,\r\n eventLabel: window.location.pathname,\r\n });\r\n };\r\n\r\n // Boundary overlay radios\r\n var overlaysString = $location.search().overlays;\r\n if (overlaysString) {\r\n // Select overlays based on url param\r\n $scope.overlayIds = overlaysString;\r\n var buttonValue = (overlaysString instanceof Array && overlaysString.length > 0)\r\n ? overlaysString[0]\r\n : overlaysString;\r\n $('.boundary-radio-hidden[value=\"' + buttonValue + '\"]')\r\n .prop(\"checked\", true);\r\n }\r\n\r\n $(\".boundary-radio-hidden, .boundary-radio-hidden::before, .boundary-overlay-list label\").click(function (e) {\r\n $(\".boundary-radio-hidden\").each(function (_index, radio) {\r\n $(radio).prop(\"checked\", null);\r\n });\r\n\r\n var $hiddenRadio = $(\".boundary-radio-hidden\", $(e.target).parents(\"li\"));\r\n $hiddenRadio.prop(\"checked\", true);\r\n\r\n var radioVal = $hiddenRadio.val();\r\n var overlayVals = radioVal ? [radioVal, radioVal + \"_label\"] : null;\r\n $scope.overlayIds = overlayVals;\r\n\r\n // Update url param with user's selected overlay\r\n $location.search(\"overlays\", overlayVals);\r\n\r\n $scope.rrMap.renderOverlays();\r\n\r\n e.preventDefault();\r\n e.stopPropagation();\r\n });\r\n\r\n // Show/hide the legend when the \"+/-\" button is clicked\r\n $(\".icon-map-overlay-control\").on(\"click\", function (e) {\r\n e.preventDefault();\r\n\r\n var $content = $(\".map-overlay-content\", $(e.target).parents(\".map-overlay-block\"));\r\n if (self.isClosed) {\r\n // Show it\r\n $content.parent().removeClass(\"closed\");\r\n $content.slideDown();\r\n self.isClosed = false;\r\n }\r\n else {\r\n // Hide it\r\n $content.slideUp(400, function () {\r\n $content.parent().addClass(\"closed\");\r\n });\r\n self.isClosed = true;\r\n }\r\n });\r\n\r\n $scope.rrMap = {\r\n // Only need to render the full map once\r\n hasRendered: false,\r\n\r\n // Used to determine functionality for live site vs. \"Download Image\" mode\r\n mapOptions: {\r\n zoomControl: $scope.currentURL.search().cap ? false : true,\r\n scrollWheel: false,\r\n },\r\n\r\n // `forceInactive` is used to force a community shape back to its non-highlighted default state,\r\n // specifically when a user is hovering on a community and clicks to deselect it. Without\r\n // forcing a non-highlighted state, the code would detect the `mouseover` and highlight it\r\n styleShape: function(fips, forceInactive) {\r\n // It's possible this method will be called before the map is rendered\r\n // If so, just return out. We'll update the shape style eventually.\r\n // TODO - Feels like a race condition. What's causing this?\r\n if (!$scope.cShapes) {\r\n return false;\r\n }\r\n\r\n // Sugar\r\n var shape = $scope.cShapes[fips];\r\n var community = $scope.state.communitiesByFips[fips];\r\n\r\n // Set the shape's background color\r\n // First, make sure the shape that the user is interacting with is an actual community,\r\n // not a non-community placeholder shape on the map\r\n if (!community) {\r\n return; \r\n }\r\n\r\n var determineShapeColorUsingLegend = true;\r\n\r\n var curCommunity = $scope.state.communitiesByFips[fips];\r\n var indicatorsForCurCommunity = curCommunity.indicators;\r\n var selectedIndicator = $scope.state.selectedIndicator;\r\n if (selectedIndicator) {\r\n var curIndicatorId = selectedIndicator.id;\r\n var curIndicatorForCurCommunity = indicatorsForCurCommunity[curIndicatorId];\r\n \r\n if (curIndicatorForCurCommunity) {\r\n var indicatorValueForCurLocation = curIndicatorForCurCommunity.value;\r\n \r\n // Try to determine the color for the shape by comparing its community's data value with the bounds from the legend\r\n var index = $scope.state.mapLegend.findIndex((legendItem) => {\r\n return (legendItem.lowDataValue <= indicatorValueForCurLocation && indicatorValueForCurLocation < legendItem.highDataValue); \r\n });\r\n \r\n determineShapeColorUsingLegend = index >= 0;\r\n }\r\n else {\r\n determineShapeColorUsingLegend = false;\r\n }\r\n }\r\n else {\r\n determineShapeColorUsingLegend = false;\r\n }\r\n\r\n if (determineShapeColorUsingLegend) {\r\n $scope.cShapes[fips].fillColor = $scope.legend.colors[index];\r\n }\r\n else {\r\n if (selectedIndicator && selectedIndicator.id === $scope.state.selectedDomain.id) {\r\n $scope.cShapes[fips].fillColor = $scope.legend.colorByGroup[community.group];\r\n }\r\n else if ($scope.legend.colorByCommunity && $scope.legend.colorByCommunity[community.id]) {\r\n $scope.cShapes[fips].fillColor = $scope.legend.colorByCommunity[community.id];\r\n }\r\n else {\r\n $scope.cShapes[fips].fillColor = $scope.legend.colorByGroup[community.group];\r\n }\r\n }\r\n\r\n // Check if shape is hovered, but we're not forcing the default un-highlighted state\r\n if (\r\n !forceInactive &&\r\n ($scope.mapHoverBox.hoveredCommunity.id === community.id ||\r\n $scope.state.selectedCommunities.communityIsSelected(\r\n community.id,\r\n ))\r\n ) {\r\n shape.setOptions({\r\n strokeColor: \"#dea003\",\r\n strokeWeight: 3,\r\n zIndex: 99,\r\n });\r\n }\r\n else {\r\n shape.setOptions({\r\n strokeColor: \"#fff\",\r\n strokeWeight: 1,\r\n zIndex: 1,\r\n });\r\n }\r\n },\r\n actions: {\r\n mouseover: function(event, shape, map) {\r\n $scope.mapHoverBox.hoveredCommunity =\r\n $scope.state.communitiesByFips[shape.fips];\r\n\r\n $scope.mapHoverBox.communityRank = $scope.mapHoverBox.hoveredCommunity\r\n ? $scope.mapHoverBox.hoveredCommunity.rank\r\n : 0;\r\n\r\n // When hovering over a community, mark this flag as false so we don't also show the asset pop-out\r\n $scope.mapHoverBox.isAsset = false;\r\n\r\n if ($scope.mapHoverBox.hoveredCommunity) {\r\n var eventLoc = $scope.latLng2Point(event.latLng, $scope.map);\r\n\r\n $scope.mapHoverBox.posLeft = eventLoc.x + 25;\r\n $scope.mapHoverBox.posTop = eventLoc.y - 50;\r\n }\r\n\r\n $scope.$apply();\r\n $scope.rrMap.styleShape(shape.fips);\r\n },\r\n mouseout: function(event, shape, map) {\r\n // Reset the map hover box\r\n $scope.mapHoverBox.posLeft = null;\r\n $scope.mapHoverBox.posTop = null;\r\n $scope.mapHoverBox.communityRank = null;\r\n $scope.mapHoverBox.hoveredCommunity = {};\r\n $scope.$apply();\r\n\r\n $scope.rrMap.styleShape(shape.fips);\r\n },\r\n click: function(event, shape, map) {\r\n // Tracks when a user clicks on a community on a map on the \"Risk Ranking\" page\r\n var community = $scope.state.communitiesByFips[shape.fips];\r\n if (community) {\r\n dataLayer.push({\r\n event: \"Map Clicks\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Map Click|\" + community.name,\r\n eventLabel: window.location.pathname,\r\n });\r\n $scope.state.selectedCommunities.toggleCommunity(community.id);\r\n }\r\n else {\r\n console.error(\"No community found for this shape:\", shape);\r\n\t\t\t\t\t\t\r\n var latitude = event.latLng.lat();\r\n var longitude = event.latLng.lng();\r\n console.debug(latitude + \", \" + longitude);\r\n }\r\n },\r\n },\r\n\r\n // This is wrapped in a function so we can hold off on generating the map until all the\r\n // community data we need has successfully loaded\r\n render: function() {\r\n if (!$scope.rrMap.hasRendered) {\r\n NgMap.getMap().then(function(map) {\r\n $scope.rrMap.labelEngine = new CCCLabelEngine(map);\r\n var shapesLoadingId = LoadingService.taskStart(\r\n $scope,\r\n \"Loading Map Shapes\",\r\n );\r\n\r\n $http({\r\n method: \"GET\",\r\n url:\r\n \"/api/MapData/GetWktPolygons?locationTypeId=3&isSimplified=true\",\r\n cache: true,\r\n }).then(\r\n function successCallback(response) {\r\n $scope.rrMap.hasRendered = true;\r\n\r\n $scope.cShapes = {};\r\n\r\n // Parse out the WKT text into google API Polygons\r\n for (var fips in response.data) {\r\n var wktPolygon = response.data[fips];\r\n\r\n var polygon = ccc.viz.map.util.MapUtil.wktToGooglePolygon(\r\n wktPolygon,\r\n );\r\n\r\n polygon.setOptions({\r\n fips: fips,\r\n });\r\n\r\n $scope.cShapes[fips] = polygon;\r\n\r\n $scope.rrMap.styleShape(fips);\r\n\r\n // Render the shape on the map itself\r\n $scope.cShapes[fips].setMap(map);\r\n\r\n google.maps.event.addListener(\r\n $scope.cShapes[fips],\r\n \"mouseover\",\r\n function(event) {\r\n $scope.rrMap.actions.mouseover(event, this, map);\r\n },\r\n );\r\n\r\n google.maps.event.addListener(\r\n $scope.cShapes[fips],\r\n \"mouseout\",\r\n function(event) {\r\n $scope.rrMap.actions.mouseout(event, this, map);\r\n },\r\n );\r\n\r\n google.maps.event.addListener(\r\n $scope.cShapes[fips],\r\n \"click\",\r\n function(event) {\r\n var latitude = event.latLng.lat();\r\n var longitude = event.latLng.lng();\r\n console.debug( latitude + \", \" + longitude );\r\n\r\n $scope.rrMap.actions.click(event, this, map);\r\n },\r\n );\r\n }\r\n\r\n LoadingService.taskEnd($scope, shapesLoadingId);\r\n\r\n $scope.rrMap.renderOverlays();\r\n\r\n if (!$attrs.assetMap) {\r\n $timeout(function() {\r\n // At this point, the map should be all done rendering\r\n if ($scope.currentURL.search().cap) {\r\n window.triggerScreenshot();\r\n }\r\n }, 750);\r\n }\r\n },\r\n function errorCallback(response) {\r\n console.warn(\"Couldn't load map shapes!\");\r\n },\r\n );\r\n\r\n // Check URL params for default zoom level and center point\r\n if ($location.search().zoom) {\r\n map.setZoom(+$location.search().zoom);\r\n }\r\n\r\n if ($location.search().center) {\r\n var rawLatLng = $location.search().center.split(\",\");\r\n var latLng = {\r\n lat: +rawLatLng[0],\r\n lng: +rawLatLng[1],\r\n };\r\n map.setCenter(latLng);\r\n }\r\n\r\n // Update the URL params when a user zooms, or drags the map around\r\n google.maps.event.addListener(map, \"zoom_changed\", function() {\r\n $scope.$apply(function() {\r\n $location.search(\"zoom\", map.getZoom());\r\n });\r\n });\r\n\r\n google.maps.event.addListener(map, \"center_changed\", function() {\r\n $scope.$apply(function() {\r\n $location.search(\r\n \"center\",\r\n map.getCenter().lat() + \",\" + map.getCenter().lng(),\r\n );\r\n });\r\n });\r\n google.maps.event.addListener(map, \"idle\", () => $scope.rrMap.renderLabels());\r\n\r\n }); // NgMap\r\n } // hasRendered\r\n }, // render()\r\n\r\n labelLoadingTaskDone: function(labelsLoadingId) {\r\n return () => {\r\n LoadingService.taskEnd($scope, labelsLoadingId);\r\n };\r\n },\r\n\r\n overlayColors: {\r\n \"0\": \"#E7298A\",\r\n \"1\": \"#E7298A\",\r\n \"2\": \"#E7298A\",\r\n \"3\": \"#E7298A\",\r\n \"4\": \"#E7298A\",\r\n \"5\": \"#E7298A\",\r\n \"6\": \"#E7298A\",\r\n \"7\": \"#E7298A\",\r\n },\r\n\r\n removeOverlays: function () {\r\n // Remove old shapes\r\n if ($scope.overlayShapes) {\r\n for (var index in $scope.overlayShapes) {\r\n var polygon = $scope.overlayShapes[index];\r\n if (polygon && polygon.setMap) {\r\n polygon.setMap(null);\r\n }\r\n }\r\n }\r\n },\r\n\r\n removeOverlayMarkers: function () {\r\n // Remove old markers\r\n if ($scope.overlayMarkers) {\r\n for (var index in $scope.overlayMarkers) {\r\n var marker = $scope.overlayMarkers[index];\r\n if (marker && marker.setMap) {\r\n marker.setMap(null);\r\n }\r\n }\r\n }\r\n },\r\n\r\n renderLabels: function() {\r\n // TO DO - Loading Service tasks for labels\r\n // var labelsLoadingId = LoadingService.taskStart($scope, \"Loading Labels\");\r\n $scope.rrMap.labelEngine.renderLabels($scope.overlayMarkers);//, null, $scope.rrMap.labelLoadingTaskDone(labelsLoadingId));\r\n },\r\n\r\n renderOverlays: function () {\r\n var overlaysLoadingId = LoadingService.taskStart($scope, \"Loading District Overlays\");\r\n\r\n $scope.rrMap.removeOverlays();\r\n $scope.rrMap.removeOverlayMarkers();\r\n\r\n const zipcodesOverlayTypeId = \"4\";\r\n var labelClasslist = \"overlay-label overlay-label--hidden\"; // Hide labels until we're ready, once LabelGun prunes the overlapping ones\r\n var overlayTypeIds = $scope.overlayIds;\r\n if (overlayTypeIds instanceof Array && overlayTypeIds.indexOf(zipcodesOverlayTypeId) >= 0) {\t\t\t\t\t\t\r\n labelClasslist += \" overlay-label--small\"; // Use smaller font for zipcodes\r\n }\r\n\r\n // global\r\n var hardcodedPositions = hardcodedPositionsByOverlayTypeAndFips;\r\n NgMap.getMap().then(function (map) {\r\n $http({\r\n method: \"GET\",\r\n url: \"/api/MapData/GetWktOverlayPolygons?overlays=\" + $scope.overlayIds, //overlayTypesToRequest,\r\n cache: true,\r\n }).then(function successCallback(response) {\r\n // Remove references to the marker and shape objects as late as possible\r\n // Doing this too soon results in orphaned markers and shapes after zooming\r\n // This is because Google's setMap() is asynchronous\r\n // See $scope.rrMap.removeOverlays(), $scope.rrMap.removeOverlayMarkers()\r\n $scope.overlayShapes = [];\r\n $scope.overlayMarkers = [];\r\n\r\n // Parse out the WKT text into google API Polygons\r\n for (var key in response.data) {\r\n var tokens = key.split(\"|\");\r\n var overlayType = tokens[0];\r\n var fips = tokens[1];\r\n var labelContent = tokens[2];\r\n\r\n var wktPolygon = response.data[key];\r\n\r\n var polygon = ccc.viz.map.util.MapUtil.wktToGooglePolygon(wktPolygon);\r\n\r\n polygon.setOptions({\r\n clickable: false,\r\n fips: fips,\r\n overlayType: overlayType,\r\n fillColor: \"#ffffff\",\r\n fillOpacity: 0.0,\r\n strokeColor: $scope.rrMap.overlayColors[overlayType],\r\n strokeOpacity: 0.9,\r\n strokeWeight: 1,\r\n zIndex: 100 + parseInt(overlayType),\r\n });\r\n\r\n // Render the shape on the map itself\r\n polygon.setMap(map);\r\n $scope.overlayShapes.push(polygon);\r\n\r\n // Add label marker for the polygon\r\n var noIcon = {\r\n // Workaround to have a label with no icon\r\n path: google.maps.SymbolPath.CIRCLE,\r\n scale: 0,\r\n };\r\n\t\t\t\t\t\t\t\r\n // account for label text itself\r\n var labelXOffset = 5 + fips.length * 4;\r\n var labelYOffset = 15;\r\n var labelAnchor = new google.maps.Point(labelXOffset, labelYOffset);\r\n\t\t\t\t\t\t\t\r\n // hardcoded positions\r\n var positions;\r\n if (hardcodedPositions && hardcodedPositions[overlayType]) {\r\n positions = hardcodedPositions[overlayType][fips];\r\n }\r\n\t\t\t\t\t\t\t\r\n if (!positions || !positions.length) {\r\n // Just need one label - position over geometric center\r\n var center = polygon.getBounds().getCenter();\r\n $scope.overlayMarkers.push(new MarkerWithLabel({\r\n map: map,\r\n position: center,\r\n icon: noIcon,\r\n labelContent: labelContent,\r\n labelAnchor: labelAnchor,\r\n labelClass: labelClasslist,\r\n }));\r\n }\r\n else {\r\n positions.forEach(position => {\r\n // Hardcoded positions, and possibly multiple labels for gerrymandered/multipolygon locations\r\n $scope.overlayMarkers.push(new MarkerWithLabel({\r\n map: map,\r\n position: new google.maps.LatLng(position[0], position[1]),//position,\r\n icon: noIcon,\r\n labelContent: labelContent,\r\n labelAnchor: labelAnchor,\r\n labelClass: labelClasslist,\r\n }));\t\t\t\t\t\t\t\t\r\n });\r\n }\r\n } // foreach key in response.data\r\n LoadingService.taskEnd($scope, overlaysLoadingId);\r\n\r\n $scope.rrMap.renderLabels();\r\n }); // $http.then()\r\n }); // renderOverlays()\r\n },\r\n }; // $scope.rrMap\r\n } // RiskRankingMapController\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccRiskRankingMap\", cccRiskRankingMap);\r\n\r\n function cccRiskRankingMap() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"RiskRankingMapController\"\r\n };\r\n }\r\n})();\r\n","/* global google */\r\n\r\n(function() {\r\n \"use strict\";\r\n\r\n angular\r\n .module(\"app\")\r\n .controller(\"TypeaheadController\", [\r\n \"$scope\",\r\n \"$attrs\",\r\n \"$filter\",\r\n \"NgMap\",\r\n \"$timeout\",\r\n TypeaheadController\r\n ]);\r\n\r\n function TypeaheadController($scope, $attrs, $filter, NgMap, $timeout) {\r\n $scope.typeahead = {};\r\n\r\n $scope.resetTypeahead = function() {\r\n // Prevents first item from being selected automatically\r\n $scope.typeahead.selectedIndex = -1;\r\n\r\n $scope.typeahead.filteredResults = [];\r\n\r\n // Empty out typeahead input field\r\n $scope.queryString = \"\";\r\n };\r\n\r\n $scope.resetTypeahead();\r\n\r\n $scope.updateTypeahead = function(needle) {\r\n $scope.typeahead.filteredResults = $scope.state.communityData.filter(\r\n function(community) {\r\n return firstLettersFilter(community.name, needle);\r\n }\r\n );\r\n };\r\n\r\n $scope.focus = function(event) {\r\n $timeout(function() {\r\n $scope.updateTypeahead(\"\");\r\n }, 150);\r\n };\r\n\r\n // Typeahead filter\r\n // Requirements: find matches for community names where any word starts with a user-provided\r\n // string. Example: `b` matches `Battery Park` and `Midtown Business District`\r\n // TODO - This should probably be its own filter file\r\n function firstLettersFilter(haystack, needle) {\r\n // Split the community name by any non-word characters\r\n var parsedStr = haystack.toLowerCase().split(/\\W+/);\r\n\r\n // User input and strings to search are converted to lowercase\r\n needle = needle.toLowerCase();\r\n\r\n for (var i = 0; i < parsedStr.length; i++) {\r\n if (parsedStr[i].indexOf(needle) === 0) {\r\n // string match found!\r\n return true;\r\n }\r\n }\r\n\r\n // no string match found\r\n return false;\r\n }\r\n\r\n $scope.typeaheadKeyPressed = function(event, communityId) {\r\n // console.log(communityId);\r\n // console.log($scope.typeaheadSelected.index);\r\n if (event.key === \"ArrowDown\") {\r\n $scope.typeahead.selectedIndex += 1;\r\n }\r\n else if (event.key === \"ArrowUp\") {\r\n $scope.typeahead.selectedIndex =\r\n $scope.typeahead.selectedIndex >= -1\r\n ? $scope.typeahead.selectedIndex - 1\r\n : -1;\r\n }\r\n else if (event.key === \"Enter\") {\r\n var selectedId =\r\n $scope.typeahead.filteredResults[$scope.typeahead.selectedIndex].id;\r\n $scope.typeaheadSelected(selectedId);\r\n }\r\n else {\r\n // If the keypress isn't an arrow, it's probably a letter or number\r\n // Reset the selected index\r\n $scope.typeahead.selectedIndex = -1;\r\n }\r\n };\r\n\r\n function getBoundsZoomLevel(bounds, mapDim) {\r\n var WORLD_DIM = { height: 256, width: 256 };\r\n var ZOOM_MAX = 21;\r\n\r\n function latRad(lat) {\r\n var sin = Math.sin((lat * Math.PI) / 180);\r\n var radX2 = Math.log((1 + sin) / (1 - sin)) / 2;\r\n return Math.max(Math.min(radX2, Math.PI), -Math.PI) / 2;\r\n }\r\n\r\n function zoom(mapPx, worldPx, fraction) {\r\n return Math.floor(Math.log(mapPx / worldPx / fraction) / Math.LN2);\r\n }\r\n\r\n var ne = bounds.getNorthEast();\r\n var sw = bounds.getSouthWest();\r\n\r\n var latFraction = (latRad(ne.lat()) - latRad(sw.lat())) / Math.PI;\r\n\r\n var lngDiff = ne.lng() - sw.lng();\r\n var lngFraction = (lngDiff < 0 ? lngDiff + 360 : lngDiff) / 360;\r\n\r\n var latZoom = zoom(mapDim.height, WORLD_DIM.height, latFraction);\r\n var lngZoom = zoom(mapDim.width, WORLD_DIM.width, lngFraction);\r\n\r\n return Math.min(latZoom, lngZoom, ZOOM_MAX);\r\n }\r\n\r\n function getBounds(shape) {\r\n function extendBounds(bounds, point) {\r\n return bounds.extend(point);\r\n }\r\n\r\n return _.flatten(\r\n shape.getPaths().b.map(function(path) {\r\n return path.b;\r\n })\r\n ).reduce(extendBounds, new google.maps.LatLngBounds());\r\n }\r\n\r\n $scope.typeaheadSelected = function(id) {\r\n // Tracks when a user searches for a community from the \"Communities by Risk\" right rail on the \"Risk Ranking\" page and clicks on it\r\n var community = $scope.state.communitiesById[id];\r\n dataLayer.push({\r\n event: \"Community Search\",\r\n eventCategory: \"Interactive Clicks\",\r\n eventAction: \"Community Search|\" + community.name,\r\n eventLabel: window.location.pathname\r\n });\r\n\r\n $scope.state.selectedCommunities.toggleCommunity(id);\r\n $scope.resetTypeahead();\r\n\r\n if ($attrs.zoomOnSelection) {\r\n var bounds = getBounds($scope.cShapes[community.fips]);\r\n var center = bounds.getCenter(),\r\n mapDiv = $scope.map.getDiv(),\r\n zoom = getBoundsZoomLevel(bounds, {\r\n width: $(mapDiv).width(),\r\n height: $(mapDiv).height()\r\n });\r\n\r\n setTimeout(function() {\r\n $scope.map.setZoom(zoom);\r\n $scope.map.setCenter(center);\r\n }, 0);\r\n }\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").directive(\"cccTypeahead\", cccTypeahead);\r\n\r\n function cccTypeahead() {\r\n return {\r\n restrict: \"EA\",\r\n scope: false,\r\n controller: \"TypeaheadController\"\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").factory(\"rankingsService\", [RankingsService]);\r\n\r\n function RankingsService() {\r\n return {\r\n lowestRanked: function(rankedCommunities) {\r\n if (!rankedCommunities) return [];\r\n\r\n var lowestRank = _.first(rankedCommunities).rank;\r\n return rankedCommunities.filter(function(cd) {\r\n return cd.rank === lowestRank;\r\n });\r\n },\r\n highestRanked: function(rankedCommunities) {\r\n if (!rankedCommunities) return [];\r\n\r\n var highestRank = _.last(rankedCommunities).rank;\r\n return rankedCommunities.filter(function(cd) {\r\n return cd.rank === highestRank;\r\n });\r\n }\r\n };\r\n }\r\n})();\r\n","(function() {\r\n \"use strict\";\r\n\r\n angular.module(\"app\").filter(\"reverse\", function() {\r\n return function(items) {\r\n return Array.isArray(items) ? items.slice().reverse() : items;\r\n };\r\n });\r\n})();\r\n"]}