UNPKG

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