/
google-map.ts
399 lines (349 loc) · 12.8 KB
/
google-map.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
import {Component, ElementRef, EventEmitter, OnChanges, OnDestroy, OnInit, SimpleChange} from '@angular/core';
import {Subscription} from 'rxjs/Subscription';
import {MouseEvent} from '../map-types';
import {GoogleMapsAPIWrapper} from '../services/google-maps-api-wrapper';
import {LatLng, LatLngLiteral} from '../services/google-maps-types';
import {LatLngBounds, LatLngBoundsLiteral, MapTypeStyle} from '../services/google-maps-types';
import {CircleManager} from '../services/managers/circle-manager';
import {InfoWindowManager} from '../services/managers/info-window-manager';
import {MarkerManager} from '../services/managers/marker-manager';
import {PolygonManager} from '../services/managers/polygon-manager';
import {PolylineManager} from '../services/managers/polyline-manager';
import {KmlLayerManager} from './../services/managers/kml-layer-manager';
/**
* SebMGoogleMap renders a Google Map.
* **Important note**: To be able see a map in the browser, you have to define a height for the CSS
* class `sebm-google-map-container`.
*
* ### Example
* ```typescript
* import { Component } from '@angular/core';
* import { SebmGoogleMap } from 'angular2-google-maps/core';
*
* @Component({
* selector: 'my-map-cmp',
* directives: [SebmGoogleMap],
* styles: [`
* .sebm-google-map-container {
* height: 300px;
* }
* `],
* template: `
* <sebm-google-map [latitude]="lat" [longitude]="lng" [zoom]="zoom">
* </sebm-google-map>
* `
* })
* ```
*/
@Component({
selector: 'sebm-google-map',
providers: [
GoogleMapsAPIWrapper, MarkerManager, InfoWindowManager, CircleManager, PolylineManager,
PolygonManager, KmlLayerManager
],
inputs: [
'longitude', 'latitude', 'zoom', 'minZoom', 'maxZoom', 'draggable: mapDraggable',
'disableDoubleClickZoom', 'disableDefaultUI', 'scrollwheel', 'backgroundColor', 'draggableCursor',
'draggingCursor', 'keyboardShortcuts', 'zoomControl', 'styles', 'usePanning', 'streetViewControl',
'fitBounds', 'scaleControl', 'mapTypeControl'
],
outputs: [
'mapClick', 'mapRightClick', 'mapDblClick', 'centerChange', 'idle', 'boundsChange', 'zoomChange'
],
host: {'[class.sebm-google-map-container]': 'true'},
styles: [`
.sebm-google-map-container-inner {
width: inherit;
height: inherit;
}
.sebm-google-map-content {
display:none;
}
`],
template: `
<div class='sebm-google-map-container-inner'></div>
<div class='sebm-google-map-content'>
<ng-content></ng-content>
</div>
`
})
export class SebmGoogleMap implements OnChanges, OnInit, OnDestroy {
/**
* The longitude that defines the center of the map.
*/
longitude: number = 0;
/**
* The latitude that defines the center of the map.
*/
latitude: number = 0;
/**
* The zoom level of the map. The default zoom level is 8.
*/
zoom: number = 8;
/**
* The minimal zoom level of the map allowed. When not provided, no restrictions to the zoom level
* are enforced.
*/
minZoom: number;
/**
* The maximal zoom level of the map allowed. When not provided, no restrictions to the zoom level
* are enforced.
*/
maxZoom: number;
/**
* Enables/disables if map is draggable.
*/
draggable: boolean = true;
/**
* Enables/disables zoom and center on double click. Enabled by default.
*/
disableDoubleClickZoom: boolean = false;
/**
* Enables/disables all default UI of the Google map. Please note: When the map is created, this
* value cannot get updated.
*/
disableDefaultUI: boolean = false;
/**
* If false, disables scrollwheel zooming on the map. The scrollwheel is enabled by default.
*/
scrollwheel: boolean = true;
/**
* Color used for the background of the Map div. This color will be visible when tiles have not
* yet loaded as the user pans. This option can only be set when the map is initialized.
*/
backgroundColor: string;
/**
* The name or url of the cursor to display when mousing over a draggable map. This property uses
* the css * cursor attribute to change the icon. As with the css property, you must specify at
* least one fallback cursor that is not a URL. For example:
* [draggableCursor]="'url(http://www.example.com/icon.png), auto;'"
*/
draggableCursor: string;
/**
* The name or url of the cursor to display when the map is being dragged. This property uses the
* css cursor attribute to change the icon. As with the css property, you must specify at least
* one fallback cursor that is not a URL. For example:
* [draggingCursor]="'url(http://www.example.com/icon.png), auto;'"
*/
draggingCursor: string;
/**
* If false, prevents the map from being controlled by the keyboard. Keyboard shortcuts are
* enabled by default.
*/
keyboardShortcuts: boolean = true;
/**
* The enabled/disabled state of the Zoom control.
*/
zoomControl: boolean = true;
/**
* Styles to apply to each of the default map types. Note that for Satellite/Hybrid and Terrain
* modes, these styles will only apply to labels and geometry.
*/
styles: MapTypeStyle[] = [];
/**
* When true and the latitude and/or longitude values changes, the Google Maps panTo method is
* used to
* center the map. See: https://developers.google.com/maps/documentation/javascript/reference#Map
*/
usePanning: boolean = false;
/**
* The initial enabled/disabled state of the Street View Pegman control.
* This control is part of the default UI, and should be set to false when displaying a map type
* on which the Street View road overlay should not appear (e.g. a non-Earth map type).
*/
streetViewControl: boolean = true;
/**
* Sets the viewport to contain the given bounds.
*/
fitBounds: LatLngBoundsLiteral|LatLngBounds = null;
/**
* The initial enabled/disabled state of the Scale control. This is disabled by default.
*/
scaleControl: boolean = false;
/**
* The initial enabled/disabled state of the Map type control.
*/
mapTypeControl: boolean = false;
/**
* Map option attributes that can change over time
*/
private static _mapOptionsAttributes: string[] = [
'disableDoubleClickZoom', 'scrollwheel', 'draggable', 'draggableCursor', 'draggingCursor',
'keyboardShortcuts', 'zoomControl', 'styles', 'streetViewControl', 'zoom', 'mapTypeControl',
'minZoom', 'maxZoom'
];
private _observableSubscriptions: Subscription[] = [];
/**
* This event emitter gets emitted when the user clicks on the map (but not when they click on a
* marker or infoWindow).
*/
mapClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
/**
* This event emitter gets emitted when the user right-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
mapRightClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
/**
* This event emitter gets emitted when the user double-clicks on the map (but not when they click
* on a marker or infoWindow).
*/
mapDblClick: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
/**
* This event emitter is fired when the map center changes.
*/
centerChange: EventEmitter<LatLngLiteral> = new EventEmitter<LatLngLiteral>();
/**
* This event is fired when the viewport bounds have changed.
*/
boundsChange: EventEmitter<LatLngBounds> = new EventEmitter<LatLngBounds>();
/**
* This event is fired when the map becomes idle after panning or zooming.
*/
idle: EventEmitter<void> = new EventEmitter<void>();
/**
* This event is fired when the zoom level has changed.
*/
zoomChange: EventEmitter<number> = new EventEmitter<number>();
constructor(private _elem: ElementRef, private _mapsWrapper: GoogleMapsAPIWrapper) {}
/** @internal */
ngOnInit() {
// todo: this should be solved with a new component and a viewChild decorator
const container = this._elem.nativeElement.querySelector('.sebm-google-map-container-inner');
this._initMapInstance(container);
}
private _initMapInstance(el: HTMLElement) {
this._mapsWrapper.createMap(el, {
center: {lat: this.latitude || 0, lng: this.longitude || 0},
zoom: this.zoom,
minZoom: this.minZoom,
maxZoom: this.maxZoom,
disableDefaultUI: this.disableDefaultUI,
backgroundColor: this.backgroundColor,
draggable: this.draggable,
draggableCursor: this.draggableCursor,
draggingCursor: this.draggingCursor,
keyboardShortcuts: this.keyboardShortcuts,
zoomControl: this.zoomControl,
styles: this.styles,
streetViewControl: this.streetViewControl,
scaleControl: this.scaleControl,
mapTypeControl: this.mapTypeControl
});
// register event listeners
this._handleMapCenterChange();
this._handleMapZoomChange();
this._handleMapMouseEvents();
this._handleBoundsChange();
this._handleIdleEvent();
}
/** @internal */
ngOnDestroy() {
// unsubscribe all registered observable subscriptions
this._observableSubscriptions.forEach((s) => s.unsubscribe());
}
/* @internal */
ngOnChanges(changes: {[propName: string]: SimpleChange}) {
this._updateMapOptionsChanges(changes);
this._updatePosition(changes);
}
private _updateMapOptionsChanges(changes: {[propName: string]: SimpleChange}) {
let options: {[propName: string]: any} = {};
let optionKeys =
Object.keys(changes).filter(k => SebmGoogleMap._mapOptionsAttributes.indexOf(k) !== -1);
optionKeys.forEach((k) => { options[k] = changes[k].currentValue; });
this._mapsWrapper.setMapOptions(options);
}
/**
* Triggers a resize event on the google map instance.
* Returns a promise that gets resolved after the event was triggered.
*/
triggerResize(): Promise<void> {
// Note: When we would trigger the resize event and show the map in the same turn (which is a
// common case for triggering a resize event), then the resize event would not
// work (to show the map), so we trigger the event in a timeout.
return new Promise<void>((resolve) => {
setTimeout(
() => { return this._mapsWrapper.triggerMapEvent('resize').then(() => resolve()); });
});
}
private _updatePosition(changes: {[propName: string]: SimpleChange}) {
if (changes['latitude'] == null && changes['longitude'] == null &&
changes['fitBounds'] == null) {
// no position update needed
return;
}
// we prefer fitBounds in changes
if (changes['fitBounds'] && this.fitBounds != null) {
this._fitBounds();
return;
}
if (typeof this.latitude !== 'number' || typeof this.longitude !== 'number') {
return;
}
let newCenter = {
lat: this.latitude,
lng: this.longitude,
};
if (this.usePanning) {
this._mapsWrapper.panTo(newCenter);
} else {
this._mapsWrapper.setCenter(newCenter);
}
}
private _fitBounds() {
if (this.usePanning) {
this._mapsWrapper.panToBounds(this.fitBounds);
return;
}
this._mapsWrapper.fitBounds(this.fitBounds);
}
private _handleMapCenterChange() {
const s = this._mapsWrapper.subscribeToMapEvent<void>('center_changed').subscribe(() => {
this._mapsWrapper.getCenter().then((center: LatLng) => {
this.latitude = center.lat();
this.longitude = center.lng();
this.centerChange.emit(<LatLngLiteral>{lat: this.latitude, lng: this.longitude});
});
});
this._observableSubscriptions.push(s);
}
private _handleBoundsChange() {
const s = this._mapsWrapper.subscribeToMapEvent<void>('bounds_changed').subscribe(() => {
this._mapsWrapper.getBounds().then(
(bounds: LatLngBounds) => { this.boundsChange.emit(bounds); });
});
this._observableSubscriptions.push(s);
}
private _handleMapZoomChange() {
const s = this._mapsWrapper.subscribeToMapEvent<void>('zoom_changed').subscribe(() => {
this._mapsWrapper.getZoom().then((z: number) => {
this.zoom = z;
this.zoomChange.emit(z);
});
});
this._observableSubscriptions.push(s);
}
private _handleIdleEvent() {
const s = this._mapsWrapper.subscribeToMapEvent<void>('idle').subscribe(
() => { this.idle.emit(void 0); });
this._observableSubscriptions.push(s);
}
private _handleMapMouseEvents() {
interface Emitter {
emit(value: any): void;
}
type Event = {name: string, emitter: Emitter};
const events: Event[] = [
{name: 'click', emitter: this.mapClick},
{name: 'rightclick', emitter: this.mapRightClick},
];
events.forEach((e: Event) => {
const s = this._mapsWrapper.subscribeToMapEvent<{latLng: LatLng}>(e.name).subscribe(
(event: {latLng: LatLng}) => {
const value = <MouseEvent>{coords: {lat: event.latLng.lat(), lng: event.latLng.lng()}};
e.emitter.emit(value);
});
this._observableSubscriptions.push(s);
});
}
}