diff --git a/README.md b/README.md index b64f549..795b77c 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,12 @@ export function configure(aurelia) { options: { panControl: true, panControlOptions: { position: 9 } }, //add google.maps.MapOptions on construct (https://developers.google.com/maps/documentation/javascript/3.exp/reference#MapOptions) language:'' | 'en', // default: uses browser configuration (recommended). Set this parameter to set another language (https://developers.google.com/maps/documentation/javascript/localization) region: '' | 'US' // default: it applies a default bias for application behavior towards the United States. (https://developers.google.com/maps/documentation/javascript/localization) + markerCluster: { + enable: false, + src: 'https://cdn.rawgit.com/googlemaps/v3-utility-library/99a385c1/markerclusterer/src/markerclusterer.js', // self-hosting this file is highly recommended. (https://developers.google.com/maps/documentation/javascript/marker-clustering) + imagePath: 'https://cdn.rawgit.com/googlemaps/v3-utility-library/tree/master/markerclusterer/images/m', // the base URL where the images representing the clusters will be found. The full URL will be: `{imagePath}{[1-5]}`.`{imageExtension}` e.g. `foo/1.png`. Self-hosting these images is highly recommended. (https://developers.google.com/maps/documentation/javascript/marker-clustering) + imageExtension: 'png', + } }); }) } @@ -195,6 +201,12 @@ var myMarkers = [ * `custom` (optional) - store arbitrary data (e.g. an `id` field) in this object, retrieve it from the `googlemap:marker:click` event payload * `infoWindow` (optional) - object - If set, the `googlemap:marker:click` evetn will not be called, instead an infowindow containing the given content will show up - see [docs](https://developers.google.com/maps/documentation/javascript/infowindows) + +### Marker clustering + +This options allows the clustering of markers that are near each other. +All the config and styling options can be found [here](https://github.com/googlemaps/v3-utility-library/blob/master/markerclusterer/src/markerclusterer.js#L40). + ### Drawing Mode Allows usage of Google Maps' Drawing API to add Markers, Polylines, Polygons, Rectangles and Circles to the map instance. To allow this, simply set the defaults as below: diff --git a/src/configure.ts b/src/configure.ts index 6e63174..fb92a81 100644 --- a/src/configure.ts +++ b/src/configure.ts @@ -3,6 +3,7 @@ export interface ConfigInterface { apiKey: string; apiLibraries: string; options: any; + markerCluster: {enable: boolean, src?: string, imagePath?: string, imageExtension?: string} } export class Configure { @@ -15,12 +16,20 @@ export class Configure { apiLibraries: '', region: '', language: '', - options: {} + options: {}, + markerCluster: { + enable: false, + src: 'https://cdn.rawgit.com/googlemaps/v3-utility-library/99a385c1/markerclusterer/src/markerclusterer.js', + imagePath: 'https://raw.githubusercontent.com/googlemaps/v3-utility-library/99a385c1/markerclusterer/images/m', + imageExtension: 'png', + } }; } options(obj: ConfigInterface) { - Object.assign(this._config, obj); + Object.assign(this._config, obj, { + markerCluster: Object.assign({}, this._config.markerCluster, obj.markerCluster) + }); } get(key: string) { diff --git a/src/google-maps-api.ts b/src/google-maps-api.ts index 88b4048..1856912 100644 --- a/src/google-maps-api.ts +++ b/src/google-maps-api.ts @@ -20,10 +20,18 @@ export class GoogleMapsAPI { // google has not been defined yet let script = document.createElement('script'); + let params = [ + this.config.get('apiKey') ? `key=${this.config.get('apiKey')}&` : '', + this.config.get('apiLibraries') ? `libraries=${this.config.get('apiLibraries')}` : '', + this.config.get('language') ? `language=${this.config.get('language')}` : '', + this.config.get('region') ? `region=${this.config.get('region')}` : '', + 'callback=aureliaGoogleMapsCallback', + ]; + script.type = 'text/javascript'; script.async = true; script.defer = true; - script.src = `${this.config.get('apiScript')}?key=${this.config.get('apiKey')}&libraries=${this.config.get('apiLibraries')}&language=${this.config.get('language')}®ion=${this.config.get('region')}&callback=aureliaGoogleMapsCallback`; + script.src = `${this.config.get('apiScript')}?${params.join('&')}`; document.body.appendChild(script); this._scriptPromise = new Promise((resolve, reject) => { diff --git a/src/google-maps.ts b/src/google-maps.ts index f378c78..2290891 100644 --- a/src/google-maps.ts +++ b/src/google-maps.ts @@ -6,6 +6,7 @@ import { getLogger } from 'aurelia-logging'; import { Configure } from './configure'; import { GoogleMapsAPI } from './google-maps-api'; +import { MarkerClustering } from './marker-clustering'; import { Events } from './events'; @@ -26,13 +27,14 @@ export interface Marker { @noView() @customElement('google-map') -@inject(Element, TaskQueue, Configure, BindingEngine, GoogleMapsAPI) +@inject(Element, TaskQueue, Configure, BindingEngine, GoogleMapsAPI, MarkerClustering) export class GoogleMaps { private element: Element; private taskQueue: TaskQueue; private config: any; private bindingEngine: BindingEngine; private googleMapsApi: GoogleMapsAPI; + private markerClustering: MarkerClustering; private _currentInfoWindow: any = null; @bindable longitude: number = 0; @@ -61,12 +63,20 @@ export class GoogleMaps { public _renderedPolygons: any = []; public _polygonsSubscription: any = null; - constructor(element: Element, taskQueue: TaskQueue, config: Configure, bindingEngine: BindingEngine, googleMapsApi: GoogleMapsAPI) { + constructor( + element: Element, + taskQueue: TaskQueue, + config: Configure, + bindingEngine: BindingEngine, + googleMapsApi: GoogleMapsAPI, + markerClustering: MarkerClustering, + ) { this.element = element; this.taskQueue = taskQueue; this.config = config; this.bindingEngine = bindingEngine; this.googleMapsApi = googleMapsApi; + this.markerClustering = markerClustering; if (!config.get('apiScript')) { logger.error('No API script is defined.'); @@ -75,7 +85,8 @@ export class GoogleMaps { if (!config.get('apiKey') && config.get('apiKey') !== false) { logger.error('No API key has been specified.'); } - + + this.markerClustering.loadScript(); this._scriptPromise = this.googleMapsApi.getMapsInstance(); let self: GoogleMaps = this; @@ -120,6 +131,7 @@ export class GoogleMaps { }); this._renderedMarkers = []; + this.markerClustering.renderClusters(this.map, []); } attached() { @@ -289,6 +301,8 @@ export class GoogleMaps { // Send up and event to let the parent know a new marker has been rendered dispatchEvent(Events.MARKERRENDERED, { createdMarker, marker }, this.element); + }).then(() => { + this.markerClustering.renderClusters(this.map, this._renderedMarkers); }); }); } @@ -389,6 +403,8 @@ export class GoogleMaps { // Wait until all of the renderMarker calls have been resolved return Promise.all(markerPromises); }).then(() => { + this.markerClustering.renderClusters(this.map, this._renderedMarkers); + /** * We queue up a task to update the bounds, because in the case of multiple bound properties changing all at once, * we need to let Aurelia handle updating the other properties before we actually trigger a re-render of the map @@ -457,6 +473,8 @@ export class GoogleMaps { * Wait for all of the promises to resolve for rendering markers */ Promise.all(renderPromises).then(() => { + this.markerClustering.renderClusters(this.map, this._renderedMarkers); + /** * We queue up a task to update the bounds, because in the case of multiple bound properties changing all at once, * we need to let Aurelia handle updating the other properties before we actually trigger a re-render of the map @@ -568,7 +586,7 @@ export class GoogleMaps { /** * Get the given constant that Google's library uses. Defaults to MARKER - * @param type + * @param type */ getOverlayType(type: any = '') { switch (type.toUpperCase()) { @@ -605,7 +623,7 @@ export class GoogleMaps { /** * Update the drawing mode, called by aurelia binding - * @param newval + * @param newval */ drawModeChanged(newval: any = '') { this.initDrawingManager() @@ -821,4 +839,4 @@ function dispatchEvent(name: string, detail: any, target: Element, bubbles = tru } target.dispatchEvent(changeEvent); -} \ No newline at end of file +} diff --git a/src/marker-clustering.ts b/src/marker-clustering.ts new file mode 100644 index 0000000..d383fe5 --- /dev/null +++ b/src/marker-clustering.ts @@ -0,0 +1,35 @@ +import { inject } from 'aurelia-dependency-injection'; +import { Configure } from './configure'; + +@inject(Configure) +export class MarkerClustering { + private config: Configure; + + constructor(config) { + this.config = config; + } + + isEnabled() { + return this.config.get('markerCluster') && this.config.get('markerCluster').enable; + } + + loadScript() { + if (!this.isEnabled()) { + return; + } + + let script = document.createElement('script'); + + script.type = 'text/javascript'; + script.src = this.config.get('markerCluster').src; + document.body.appendChild(script); + } + + renderClusters(map, markers) { + if (!this.isEnabled()) { + return; + } + + new (window).MarkerClusterer(map, markers, this.config.get('markerCluster')); + } +}