init
This commit is contained in:
956
node_modules/@googlemaps/markerclusterer/dist/index.esm.js
generated
vendored
Normal file
956
node_modules/@googlemaps/markerclusterer/dist/index.esm.js
generated
vendored
Normal file
@@ -0,0 +1,956 @@
|
||||
import equal from 'fast-deep-equal';
|
||||
import SuperCluster from 'supercluster';
|
||||
|
||||
/*! *****************************************************************************
|
||||
Copyright (c) Microsoft Corporation.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
***************************************************************************** */
|
||||
|
||||
function __rest(s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||||
t[p[i]] = s[p[i]];
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* util class that creates a common set of convenience functions to wrap
|
||||
* shared behavior of Advanced Markers and Markers.
|
||||
*/
|
||||
class MarkerUtils {
|
||||
static isAdvancedMarkerAvailable(map) {
|
||||
return (google.maps.marker &&
|
||||
map.getMapCapabilities().isAdvancedMarkersAvailable === true);
|
||||
}
|
||||
static isAdvancedMarker(marker) {
|
||||
return (google.maps.marker &&
|
||||
marker instanceof google.maps.marker.AdvancedMarkerElement);
|
||||
}
|
||||
static setMap(marker, map) {
|
||||
if (this.isAdvancedMarker(marker)) {
|
||||
marker.map = map;
|
||||
}
|
||||
else {
|
||||
marker.setMap(map);
|
||||
}
|
||||
}
|
||||
static getPosition(marker) {
|
||||
// SuperClusterAlgorithm.calculate expects a LatLng instance so we fake it for Adv Markers
|
||||
if (this.isAdvancedMarker(marker)) {
|
||||
if (marker.position) {
|
||||
if (marker.position instanceof google.maps.LatLng) {
|
||||
return marker.position;
|
||||
}
|
||||
// since we can't cast to LatLngLiteral for reasons =(
|
||||
if (marker.position.lat && marker.position.lng) {
|
||||
return new google.maps.LatLng(marker.position.lat, marker.position.lng);
|
||||
}
|
||||
}
|
||||
return new google.maps.LatLng(null);
|
||||
}
|
||||
return marker.getPosition();
|
||||
}
|
||||
static getVisible(marker) {
|
||||
if (this.isAdvancedMarker(marker)) {
|
||||
/**
|
||||
* Always return true for Advanced Markers because the clusterer
|
||||
* uses getVisible as a way to count legacy markers not as an actual
|
||||
* indicator of visibility for some reason. Even when markers are hidden
|
||||
* Marker.getVisible returns `true` and this is used to set the marker count
|
||||
* on the cluster. See the behavior of Cluster.count
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
return marker.getVisible();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
class Cluster {
|
||||
constructor({ markers, position }) {
|
||||
this.markers = markers;
|
||||
if (position) {
|
||||
if (position instanceof google.maps.LatLng) {
|
||||
this._position = position;
|
||||
}
|
||||
else {
|
||||
this._position = new google.maps.LatLng(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
get bounds() {
|
||||
if (this.markers.length === 0 && !this._position) {
|
||||
return;
|
||||
}
|
||||
const bounds = new google.maps.LatLngBounds(this._position, this._position);
|
||||
for (const marker of this.markers) {
|
||||
bounds.extend(MarkerUtils.getPosition(marker));
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
get position() {
|
||||
return this._position || this.bounds.getCenter();
|
||||
}
|
||||
/**
|
||||
* Get the count of **visible** markers.
|
||||
*/
|
||||
get count() {
|
||||
return this.markers.filter((m) => MarkerUtils.getVisible(m)).length;
|
||||
}
|
||||
/**
|
||||
* Add a marker to the cluster.
|
||||
*/
|
||||
push(marker) {
|
||||
this.markers.push(marker);
|
||||
}
|
||||
/**
|
||||
* Cleanup references and remove marker from map.
|
||||
*/
|
||||
delete() {
|
||||
if (this.marker) {
|
||||
MarkerUtils.setMap(this.marker, null);
|
||||
this.marker = undefined;
|
||||
}
|
||||
this.markers.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Returns the markers visible in a padded map viewport
|
||||
*
|
||||
* @param map
|
||||
* @param mapCanvasProjection
|
||||
* @param markers The list of marker to filter
|
||||
* @param viewportPaddingPixels The padding in pixel
|
||||
* @returns The list of markers in the padded viewport
|
||||
*/
|
||||
const filterMarkersToPaddedViewport = (map, mapCanvasProjection, markers, viewportPaddingPixels) => {
|
||||
const extendedMapBounds = extendBoundsToPaddedViewport(map.getBounds(), mapCanvasProjection, viewportPaddingPixels);
|
||||
return markers.filter((marker) => extendedMapBounds.contains(MarkerUtils.getPosition(marker)));
|
||||
};
|
||||
/**
|
||||
* Extends a bounds by a number of pixels in each direction
|
||||
*/
|
||||
const extendBoundsToPaddedViewport = (bounds, projection, numPixels) => {
|
||||
const { northEast, southWest } = latLngBoundsToPixelBounds(bounds, projection);
|
||||
const extendedPixelBounds = extendPixelBounds({ northEast, southWest }, numPixels);
|
||||
return pixelBoundsToLatLngBounds(extendedPixelBounds, projection);
|
||||
};
|
||||
/**
|
||||
* Gets the extended bounds as a bbox [westLng, southLat, eastLng, northLat]
|
||||
*/
|
||||
const getPaddedViewport = (bounds, projection, pixels) => {
|
||||
const extended = extendBoundsToPaddedViewport(bounds, projection, pixels);
|
||||
const ne = extended.getNorthEast();
|
||||
const sw = extended.getSouthWest();
|
||||
return [sw.lng(), sw.lat(), ne.lng(), ne.lat()];
|
||||
};
|
||||
/**
|
||||
* Returns the distance between 2 positions.
|
||||
*
|
||||
* @hidden
|
||||
*/
|
||||
const distanceBetweenPoints = (p1, p2) => {
|
||||
const R = 6371; // Radius of the Earth in km
|
||||
const dLat = ((p2.lat - p1.lat) * Math.PI) / 180;
|
||||
const dLon = ((p2.lng - p1.lng) * Math.PI) / 180;
|
||||
const sinDLat = Math.sin(dLat / 2);
|
||||
const sinDLon = Math.sin(dLon / 2);
|
||||
const a = sinDLat * sinDLat +
|
||||
Math.cos((p1.lat * Math.PI) / 180) *
|
||||
Math.cos((p2.lat * Math.PI) / 180) *
|
||||
sinDLon *
|
||||
sinDLon;
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
};
|
||||
/**
|
||||
* Converts a LatLng bound to pixels.
|
||||
*
|
||||
* @hidden
|
||||
*/
|
||||
const latLngBoundsToPixelBounds = (bounds, projection) => {
|
||||
return {
|
||||
northEast: projection.fromLatLngToDivPixel(bounds.getNorthEast()),
|
||||
southWest: projection.fromLatLngToDivPixel(bounds.getSouthWest()),
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Extends a pixel bounds by numPixels in all directions.
|
||||
*
|
||||
* @hidden
|
||||
*/
|
||||
const extendPixelBounds = ({ northEast, southWest }, numPixels) => {
|
||||
northEast.x += numPixels;
|
||||
northEast.y -= numPixels;
|
||||
southWest.x -= numPixels;
|
||||
southWest.y += numPixels;
|
||||
return { northEast, southWest };
|
||||
};
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const pixelBoundsToLatLngBounds = ({ northEast, southWest }, projection) => {
|
||||
const sw = projection.fromDivPixelToLatLng(southWest);
|
||||
const ne = projection.fromDivPixelToLatLng(northEast);
|
||||
return new google.maps.LatLngBounds(sw, ne);
|
||||
};
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
class AbstractAlgorithm {
|
||||
constructor({ maxZoom = 16 }) {
|
||||
this.maxZoom = maxZoom;
|
||||
}
|
||||
/**
|
||||
* Helper function to bypass clustering based upon some map state such as
|
||||
* zoom, number of markers, etc.
|
||||
*
|
||||
* ```typescript
|
||||
* cluster({markers, map}: AlgorithmInput): Cluster[] {
|
||||
* if (shouldBypassClustering(map)) {
|
||||
* return this.noop({markers})
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
noop({ markers, }) {
|
||||
return noop(markers);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Abstract viewport algorithm proves a class to filter markers by a padded
|
||||
* viewport. This is a common optimization.
|
||||
*
|
||||
* @hidden
|
||||
*/
|
||||
class AbstractViewportAlgorithm extends AbstractAlgorithm {
|
||||
constructor(_a) {
|
||||
var { viewportPadding = 60 } = _a, options = __rest(_a, ["viewportPadding"]);
|
||||
super(options);
|
||||
this.viewportPadding = 60;
|
||||
this.viewportPadding = viewportPadding;
|
||||
}
|
||||
calculate({ markers, map, mapCanvasProjection, }) {
|
||||
if (map.getZoom() >= this.maxZoom) {
|
||||
return {
|
||||
clusters: this.noop({
|
||||
markers,
|
||||
}),
|
||||
changed: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
clusters: this.cluster({
|
||||
markers: filterMarkersToPaddedViewport(map, mapCanvasProjection, markers, this.viewportPadding),
|
||||
map,
|
||||
mapCanvasProjection,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const noop = (markers) => {
|
||||
const clusters = markers.map((marker) => new Cluster({
|
||||
position: MarkerUtils.getPosition(marker),
|
||||
markers: [marker],
|
||||
}));
|
||||
return clusters;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* The default Grid algorithm historically used in Google Maps marker
|
||||
* clustering.
|
||||
*
|
||||
* The Grid algorithm does not implement caching and markers may flash as the
|
||||
* viewport changes. Instead use {@link SuperClusterAlgorithm}.
|
||||
*/
|
||||
class GridAlgorithm extends AbstractViewportAlgorithm {
|
||||
constructor(_a) {
|
||||
var { maxDistance = 40000, gridSize = 40 } = _a, options = __rest(_a, ["maxDistance", "gridSize"]);
|
||||
super(options);
|
||||
this.clusters = [];
|
||||
this.state = { zoom: -1 };
|
||||
this.maxDistance = maxDistance;
|
||||
this.gridSize = gridSize;
|
||||
}
|
||||
calculate({ markers, map, mapCanvasProjection, }) {
|
||||
const state = { zoom: map.getZoom() };
|
||||
let changed = false;
|
||||
if (this.state.zoom >= this.maxZoom && state.zoom >= this.maxZoom) ;
|
||||
else {
|
||||
changed = !equal(this.state, state);
|
||||
}
|
||||
this.state = state;
|
||||
if (map.getZoom() >= this.maxZoom) {
|
||||
return {
|
||||
clusters: this.noop({
|
||||
markers,
|
||||
}),
|
||||
changed,
|
||||
};
|
||||
}
|
||||
return {
|
||||
clusters: this.cluster({
|
||||
markers: filterMarkersToPaddedViewport(map, mapCanvasProjection, markers, this.viewportPadding),
|
||||
map,
|
||||
mapCanvasProjection,
|
||||
}),
|
||||
};
|
||||
}
|
||||
cluster({ markers, map, mapCanvasProjection, }) {
|
||||
this.clusters = [];
|
||||
markers.forEach((marker) => {
|
||||
this.addToClosestCluster(marker, map, mapCanvasProjection);
|
||||
});
|
||||
return this.clusters;
|
||||
}
|
||||
addToClosestCluster(marker, map, projection) {
|
||||
let maxDistance = this.maxDistance; // Some large number
|
||||
let cluster = null;
|
||||
for (let i = 0; i < this.clusters.length; i++) {
|
||||
const candidate = this.clusters[i];
|
||||
const distance = distanceBetweenPoints(candidate.bounds.getCenter().toJSON(), MarkerUtils.getPosition(marker).toJSON());
|
||||
if (distance < maxDistance) {
|
||||
maxDistance = distance;
|
||||
cluster = candidate;
|
||||
}
|
||||
}
|
||||
if (cluster &&
|
||||
extendBoundsToPaddedViewport(cluster.bounds, projection, this.gridSize).contains(MarkerUtils.getPosition(marker))) {
|
||||
cluster.push(marker);
|
||||
}
|
||||
else {
|
||||
const cluster = new Cluster({ markers: [marker] });
|
||||
this.clusters.push(cluster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Noop algorithm does not generate any clusters or filter markers by the an extended viewport.
|
||||
*/
|
||||
class NoopAlgorithm extends AbstractAlgorithm {
|
||||
constructor(_a) {
|
||||
var options = __rest(_a, []);
|
||||
super(options);
|
||||
}
|
||||
calculate({ markers, map, mapCanvasProjection, }) {
|
||||
return {
|
||||
clusters: this.cluster({ markers, map, mapCanvasProjection }),
|
||||
changed: false,
|
||||
};
|
||||
}
|
||||
cluster(input) {
|
||||
return this.noop(input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
|
||||
*
|
||||
* @see https://www.npmjs.com/package/supercluster for more information on options.
|
||||
*/
|
||||
class SuperClusterAlgorithm extends AbstractAlgorithm {
|
||||
constructor(_a) {
|
||||
var { maxZoom, radius = 60 } = _a, options = __rest(_a, ["maxZoom", "radius"]);
|
||||
super({ maxZoom });
|
||||
this.state = { zoom: -1 };
|
||||
this.superCluster = new SuperCluster(Object.assign({ maxZoom: this.maxZoom, radius }, options));
|
||||
}
|
||||
calculate(input) {
|
||||
let changed = false;
|
||||
const state = { zoom: input.map.getZoom() };
|
||||
if (!equal(input.markers, this.markers)) {
|
||||
changed = true;
|
||||
// TODO use proxy to avoid copy?
|
||||
this.markers = [...input.markers];
|
||||
const points = this.markers.map((marker) => {
|
||||
const position = MarkerUtils.getPosition(marker);
|
||||
const coordinates = [position.lng(), position.lat()];
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates,
|
||||
},
|
||||
properties: { marker },
|
||||
};
|
||||
});
|
||||
this.superCluster.load(points);
|
||||
}
|
||||
if (!changed) {
|
||||
if (this.state.zoom <= this.maxZoom || state.zoom <= this.maxZoom) {
|
||||
changed = !equal(this.state, state);
|
||||
}
|
||||
}
|
||||
this.state = state;
|
||||
if (changed) {
|
||||
this.clusters = this.cluster(input);
|
||||
}
|
||||
return { clusters: this.clusters, changed };
|
||||
}
|
||||
cluster({ map }) {
|
||||
return this.superCluster
|
||||
.getClusters([-180, -90, 180, 90], Math.round(map.getZoom()))
|
||||
.map((feature) => this.transformCluster(feature));
|
||||
}
|
||||
transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }) {
|
||||
if (properties.cluster) {
|
||||
return new Cluster({
|
||||
markers: this.superCluster
|
||||
.getLeaves(properties.cluster_id, Infinity)
|
||||
.map((leaf) => leaf.properties.marker),
|
||||
position: { lat, lng },
|
||||
});
|
||||
}
|
||||
const marker = properties.marker;
|
||||
return new Cluster({
|
||||
markers: [marker],
|
||||
position: MarkerUtils.getPosition(marker),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* A very fast JavaScript algorithm for geospatial point clustering using KD trees.
|
||||
*
|
||||
* @see https://www.npmjs.com/package/supercluster for more information on options.
|
||||
*/
|
||||
class SuperClusterViewportAlgorithm extends AbstractViewportAlgorithm {
|
||||
constructor(_a) {
|
||||
var { maxZoom, radius = 60, viewportPadding = 60 } = _a, options = __rest(_a, ["maxZoom", "radius", "viewportPadding"]);
|
||||
super({ maxZoom, viewportPadding });
|
||||
this.superCluster = new SuperCluster(Object.assign({ maxZoom: this.maxZoom, radius }, options));
|
||||
this.state = { zoom: -1, view: [0, 0, 0, 0] };
|
||||
}
|
||||
calculate(input) {
|
||||
const state = {
|
||||
zoom: Math.round(input.map.getZoom()),
|
||||
view: getPaddedViewport(input.map.getBounds(), input.mapCanvasProjection, this.viewportPadding),
|
||||
};
|
||||
let changed = !equal(this.state, state);
|
||||
if (!equal(input.markers, this.markers)) {
|
||||
changed = true;
|
||||
// TODO use proxy to avoid copy?
|
||||
this.markers = [...input.markers];
|
||||
const points = this.markers.map((marker) => {
|
||||
const position = MarkerUtils.getPosition(marker);
|
||||
const coordinates = [position.lng(), position.lat()];
|
||||
return {
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates,
|
||||
},
|
||||
properties: { marker },
|
||||
};
|
||||
});
|
||||
this.superCluster.load(points);
|
||||
}
|
||||
if (changed) {
|
||||
this.clusters = this.cluster(input);
|
||||
this.state = state;
|
||||
}
|
||||
return { clusters: this.clusters, changed };
|
||||
}
|
||||
cluster({ map, mapCanvasProjection }) {
|
||||
/* recalculate new state because we can't use the cached version. */
|
||||
const state = {
|
||||
zoom: Math.round(map.getZoom()),
|
||||
view: getPaddedViewport(map.getBounds(), mapCanvasProjection, this.viewportPadding),
|
||||
};
|
||||
return this.superCluster
|
||||
.getClusters(state.view, state.zoom)
|
||||
.map((feature) => this.transformCluster(feature));
|
||||
}
|
||||
transformCluster({ geometry: { coordinates: [lng, lat], }, properties, }) {
|
||||
if (properties.cluster) {
|
||||
return new Cluster({
|
||||
markers: this.superCluster
|
||||
.getLeaves(properties.cluster_id, Infinity)
|
||||
.map((leaf) => leaf.properties.marker),
|
||||
position: { lat, lng },
|
||||
});
|
||||
}
|
||||
const marker = properties.marker;
|
||||
return new Cluster({
|
||||
markers: [marker],
|
||||
position: MarkerUtils.getPosition(marker),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Provides statistics on all clusters in the current render cycle for use in {@link Renderer.render}.
|
||||
*/
|
||||
class ClusterStats {
|
||||
constructor(markers, clusters) {
|
||||
this.markers = { sum: markers.length };
|
||||
const clusterMarkerCounts = clusters.map((a) => a.count);
|
||||
const clusterMarkerSum = clusterMarkerCounts.reduce((a, b) => a + b, 0);
|
||||
this.clusters = {
|
||||
count: clusters.length,
|
||||
markers: {
|
||||
mean: clusterMarkerSum / clusters.length,
|
||||
sum: clusterMarkerSum,
|
||||
min: Math.min(...clusterMarkerCounts),
|
||||
max: Math.max(...clusterMarkerCounts),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
class DefaultRenderer {
|
||||
/**
|
||||
* The default render function for the library used by {@link MarkerClusterer}.
|
||||
*
|
||||
* Currently set to use the following:
|
||||
*
|
||||
* ```typescript
|
||||
* // change color if this cluster has more markers than the mean cluster
|
||||
* const color =
|
||||
* count > Math.max(10, stats.clusters.markers.mean)
|
||||
* ? "#ff0000"
|
||||
* : "#0000ff";
|
||||
*
|
||||
* // create svg url with fill color
|
||||
* const svg = window.btoa(`
|
||||
* <svg fill="${color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240">
|
||||
* <circle cx="120" cy="120" opacity=".6" r="70" />
|
||||
* <circle cx="120" cy="120" opacity=".3" r="90" />
|
||||
* <circle cx="120" cy="120" opacity=".2" r="110" />
|
||||
* <circle cx="120" cy="120" opacity=".1" r="130" />
|
||||
* </svg>`);
|
||||
*
|
||||
* // create marker using svg icon
|
||||
* return new google.maps.Marker({
|
||||
* position,
|
||||
* icon: {
|
||||
* url: `data:image/svg+xml;base64,${svg}`,
|
||||
* scaledSize: new google.maps.Size(45, 45),
|
||||
* },
|
||||
* label: {
|
||||
* text: String(count),
|
||||
* color: "rgba(255,255,255,0.9)",
|
||||
* fontSize: "12px",
|
||||
* },
|
||||
* // adjust zIndex to be above other markers
|
||||
* zIndex: 1000 + count,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
render({ count, position }, stats, map) {
|
||||
// change color if this cluster has more markers than the mean cluster
|
||||
const color = count > Math.max(10, stats.clusters.markers.mean) ? "#ff0000" : "#0000ff";
|
||||
// create svg literal with fill color
|
||||
const svg = `<svg fill="${color}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="50" height="50">
|
||||
<circle cx="120" cy="120" opacity=".6" r="70" />
|
||||
<circle cx="120" cy="120" opacity=".3" r="90" />
|
||||
<circle cx="120" cy="120" opacity=".2" r="110" />
|
||||
<text x="50%" y="50%" style="fill:#fff" text-anchor="middle" font-size="50" dominant-baseline="middle" font-family="roboto,arial,sans-serif">${count}</text>
|
||||
</svg>`;
|
||||
const title = `Cluster of ${count} markers`,
|
||||
// adjust zIndex to be above other markers
|
||||
zIndex = Number(google.maps.Marker.MAX_ZINDEX) + count;
|
||||
if (MarkerUtils.isAdvancedMarkerAvailable(map)) {
|
||||
// create cluster SVG element
|
||||
const parser = new DOMParser();
|
||||
const svgEl = parser.parseFromString(svg, "image/svg+xml").documentElement;
|
||||
svgEl.setAttribute("transform", "translate(0 25)");
|
||||
const clusterOptions = {
|
||||
map,
|
||||
position,
|
||||
zIndex,
|
||||
title,
|
||||
content: svgEl,
|
||||
};
|
||||
return new google.maps.marker.AdvancedMarkerElement(clusterOptions);
|
||||
}
|
||||
const clusterOptions = {
|
||||
position,
|
||||
zIndex,
|
||||
title,
|
||||
icon: {
|
||||
url: `data:image/svg+xml;base64,${btoa(svg)}`,
|
||||
anchor: new google.maps.Point(25, 25),
|
||||
},
|
||||
};
|
||||
return new google.maps.Marker(clusterOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2019 Google LLC. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Extends an object's prototype by another's.
|
||||
*
|
||||
* @param type1 The Type to be extended.
|
||||
* @param type2 The Type to extend with.
|
||||
* @ignore
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function extend(type1, type2) {
|
||||
/* istanbul ignore next */
|
||||
// eslint-disable-next-line prefer-const
|
||||
for (let property in type2.prototype) {
|
||||
type1.prototype[property] = type2.prototype[property];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class OverlayViewSafe {
|
||||
constructor() {
|
||||
// MarkerClusterer implements google.maps.OverlayView interface. We use the
|
||||
// extend function to extend MarkerClusterer with google.maps.OverlayView
|
||||
// because it might not always be available when the code is defined so we
|
||||
// look for it at the last possible moment. If it doesn't exist now then
|
||||
// there is no point going ahead :)
|
||||
extend(OverlayViewSafe, google.maps.OverlayView);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
var MarkerClustererEvents;
|
||||
(function (MarkerClustererEvents) {
|
||||
MarkerClustererEvents["CLUSTERING_BEGIN"] = "clusteringbegin";
|
||||
MarkerClustererEvents["CLUSTERING_END"] = "clusteringend";
|
||||
MarkerClustererEvents["CLUSTER_CLICK"] = "click";
|
||||
})(MarkerClustererEvents || (MarkerClustererEvents = {}));
|
||||
const defaultOnClusterClickHandler = (_, cluster, map) => {
|
||||
map.fitBounds(cluster.bounds);
|
||||
};
|
||||
/**
|
||||
* MarkerClusterer creates and manages per-zoom-level clusters for large amounts
|
||||
* of markers. See {@link MarkerClustererOptions} for more details.
|
||||
*
|
||||
*/
|
||||
class MarkerClusterer extends OverlayViewSafe {
|
||||
constructor({ map, markers = [], algorithmOptions = {}, algorithm = new SuperClusterAlgorithm(algorithmOptions), renderer = new DefaultRenderer(), onClusterClick = defaultOnClusterClickHandler, }) {
|
||||
super();
|
||||
this.markers = [...markers];
|
||||
this.clusters = [];
|
||||
this.algorithm = algorithm;
|
||||
this.renderer = renderer;
|
||||
this.onClusterClick = onClusterClick;
|
||||
if (map) {
|
||||
this.setMap(map);
|
||||
}
|
||||
}
|
||||
addMarker(marker, noDraw) {
|
||||
if (this.markers.includes(marker)) {
|
||||
return;
|
||||
}
|
||||
this.markers.push(marker);
|
||||
if (!noDraw) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
addMarkers(markers, noDraw) {
|
||||
markers.forEach((marker) => {
|
||||
this.addMarker(marker, true);
|
||||
});
|
||||
if (!noDraw) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
removeMarker(marker, noDraw) {
|
||||
const index = this.markers.indexOf(marker);
|
||||
if (index === -1) {
|
||||
// Marker is not in our list of markers, so do nothing:
|
||||
return false;
|
||||
}
|
||||
MarkerUtils.setMap(marker, null);
|
||||
this.markers.splice(index, 1); // Remove the marker from the list of managed markers
|
||||
if (!noDraw) {
|
||||
this.render();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
removeMarkers(markers, noDraw) {
|
||||
let removed = false;
|
||||
markers.forEach((marker) => {
|
||||
removed = this.removeMarker(marker, true) || removed;
|
||||
});
|
||||
if (removed && !noDraw) {
|
||||
this.render();
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
clearMarkers(noDraw) {
|
||||
this.markers.length = 0;
|
||||
if (!noDraw) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Recalculates and draws all the marker clusters.
|
||||
*/
|
||||
render() {
|
||||
const map = this.getMap();
|
||||
if (map instanceof google.maps.Map && map.getProjection()) {
|
||||
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTERING_BEGIN, this);
|
||||
const { clusters, changed } = this.algorithm.calculate({
|
||||
markers: this.markers,
|
||||
map,
|
||||
mapCanvasProjection: this.getProjection(),
|
||||
});
|
||||
// Allow algorithms to return flag on whether the clusters/markers have changed.
|
||||
if (changed || changed == undefined) {
|
||||
// Accumulate the markers of the clusters composed of a single marker.
|
||||
// Those clusters directly use the marker.
|
||||
// Clusters with more than one markers use a group marker generated by a renderer.
|
||||
const singleMarker = new Set();
|
||||
for (const cluster of clusters) {
|
||||
if (cluster.markers.length == 1) {
|
||||
singleMarker.add(cluster.markers[0]);
|
||||
}
|
||||
}
|
||||
const groupMarkers = [];
|
||||
// Iterate the clusters that are currently rendered.
|
||||
for (const cluster of this.clusters) {
|
||||
if (cluster.marker == null) {
|
||||
continue;
|
||||
}
|
||||
if (cluster.markers.length == 1) {
|
||||
if (!singleMarker.has(cluster.marker)) {
|
||||
// The marker:
|
||||
// - was previously rendered because it is from a cluster with 1 marker,
|
||||
// - should no more be rendered as it is not in singleMarker.
|
||||
MarkerUtils.setMap(cluster.marker, null);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Delay the removal of old group markers to avoid flickering.
|
||||
groupMarkers.push(cluster.marker);
|
||||
}
|
||||
}
|
||||
this.clusters = clusters;
|
||||
this.renderClusters();
|
||||
// Delayed removal of the markers of the former groups.
|
||||
requestAnimationFrame(() => groupMarkers.forEach((marker) => MarkerUtils.setMap(marker, null)));
|
||||
}
|
||||
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTERING_END, this);
|
||||
}
|
||||
}
|
||||
onAdd() {
|
||||
this.idleListener = this.getMap().addListener("idle", this.render.bind(this));
|
||||
this.render();
|
||||
}
|
||||
onRemove() {
|
||||
google.maps.event.removeListener(this.idleListener);
|
||||
this.reset();
|
||||
}
|
||||
reset() {
|
||||
this.markers.forEach((marker) => MarkerUtils.setMap(marker, null));
|
||||
this.clusters.forEach((cluster) => cluster.delete());
|
||||
this.clusters = [];
|
||||
}
|
||||
renderClusters() {
|
||||
// Generate stats to pass to renderers.
|
||||
const stats = new ClusterStats(this.markers, this.clusters);
|
||||
const map = this.getMap();
|
||||
this.clusters.forEach((cluster) => {
|
||||
if (cluster.markers.length === 1) {
|
||||
cluster.marker = cluster.markers[0];
|
||||
}
|
||||
else {
|
||||
// Generate the marker to represent the group.
|
||||
cluster.marker = this.renderer.render(cluster, stats, map);
|
||||
// Make sure all individual markers are removed from the map.
|
||||
cluster.markers.forEach((marker) => MarkerUtils.setMap(marker, null));
|
||||
if (this.onClusterClick) {
|
||||
cluster.marker.addListener("click",
|
||||
/* istanbul ignore next */
|
||||
(event) => {
|
||||
google.maps.event.trigger(this, MarkerClustererEvents.CLUSTER_CLICK, cluster);
|
||||
this.onClusterClick(event, cluster, map);
|
||||
});
|
||||
}
|
||||
}
|
||||
MarkerUtils.setMap(cluster.marker, map);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { AbstractAlgorithm, AbstractViewportAlgorithm, Cluster, ClusterStats, DefaultRenderer, GridAlgorithm, MarkerClusterer, MarkerClustererEvents, MarkerUtils, NoopAlgorithm, SuperClusterAlgorithm, SuperClusterViewportAlgorithm, defaultOnClusterClickHandler, distanceBetweenPoints, extendBoundsToPaddedViewport, extendPixelBounds, filterMarkersToPaddedViewport, getPaddedViewport, noop, pixelBoundsToLatLngBounds };
|
||||
//# sourceMappingURL=index.esm.js.map
|
||||
Reference in New Issue
Block a user