Skip to content

Commit

Permalink
feat(*): add clustered markers support
Browse files Browse the repository at this point in the history
Closes #1044
  • Loading branch information
jigfox authored and sebholstein committed Aug 22, 2017
1 parent dd9a087 commit 5cbc515
Show file tree
Hide file tree
Showing 19 changed files with 1,276 additions and 31 deletions.
4 changes: 3 additions & 1 deletion docs/content/api-docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ title = "API Docs for Angular Google Maps"
* [@agm/core](./agm-core/modules/AgmCoreModule.html)
Provides Angular integration solutions for the official Google Maps Core API v3
* [@agm/snazzy-info-window](./agm-snazzy-info-window/modules/AgmSnazzyInfoWindowModule.html)
Styled Info Windows with [Snazzy Info Window](https://github.com/atmist/snazzy-info-window)
Styled Info Windows with [Snazzy Info Window](https://github.com/atmist/snazzy-info-window)
* [@agm/js-marker-clusterer](./js-marker-clusterer/modules/AgmJsMarkerClusterer.html)
Clustered markers with [googlemaps/js-marker-clusterer](https://github.com/googlemaps/js-marker-clusterer)
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@agm/dummy",
"name": "@agm/_dev",
"private": true,
"version": "1.0.0-beta.0",
"description": "Angular 2+ components for Google Maps",
Expand All @@ -25,8 +25,9 @@
"packagejson": "node ./scripts/create-package-json.js",
"copyassets": "node ./scripts/copy-package-assets.js",
"scripts": "npm run ngc:esm && npm run bundle",
"bundle": "npm run bundle:umd:core",
"bundle": "npm run bundle:umd:core && npm run bundle:umd:jsmarkerclusterer",
"bundle:umd:core": "rollup -c rollup.core.config.js",
"bundle:umd:jsmarkerclusterer": "rollup -c rollup.js-marker-clusterer.config.js",
"ngc:esm": "ngc -p tsconfig.json",
"clang:format": "clang-format --glob=packages/**/*.ts -i",
"postngc:esm": "mkdir -p dist/ && cp -R .tmp/esm/* dist/ && rimraf packages/**/*.ngfactory.ts",
Expand All @@ -35,9 +36,10 @@
"ci": "npm run build && npm run test",
"docs:clean": "rimraf docs/public",
"docs:hugo": "cd docs && hugo",
"docs": "npm run docs:hugo && npm run apidocs:core && npm run apidocs:snazzy",
"docs": "npm run docs:hugo && npm run apidocs:core && npm run apidocs:snazzy && npm run apidocs:jsmarkerclusterer",
"apidocs:core": "compodoc -p packages/core/tsconfig.compodoc.json --name @agm/core --output docs/public/api-docs/agm-core/ --disablePrivateOrInternalSupport --hideGenerator --disableCoverage",
"apidocs:snazzy": "compodoc -p packages/snazzy-info-window/tsconfig.compodoc.json --name @agm/snazzy-info-window --output docs/public/api-docs/agm-snazzy-info-window/ --disablePrivateOrInternalSupport --hideGenerator --disableCoverage"
"apidocs:snazzy": "compodoc -p packages/snazzy-info-window/tsconfig.compodoc.json --name @agm/snazzy-info-window --output docs/public/api-docs/agm-snazzy-info-window/ --disablePrivateOrInternalSupport --hideGenerator --disableCoverage",
"apidocs:jsmarkerclusterer": "compodoc -p packages/js-marker-clusterer/tsconfig.compodoc.json --name @agm/js-marker-clusterer --output docs/public/api-docs/js-marker-clusterer/ --disablePrivateOrInternalSupport --hideGenerator --disableCoverage"
},
"author": "Sebastian Müller <info@sebastian-mueller.net>",
"license": "MIT",
Expand Down Expand Up @@ -73,6 +75,7 @@
"html-webpack-plugin": "^2.8.1",
"istanbul-instrumenter-loader": "^2.0.0",
"jasmine-core": "2.5.0",
"js-marker-clusterer": "^1.0.0",
"karma": "^1.3.0",
"karma-chrome-launcher": "2.0.0",
"karma-coverage": "^1.1.1",
Expand Down
6 changes: 4 additions & 2 deletions packages/core/services/google-maps-api-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@ export class GoogleMapsAPIWrapper {
/**
* Creates a google map marker with the map context
*/
createMarker(options: mapTypes.MarkerOptions = <mapTypes.MarkerOptions>{}):
createMarker(options: mapTypes.MarkerOptions = <mapTypes.MarkerOptions>{}, addToMap: boolean = true):
Promise<mapTypes.Marker> {
return this._map.then((map: mapTypes.GoogleMap) => {
options.map = map;
if (addToMap) {
options.map = map;
}
return new google.maps.Marker(options);
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/core/services/managers/marker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {Marker} from './../google-maps-types';

@Injectable()
export class MarkerManager {
private _markers: Map<AgmMarker, Promise<Marker>> =
protected _markers: Map<AgmMarker, Promise<Marker>> =
new Map<AgmMarker, Promise<Marker>>();

constructor(private _mapsWrapper: GoogleMapsAPIWrapper, private _zone: NgZone) {}
constructor(protected _mapsWrapper: GoogleMapsAPIWrapper, protected _zone: NgZone) {}

deleteMarker(marker: AgmMarker): Promise<void> {
const m = this._markers.get(marker);
Expand Down
1 change: 1 addition & 0 deletions packages/js-marker-clusterer/directives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {AgmMarkerCluster} from './directives/marker-cluster';
124 changes: 124 additions & 0 deletions packages/js-marker-clusterer/directives/marker-cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import {Directive, Input, OnDestroy, OnChanges, OnInit, SimpleChange} from '@angular/core';

import {ClusterManager} from '../services/managers/cluster-manager';
import {MarkerManager} from '@agm/core';

import {ClusterOptions, ClusterStyle} 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 {
* height: 300px;
* }
* `],
* template: `
* <agm-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
* <agm-marker-cluster>
* <agm-marker [latitude]="lat" [longitude]="lng" [label]="'M'">
* </agm-marker>
* <agm-marker [latitude]="lat2" [longitude]="lng2" [label]="'N'">
* </agm-marker>
* </agm-marker-cluster>
* </agm-map>
* `
* })
* ```
*/
@Directive({
selector: 'agm-marker-cluster',
providers: [ClusterManager, {provide: MarkerManager, useExisting: ClusterManager}]
})
export class AgmMarkerCluster implements OnDestroy, OnChanges, OnInit, ClusterOptions {
/**
* 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: ClusterStyle;

@Input() imagePath: string;
@Input() imageExtension: string;

constructor(private _clusterManager: ClusterManager) {}

/** @internal */
ngOnDestroy() {
this._clusterManager.clearMarkers();
}

/** @internal */
ngOnChanges(changes: {[key: string]: SimpleChange }) {
if (changes['gridSize']) {
this._clusterManager.setGridSize(this);
}
if (changes['maxZoom']) {
this._clusterManager.setMaxZoom(this);
}
if (changes['styles']) {
this._clusterManager.setStyles(this);
}
if (changes['zoomOnClick']) {
this._clusterManager.setZoomOnClick(this);
}
if (changes['averageCenter']) {
this._clusterManager.setAverageCenter(this);
}
if (changes['minimumClusterSize']) {
this._clusterManager.setMinimumClusterSize(this);
}
if (changes['styles']) {
this._clusterManager.setStyles(this);
}
if (changes['imagePath']) {
this._clusterManager.setImagePath(this);
}
if (changes['imageExtension']) {
this._clusterManager.setImageExtension(this);
}
}

/** @internal */
ngOnInit() {
this._clusterManager.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,
});
}
}
7 changes: 7 additions & 0 deletions packages/js-marker-clusterer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// main modules
export * from './directives';
export * from './services';

// we explicitly export the module here to prevent this Ionic 2 bug:
// http://stevemichelotti.com/integrate-angular-2-google-maps-into-ionic-2/
export { AgmJsMarkerClustererModule } from './js-marker-clusterer.module';
11 changes: 11 additions & 0 deletions packages/js-marker-clusterer/js-marker-clusterer.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {NgModule} from '@angular/core';
import {AgmCoreModule} from '@agm/core';
import {AgmMarkerCluster} from './directives/marker-cluster';

@NgModule({
imports: [AgmCoreModule],
declarations: [AgmMarkerCluster],
exports: [AgmMarkerCluster]
})
export class AgmJsMarkerClustererModule {
}
6 changes: 6 additions & 0 deletions packages/js-marker-clusterer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@agm/clusterer-src",
"dependencies": {
"@agm/core": "file:../core"
}
}
28 changes: 28 additions & 0 deletions packages/js-marker-clusterer/package.tpl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@agm/js-marker-clusterer",
"version": "INSERT_HERE_VIA_BUILD_PROCESS",
"private": true,
"description": "Angular Google Maps (AGM) extension for js-marker-clusterer support",
"repository": {
"type": "git",
"url": "git+https://github.com/SebastianM/angular-google-maps.git"
},
"keywords": [
"angular-google-maps",
"angular",
"js-marker-clusterer",
"google-maps",
"agm"
],
"author": "Jens Fahnenbruck <jigfox@me.com>",
"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"
}
1 change: 1 addition & 0 deletions packages/js-marker-clusterer/services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {ClusterManager} from './services/managers/cluster-manager';
114 changes: 114 additions & 0 deletions packages/js-marker-clusterer/services/google-clusterer-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {Marker, GoogleMap, LatLngBounds} from '@agm/core/services/google-maps-types';

export interface CalculatorResult {
text: string;
index: number;
}

export type CalculateFunction = (marker: Marker[], count: number) => CalculatorResult;

export interface MarkerClustererInstance {
zoomOnClick_: boolean;
averageCenter_: boolean;
imagePath_: string;
minimumClusterSize_: number;
imageExtension_: string;
new(map: GoogleMap, marker: Marker[], options: ClusterOptions): MarkerClustererInstance;
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(): ClusterStyle;
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: ClusterStyle): void;
}

export interface ClusterOptions {
/**
* 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?: ClusterStyle;

imagePath?: string;
imageExtension?: string;
}

export interface ClusterStyle {
/**
* 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];
}
Loading

0 comments on commit 5cbc515

Please sign in to comment.