{"version":3,"file":"markerclustererplus.min.js","sources":["../src/overlay-view-safe.ts","../src/cluster-icon.ts","../src/cluster.ts","../src/markerclusterer.ts"],"sourcesContent":["/**\n * Copyright 2019 Google LLC. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface OverlayViewSafe extends google.maps.OverlayView {}\n\n/**\n * Extends an object's prototype by another's.\n *\n * @param type1 The Type to be extended.\n * @param type2 The Type to extend with.\n * @ignore\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction extend(type1: any, type2: any): void {\n for (const property in type2.prototype) {\n type1.prototype[property] = type2.prototype[property];\n }\n}\n\n/**\n * @ignore\n */\nexport class OverlayViewSafe {\n constructor() {\n // MarkerClusterer implements google.maps.OverlayView interface. We use the\n // extend function to extend MarkerClusterer with google.maps.OverlayView\n // because it might not always be available when the code is defined so we\n // look for it at the last possible moment. If it doesn't exist now then\n // there is no point going ahead :)\n extend(OverlayViewSafe, google.maps.OverlayView);\n }\n}\n","/**\n * Copyright 2019 Google LLC. All Rights Reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * This class represents the object for values in the `styles` array passed\n * to the {@link MarkerClusterer} constructor. The element in this array that is used to\n * style the cluster icon is determined by calling the `calculator` function.\n */\nimport { Cluster } from \"./cluster\";\nimport { OverlayViewSafe } from \"./overlay-view-safe\";\n\nexport interface ClusterIconStyle {\n /** The URL of the cluster icon image file. Required. */\n url: string;\n /** Height The display height (in pixels) of the cluster icon. Required. */\n height: number;\n /** Width The display width (in pixels) of the cluster icon. Required. */\n width: number;\n /**\n * The position (in pixels) from the center of the cluster icon to\n * where the text label is to be centered and drawn. The format is `[yoffset, xoffset]`\n * where `yoffset` increases as you go down from center and `xoffset`\n * increases to the right of center. The default is `[0, 0]`.\n */\n anchorText?: [number, number];\n /**\n * The anchor position (in pixels) of the cluster icon. This is the\n * spot on the cluster icon that is to be aligned with the cluster position. The format is\n * `[yoffset, xoffset]` where `yoffset` increases as you go down and\n * `xoffset` increases to the right of the top-left corner of the icon. The default\n * anchor position is the center of the cluster icon.\n */\n anchorIcon?: [number, number];\n /**\n * The color of the label text shown on the cluster icon.\n * @default `\"black\"`\n */\n textColor?: string;\n /** The size (in pixels) of the label text shown on the cluster icon.\n * @default `11`\n */\n textSize?: number;\n /**\n * The value of the CSS `text-decoration`\n * property for the label text shown on the cluster icon.\n *\n * @default `\"none\"`\n */\n textDecoration?: string;\n /**\n * The value of the CSS `font-weight`\n * property for the label text shown on the cluster icon.\n *\n * @default `\"bold\"`\n */\n fontWeight?: string;\n /**\n * The value of the CSS `font-style`\n * property for the label text shown on the cluster icon.\n *\n * @default `\"normal\"`\n */\n fontStyle?: string;\n /**\n * The value of the CSS `font-family`\n * property for the label text shown on the cluster icon.\n * @default `\"Arial,sans-serif\"`\n */\n fontFamily?: string;\n /**\n * The position of the cluster icon image\n * within the image defined by `url`. The format is `\"xpos ypos\"`\n * (the same format as for the CSS `background-position` property). You must set\n * this property appropriately when the image defined by `url` represents a sprite\n * containing multiple images. Note that the position must be specified in px units.\n *\n * @default `\"0 0\"`\n */\n backgroundPosition?: string;\n}\n\n/**\n * @description This is an object containing general information about a cluster icon. This is\n * the object that a `calculator` function returns.\n */\nexport interface ClusterIconInfo {\n /**\n * The text of the label to be shown on the cluster icon.\n */\n text: string;\n /**\n * The index plus 1 of the element in the `styles`\n */\n index: number;\n /**\n * The tooltip to display when the mouse moves over the cluster icon.\n * If this value is `undefined` or `\"\"`, `title` is set to the\n * value of the `title` property passed to the MarkerClusterer.\n */\n title: string;\n}\n\n/**\n * A cluster icon.\n */\nexport class ClusterIcon extends OverlayViewSafe {\n private className_ = this.cluster_.getMarkerClusterer().getClusterClass();\n private center_: google.maps.LatLng = null;\n private div_: HTMLDivElement = null;\n private sums_: ClusterIconInfo = null;\n private visible_ = false;\n\n private url_: string;\n private height_: number;\n private width_: number;\n private anchorText_: [number, number];\n private anchorIcon_: [number, number];\n private textColor_: string;\n private textSize_: number;\n private textDecoration_: string;\n private fontWeight_: string;\n private fontStyle_: string;\n private fontFamily_: string;\n private backgroundPosition_: string;\n\n private boundsChangedListener_: google.maps.MapsEventListener;\n\n /**\n * @param cluster_ The cluster with which the icon is to be associated.\n * @param styles_ An array of {@link ClusterIconStyle} defining the cluster icons\n * to use for various cluster sizes.\n */\n constructor(private cluster_: Cluster, private styles_: ClusterIconStyle[]) {\n super();\n\n this.setMap(cluster_.getMap()); // Note: this causes onAdd to be called\n }\n\n /**\n * Adds the icon to the DOM.\n */\n onAdd(): void {\n let cMouseDownInCluster: boolean;\n let cDraggingMapByCluster: boolean;\n\n const mc = this.cluster_.getMarkerClusterer();\n\n const [major, minor] = google.maps.version.split(\".\");\n\n const gmVersion = parseInt(major, 10) * 100 + parseInt(minor, 10);\n\n this.div_ = document.createElement(\"div\");\n this.div_.className = this.className_;\n if (this.visible_) {\n this.show();\n }\n\n this.getPanes().overlayMouseTarget.appendChild(this.div_);\n\n // Fix for Issue 157\n this.boundsChangedListener_ = google.maps.event.addListener(\n this.getMap(),\n \"bounds_changed\",\n function() {\n cDraggingMapByCluster = cMouseDownInCluster;\n }\n );\n\n google.maps.event.addDomListener(this.div_, \"mousedown\", () => {\n cMouseDownInCluster = true;\n cDraggingMapByCluster = false;\n });\n\n // March 1, 2018: Fix for this 3.32 exp bug, https://issuetracker.google.com/issues/73571522\n // But it doesn't work with earlier releases so do a version check.\n if (gmVersion >= 332) {\n // Ugly version-dependent code\n google.maps.event.addDomListener(this.div_, \"touchstart\", e => {\n e.stopPropagation();\n });\n }\n\n google.maps.event.addDomListener(this.div_, \"click\", e => {\n cMouseDownInCluster = false;\n if (!cDraggingMapByCluster) {\n /**\n * This event is fired when a cluster marker is clicked.\n * @name MarkerClusterer#click\n * @param {Cluster} c The cluster that was clicked.\n * @event\n */\n google.maps.event.trigger(mc, \"click\", this.cluster_);\n google.maps.event.trigger(mc, \"clusterclick\", this.cluster_); // deprecated name\n\n // The default click handler follows. Disable it by setting\n // the zoomOnClick property to false.\n if (mc.getZoomOnClick()) {\n // Zoom into the cluster.\n const mz = mc.getMaxZoom();\n const theBounds = this.cluster_.getBounds();\n (mc.getMap() as google.maps.Map).fitBounds(theBounds);\n // There is a fix for Issue 170 here:\n setTimeout(function() {\n (mc.getMap() as google.maps.Map).fitBounds(theBounds);\n // Don't zoom beyond the max zoom level\n if (mz !== null && mc.getMap().getZoom() > mz) {\n mc.getMap().setZoom(mz + 1);\n }\n }, 100);\n }\n\n // Prevent event propagation to the map:\n e.cancelBubble = true;\n if (e.stopPropagation) {\n e.stopPropagation();\n }\n }\n });\n\n google.maps.event.addDomListener(this.div_, \"mouseover\", () => {\n /**\n * This event is fired when the mouse moves over a cluster marker.\n * @name MarkerClusterer#mouseover\n * @param {Cluster} c The cluster that the mouse moved over.\n * @event\n */\n google.maps.event.trigger(mc, \"mouseover\", this.cluster_);\n });\n\n google.maps.event.addDomListener(this.div_, \"mouseout\", () => {\n /**\n * This event is fired when the mouse moves out of a cluster marker.\n * @name MarkerClusterer#mouseout\n * @param {Cluster} c The cluster that the mouse moved out of.\n * @event\n */\n google.maps.event.trigger(mc, \"mouseout\", this.cluster_);\n });\n }\n\n /**\n * Removes the icon from the DOM.\n */\n onRemove(): void {\n if (this.div_ && this.div_.parentNode) {\n this.hide();\n google.maps.event.removeListener(this.boundsChangedListener_);\n google.maps.event.clearInstanceListeners(this.div_);\n this.div_.parentNode.removeChild(this.div_);\n this.div_ = null;\n }\n }\n\n /**\n * Draws the icon.\n */\n draw(): void {\n if (this.visible_) {\n const pos = this.getPosFromLatLng_(this.center_);\n this.div_.style.top = pos.y + \"px\";\n this.div_.style.left = pos.x + \"px\";\n }\n }\n\n /**\n * Hides the icon.\n */\n hide(): void {\n if (this.div_) {\n this.div_.style.display = \"none\";\n }\n this.visible_ = false;\n }\n\n /**\n * Positions and shows the icon.\n */\n show(): void {\n if (this.div_) {\n const mc = this.cluster_.getMarkerClusterer();\n const ariaLabel = mc.ariaLabelFn(this.sums_.text);\n // NOTE: values must be specified in px units\n const bp = this.backgroundPosition_.split(\" \");\n const spriteH = parseInt(bp[0].replace(/^\\s+|\\s+$/g, \"\"), 10);\n const spriteV = parseInt(bp[1].replace(/^\\s+|\\s+$/g, \"\"), 10);\n this.div_.style.cssText = this.createCss_(\n this.getPosFromLatLng_(this.center_)\n );\n\n let imgDimensions = \"\";\n if (this.cluster_.getMarkerClusterer().getEnableRetinaIcons()) {\n imgDimensions = `width: ${this.width_}px; height: ${this.height_}px`;\n } else {\n const [Y1, X1, Y2, X2] = [\n -1 * spriteV,\n -1 * spriteH + this.width_,\n -1 * spriteV + this.height_,\n -1 * spriteH\n ];\n\n imgDimensions = `clip: rect(${Y1}px, ${X1}px, ${Y2}px, ${X2}px)`;\n }\n\n const imgStyle = [\n `position: absolute`,\n `top: ${spriteV}px`,\n `left: ${spriteH}px`,\n imgDimensions\n ].join(\";\");\n\n const divStyle = [\n `position: absolute`,\n `top: ${this.anchorText_[0]}px`,\n `left: ${this.anchorText_[1]}px`,\n `color: ${this.textColor_}`,\n `font-size: ${this.textSize_}px`,\n `font-family: ${this.fontFamily_}`,\n `font-weight: ${this.fontWeight_}`,\n `font-style: ${this.fontStyle_}`,\n `text-decoration: ${this.textDecoration_}`,\n `text-align: center`,\n `width: ${this.width_}px`,\n `line-height: ${this.height_}px`\n ].join(\";\");\n\n this.div_.innerHTML = `\n\n
\n * This is an enhanced V3 implementation of the V2 MarkerClusterer by Xiaoxi Wu. It is\n * based on the V3 MarkerClusterer port by Luke Mahe. MarkerClustererPlus was created\n * by Gary Little.\n *
\n * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It\n * adds support for the `ignoreHidden`, `title`, `batchSizeIE`,\n * and `calculator` properties as well as support for four more events. It also allows\n * greater control over the styling of the text that appears on the cluster marker. The\n * documentation has been significantly improved and the overall code has been simplified and\n * polished. Very large numbers of markers can now be managed without causing Javascript timeout\n * errors on Internet Explorer. Note that the name of the `clusterclick` event has been\n * deprecated. The new name is `click`, so please change your application code now.\n */\n\nimport { Cluster } from \"./cluster\";\nimport { ClusterIconInfo, ClusterIconStyle } from \"./cluster-icon\";\nimport { OverlayViewSafe } from \"./overlay-view-safe\";\n\n/**\n * @param text The text attribute of the cluster\n */\ntype AriaLabelFn = (text: string) => string;\n\nexport type Calculator = (\n markers: google.maps.Marker[],\n clusterIconStylesCount: number\n) => ClusterIconInfo;\n\nexport interface ClusterAugmentedMarker extends google.maps.Marker {\n isAdded?: boolean;\n}\n\n/**\n * This event is fired on the {@link MarkerClusterer} instance when the `MarkerClusterer` stops clustering markers.\n *\n * Example:\n * ```typescript\n * mc.addListener('clusteringend', (mc: MarkerClusterer) => {})\n * ```\n *\n * @param mc The MarkerClusterer whose markers are being clustered.\n * @event clusteringend\n */\nexport declare function clusteringend(mc: MarkerClusterer): void;\n\n/**\n * This event is fired on the {@link MarkerClusterer} instance when the `MarkerClusterer` begins clustering markers.\n *\n * Example:\n * ```typescript\n * mc.addListener('clusteringbegin', (mc: MarkerClusterer) => {})\n * ```\n *\n * @param mc The MarkerClusterer whose markers are being clustered.\n * @event clusteringbegin\n */\nexport declare function clusteringbegin(mc: MarkerClusterer): void;\n\n/**\n * Optional parameter passed to the {@link MarkerClusterer} constructor.\n */\nexport interface MarkerClustererOptions {\n /**\n * The grid size of a cluster in pixels. The grid is a square.\n *\n * @default `60`\n */\n gridSize?: number;\n /**\n * The maximum zoom level at which clustering is enabled or\n * `null` if clustering is to be enabled at all zoom levels.\n *\n * @default `null`\n */\n maxZoom?: number;\n\n /**\n * Whether to zoom the map when a cluster marker is\n * clicked. You may want to set this to `false` if you have installed a handler\n * for the `click` event and it deals with zooming on its own.\n *\n * @default `true`\n */\n zoomOnClick?: boolean;\n /***\n * Whether the position of a cluster marker should be\n * the average position of all markers in the cluster. If set to `false`, the\n * cluster marker is positioned at the location of the first marker added to the cluster.\n *\n * @default `false`\n */\n averageCenter?: boolean;\n /**\n * The minimum number of markers needed in a cluster\n * before the markers are hidden and a cluster marker appears.\n *\n * @default `2`\n */\n minimumClusterSize?: number;\n\n /**\n * the z-index of a cluster.\n *\n * @default `google.maps.Marker.MAX_ZINDEX + 1`\n */\n zIndex?: number;\n\n /**\n * Whether to ignore hidden markers in clusters. You\n * may want to set this to `true` to ensure that hidden markers are not included\n * in the marker count that appears on a cluster marker (this count is the value of the\n * `text` property of the result returned by the default {@link calculator}).\n * If set to `true` and you change the visibility of a marker being clustered, be\n * sure to also call {@link MarkerClusterer#repaint()}.\n *\n * @default `false`\n */\n ignoreHidden?: boolean;\n /**\n * The tooltip to display when the mouse moves over a cluster\n * marker. (Alternatively, you can use a custom `calculator` function to specify a\n * different tooltip for each cluster marker.)\n *\n * @default `\"\"`\n */\n title?: string;\n /**\n * The function used to determine\n * the text to be displayed on a cluster marker and the index indicating which style to use\n * for the cluster marker. The input parameters for the function are (1) the array of markers\n * represented by a cluster marker and (2) the number of cluster icon styles. It returns a\n * {@link ClusterIconInfo} object. The default `calculator` returns a\n * `text` property which is the number of markers in the cluster and an\n * `index` property which is one higher than the lowest integer such that\n * `10^i` exceeds the number of markers in the cluster, or the size of the styles\n * array, whichever is less. The `styles` array element used has an index of\n * `index` minus 1. For example, the default `calculator` returns a\n * `text` value of `\"125\"` and an `index` of `3`\n * for a cluster icon representing 125 markers so the element used in the `styles`\n * array is `2`. A `calculator` may also return a `title`\n * property that contains the text of the tooltip to be used for the cluster marker. If\n * `title` is not defined, the tooltip is set to the value of the `title`\n * property for the MarkerClusterer.\n *\n * @default {@link MarkerClusterer.CALCULATOR}\n */\n calculator?: Calculator;\n /**\n * The name of the CSS class defining general styles\n * for the cluster markers. Use this class to define CSS styles that are not set up by the code\n * that processes the `styles` array.\n *\n * @default `\"cluster\"`\n */\n clusterClass?: string;\n /**\n * An array of {@link ClusterIconStyle} elements defining the styles\n * of the cluster markers to be used. The element to be used to style a given cluster marker\n * is determined by the function defined by the `calculator` property.\n * The default is an array of {@link ClusterIconStyle} elements whose properties are derived\n * from the values for `imagePath`, `imageExtension`, and `imageSizes`.\n *\n * @default `styles`\n */\n styles?: ClusterIconStyle[];\n /**\n * Whether to allow the use of cluster icons that\n * have sizes that are some multiple (typically double) of their actual display size. Icons such\n * as these look better when viewed on high-resolution monitors such as Apple's Retina displays.\n * Note: if this property is `true`, sprites cannot be used as cluster icons.\n *\n * @default `false`\n */\n enableRetinaIcons?: boolean;\n /**\n * Set this property to the number of markers to be processed in a single batch when using\n * a browser other than Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).\n *\n * @default `MarkerClusterer.BATCH_SIZE`\n */\n batchSize?: number;\n /**\n * When Internet Explorer is\n * being used, markers are processed in several batches with a small delay inserted between\n * each batch in an attempt to avoid Javascript timeout errors. Set this property to the\n * number of markers to be processed in a single batch; select as high a number as you can\n * without causing a timeout error in the browser. This number might need to be as low as 100\n * if 15,000 markers are being managed, for example.\n *\n * @default `MarkerClusterer.BATCH_SIZE_IE`\n */\n batchSizeIE?: number;\n /**\n * The full URL of the root name of the group of image files to use for cluster icons.\n * The complete file name is of the form `imagePath`n.`imageExtension`\n * where n is the image file number (1, 2, etc.).\n *\n * @default `MarkerClusterer.IMAGE_PATH`\n */\n imagePath?: string;\n /**\n * The extension name for the cluster icon image files (e.g., `\"png\"` or\n * `\"jpg\"`).\n *\n * @default `MarkerClusterer.IMAGE_EXTENSION`\n */\n imageExtension?: string;\n /**\n * An array of numbers containing the widths of the group of\n * `imagePath`n.`imageExtension` image files.\n * (The images are assumed to be square.)\n *\n * @default `MarkerClusterer.IMAGE_SIZES`\n */\n imageSizes?: number[];\n /**\n * A function to take the text attribute associated with the cluster and output a string to attach an\n * ariaLabel to the cluster\n */\n ariaLabelFn?: AriaLabelFn;\n}\n\n/**\n * @ignore\n */\nconst getOption =