diff --git a/package.json b/package.json
index c0331dca1..7bee425fa 100644
--- a/package.json
+++ b/package.json
@@ -91,7 +91,8 @@
"tslint-loader": "^3.4.3",
"typescript": "~2.2.0",
"webpack": "2.3.3",
- "zone.js": "^0.8.4"
+ "zone.js": "^0.8.4",
+ "js-marker-clusterer": "^1.0.0"
},
"jspm": {
"jspmNodeConversion": false,
diff --git a/packages/clusterer/clusterer.module.ts b/packages/clusterer/clusterer.module.ts
new file mode 100644
index 000000000..69c01bf2b
--- /dev/null
+++ b/packages/clusterer/clusterer.module.ts
@@ -0,0 +1,13 @@
+import {NgModule} from '@angular/core';
+import {AgmCoreModule} from '../core';
+import {AgmMarkerCluster} from './directives/cluster';
+import {ClusterManager} from './services/managers/cluster-manager';
+
+@NgModule({
+ imports: [AgmCoreModule],
+ declarations: [AgmMarkerCluster],
+ exports: [AgmMarkerCluster, AgmCoreModule],
+ providers: [ClusterManager],
+})
+export class AgmClustererModule {
+}
diff --git a/packages/clusterer/directives.ts b/packages/clusterer/directives.ts
new file mode 100644
index 000000000..585ce914d
--- /dev/null
+++ b/packages/clusterer/directives.ts
@@ -0,0 +1 @@
+export {AgmMarkerCluster} from './directives/cluster';
diff --git a/packages/clusterer/directives/cluster.ts b/packages/clusterer/directives/cluster.ts
new file mode 100644
index 000000000..fb29ad85d
--- /dev/null
+++ b/packages/clusterer/directives/cluster.ts
@@ -0,0 +1,124 @@
+import {Directive, Input, OnDestroy, OnChanges, OnInit, SimpleChange} from '@angular/core';
+
+import {ClusterManager} from '../services/managers/cluster-manager';
+import {MarkerManager} from '../../core/services/managers/marker-manager';
+
+import {IClusterOptions, IClusterStyle} from '../services/google-clusterer-types';
+
+/**
+ * AgmMarkerCluster clusters map marker if they are near together
+ *
+ * ### Example
+ * ```typescript
+ * import { Component } from '@angular/core';
+ *
+ * @Component({
+ * selector: 'my-map-cmp',
+ * styles: [`
+ * .agm-map-container {
+ * height: 300px;
+ * }
+ * `],
+ * template: `
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * `
+ * })
+ * ```
+ */
+@Directive({
+ selector: 'agm-cluster',
+ providers: [ClusterManager, {provide: MarkerManager, useExisting: ClusterManager}]
+})
+export class AgmMarkerCluster implements OnDestroy, OnChanges, OnInit, IClusterOptions {
+ /**
+ * The grid size of a cluster in pixels
+ */
+ @Input() gridSize: number;
+
+ /**
+ * The maximum zoom level that a marker can be part of a cluster.
+ */
+ @Input() maxZoom: number;
+
+ /**
+ * Whether the default behaviour of clicking on a cluster is to zoom into it.
+ */
+ @Input() zoomOnClick: boolean;
+
+ /**
+ * Whether the center of each cluster should be the average of all markers in the cluster.
+ */
+ @Input() averageCenter: boolean;
+
+ /**
+ * The minimum number of markers to be in a cluster before the markers are hidden and a count is shown.
+ */
+ @Input() minimumClusterSize: number;
+
+ /**
+ * An object that has style properties.
+ */
+ @Input() styles: IClusterStyle;
+
+ @Input() imagePath: string;
+ @Input() imageExtension: string;
+
+ constructor(private cluster: ClusterManager) {}
+
+ /** @internal */
+ ngOnDestroy() {
+ this.cluster.clearMarkers();
+ }
+
+ /** @internal */
+ ngOnChanges(changes: {[key: string]: SimpleChange }) {
+ if (changes['gridSize']) {
+ this.cluster.setGridSize(this);
+ }
+ if (changes['maxZoom']) {
+ this.cluster.setMaxZoom(this);
+ }
+ if (changes['styles']) {
+ this.cluster.setStyles(this);
+ }
+ if (changes['zoomOnClick']) {
+ this.cluster.setZoomOnClick(this);
+ }
+ if (changes['averageCenter']) {
+ this.cluster.setAverageCenter(this);
+ }
+ if (changes['minimumClusterSize']) {
+ this.cluster.setMinimumClusterSize(this);
+ }
+ if (changes['styles']) {
+ this.cluster.setStyles(this);
+ }
+ if (changes['imagePath']) {
+ this.cluster.setImagePath(this);
+ }
+ if (changes['imageExtension']) {
+ this.cluster.setImageExtension(this);
+ }
+ }
+
+ /** @internal */
+ ngOnInit() {
+ this.cluster.init({
+ gridSize: this.gridSize,
+ maxZoom: this.maxZoom,
+ zoomOnClick: this.zoomOnClick,
+ averageCenter: this.averageCenter,
+ minimumClusterSize: this.minimumClusterSize,
+ styles: this.styles,
+ imagePath: this.imagePath,
+ imageExtension: this.imageExtension,
+ });
+ }
+}
diff --git a/packages/clusterer/index.ts b/packages/clusterer/index.ts
new file mode 100644
index 000000000..431c481f1
--- /dev/null
+++ b/packages/clusterer/index.ts
@@ -0,0 +1,8 @@
+// main modules
+export * from './directives';
+export * from './services';
+
+// core module
+// we explicitly export the module here to prevent this Ionic 2 bug:
+// http://stevemichelotti.com/integrate-angular-2-google-maps-into-ionic-2/
+export {AgmClustererModule} from './clusterer.module';
diff --git a/packages/clusterer/package.json b/packages/clusterer/package.json
new file mode 100644
index 000000000..9b6d89df1
--- /dev/null
+++ b/packages/clusterer/package.json
@@ -0,0 +1,6 @@
+{
+ "name": "@agm/clusterer-src",
+ "dependencies": {
+ "@agm/core": "file:../core"
+ }
+}
diff --git a/packages/clusterer/package.tpl.json b/packages/clusterer/package.tpl.json
new file mode 100644
index 000000000..f6f0260ac
--- /dev/null
+++ b/packages/clusterer/package.tpl.json
@@ -0,0 +1,29 @@
+{
+ "name": "@agm/clusterer",
+ "version": "INSERT_HERE_VIA_BUILD_PROCESS",
+ "private": true,
+ "description": "Angular Google Maps (AGM) package for Snazzy Info Window support",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/SebastianM/angular-google-maps.git"
+ },
+ "keywords": [
+ "angular-google-maps",
+ "angular",
+ "js-marker-clusterer",
+ "info-window",
+ "google-maps",
+ "agm"
+ ],
+ "author": "Jens Fahnenbruck ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/SebastianM/angular-google-maps/issues"
+ },
+ "peerDependencies": {
+ "@angular/core": "^4.0.0",
+ "@agm/core": "^1.0.0-beta.0",
+ "js-marker-clusterer": "^1.0.0"
+ },
+ "homepage": "https://github.com/SebastianM/angular-google-maps#readme"
+}
diff --git a/packages/clusterer/services.ts b/packages/clusterer/services.ts
new file mode 100644
index 000000000..06f16a130
--- /dev/null
+++ b/packages/clusterer/services.ts
@@ -0,0 +1 @@
+export {ClusterManager} from './services/managers/cluster-manager';
diff --git a/packages/clusterer/services/google-clusterer-types.ts b/packages/clusterer/services/google-clusterer-types.ts
new file mode 100644
index 000000000..e0426255e
--- /dev/null
+++ b/packages/clusterer/services/google-clusterer-types.ts
@@ -0,0 +1,114 @@
+import {Marker, GoogleMap, LatLngBounds} from '../../core/services/google-maps-types';
+
+export interface CalculatorResult {
+ text: string;
+ index: number;
+}
+
+export type CalculateFunction = (marker: Marker[], count: number) => CalculatorResult;
+
+export interface IMarkerClusterer {
+ zoomOnClick_: boolean;
+ averageCenter_: boolean;
+ imagePath_: string;
+ minimumClusterSize_: number;
+ imageExtension_: string;
+ new(map: GoogleMap, marker: Marker[], options: IClusterOptions): IMarkerClusterer;
+ addMarker(marker: Marker, noDraw?: boolean): void;
+ addMarkers(markers: Marker[], noDraw?: boolean): void;
+ clearMarkers(): void;
+ getCalculator(): CalculateFunction;
+ getExtendedBounds(bounds: LatLngBounds): LatLngBounds;
+ getGridSize(): number;
+ getMap(): GoogleMap;
+ getMarkers(): Marker[];
+ getStyles(): IClusterStyle;
+ getTotalClusters(): number;
+ getTotalMarkers(): Marker[];
+ isZoomOnClick(): boolean;
+ redraw(): void;
+ removeMarker(marker: Marker): boolean;
+ resetViewport(): void;
+ setCalculator(calculator: CalculateFunction): void;
+ setGridSize(size: number): void;
+ setMap(map: GoogleMap): void;
+ setMaxZoom(maxZoom: number): void;
+ setStyles(styles: IClusterStyle): void;
+}
+
+export interface IClusterOptions {
+ /**
+ * The grid size of a cluster in pixels.
+ */
+ gridSize?: number;
+
+ /**
+ * The maximum zoom level that a marker can be part of a cluster.
+ */
+ maxZoom?: number;
+
+ /**
+ * Whether the default behaviour of clicking on a cluster is to zoom into it.
+ */
+ zoomOnClick?: boolean;
+
+ /**
+ * Whether the center of each cluster should be the average of all markers in the cluster.
+ */
+ averageCenter?: boolean;
+
+ /**
+ * The minimum number of markers to be in a cluster before the markers are hidden and a count is shown.
+ */
+ minimumClusterSize?: number;
+
+ /**
+ * An object that has style properties.
+ */
+ styles?: IClusterStyle;
+
+ imagePath?: string;
+ imageExtension?: string;
+}
+
+export interface IClusterStyle {
+ /**
+ * The image url.
+ */
+ url?: string;
+
+ /**
+ * The image height.
+ */
+ height?: number;
+
+ /**
+ * The image width.
+ */
+ width?: number;
+
+ /**
+ * The anchor position of the label text.
+ */
+ anchor?: [number, number];
+
+ /**
+ * The text color.
+ */
+ textColor?: string;
+
+ /**
+ * The text size.
+ */
+ textSize?: number;
+
+ /**
+ * The position of the backgound x, y.
+ */
+ backgroundPosition?: string;
+
+ /**
+ * The anchor position of the icon x, y.
+ */
+ iconAnchor?: [number, number];
+}
diff --git a/packages/clusterer/services/managers/cluster-manager.spec.ts b/packages/clusterer/services/managers/cluster-manager.spec.ts
new file mode 100644
index 000000000..b44e31717
--- /dev/null
+++ b/packages/clusterer/services/managers/cluster-manager.spec.ts
@@ -0,0 +1,196 @@
+import {NgZone} from '@angular/core';
+import {TestBed, async, inject} from '@angular/core/testing';
+
+import {AgmMarker} from '../../../core/directives/marker';
+import {GoogleMapsAPIWrapper} from '../../../core/services/google-maps-api-wrapper';
+import {Marker} from '../../../core/services/google-maps-types';
+import {ClusterManager} from './cluster-manager';
+
+describe('ClusterManager', () => {
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ {provide: NgZone, useFactory: () => new NgZone({enableLongStackTrace: true})},
+ ClusterManager, {
+ provide: GoogleMapsAPIWrapper,
+ useValue: jasmine.createSpyObj('GoogleMapsAPIWrapper', ['createMarker'])
+ }
+ ]
+ });
+ });
+
+ describe('Create a new marker', () => {
+ it('should call the mapsApiWrapper when creating a new marker',
+ inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (clusterManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(clusterManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+ clusterManager.addMarker(newMarker);
+
+ expect(apiWrapper.createMarker).toHaveBeenCalledWith({
+ position: {lat: 34.4, lng: 22.3},
+ label: 'A',
+ draggable: false,
+ icon: undefined,
+ opacity: 1,
+ visible: true,
+ zIndex: 1,
+ title: undefined,
+ clickable: true
+ }, false);
+ }));
+ });
+
+ describe('Delete a marker', () => {
+ it('should set the map to null when deleting a existing marker',
+ inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (clusterManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(clusterManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+
+ const markerInstance: Marker = jasmine.createSpyObj('Marker', ['setMap']);
+ (apiWrapper.createMarker).and.returnValue(Promise.resolve(markerInstance));
+
+ clusterManager.addMarker(newMarker);
+ clusterManager.deleteMarker(newMarker).then(
+ () => { expect(markerInstance.setMap).toHaveBeenCalledWith(null); });
+ }));
+ });
+
+ describe('set marker icon', () => {
+ it('should update that marker via setIcon method when the markerUrl changes',
+ async(inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (markerManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(markerManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+
+ const markerInstance: Marker = jasmine.createSpyObj('Marker', ['setMap', 'setIcon']);
+ (apiWrapper.createMarker).and.returnValue(Promise.resolve(markerInstance));
+
+ markerManager.addMarker(newMarker);
+ expect(apiWrapper.createMarker).toHaveBeenCalledWith({
+ position: {lat: 34.4, lng: 22.3},
+ label: 'A',
+ draggable: false,
+ icon: undefined,
+ opacity: 1,
+ visible: true,
+ zIndex: 1,
+ title: undefined,
+ clickable: true
+ }, false);
+ const iconUrl = 'http://angular-maps.com/icon.png';
+ newMarker.iconUrl = iconUrl;
+ return markerManager.updateIcon(newMarker).then(
+ () => { expect(markerInstance.setIcon).toHaveBeenCalledWith(iconUrl); });
+ })));
+ });
+
+ describe('set marker opacity', () => {
+ it('should update that marker via setOpacity method when the markerOpacity changes',
+ async(inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (markerManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(markerManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+
+ const markerInstance: Marker =
+ jasmine.createSpyObj('Marker', ['setMap', 'setOpacity']);
+ (apiWrapper.createMarker).and.returnValue(Promise.resolve(markerInstance));
+
+ markerManager.addMarker(newMarker);
+ expect(apiWrapper.createMarker).toHaveBeenCalledWith({
+ position: {lat: 34.4, lng: 22.3},
+ label: 'A',
+ draggable: false,
+ icon: undefined,
+ visible: true,
+ opacity: 1,
+ zIndex: 1,
+ title: undefined,
+ clickable: true
+ }, false);
+ const opacity = 0.4;
+ newMarker.opacity = opacity;
+ return markerManager.updateOpacity(newMarker).then(
+ () => { expect(markerInstance.setOpacity).toHaveBeenCalledWith(opacity); });
+ })));
+ });
+
+ describe('set visible option', () => {
+ it('should update that marker via setVisible method when the visible changes',
+ async(inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (markerManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(markerManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+ newMarker.visible = false;
+
+ const markerInstance: Marker =
+ jasmine.createSpyObj('Marker', ['setMap', 'setVisible']);
+ (apiWrapper.createMarker).and.returnValue(Promise.resolve(markerInstance));
+
+ markerManager.addMarker(newMarker);
+ expect(apiWrapper.createMarker).toHaveBeenCalledWith({
+ position: {lat: 34.4, lng: 22.3},
+ label: 'A',
+ draggable: false,
+ icon: undefined,
+ visible: false,
+ opacity: 1,
+ zIndex: 1,
+ title: undefined,
+ clickable: true
+ }, false);
+ newMarker.visible = true;
+ return markerManager.updateVisible(newMarker).then(
+ () => { expect(markerInstance.setVisible).toHaveBeenCalledWith(true); });
+ })));
+ });
+
+ describe('set zIndex option', () => {
+ it('should update that marker via setZIndex method when the zIndex changes',
+ async(inject(
+ [ClusterManager, GoogleMapsAPIWrapper],
+ (markerManager: ClusterManager, apiWrapper: GoogleMapsAPIWrapper) => {
+ const newMarker = new AgmMarker(markerManager);
+ newMarker.latitude = 34.4;
+ newMarker.longitude = 22.3;
+ newMarker.label = 'A';
+ newMarker.visible = false;
+
+ const markerInstance: Marker = jasmine.createSpyObj('Marker', ['setMap', 'setZIndex']);
+ (apiWrapper.createMarker).and.returnValue(Promise.resolve(markerInstance));
+
+ markerManager.addMarker(newMarker);
+ expect(apiWrapper.createMarker).toHaveBeenCalledWith({
+ position: {lat: 34.4, lng: 22.3},
+ label: 'A',
+ draggable: false,
+ icon: undefined,
+ visible: false,
+ opacity: 1,
+ zIndex: 1,
+ title: undefined,
+ clickable: true
+ }, false);
+ const zIndex = 10;
+ newMarker.zIndex = zIndex;
+ return markerManager.updateZIndex(newMarker).then(
+ () => { expect(markerInstance.setZIndex).toHaveBeenCalledWith(zIndex); });
+ })));
+ });
+});
diff --git a/packages/clusterer/services/managers/cluster-manager.ts b/packages/clusterer/services/managers/cluster-manager.ts
new file mode 100644
index 000000000..a27c2b823
--- /dev/null
+++ b/packages/clusterer/services/managers/cluster-manager.ts
@@ -0,0 +1,195 @@
+import {Injectable, NgZone} from '@angular/core';
+
+import 'js-marker-clusterer';
+
+import {MarkerManager} from '../../../core/services/managers/marker-manager';
+import {GoogleMapsAPIWrapper} from '../../../core/services/google-maps-api-wrapper';
+import {AgmMarker} from '../../../core/directives/marker';
+import {AgmMarkerCluster} from './../../directives/cluster';
+// tslint:disable-next-line: no-use-before-declare
+import {Marker} from '../../../core/services/google-maps-types';
+import {IMarkerClusterer, IClusterOptions} from '../google-clusterer-types';
+
+declare var MarkerClusterer: IMarkerClusterer;
+
+class Deferred {
+ private _promise: Promise;
+ private fate: 'resolved' | 'unresolved';
+ private state: 'pending' | 'fulfilled' | 'rejected';
+ private _resolve: Function;
+ private _reject: Function;
+
+ constructor() {
+ this.state = 'pending';
+ this.fate = 'unresolved';
+ this._promise = new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ });
+
+ this.promise.then(
+ () => this.state = 'fulfilled',
+ () => this.state = 'rejected',
+ );
+ }
+
+ get promise(): Promise {
+ return this._promise;
+ }
+
+ resolve(value?: any) {
+ if (this.fate === 'resolved') {
+ throw 'Deferred cannot be resolved twice';
+ }
+ this.fate = 'resolved';
+ this._resolve(value);
+ }
+
+ reject(reason?: any) {
+ if (this.fate === 'resolved') {
+ throw 'Deferred cannot be resolved twice';
+ }
+ this.fate = 'resolved';
+ this._reject(reason);
+ }
+
+ isResolved() {
+ return this.fate === 'resolved';
+ }
+
+ isPending() {
+ return this.state === 'pending';
+ }
+
+ isFulfilled() {
+ return this.state === 'fulfilled';
+ }
+
+ isRejected() {
+ return this.state === 'rejected';
+ }
+}
+
+@Injectable()
+export class ClusterManager extends MarkerManager {
+ private _deferred: Deferred;
+
+ constructor(protected _mapsWrapper: GoogleMapsAPIWrapper, protected _zone: NgZone) {
+ super(_mapsWrapper, _zone);
+ this._deferred = new Deferred();
+ }
+
+ init(options: IClusterOptions): void {
+ this._mapsWrapper.getNativeMap().then(map => {
+ const clusterer = new MarkerClusterer(map, [], options);
+ this._deferred.resolve(clusterer);
+ return clusterer;
+ });
+ }
+
+ addMarker(marker: AgmMarker): void {
+ const clusterPromise: Promise = this._deferred.promise;
+ const markerPromise = this._mapsWrapper
+ .createMarker({
+ position: {
+ lat: marker.latitude,
+ lng: marker.longitude
+ },
+ label: marker.label,
+ draggable: marker.draggable,
+ icon: marker.iconUrl,
+ opacity: marker.opacity,
+ visible: marker.visible,
+ zIndex: marker.zIndex,
+ title: marker.title,
+ clickable: marker.clickable,
+ }, false);
+
+ Promise
+ .all([clusterPromise, markerPromise])
+ .then(([cluster, marker]) => {
+ return cluster.addMarker(marker);
+ });
+ this._markers.set(marker, markerPromise);
+ }
+
+ deleteMarker(marker: AgmMarker): Promise {
+ const m = this._markers.get(marker);
+ if (m == null) {
+ // marker already deleted
+ return Promise.resolve();
+ }
+ return m.then((m: Marker) => {
+ this._zone.run(() => {
+ this._deferred.promise.then(cluster => {
+ cluster.removeMarker(m);
+ this._markers.delete(marker);
+ });
+ });
+ });
+ }
+
+ clearMarkers(): Promise {
+ return this._deferred.promise.then(cluster => {
+ cluster.clearMarkers();
+ });
+ }
+
+ setGridSize(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ cluster.setGridSize(c.gridSize);
+ });
+ }
+
+ setMaxZoom(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ cluster.setMaxZoom(c.maxZoom);
+ });
+ }
+
+ setStyles(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ cluster.setStyles(c.styles);
+ });
+ }
+
+ setZoomOnClick(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ if (c.zoomOnClick !== undefined) {
+ cluster.zoomOnClick_ = c.zoomOnClick;
+ }
+ });
+ }
+
+ setAverageCenter(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ if (c.averageCenter !== undefined) {
+ cluster.averageCenter_ = c.averageCenter;
+ }
+ });
+ }
+
+ setImagePath(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ if (c.imagePath !== undefined) {
+ cluster.imagePath_ = c.imagePath;
+ }
+ });
+ }
+
+ setMinimumClusterSize(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ if (c.minimumClusterSize !== undefined) {
+ cluster.minimumClusterSize_ = c.minimumClusterSize;
+ }
+ });
+ }
+
+ setImageExtension(c: AgmMarkerCluster): void {
+ this._deferred.promise.then(cluster => {
+ if (c.imageExtension !== undefined) {
+ cluster.imageExtension_ = c.imageExtension;
+ }
+ });
+ }
+}
diff --git a/packages/core/services/google-maps-api-wrapper.ts b/packages/core/services/google-maps-api-wrapper.ts
index 7a611859a..006cd82e6 100644
--- a/packages/core/services/google-maps-api-wrapper.ts
+++ b/packages/core/services/google-maps-api-wrapper.ts
@@ -39,10 +39,12 @@ export class GoogleMapsAPIWrapper {
/**
* Creates a google map marker with the map context
*/
- createMarker(options: mapTypes.MarkerOptions = {}):
+ createMarker(options: mapTypes.MarkerOptions = {}, addToMap: boolean = true):
Promise {
return this._map.then((map: mapTypes.GoogleMap) => {
- options.map = map;
+ if (addToMap) {
+ options.map = map;
+ }
return new google.maps.Marker(options);
});
}
diff --git a/packages/core/services/managers/marker-manager.ts b/packages/core/services/managers/marker-manager.ts
index 518ce79d7..73f307cf8 100644
--- a/packages/core/services/managers/marker-manager.ts
+++ b/packages/core/services/managers/marker-manager.ts
@@ -9,10 +9,10 @@ import {Marker} from './../google-maps-types';
@Injectable()
export class MarkerManager {
- private _markers: Map> =
+ protected _markers: Map> =
new Map>();
- constructor(private _mapsWrapper: GoogleMapsAPIWrapper, private _zone: NgZone) {}
+ constructor(protected _mapsWrapper: GoogleMapsAPIWrapper, protected _zone: NgZone) {}
deleteMarker(marker: AgmMarker): Promise {
const m = this._markers.get(marker);
diff --git a/rollup.core.config.js b/rollup.core.config.js
index 9426e3dea..ca248e866 100644
--- a/rollup.core.config.js
+++ b/rollup.core.config.js
@@ -10,6 +10,7 @@ export default {
'@angular/compiler': 'ng.compiler',
'@angular/platform-browser': 'ng.platformBrowser',
'@angular/platform-browser-dynamic': 'ng.platformBrowserDynamic',
+ 'js-marker-clusterer': 'MarkerClusterer',
'rxjs/Subject': 'Rx',
'rxjs/observable/PromiseObservable': 'Rx',
'rxjs/operator/toPromise': 'Rx.Observable.prototype',
@@ -17,5 +18,5 @@ export default {
'rxjs/Rx': 'Rx'
},
context: 'window',
- external: ['rxjs', '@angular/core', 'rxjs/Observable']
+ external: ['rxjs', '@angular/core', 'rxjs/Observable', 'js-marker-clusterer']
}
diff --git a/scripts/packages.js b/scripts/packages.js
index ba4b5a1f3..bc95975b6 100644
--- a/scripts/packages.js
+++ b/scripts/packages.js
@@ -1,7 +1,8 @@
// NPM packages (without the org name) that we publish
const packages = [
'core',
- 'snazzy-info-window'
+ 'snazzy-info-window',
+ 'clusterer',
];
-module.exports = packages;
\ No newline at end of file
+module.exports = packages;
diff --git a/yarn.lock b/yarn.lock
index 12c21a31d..6666293a6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2183,6 +2183,10 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
+js-marker-clusterer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/js-marker-clusterer/-/js-marker-clusterer-1.0.0.tgz#c94be85ae8896819e51c131f891dd2b55558dd17"
+
js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
@@ -4024,9 +4028,9 @@ webpack-sources@^0.2.3:
source-list-map "^1.1.1"
source-map "~0.5.3"
-webpack@2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.2.tgz#7d521e6f0777a3a58985c69425263fdfe977b458"
+webpack@2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.3.3.tgz#eecc083c18fb7bf958ea4f40b57a6640c5a0cc78"
dependencies:
acorn "^4.0.4"
acorn-dynamic-import "^2.0.0"