diff --git a/src/dev-app/google-map/google-map-demo.html b/src/dev-app/google-map/google-map-demo.html
index c1c1062509eb..759fb5ca5784 100644
--- a/src/dev-app/google-map/google-map-demo.html
+++ b/src/dev-app/google-map/google-map-demo.html
@@ -9,8 +9,11 @@
(mapMousemove)="handleMove($event)"
(mapRightclick)="handleRightclick()"
[mapTypeId]="mapTypeId">
-
-
+
@for (markerPosition of markerPositions; track markerPosition) {
@@ -19,7 +22,29 @@
[options]="markerOptions"
(mapClick)="clickMarker(marker)">
}
-
+ -->
+
+
+
+ @for (markerPosition of markerPositions; track markerPosition) {
+
+
+ }
+
+
@if (hasAdvancedMarker) {
` tag. Unlike the other Google Maps components, MapMarkerClusterer does not have an `options` input, so any input (listed in the [documentation](https://googlemaps.github.io/js-markerclustererplus/index.html) for the `MarkerClusterer` class) should be set directly.
+The `MapMarkerClusterer` component wraps the [`MarkerClusterer` class](https://googlemaps.github.io/js-markerclusterer/classes/MarkerClusterer.html) from the [Google Maps JavaScript MarkerClusterer Library](https://github.com/googlemaps/js-markerclusterer). The `MapMarkerClusterer` component displays a cluster of markers that are children of the `` tag. Unlike the other Google Maps components, MapMarkerClusterer does not have an `options` input, so any input (listed in the [documentation](https://googlemaps.github.io/js-markerclusterer/) for the `MarkerClusterer` class) should be set directly.
## Loading the Library
-Like the Google Maps JavaScript API, the MarkerClustererPlus library needs to be loaded separately. This can be accomplished by using this script tag:
+Like the Google Maps JavaScript API, the MarkerClusterer library needs to be loaded separately. This can be accomplished by using this script tag:
```html
-
+
```
Additional information can be found by looking at [Marker Clustering](https://developers.google.com/maps/documentation/javascript/marker-clustering) in the Google Maps JavaScript API documentation.
@@ -17,20 +17,18 @@ Additional information can be found by looking at [Marker Clustering](https://de
```typescript
// google-map-demo.component.ts
import {Component} from '@angular/core';
-import {GoogleMap, MapMarker, MapMarkerClusterer} from '@angular/google-maps';
+import {GoogleMap, MapAdvancedMarker, MapMarkerClusterer} from '@angular/google-maps';
@Component({
selector: 'google-map-demo',
templateUrl: 'google-map-demo.html',
standalone: true,
- imports: [GoogleMap, MapMarker, MapMarkerClusterer],
+ imports: [GoogleMap, MapAdvancedMarker, MapMarkerClusterer],
})
export class GoogleMapDemo {
center: google.maps.LatLngLiteral = {lat: 24, lng: 12};
zoom = 4;
markerPositions: google.maps.LatLngLiteral[] = [];
- markerClustererImagePath =
- 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m';
addMarker(event: google.maps.MapMouseEvent) {
this.markerPositions.push(event.latLng.toJSON());
@@ -40,15 +38,16 @@ export class GoogleMapDemo {
```html
-
-
+ (mapClick)="addMarker($event)"
+ [mapId]="'123'">
+
@for (position of markerPositions; track position) {
-
+
}
diff --git a/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts b/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts
index 1274056c6a96..32dceda13ee2 100644
--- a/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts
+++ b/src/google-maps/map-marker-clusterer/map-marker-clusterer.spec.ts
@@ -1,8 +1,8 @@
import {Component, ViewChild} from '@angular/core';
-import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing';
+import {waitForAsync, ComponentFixture, TestBed} from '@angular/core/testing';
-import {DEFAULT_OPTIONS, GoogleMap} from '../google-map/google-map';
-import {MapMarker} from '../map-marker/map-marker';
+import {DEFAULT_OPTIONS} from '../google-map/google-map';
+import {GoogleMapsModule} from '../google-maps-module';
import {
createMapConstructorSpy,
createMapSpy,
@@ -12,13 +12,6 @@ import {
createMarkerSpy,
} from '../testing/fake-google-map-utils';
import {MapMarkerClusterer} from './map-marker-clusterer';
-import {
- AriaLabelFn,
- Calculator,
- ClusterIconStyle,
- MarkerClusterer,
- MarkerClustererOptions,
-} from './marker-clusterer-types';
describe('MapMarkerClusterer', () => {
let mapSpy: jasmine.SpyObj;
@@ -28,9 +21,18 @@ describe('MapMarkerClusterer', () => {
const anyMarkerMatcher = jasmine.any(Object) as unknown as google.maps.Marker;
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [GoogleMapsModule],
+ declarations: [TestApp],
+ });
+ }));
+
beforeEach(() => {
+ TestBed.compileComponents();
+
mapSpy = createMapSpy(DEFAULT_OPTIONS);
- createMapConstructorSpy(mapSpy);
+ createMapConstructorSpy(mapSpy).and.callThrough();
const markerSpy = createMarkerSpy({});
// The spy target function cannot be an arrow-function as this breaks when created
@@ -40,148 +42,90 @@ describe('MapMarkerClusterer', () => {
});
markerClustererSpy = createMarkerClustererSpy();
- markerClustererConstructorSpy = createMarkerClustererConstructorSpy(markerClustererSpy);
+ markerClustererConstructorSpy =
+ createMarkerClustererConstructorSpy(markerClustererSpy).and.callThrough();
fixture = TestBed.createComponent(TestApp);
});
afterEach(() => {
(window.google as any) = undefined;
- (window as any).MarkerClusterer = undefined;
+ (window as any).markerClusterer = undefined;
});
- it('throws an error if the clustering library has not been loaded', fakeAsync(() => {
- (window as any).MarkerClusterer = undefined;
- markerClustererConstructorSpy = createMarkerClustererConstructorSpy(markerClustererSpy, false);
-
- expect(() => {
- fixture.detectChanges();
- flush();
- }).toThrowError(/MarkerClusterer class not found, cannot construct a marker cluster/);
- }));
+ it('throws an error if the clustering library has not been loaded', () => {
+ (window as any).markerClusterer = undefined;
+ markerClustererConstructorSpy = createMarkerClustererConstructorSpy(
+ markerClustererSpy,
+ false,
+ ).and.callThrough();
+
+ expect(() => fixture.detectChanges()).toThrow(
+ new Error(
+ 'MarkerClusterer class not found, cannot construct a marker cluster. ' +
+ 'Please install the MarkerClusterer library: ' +
+ 'https://github.com/googlemaps/js-markerclusterer',
+ ),
+ );
+ });
- it('initializes a Google Map Marker Clusterer', fakeAsync(() => {
+ it('initializes a Google Map Marker Clusterer', () => {
fixture.detectChanges();
- flush();
-
- expect(markerClustererConstructorSpy).toHaveBeenCalledWith(mapSpy, [], {
- ariaLabelFn: undefined,
- averageCenter: undefined,
- batchSize: undefined,
- batchSizeIE: undefined,
- calculator: undefined,
- clusterClass: undefined,
- enableRetinaIcons: undefined,
- gridSize: undefined,
- ignoreHidden: undefined,
- imageExtension: undefined,
- imagePath: undefined,
- imageSizes: undefined,
- maxZoom: undefined,
- minimumClusterSize: undefined,
- styles: undefined,
- title: undefined,
- zIndex: undefined,
- zoomOnClick: undefined,
+
+ expect(markerClustererConstructorSpy).toHaveBeenCalledWith({
+ map: mapSpy,
+ renderer: undefined,
+ algorithm: undefined,
+ onClusterClick: jasmine.any(Function),
});
- }));
+ });
- it('sets marker clusterer inputs', fakeAsync(() => {
- fixture.componentInstance.ariaLabelFn = (testString: string) => testString;
- fixture.componentInstance.averageCenter = true;
- fixture.componentInstance.batchSize = 1;
- fixture.componentInstance.clusterClass = 'testClusterClass';
- fixture.componentInstance.enableRetinaIcons = true;
- fixture.componentInstance.gridSize = 2;
- fixture.componentInstance.ignoreHidden = true;
- fixture.componentInstance.imageExtension = 'testImageExtension';
- fixture.componentInstance.imagePath = 'testImagePath';
- fixture.componentInstance.imageSizes = [3];
- fixture.componentInstance.maxZoom = 4;
- fixture.componentInstance.minimumClusterSize = 5;
- fixture.componentInstance.styles = [];
- fixture.componentInstance.title = 'testTitle';
- fixture.componentInstance.zIndex = 6;
- fixture.componentInstance.zoomOnClick = true;
+ it('sets marker clusterer inputs', () => {
+ fixture.componentInstance.algorithm = {name: 'custom'};
+ fixture.componentInstance.renderer = {render: () => null!};
fixture.detectChanges();
- flush();
-
- expect(markerClustererConstructorSpy).toHaveBeenCalledWith(mapSpy, [], {
- ariaLabelFn: jasmine.any(Function),
- averageCenter: true,
- batchSize: 1,
- batchSizeIE: undefined,
- calculator: undefined,
- clusterClass: 'testClusterClass',
- enableRetinaIcons: true,
- gridSize: 2,
- ignoreHidden: true,
- imageExtension: 'testImageExtension',
- imagePath: 'testImagePath',
- imageSizes: [3],
- maxZoom: 4,
- minimumClusterSize: 5,
- styles: [],
- title: 'testTitle',
- zIndex: 6,
- zoomOnClick: true,
+
+ expect(markerClustererConstructorSpy).toHaveBeenCalledWith({
+ map: mapSpy,
+ algorithm: fixture.componentInstance.algorithm,
+ renderer: fixture.componentInstance.renderer,
+ onClusterClick: jasmine.any(Function),
});
- }));
+ });
- it('sets marker clusterer options', fakeAsync(() => {
- fixture.detectChanges();
- flush();
- const options: MarkerClustererOptions = {
- enableRetinaIcons: true,
- gridSize: 1337,
- ignoreHidden: true,
- imageExtension: 'png',
- };
- fixture.componentInstance.options = options;
+ it('recreates the clusterer if the options change', () => {
+ fixture.componentInstance.algorithm = {name: 'custom1'};
fixture.detectChanges();
- expect(markerClustererSpy.setOptions).toHaveBeenCalledWith(jasmine.objectContaining(options));
- }));
- it('gives precedence to specific inputs over options', fakeAsync(() => {
- fixture.detectChanges();
- flush();
- const options: MarkerClustererOptions = {
- enableRetinaIcons: true,
- gridSize: 1337,
- ignoreHidden: true,
- imageExtension: 'png',
- };
- const expectedOptions: MarkerClustererOptions = {
- enableRetinaIcons: false,
- gridSize: 42,
- ignoreHidden: false,
- imageExtension: 'jpeg',
- };
- fixture.componentInstance.enableRetinaIcons = expectedOptions.enableRetinaIcons;
- fixture.componentInstance.gridSize = expectedOptions.gridSize;
- fixture.componentInstance.ignoreHidden = expectedOptions.ignoreHidden;
- fixture.componentInstance.imageExtension = expectedOptions.imageExtension;
- fixture.componentInstance.options = options;
+ expect(markerClustererConstructorSpy).toHaveBeenCalledWith({
+ map: mapSpy,
+ algorithm: jasmine.objectContaining({name: 'custom1'}),
+ renderer: undefined,
+ onClusterClick: jasmine.any(Function),
+ });
+
+ fixture.componentInstance.algorithm = {name: 'custom2'};
fixture.detectChanges();
- expect(markerClustererSpy.setOptions).toHaveBeenCalledWith(
- jasmine.objectContaining(expectedOptions),
- );
- }));
+ expect(markerClustererConstructorSpy).toHaveBeenCalledWith({
+ map: mapSpy,
+ algorithm: jasmine.objectContaining({name: 'custom2'}),
+ renderer: undefined,
+ onClusterClick: jasmine.any(Function),
+ });
+ });
- it('sets Google Maps Markers in the MarkerClusterer', fakeAsync(() => {
+ it('sets Google Maps Markers in the MarkerClusterer', () => {
fixture.detectChanges();
- flush();
expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([
anyMarkerMatcher,
anyMarkerMatcher,
]);
- }));
+ });
- it('updates Google Maps Markers in the Marker Clusterer', fakeAsync(() => {
+ it('updates Google Maps Markers in the Marker Clusterer', () => {
fixture.detectChanges();
- flush();
expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([
anyMarkerMatcher,
@@ -190,109 +134,40 @@ describe('MapMarkerClusterer', () => {
fixture.componentInstance.state = 'state2';
fixture.detectChanges();
- flush();
expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true);
expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith([anyMarkerMatcher], true);
- expect(markerClustererSpy.repaint).toHaveBeenCalledTimes(1);
+ expect(markerClustererSpy.render).toHaveBeenCalledTimes(1);
fixture.componentInstance.state = 'state0';
fixture.detectChanges();
- flush();
expect(markerClustererSpy.addMarkers).toHaveBeenCalledWith([], true);
expect(markerClustererSpy.removeMarkers).toHaveBeenCalledWith(
[anyMarkerMatcher, anyMarkerMatcher],
true,
);
- expect(markerClustererSpy.repaint).toHaveBeenCalledTimes(2);
- }));
+ expect(markerClustererSpy.render).toHaveBeenCalledTimes(2);
+ });
- it('exposes marker clusterer methods', fakeAsync(() => {
+ it('initializes event handlers on the map related to clustering', () => {
fixture.detectChanges();
- flush();
- const markerClustererComponent = fixture.componentInstance.markerClusterer;
- markerClustererComponent.fitMapToMarkers(5);
- expect(markerClustererSpy.fitMapToMarkers).toHaveBeenCalledWith(5);
-
- markerClustererSpy.getAverageCenter.and.returnValue(true);
- expect(markerClustererComponent.getAverageCenter()).toBe(true);
-
- markerClustererSpy.getBatchSizeIE.and.returnValue(6);
- expect(markerClustererComponent.getBatchSizeIE()).toBe(6);
-
- const calculator = (markers: google.maps.Marker[], count: number) => ({
- index: 1,
- text: 'testText',
- title: 'testTitle',
- });
- markerClustererSpy.getCalculator.and.returnValue(calculator);
- expect(markerClustererComponent.getCalculator()).toBe(calculator);
-
- markerClustererSpy.getClusterClass.and.returnValue('testClusterClass');
- expect(markerClustererComponent.getClusterClass()).toBe('testClusterClass');
-
- markerClustererSpy.getClusters.and.returnValue([]);
- expect(markerClustererComponent.getClusters()).toEqual([]);
-
- markerClustererSpy.getEnableRetinaIcons.and.returnValue(true);
- expect(markerClustererComponent.getEnableRetinaIcons()).toBe(true);
-
- markerClustererSpy.getGridSize.and.returnValue(7);
- expect(markerClustererComponent.getGridSize()).toBe(7);
-
- markerClustererSpy.getIgnoreHidden.and.returnValue(true);
- expect(markerClustererComponent.getIgnoreHidden()).toBe(true);
-
- markerClustererSpy.getImageExtension.and.returnValue('testImageExtension');
- expect(markerClustererComponent.getImageExtension()).toBe('testImageExtension');
-
- markerClustererSpy.getImagePath.and.returnValue('testImagePath');
- expect(markerClustererComponent.getImagePath()).toBe('testImagePath');
-
- markerClustererSpy.getImageSizes.and.returnValue([]);
- expect(markerClustererComponent.getImageSizes()).toEqual([]);
-
- markerClustererSpy.getMaxZoom.and.returnValue(8);
- expect(markerClustererComponent.getMaxZoom()).toBe(8);
-
- markerClustererSpy.getMinimumClusterSize.and.returnValue(9);
- expect(markerClustererComponent.getMinimumClusterSize()).toBe(9);
-
- markerClustererSpy.getStyles.and.returnValue([]);
- expect(markerClustererComponent.getStyles()).toEqual([]);
-
- markerClustererSpy.getTitle.and.returnValue('testTitle');
- expect(markerClustererComponent.getTitle()).toBe('testTitle');
-
- markerClustererSpy.getTotalClusters.and.returnValue(10);
- expect(markerClustererComponent.getTotalClusters()).toBe(10);
-
- markerClustererSpy.getTotalMarkers.and.returnValue(11);
- expect(markerClustererComponent.getTotalMarkers()).toBe(11);
+ expect(mapSpy.addListener).toHaveBeenCalledWith('clusteringbegin', jasmine.any(Function));
+ expect(mapSpy.addListener).not.toHaveBeenCalledWith('clusteringend', jasmine.any(Function));
+ });
- markerClustererSpy.getZIndex.and.returnValue(12);
- expect(markerClustererComponent.getZIndex()).toBe(12);
+ it('emits to clusterClick when the `onClusterClick` callback is invoked', () => {
+ fixture.detectChanges();
- markerClustererSpy.getZoomOnClick.and.returnValue(true);
- expect(markerClustererComponent.getZoomOnClick()).toBe(true);
- }));
+ expect(fixture.componentInstance.onClusterClick).not.toHaveBeenCalled();
- it('initializes marker clusterer event handlers', fakeAsync(() => {
+ const callback = markerClustererConstructorSpy.calls.mostRecent().args[0].onClusterClick;
+ callback({}, {}, {});
fixture.detectChanges();
- flush();
- expect(markerClustererSpy.addListener).toHaveBeenCalledWith(
- 'clusteringbegin',
- jasmine.any(Function),
- );
- expect(markerClustererSpy.addListener).not.toHaveBeenCalledWith(
- 'clusteringend',
- jasmine.any(Function),
- );
- expect(markerClustererSpy.addListener).toHaveBeenCalledWith('click', jasmine.any(Function));
- }));
+ expect(fixture.componentInstance.onClusterClick).toHaveBeenCalledTimes(1);
+ });
});
@Component({
@@ -300,67 +175,22 @@ describe('MapMarkerClusterer', () => {
template: `
- @if (state === 'state1') {
-
- }
- @if (state === 'state1' || state === 'state2') {
-
- }
- @if (state === 'state2') {
-
- }
+ (clusterClick)="onClusterClick()"
+ [renderer]="renderer"
+ [algorithm]="algorithm">
+
+
+
`,
- standalone: true,
- imports: [GoogleMap, MapMarker, MapMarkerClusterer],
})
class TestApp {
@ViewChild(MapMarkerClusterer) markerClusterer: MapMarkerClusterer;
-
- ariaLabelFn?: AriaLabelFn;
- averageCenter?: boolean;
- batchSize?: number;
- batchSizeIE?: number;
- calculator?: Calculator;
- clusterClass?: string;
- enableRetinaIcons?: boolean;
- gridSize?: number;
- ignoreHidden?: boolean;
- imageExtension?: string;
- imagePath?: string;
- imageSizes?: number[];
- maxZoom?: number;
- minimumClusterSize?: number;
- styles?: ClusterIconStyle[];
- title?: string;
- zIndex?: number;
- zoomOnClick?: boolean;
- options?: MarkerClustererOptions;
-
+ renderer: Renderer;
+ algorithm: Algorithm;
state = 'state1';
-
- onClusteringBegin() {}
- onClusterClick() {}
+ onClusteringBegin = jasmine.createSpy('onclusteringbegin spy');
+ onClusterClick = jasmine.createSpy('clusterClick spy');
}
diff --git a/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts b/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts
index c9edcf57c9c4..6e1b1ba916d2 100644
--- a/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts
+++ b/src/google-maps/map-marker-clusterer/map-marker-clusterer.ts
@@ -1,3 +1,4 @@
+import {MapAdvancedMarker} from './../map-advanced-marker/map-advanced-marker';
/**
* @license
* Copyright Google LLC All Rights Reserved.
@@ -8,6 +9,7 @@
// Workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1265
///
+///
import {
AfterContentInit,
@@ -26,30 +28,18 @@ import {
ViewEncapsulation,
inject,
} from '@angular/core';
-import {Observable, Subject} from 'rxjs';
-import {take, takeUntil} from 'rxjs/operators';
+import {Observable, Subscription, combineLatest, merge} from 'rxjs';
import {GoogleMap} from '../google-map/google-map';
import {MapEventManager} from '../map-event-manager';
import {MapMarker} from '../map-marker/map-marker';
-import {
- AriaLabelFn,
- Calculator,
- Cluster,
- ClusterIconStyle,
- MarkerClusterer as MarkerClustererInstance,
- MarkerClustererOptions,
-} from './marker-clusterer-types';
-
-/** Default options for a clusterer. */
-const DEFAULT_CLUSTERER_OPTIONS: MarkerClustererOptions = {};
-/**
- * The clusterer has to be defined and referred to as a global variable,
- * otherwise it'll cause issues when minified through Closure.
- */
-declare const MarkerClusterer: typeof MarkerClustererInstance;
+declare const markerClusterer: {
+ MarkerClusterer: typeof MarkerClusterer;
+ defaultOnClusterClickHandler: onClusterClickHandler;
+};
+type MapMarkerComponent = MapMarker | MapAdvancedMarker;
/**
* Angular component for implementing a Google Maps Marker Clusterer.
*
@@ -59,473 +49,203 @@ declare const MarkerClusterer: typeof MarkerClustererInstance;
selector: 'map-marker-clusterer',
exportAs: 'mapMarkerClusterer',
changeDetection: ChangeDetectionStrategy.OnPush,
- standalone: true,
- template: '',
+ template: '',
encapsulation: ViewEncapsulation.None,
+ standalone: true,
})
-export class MapMarkerClusterer implements OnInit, AfterContentInit, OnChanges, OnDestroy {
- private readonly _currentMarkers = new Set();
- private readonly _eventManager = new MapEventManager(inject(NgZone));
- private readonly _destroy = new Subject();
+export class MapMarkerClusterer implements OnInit, OnChanges, OnDestroy {
+ private readonly _currentMarkers = new Set();
+ private readonly _ngZone = inject(NgZone);
+ private readonly _closestMapEventManager = new MapEventManager(this._ngZone);
+ private _markersSubscription = Subscription.EMPTY;
/** Whether the clusterer is allowed to be initialized. */
private readonly _canInitialize: boolean;
+ /**
+ * Used to customize how the marker cluster is rendered.
+ * See https://googlemaps.github.io/js-markerclusterer/interfaces/Renderer.html.
+ */
@Input()
- ariaLabelFn: AriaLabelFn = () => '';
-
- @Input()
- set averageCenter(averageCenter: boolean) {
- this._averageCenter = averageCenter;
- }
- private _averageCenter: boolean;
-
- @Input() batchSize?: number;
-
- @Input()
- set batchSizeIE(batchSizeIE: number) {
- this._batchSizeIE = batchSizeIE;
- }
- private _batchSizeIE: number;
-
- @Input()
- set calculator(calculator: Calculator) {
- this._calculator = calculator;
- }
- private _calculator: Calculator;
-
- @Input()
- set clusterClass(clusterClass: string) {
- this._clusterClass = clusterClass;
- }
- private _clusterClass: string;
-
- @Input()
- set enableRetinaIcons(enableRetinaIcons: boolean) {
- this._enableRetinaIcons = enableRetinaIcons;
- }
- private _enableRetinaIcons: boolean;
-
- @Input()
- set gridSize(gridSize: number) {
- this._gridSize = gridSize;
- }
- private _gridSize: number;
-
- @Input()
- set ignoreHidden(ignoreHidden: boolean) {
- this._ignoreHidden = ignoreHidden;
- }
- private _ignoreHidden: boolean;
-
- @Input()
- set imageExtension(imageExtension: string) {
- this._imageExtension = imageExtension;
- }
- private _imageExtension: string;
-
- @Input()
- set imagePath(imagePath: string) {
- this._imagePath = imagePath;
- }
- private _imagePath: string;
-
- @Input()
- set imageSizes(imageSizes: number[]) {
- this._imageSizes = imageSizes;
- }
- private _imageSizes: number[];
-
- @Input()
- set maxZoom(maxZoom: number) {
- this._maxZoom = maxZoom;
- }
- private _maxZoom: number;
-
- @Input()
- set minimumClusterSize(minimumClusterSize: number) {
- this._minimumClusterSize = minimumClusterSize;
- }
- private _minimumClusterSize: number;
-
- @Input()
- set styles(styles: ClusterIconStyle[]) {
- this._styles = styles;
- }
- private _styles: ClusterIconStyle[];
-
- @Input()
- set title(title: string) {
- this._title = title;
- }
- private _title: string;
-
- @Input()
- set zIndex(zIndex: number) {
- this._zIndex = zIndex;
- }
- private _zIndex: number;
-
- @Input()
- set zoomOnClick(zoomOnClick: boolean) {
- this._zoomOnClick = zoomOnClick;
- }
- private _zoomOnClick: boolean;
-
- @Input()
- set options(options: MarkerClustererOptions) {
- this._options = options;
- }
- private _options: MarkerClustererOptions;
+ renderer: Renderer;
/**
- * See
- * googlemaps.github.io/v3-utility-library/modules/
- * _google_markerclustererplus.html#clusteringbegin
+ * Algorithm used to cluster the markers.
+ * See https://googlemaps.github.io/js-markerclusterer/interfaces/Algorithm.html.
*/
+ @Input()
+ algorithm: Algorithm;
+
+ /** Emits when clustering has started. */
@Output() readonly clusteringbegin: Observable =
- this._eventManager.getLazyEmitter('clusteringbegin');
+ this._closestMapEventManager.getLazyEmitter('clusteringbegin');
- /**
- * See
- * googlemaps.github.io/v3-utility-library/modules/_google_markerclustererplus.html#clusteringend
- */
+ /** Emits when clustering is done. */
@Output() readonly clusteringend: Observable =
- this._eventManager.getLazyEmitter('clusteringend');
+ this._closestMapEventManager.getLazyEmitter('clusteringend');
/** Emits when a cluster has been clicked. */
@Output()
- readonly clusterClick: Observable = this._eventManager.getLazyEmitter('click');
+ readonly clusterClick: EventEmitter = new EventEmitter();
@ContentChildren(MapMarker, {descendants: true}) _markers: QueryList;
+ @ContentChildren(MapAdvancedMarker, {descendants: true})
+ _advancedMarkers: QueryList;
- /**
- * The underlying MarkerClusterer object.
- *
- * See
- * googlemaps.github.io/v3-utility-library/classes/
- * _google_markerclustererplus.markerclusterer.html
- */
- markerClusterer?: MarkerClustererInstance;
-
- /** Event emitted when the clusterer is initialized. */
- @Output() readonly markerClustererInitialized: EventEmitter =
- new EventEmitter();
+ /** Underlying MarkerClusterer object used to interact with Google Maps. */
+ markerClusterer?: MarkerClusterer;
- constructor(
- private readonly _googleMap: GoogleMap,
- private readonly _ngZone: NgZone,
- ) {
- this._canInitialize = _googleMap._isBrowser;
+ constructor(private readonly _googleMap: GoogleMap) {
+ this._canInitialize = this._googleMap._isBrowser;
}
ngOnInit() {
if (this._canInitialize) {
+ this._init();
+ // The `clusteringbegin` and `clusteringend` events are
+ // emitted on the map so that's why set it as the target.
+ this._closestMapEventManager.setTarget(this._googleMap.googleMap!);
+ }
+ }
+
+ _init() {
+ if (markerClusterer?.MarkerClusterer && this._googleMap.googleMap) {
+ this._createCluster(this._googleMap.googleMap);
+ this._watchForMarkerChanges();
+ } else {
this._ngZone.runOutsideAngular(() => {
this._googleMap._resolveMap().then(map => {
- if (
- typeof MarkerClusterer !== 'function' &&
- (typeof ngDevMode === 'undefined' || ngDevMode)
- ) {
- throw Error(
- 'MarkerClusterer class not found, cannot construct a marker cluster. ' +
- 'Please install the MarkerClustererPlus library: ' +
- 'https://github.com/googlemaps/js-markerclustererplus',
- );
- }
-
- // Create the object outside the zone so its events don't trigger change detection.
- // We'll bring it back in inside the `MapEventManager` only for the events that the
- // user has subscribed to.
- this.markerClusterer = this._ngZone.runOutsideAngular(() => {
- return new MarkerClusterer(map, [], this._combineOptions());
- });
-
- this._assertInitialized();
- this._eventManager.setTarget(this.markerClusterer);
- this.markerClustererInitialized.emit(this.markerClusterer);
+ this._createCluster(map);
+ this._watchForMarkerChanges();
});
});
}
}
- ngAfterContentInit() {
- if (this._canInitialize) {
- if (this.markerClusterer) {
- this._watchForMarkerChanges();
- } else {
- this.markerClustererInitialized
- .pipe(take(1), takeUntil(this._destroy))
- .subscribe(() => this._watchForMarkerChanges());
- }
- }
- }
-
ngOnChanges(changes: SimpleChanges) {
- const {
- markerClusterer: clusterer,
- ariaLabelFn,
- _averageCenter,
- _batchSizeIE,
- _calculator,
- _styles,
- _clusterClass,
- _enableRetinaIcons,
- _gridSize,
- _ignoreHidden,
- _imageExtension,
- _imagePath,
- _imageSizes,
- _maxZoom,
- _minimumClusterSize,
- _title,
- _zIndex,
- _zoomOnClick,
- } = this;
+ const change = changes['renderer'] || changes['algorithm'];
- if (clusterer) {
- if (changes['options']) {
- clusterer.setOptions(this._combineOptions());
- }
- if (changes['ariaLabelFn']) {
- clusterer.ariaLabelFn = ariaLabelFn;
- }
- if (changes['averageCenter'] && _averageCenter !== undefined) {
- clusterer.setAverageCenter(_averageCenter);
- }
- if (changes['batchSizeIE'] && _batchSizeIE !== undefined) {
- clusterer.setBatchSizeIE(_batchSizeIE);
- }
- if (changes['calculator'] && !!_calculator) {
- clusterer.setCalculator(_calculator);
- }
- if (changes['clusterClass'] && _clusterClass !== undefined) {
- clusterer.setClusterClass(_clusterClass);
- }
- if (changes['enableRetinaIcons'] && _enableRetinaIcons !== undefined) {
- clusterer.setEnableRetinaIcons(_enableRetinaIcons);
- }
- if (changes['gridSize'] && _gridSize !== undefined) {
- clusterer.setGridSize(_gridSize);
- }
- if (changes['ignoreHidden'] && _ignoreHidden !== undefined) {
- clusterer.setIgnoreHidden(_ignoreHidden);
- }
- if (changes['imageExtension'] && _imageExtension !== undefined) {
- clusterer.setImageExtension(_imageExtension);
- }
- if (changes['imagePath'] && _imagePath !== undefined) {
- clusterer.setImagePath(_imagePath);
- }
- if (changes['imageSizes'] && _imageSizes) {
- clusterer.setImageSizes(_imageSizes);
- }
- if (changes['maxZoom'] && _maxZoom !== undefined) {
- clusterer.setMaxZoom(_maxZoom);
- }
- if (changes['minimumClusterSize'] && _minimumClusterSize !== undefined) {
- clusterer.setMinimumClusterSize(_minimumClusterSize);
- }
- if (changes['styles'] && _styles) {
- clusterer.setStyles(_styles);
- }
- if (changes['title'] && _title !== undefined) {
- clusterer.setTitle(_title);
- }
- if (changes['zIndex'] && _zIndex !== undefined) {
- clusterer.setZIndex(_zIndex);
- }
- if (changes['zoomOnClick'] && _zoomOnClick !== undefined) {
- clusterer.setZoomOnClick(_zoomOnClick);
- }
+ // Since the options are set in the constructor, we have to recreate the cluster if they change.
+ if (this.markerClusterer && this._googleMap.googleMap && change && !change.isFirstChange()) {
+ this._createCluster(this._googleMap.googleMap);
+ this._watchForMarkerChanges();
}
}
ngOnDestroy() {
- this._destroy.next();
- this._destroy.complete();
- this._eventManager.destroy();
- this.markerClusterer?.setMap(null);
- }
-
- fitMapToMarkers(padding: number | google.maps.Padding) {
- this._assertInitialized();
- this.markerClusterer.fitMapToMarkers(padding);
+ this._markersSubscription.unsubscribe();
+ this._closestMapEventManager.destroy();
+ this._destroyCluster();
}
- getAverageCenter(): boolean {
- this._assertInitialized();
- return this.markerClusterer.getAverageCenter();
- }
-
- getBatchSizeIE(): number {
- this._assertInitialized();
- return this.markerClusterer.getBatchSizeIE();
- }
-
- getCalculator(): Calculator {
- this._assertInitialized();
- return this.markerClusterer.getCalculator();
- }
-
- getClusterClass(): string {
- this._assertInitialized();
- return this.markerClusterer.getClusterClass();
- }
-
- getClusters(): Cluster[] {
- this._assertInitialized();
- return this.markerClusterer.getClusters();
- }
-
- getEnableRetinaIcons(): boolean {
- this._assertInitialized();
- return this.markerClusterer.getEnableRetinaIcons();
- }
-
- getGridSize(): number {
- this._assertInitialized();
- return this.markerClusterer.getGridSize();
- }
-
- getIgnoreHidden(): boolean {
- this._assertInitialized();
- return this.markerClusterer.getIgnoreHidden();
- }
-
- getImageExtension(): string {
- this._assertInitialized();
- return this.markerClusterer.getImageExtension();
- }
-
- getImagePath(): string {
- this._assertInitialized();
- return this.markerClusterer.getImagePath();
- }
-
- getImageSizes(): number[] {
- this._assertInitialized();
- return this.markerClusterer.getImageSizes();
- }
-
- getMaxZoom(): number {
- this._assertInitialized();
- return this.markerClusterer.getMaxZoom();
- }
-
- getMinimumClusterSize(): number {
- this._assertInitialized();
- return this.markerClusterer.getMinimumClusterSize();
- }
-
- getStyles(): ClusterIconStyle[] {
- this._assertInitialized();
- return this.markerClusterer.getStyles();
- }
-
- getTitle(): string {
- this._assertInitialized();
- return this.markerClusterer.getTitle();
- }
-
- getTotalClusters(): number {
- this._assertInitialized();
- return this.markerClusterer.getTotalClusters();
- }
-
- getTotalMarkers(): number {
- this._assertInitialized();
- return this.markerClusterer.getTotalMarkers();
- }
-
- getZIndex(): number {
- this._assertInitialized();
- return this.markerClusterer.getZIndex();
- }
-
- getZoomOnClick(): boolean {
+ private _watchForMarkerChanges() {
this._assertInitialized();
- return this.markerClusterer.getZoomOnClick();
+ const initialMarkers: Marker[] = [];
+ const mapMarkerComponent = [
+ ...this._markers.toArray(),
+ this._advancedMarkers.toArray(),
+ ] as MapMarkerComponent[];
+ for (const marker of this._getInternalMarkers(mapMarkerComponent)) {
+ this._currentMarkers.add(marker);
+ initialMarkers.push(marker);
+ }
+ this.markerClusterer.addMarkers(initialMarkers);
+
+ this._markersSubscription.unsubscribe();
+ this._markersSubscription = merge(
+ this._markers.changes,
+ this._advancedMarkers.changes,
+ ).subscribe((markerComponents: MapMarker[] | MapAdvancedMarker[]) => {
+ this._assertInitialized();
+ const newMarkers = new Set(this._getInternalMarkers(markerComponents));
+ const markersToAdd: Marker[] = [];
+ const markersToRemove: Marker[] = [];
+ for (const marker of Array.from(newMarkers)) {
+ if (!this._currentMarkers.has(marker)) {
+ this._currentMarkers.add(marker);
+ markersToAdd.push(marker);
+ }
+ }
+ for (const marker of Array.from(this._currentMarkers)) {
+ if (!newMarkers.has(marker)) {
+ markersToRemove.push(marker);
+ }
+ }
+ this.markerClusterer.addMarkers(markersToAdd, true);
+ this.markerClusterer.removeMarkers(markersToRemove, true);
+ this.markerClusterer.render();
+ for (const marker of markersToRemove) {
+ this._currentMarkers.delete(marker);
+ }
+ });
}
- private _combineOptions(): MarkerClustererOptions {
- const options = this._options || DEFAULT_CLUSTERER_OPTIONS;
- return {
- ...options,
- ariaLabelFn: this.ariaLabelFn ?? options.ariaLabelFn,
- averageCenter: this._averageCenter ?? options.averageCenter,
- batchSize: this.batchSize ?? options.batchSize,
- batchSizeIE: this._batchSizeIE ?? options.batchSizeIE,
- calculator: this._calculator ?? options.calculator,
- clusterClass: this._clusterClass ?? options.clusterClass,
- enableRetinaIcons: this._enableRetinaIcons ?? options.enableRetinaIcons,
- gridSize: this._gridSize ?? options.gridSize,
- ignoreHidden: this._ignoreHidden ?? options.ignoreHidden,
- imageExtension: this._imageExtension ?? options.imageExtension,
- imagePath: this._imagePath ?? options.imagePath,
- imageSizes: this._imageSizes ?? options.imageSizes,
- maxZoom: this._maxZoom ?? options.maxZoom,
- minimumClusterSize: this._minimumClusterSize ?? options.minimumClusterSize,
- styles: this._styles ?? options.styles,
- title: this._title ?? options.title,
- zIndex: this._zIndex ?? options.zIndex,
- zoomOnClick: this._zoomOnClick ?? options.zoomOnClick,
- };
- }
+ private _createCluster(map: google.maps.Map | undefined) {
+ if (
+ !markerClusterer?.MarkerClusterer &&
+ (typeof ngDevMode === 'undefined' || ngDevMode) &&
+ !map
+ ) {
+ throw Error(
+ 'MarkerClusterer class not found, cannot construct a marker cluster. ' +
+ 'Please install the MarkerClusterer library: ' +
+ 'https://github.com/googlemaps/js-markerclusterer',
+ );
+ }
- private _watchForMarkerChanges() {
- this._assertInitialized();
+ this._destroyCluster();
+ // Create the object outside the zone so its events don't trigger change detection.
+ // We'll bring it back in inside the `MapEventManager` only for the events that the
+ // user has subscribed to.
this._ngZone.runOutsideAngular(() => {
- this._getInternalMarkers(this._markers).then(markers => {
- const initialMarkers: google.maps.Marker[] = [];
- for (const marker of markers) {
- this._currentMarkers.add(marker);
- initialMarkers.push(marker);
- }
- this.markerClusterer.addMarkers(initialMarkers);
+ this.markerClusterer = new markerClusterer.MarkerClusterer({
+ map: map,
+ renderer: this.renderer,
+ algorithm: this.algorithm,
+ onClusterClick: (event, cluster, map) => {
+ if (this.clusterClick.observers.length) {
+ this._ngZone.run(() => this.clusterClick.emit(cluster));
+ } else {
+ markerClusterer.defaultOnClusterClickHandler(event, cluster, map);
+ }
+ },
});
});
+ }
- this._markers.changes
- .pipe(takeUntil(this._destroy))
- .subscribe((markerComponents: MapMarker[]) => {
- this._assertInitialized();
- this._ngZone.runOutsideAngular(() => {
- this._getInternalMarkers(markerComponents).then(markers => {
- const newMarkers = new Set(markers);
- const markersToAdd: google.maps.Marker[] = [];
- const markersToRemove: google.maps.Marker[] = [];
- for (const marker of Array.from(newMarkers)) {
- if (!this._currentMarkers.has(marker)) {
- this._currentMarkers.add(marker);
- markersToAdd.push(marker);
- }
- }
- for (const marker of Array.from(this._currentMarkers)) {
- if (!newMarkers.has(marker)) {
- markersToRemove.push(marker);
- }
- }
- this.markerClusterer.addMarkers(markersToAdd, true);
- this.markerClusterer.removeMarkers(markersToRemove, true);
- this.markerClusterer.repaint();
- for (const marker of markersToRemove) {
- this._currentMarkers.delete(marker);
- }
- });
- });
- });
+ private _destroyCluster() {
+ // TODO(crisbeto): the naming here seems odd, but the `MarkerCluster` method isn't
+ // exposed. All this method seems to do at the time of writing is to call into `reset`.
+ // See: https://github.com/googlemaps/js-markerclusterer/blob/main/src/markerclusterer.ts#L205
+ this.markerClusterer?.onRemove();
+ this.markerClusterer = undefined;
}
- private _getInternalMarkers(
- markers: MapMarker[] | QueryList,
- ): Promise {
- return Promise.all(markers.map(markerComponent => markerComponent._resolveMarker()));
+ private _getInternalMarkers(markers: MapMarkerComponent[]): Marker[] {
+ return markers
+ .filter(markerComponent => {
+ if (markerComponent instanceof MapMarker) {
+ return markerComponent.marker!;
+ }
+ return markerComponent.advancedMarker!;
+ })
+ .map(markerComponent => {
+ if (markerComponent instanceof MapMarker) {
+ return markerComponent.marker!;
+ }
+ return markerComponent.advancedMarker!;
+ });
}
- private _assertInitialized(): asserts this is {markerClusterer: MarkerClustererInstance} {
+ private _assertInitialized(): asserts this is {markerClusterer: MarkerClusterer} {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
+ if (!this._googleMap.googleMap) {
+ throw Error(
+ 'Cannot access Google Map information before the API has been initialized. ' +
+ 'Please wait for the API to load before trying to interact with it.',
+ );
+ }
if (!this.markerClusterer) {
throw Error(
'Cannot interact with a MarkerClusterer before it has been initialized. ' +
diff --git a/src/google-maps/map-marker-clusterer/marker-clusterer-types.ts b/src/google-maps/map-marker-clusterer/marker-clusterer-types.ts
index 8c5f99d34c33..3295a9deaeac 100644
--- a/src/google-maps/map-marker-clusterer/marker-clusterer-types.ts
+++ b/src/google-maps/map-marker-clusterer/marker-clusterer-types.ts
@@ -8,177 +8,155 @@
///
+// tslint:disable
+
+declare type onClusterClickHandler = (
+ event: google.maps.MapMouseEvent,
+ cluster: Cluster,
+ map: google.maps.Map,
+) => void;
+
+type Marker = google.maps.Marker | google.maps.marker.AdvancedMarkerElement;
+
/**
- * Class for clustering markers on a Google Map.
- *
- * See
- * googlemaps.github.io/v3-utility-library/classes/_google_markerclustererplus.markerclusterer.html
+ * MarkerClusterer creates and manages per-zoom-level clusters for large amounts
+ * of markers. See {@link MarkerClustererOptions} for more details.
*/
-export declare class MarkerClusterer {
- constructor(
- map: google.maps.Map,
- markers?: google.maps.Marker[],
- options?: MarkerClustererOptions,
- );
- ariaLabelFn: AriaLabelFn;
- static BATCH_SIZE: number;
- static BATCH_SIZE_IE: number;
- static IMAGE_EXTENSION: string;
- static IMAGE_PATH: string;
- static IMAGE_SIZES: number[];
- addListener(eventName: string, handler: Function): google.maps.MapsEventListener;
- addMarker(marker: MarkerClusterer, nodraw: boolean): void;
- addMarkers(markers: google.maps.Marker[], nodraw?: boolean): void;
- bindTo(key: string, target: google.maps.MVCObject, targetKey: string, noNotify: boolean): void;
- changed(key: string): void;
- clearMarkers(): void;
- fitMapToMarkers(padding: number | google.maps.Padding): void;
- get(key: string): any;
- getAverageCenter(): boolean;
- getBatchSizeIE(): number;
- getCalculator(): Calculator;
- getClusterClass(): string;
- getClusters(): Cluster[];
- getEnableRetinaIcons(): boolean;
- getGridSize(): number;
- getIgnoreHidden(): boolean;
- getImageExtension(): string;
- getImagePath(): string;
- getImageSizes(): number[];
- getMap(): google.maps.Map | google.maps.StreetViewPanorama;
- getMarkers(): google.maps.Marker[];
- getMaxZoom(): number;
- getMinimumClusterSize(): number;
- getPanes(): google.maps.MapPanes;
- getProjection(): google.maps.MapCanvasProjection;
- getStyles(): ClusterIconStyle[];
- getTitle(): string;
- getTotalClusters(): number;
- getTotalMarkers(): number;
- getZIndex(): number;
- getZoomOnClick(): boolean;
- notify(key: string): void;
- removeMarker(marker: google.maps.Marker, nodraw: boolean): boolean;
- removeMarkers(markers: google.maps.Marker[], nodraw?: boolean): boolean;
- repaint(): void;
- set(key: string, value: any): void;
- setAverageCenter(averageCenter: boolean): void;
- setBatchSizeIE(batchSizeIE: number): void;
- setCalculator(calculator: Calculator): void;
- setClusterClass(clusterClass: string): void;
- setEnableRetinaIcons(enableRetinaIcons: boolean): void;
- setGridSize(gridSize: number): void;
- setIgnoreHidden(ignoreHidden: boolean): void;
- setImageExtension(imageExtension: string): void;
- setImagePath(imagePath: string): void;
- setImageSizes(imageSizes: number[]): void;
- setMap(map: google.maps.Map | null): void;
- setMaxZoom(maxZoom: number): void;
- setMinimumClusterSize(minimumClusterSize: number): void;
- setStyles(styles: ClusterIconStyle[]): void;
- setTitle(title: string): void;
- setValues(values: any): void;
- setZIndex(zIndex: number): void;
- setZoomOnClick(zoomOnClick: boolean): void;
- // Note: This one doesn't appear in the docs page, but it exists at runtime.
- setOptions(options: MarkerClustererOptions): void;
- unbind(key: string): void;
- unbindAll(): void;
- static CALCULATOR(markers: google.maps.Marker[], numStyles: number): ClusterIconInfo;
- static withDefaultStyle(overrides: ClusterIconStyle): ClusterIconStyle;
+declare class MarkerClusterer {
+ /** @see {@link MarkerClustererOptions.onClusterClick} */
+ onClusterClick: onClusterClickHandler;
+ constructor({map, markers, algorithm, renderer, onClusterClick}: MarkerClustererOptions);
+ addMarker(marker: Marker, noDraw?: boolean): void;
+ addMarkers(markers: Marker[], noDraw?: boolean): void;
+ removeMarker(marker: Marker, noDraw?: boolean): boolean;
+ removeMarkers(markers: Marker[], noDraw?: boolean): boolean;
+ clearMarkers(noDraw?: boolean): void;
+ /**
+ * Recalculates and draws all the marker clusters.
+ */
+ render(): void;
+ onAdd(): void;
+ onRemove(): void;
}
-/**
- * Cluster class from the @google/markerclustererplus library.
- *
- * See googlemaps.github.io/v3-utility-library/classes/_google_markerclustererplus.cluster.html
- */
-export declare class Cluster {
- constructor(markerClusterer: MarkerClusterer);
- getCenter(): google.maps.LatLng;
- getMarkers(): google.maps.Marker[];
- getSize(): number;
- updateIcon(): void;
+interface MarkerClustererOptions {
+ markers?: Marker[];
+ /**
+ * An algorithm to cluster markers. Default is {@link SuperClusterAlgorithm}. Must
+ * provide a `calculate` method accepting {@link AlgorithmInput} and returning
+ * an array of {@link Cluster}.
+ */
+ algorithm?: Algorithm;
+ map?: google.maps.Map | null;
+ /**
+ * An object that converts a {@link Cluster} into a `Marker`.
+ * Default is {@link DefaultRenderer}.
+ */
+ renderer?: Renderer;
+ onClusterClick?: onClusterClickHandler;
}
-/**
- * Options for constructing a MarkerClusterer from the @google/markerclustererplus library.
- *
- * See
- * googlemaps.github.io/v3-utility-library/classes/
- * _google_markerclustererplus.markerclustereroptions.html
- */
-export declare interface MarkerClustererOptions {
- ariaLabelFn?: AriaLabelFn;
- averageCenter?: boolean;
- batchSize?: number;
- batchSizeIE?: number;
- calculator?: Calculator;
- clusterClass?: string;
- enableRetinaIcons?: boolean;
- gridSize?: number;
- ignoreHidden?: boolean;
- imageExtension?: string;
- imagePath?: string;
- imageSizes?: number[];
- maxZoom?: number;
- minimumClusterSize?: number;
- styles?: ClusterIconStyle[];
- title?: string;
- zIndex?: number;
- zoomOnClick?: boolean;
+declare enum MarkerClustererEvents {
+ CLUSTERING_BEGIN = 'clusteringbegin',
+ CLUSTERING_END = 'clusteringend',
+ CLUSTER_CLICK = 'click',
}
-/**
- * Style interface for a marker cluster icon.
- *
- * See
- * googlemaps.github.io/v3-utility-library/interfaces/
- * _google_markerclustererplus.clustericonstyle.html
- */
-export declare interface ClusterIconStyle {
- anchorIcon?: [number, number];
- anchorText?: [number, number];
- backgroundPosition?: string;
- className?: string;
- fontFamily?: string;
- fontStyle?: string;
- fontWeight?: string;
- height: number;
- textColor?: string;
- textDecoration?: string;
- textLineHeight?: number;
- textSize?: number;
- url?: string;
- width: number;
+interface ClusterOptions {
+ position?: google.maps.LatLng | google.maps.LatLngLiteral;
+ markers?: Marker[];
}
-/**
- * Info interface for a marker cluster icon.
- *
- * See
- * googlemaps.github.io/v3-utility-library/interfaces/
- * _google_markerclustererplus.clustericoninfo.html
- */
-export declare interface ClusterIconInfo {
- index: number;
- text: string;
- title: string;
+declare class Cluster {
+ marker: Marker;
+ readonly markers?: Marker[];
+ constructor({markers, position}: ClusterOptions);
+ get bounds(): google.maps.LatLngBounds | undefined;
+ get position(): google.maps.LatLng;
+ /**
+ * Get the count of **visible** markers.
+ */
+ get count(): number;
+ /**
+ * Add a marker to the cluster.
+ */
+ push(marker: Marker): void;
+ /**
+ * Cleanup references and remove marker from map.
+ */
+ delete(): void;
}
-/**
- * Function type alias for determining the aria label on a Google Maps marker cluster.
- *
- * See googlemaps.github.io/v3-utility-library/modules/_google_markerclustererplus.html#arialabelfn
- */
-export declare type AriaLabelFn = (text: string) => string;
+declare class ClusterStats {
+ readonly markers: {
+ sum: number;
+ };
+ readonly clusters: {
+ count: number;
+ markers: {
+ mean: number;
+ sum: number;
+ min: number;
+ max: number;
+ };
+ };
+ constructor(markers: Marker[], clusters: Cluster[]);
+}
-/**
- * Function type alias for calculating how a marker cluster is displayed.
- *
- * See googlemaps.github.io/v3-utility-library/modules/_google_markerclustererplus.html#calculator
- */
-export declare type Calculator = (
- markers: google.maps.Marker[],
- clusterIconStylesCount: number,
-) => ClusterIconInfo;
+interface Renderer {
+ /**
+ * Turn a {@link Cluster} into a `Marker`.
+ *
+ * Below is a simple example to create a marker with the number of markers in the cluster as a label.
+ *
+ * ```typescript
+ * return new Marker({
+ * position,
+ * label: String(markers.length),
+ * });
+ * ```
+ */
+ render(cluster: Cluster, stats: ClusterStats): Marker;
+}
+
+declare class DefaultRenderer implements Renderer {
+ /**
+ * 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(`
+ * `);
+ *
+ * // create marker using svg icon
+ * return new 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}: Cluster, stats: ClusterStats): Marker;
+}
diff --git a/src/google-maps/public-api.ts b/src/google-maps/public-api.ts
index ed4a5a3251b3..9c7fb7b9dff0 100644
--- a/src/google-maps/public-api.ts
+++ b/src/google-maps/public-api.ts
@@ -30,10 +30,4 @@ export {MapTrafficLayer} from './map-traffic-layer/map-traffic-layer';
export {MapTransitLayer} from './map-transit-layer/map-transit-layer';
export {MapHeatmapLayer, HeatmapData} from './map-heatmap-layer/map-heatmap-layer';
export {MapGeocoder, MapGeocoderResponse} from './map-geocoder/map-geocoder';
-export {
- MarkerClustererOptions,
- ClusterIconStyle,
- AriaLabelFn,
- Calculator,
-} from './map-marker-clusterer/marker-clusterer-types';
export {MapEventManager} from './map-event-manager';