UNPKG

@agm/core

Version:

Angular components for Google Maps

1,453 lines (1,434 loc) 137 kB
import { __awaiter } from 'tslib'; import { Injectable, NgZone, ɵɵdefineInjectable, ɵɵinject, InjectionToken, Optional, Inject, LOCALE_ID, Directive, Input, EventEmitter, Output, Self, Component, ElementRef, PLATFORM_ID, ContentChildren, QueryList, forwardRef, NgModule } from '@angular/core'; import { Observable, BehaviorSubject, from, timer, ReplaySubject, bindCallback, of, throwError, fromEventPattern, merge, Subject } from 'rxjs'; import { flatMap, sample, switchMap, map, shareReplay, multicast, startWith, skip, distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { isPlatformServer } from '@angular/common'; class MapsAPILoader { } MapsAPILoader.decorators = [ { type: Injectable } ]; /** * Wrapper class that handles the communication with the Google Maps Javascript * API v3 */ class GoogleMapsAPIWrapper { constructor(_loader, _zone) { this._loader = _loader; this._zone = _zone; this._map = new Promise((resolve) => { this._mapResolver = resolve; }); } createMap(el, mapOptions) { return this._zone.runOutsideAngular(() => { return this._loader.load().then(() => { const map = new google.maps.Map(el, mapOptions); this._mapResolver(map); return; }); }); } setMapOptions(options) { return this._zone.runOutsideAngular(() => { this._map.then((m) => { m.setOptions(options); }); }); } /** * Creates a google map marker with the map context */ createMarker(options = {}, addToMap = true) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => { if (addToMap) { options.map = map; } return new google.maps.Marker(options); }); }); } createInfoWindow(options) { return this._zone.runOutsideAngular(() => { return this._map.then(() => new google.maps.InfoWindow(options)); }); } /** * Creates a google.map.Circle for the current map. */ createCircle(options) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => { options.map = map; return new google.maps.Circle(options); }); }); } /** * Creates a google.map.Rectangle for the current map. */ createRectangle(options) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => { options.map = map; return new google.maps.Rectangle(options); }); }); } createPolyline(options) { return this._zone.runOutsideAngular(() => { return this.getNativeMap().then((map) => { const line = new google.maps.Polyline(options); line.setMap(map); return line; }); }); } createPolygon(options) { return this._zone.runOutsideAngular(() => { return this.getNativeMap().then((map) => { const polygon = new google.maps.Polygon(options); polygon.setMap(map); return polygon; }); }); } /** * Creates a new google.map.Data layer for the current map */ createDataLayer(options) { return this._zone.runOutsideAngular(() => { return this._map.then(m => { const data = new google.maps.Data(options); data.setMap(m); return data; }); }); } /** * Creates a TransitLayer instance for a map * @returns a new transit layer object */ createTransitLayer() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => { const newLayer = new google.maps.TransitLayer(); newLayer.setMap(map); return newLayer; }); }); } /** * Creates a BicyclingLayer instance for a map * @returns a new bicycling layer object */ createBicyclingLayer() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => { const newLayer = new google.maps.BicyclingLayer(); newLayer.setMap(map); return newLayer; }); }); } /** * Determines if given coordinates are insite a Polygon path. */ containsLocation(latLng, polygon) { return this._map.then(() => google.maps.geometry.poly.containsLocation(latLng, polygon)); } subscribeToMapEvent(eventName) { return new Observable((observer) => { this._map.then(m => m.addListener(eventName, () => this._zone.run(() => observer.next(arguments[0])))); }); } clearInstanceListeners() { return this._zone.runOutsideAngular(() => { this._map.then((map) => { google.maps.event.clearInstanceListeners(map); }); }); } setCenter(latLng) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.setCenter(latLng)); }); } getZoom() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.getZoom()); }); } getBounds() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.getBounds()); }); } getMapTypeId() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.getMapTypeId()); }); } setZoom(zoom) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.setZoom(zoom)); }); } getCenter() { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.getCenter()); }); } panTo(latLng) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.panTo(latLng)); }); } panBy(x, y) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.panBy(x, y)); }); } fitBounds(latLng, padding) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.fitBounds(latLng, padding)); }); } panToBounds(latLng, padding) { return this._zone.runOutsideAngular(() => { return this._map.then((map) => map.panToBounds(latLng, padding)); }); } /** * Returns the native Google Maps Map instance. Be careful when using this instance directly. */ getNativeMap() { return this._map; } /** * Triggers the given event name on the map instance. */ triggerMapEvent(eventName) { return this._map.then((m) => google.maps.event.trigger(m, eventName)); } } GoogleMapsAPIWrapper.decorators = [ { type: Injectable } ]; GoogleMapsAPIWrapper.ctorParameters = () => [ { type: MapsAPILoader }, { type: NgZone } ]; class CircleManager { constructor(_apiWrapper, _zone) { this._apiWrapper = _apiWrapper; this._zone = _zone; this._circles = new Map(); } addCircle(circle) { this._apiWrapper.getNativeMap().then(() => this._circles.set(circle, this._apiWrapper.createCircle({ center: { lat: circle.latitude, lng: circle.longitude }, clickable: circle.clickable, draggable: circle.draggable, editable: circle.editable, fillColor: circle.fillColor, fillOpacity: circle.fillOpacity, radius: circle.radius, strokeColor: circle.strokeColor, strokeOpacity: circle.strokeOpacity, strokePosition: google.maps.StrokePosition[circle.strokePosition], strokeWeight: circle.strokeWeight, visible: circle.visible, zIndex: circle.zIndex, }))); } /** * Removes the given circle from the map. */ removeCircle(circle) { return this._circles.get(circle).then((c) => { c.setMap(null); this._circles.delete(circle); }); } setOptions(circle, options) { return __awaiter(this, void 0, void 0, function* () { return this._circles.get(circle).then((c) => { const actualParam = options.strokePosition; options.strokePosition = google.maps.StrokePosition[actualParam]; c.setOptions(options); }); }); } getBounds(circle) { return this._circles.get(circle).then((c) => c.getBounds()); } getCenter(circle) { return this._circles.get(circle).then((c) => c.getCenter()); } getRadius(circle) { return this._circles.get(circle).then((c) => c.getRadius()); } setCenter(circle) { return this._circles.get(circle).then(c => c.setCenter({ lat: circle.latitude, lng: circle.longitude })); } setEditable(circle) { return this._circles.get(circle).then(c => c.setEditable(circle.editable)); } setDraggable(circle) { return this._circles.get(circle).then(c => c.setDraggable(circle.draggable)); } setVisible(circle) { return this._circles.get(circle).then(c => c.setVisible(circle.visible)); } setRadius(circle) { return this._circles.get(circle).then(c => c.setRadius(circle.radius)); } getNativeCircle(circle) { return this._circles.get(circle); } createEventObservable(eventName, circle) { return new Observable((observer) => { let listener = null; this._circles.get(circle).then((c) => { listener = c.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); return () => { if (listener !== null) { listener.remove(); } }; }); } } CircleManager.decorators = [ { type: Injectable } ]; CircleManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; /** * Manages all Data Layers for a Google Map instance. */ class DataLayerManager { constructor(_wrapper, _zone) { this._wrapper = _wrapper; this._zone = _zone; this._layers = new Map(); } /** * Adds a new Data Layer to the map. */ addDataLayer(layer) { const newLayer = this._wrapper.createDataLayer({ style: layer.style, }) .then(d => { if (layer.geoJson) { // NOTE: accessing "features" on google.maps.Data is undocumented this.getDataFeatures(d, layer.geoJson).then(features => d.features = features); } return d; }); this._layers.set(layer, newLayer); } deleteDataLayer(layer) { this._layers.get(layer).then(l => { l.setMap(null); this._layers.delete(layer); }); } updateGeoJson(layer, geoJson) { this._layers.get(layer).then(l => { l.forEach(feature => { l.remove(feature); // NOTE: accessing "features" on google.maps.Data is undocumented const index = l.features.indexOf(feature, 0); if (index > -1) { l.features.splice(index, 1); } }); this.getDataFeatures(l, geoJson).then(features => l.features = features); }); } setDataOptions(layer, options) { this._layers.get(layer).then(l => { l.setControlPosition(options.controlPosition); l.setControls(options.controls); l.setDrawingMode(options.drawingMode); l.setStyle(options.style); }); } /** * Creates a Google Maps event listener for the given DataLayer as an Observable */ createEventObservable(eventName, layer) { return new Observable((observer) => { this._layers.get(layer).then((d) => { d.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); }); } /** * Extract features from a geoJson using google.maps Data Class * @param d : google.maps.Data class instance * @param geoJson : url or geojson object */ getDataFeatures(d, geoJson) { return new Promise((resolve, reject) => { if (typeof geoJson === 'object') { try { const features = d.addGeoJson(geoJson); resolve(features); } catch (e) { reject(e); } } else if (typeof geoJson === 'string') { d.loadGeoJson(geoJson, null, resolve); } else { reject(`Impossible to extract features from geoJson: wrong argument type`); } }); } } DataLayerManager.decorators = [ { type: Injectable } ]; DataLayerManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; /** * Class to implement when you what to be able to make it work with the auto fit bounds feature * of AGM. */ class FitBoundsAccessor { } /** * The FitBoundsService is responsible for computing the bounds of the a single map. */ class FitBoundsService { constructor(loader) { this._boundsChangeSampleTime$ = new BehaviorSubject(200); this._includeInBounds$ = new BehaviorSubject(new Map()); this.bounds$ = from(loader.load()).pipe(flatMap(() => this._includeInBounds$), sample(this._boundsChangeSampleTime$.pipe(switchMap(time => timer(0, time)))), map(includeInBounds => this._generateBounds(includeInBounds)), shareReplay(1)); } _generateBounds(includeInBounds) { const bounds = new google.maps.LatLngBounds(); includeInBounds.forEach(b => bounds.extend(b)); return bounds; } addToBounds(latLng) { const id = this._createIdentifier(latLng); if (this._includeInBounds$.value.has(id)) { return; } const boundsMap = this._includeInBounds$.value; boundsMap.set(id, latLng); this._includeInBounds$.next(boundsMap); } removeFromBounds(latLng) { const boundsMap = this._includeInBounds$.value; boundsMap.delete(this._createIdentifier(latLng)); this._includeInBounds$.next(boundsMap); } changeFitBoundsChangeSampleTime(timeMs) { this._boundsChangeSampleTime$.next(timeMs); } getBounds$() { return this.bounds$; } _createIdentifier(latLng) { return `${latLng.lat}+${latLng.lng}`; } } FitBoundsService.decorators = [ { type: Injectable } ]; FitBoundsService.ctorParameters = () => [ { type: MapsAPILoader } ]; class AgmGeocoder { constructor(loader) { const connectableGeocoder$ = new Observable(subscriber => { loader.load().then(() => subscriber.next()); }) .pipe(map(() => this._createGeocoder()), multicast(new ReplaySubject(1))); connectableGeocoder$.connect(); // ignore the subscription // since we will remain subscribed till application exits this.geocoder$ = connectableGeocoder$; } geocode(request) { return this.geocoder$.pipe(switchMap((geocoder) => this._getGoogleResults(geocoder, request))); } _getGoogleResults(geocoder, request) { const geocodeObservable = bindCallback(geocoder.geocode); return geocodeObservable(request).pipe(switchMap(([results, status]) => { if (status === google.maps.GeocoderStatus.OK) { return of(results); } return throwError(status); })); } _createGeocoder() { return new google.maps.Geocoder(); } } AgmGeocoder.ɵprov = ɵɵdefineInjectable({ factory: function AgmGeocoder_Factory() { return new AgmGeocoder(ɵɵinject(MapsAPILoader)); }, token: AgmGeocoder, providedIn: "root" }); AgmGeocoder.decorators = [ { type: Injectable, args: [{ providedIn: 'root' },] } ]; AgmGeocoder.ctorParameters = () => [ { type: MapsAPILoader } ]; class WindowRef { getNativeWindow() { return window; } } class DocumentRef { getNativeDocument() { return document; } } const BROWSER_GLOBALS_PROVIDERS = [WindowRef, DocumentRef]; var GoogleMapsScriptProtocol; (function (GoogleMapsScriptProtocol) { GoogleMapsScriptProtocol[GoogleMapsScriptProtocol["HTTP"] = 1] = "HTTP"; GoogleMapsScriptProtocol[GoogleMapsScriptProtocol["HTTPS"] = 2] = "HTTPS"; GoogleMapsScriptProtocol[GoogleMapsScriptProtocol["AUTO"] = 3] = "AUTO"; })(GoogleMapsScriptProtocol || (GoogleMapsScriptProtocol = {})); /** * Token for the config of the LazyMapsAPILoader. Please provide an object of type {@link * LazyMapsAPILoaderConfig}. */ const LAZY_MAPS_API_CONFIG = new InjectionToken('angular-google-maps LAZY_MAPS_API_CONFIG'); class LazyMapsAPILoader extends MapsAPILoader { constructor(config = null, w, d, localeId) { super(); this.localeId = localeId; this._SCRIPT_ID = 'agmGoogleMapsApiScript'; this.callbackName = `agmLazyMapsAPILoader`; this._config = config || {}; this._windowRef = w; this._documentRef = d; } load() { const window = this._windowRef.getNativeWindow(); if (window.google && window.google.maps) { // Google maps already loaded on the page. return Promise.resolve(); } if (this._scriptLoadingPromise) { return this._scriptLoadingPromise; } // this can happen in HMR situations or Stackblitz.io editors. const scriptOnPage = this._documentRef.getNativeDocument().getElementById(this._SCRIPT_ID); if (scriptOnPage) { this._assignScriptLoadingPromise(scriptOnPage); return this._scriptLoadingPromise; } const script = this._documentRef.getNativeDocument().createElement('script'); script.type = 'text/javascript'; script.async = true; script.defer = true; script.id = this._SCRIPT_ID; script.src = this._getScriptSrc(this.callbackName); this._assignScriptLoadingPromise(script); this._documentRef.getNativeDocument().body.appendChild(script); return this._scriptLoadingPromise; } _assignScriptLoadingPromise(scriptElem) { this._scriptLoadingPromise = new Promise((resolve, reject) => { this._windowRef.getNativeWindow()[this.callbackName] = () => { resolve(); }; scriptElem.onerror = (error) => { reject(error); }; }); } _getScriptSrc(callbackName) { const protocolType = (this._config && this._config.protocol) || GoogleMapsScriptProtocol.HTTPS; let protocol; switch (protocolType) { case GoogleMapsScriptProtocol.AUTO: protocol = ''; break; case GoogleMapsScriptProtocol.HTTP: protocol = 'http:'; break; case GoogleMapsScriptProtocol.HTTPS: protocol = 'https:'; break; } const hostAndPath = this._config.hostAndPath || 'maps.googleapis.com/maps/api/js'; const queryParams = { v: this._config.apiVersion || 'quarterly', callback: callbackName, key: this._config.apiKey, client: this._config.clientId, channel: this._config.channel, libraries: this._config.libraries, region: this._config.region, language: this._config.language || (this.localeId !== 'en-US' ? this.localeId : null), }; const params = Object.keys(queryParams) .filter((k) => queryParams[k] != null) .filter((k) => { // remove empty arrays return !Array.isArray(queryParams[k]) || (Array.isArray(queryParams[k]) && queryParams[k].length > 0); }) .map((k) => { // join arrays as comma seperated strings const i = queryParams[k]; if (Array.isArray(i)) { return { key: k, value: i.join(',') }; } return { key: k, value: queryParams[k] }; }) .map((entry) => { return `${entry.key}=${entry.value}`; }) .join('&'); return `${protocol}//${hostAndPath}?${params}`; } } LazyMapsAPILoader.decorators = [ { type: Injectable } ]; LazyMapsAPILoader.ctorParameters = () => [ { type: undefined, decorators: [{ type: Optional }, { type: Inject, args: [LAZY_MAPS_API_CONFIG,] }] }, { type: WindowRef }, { type: DocumentRef }, { type: String, decorators: [{ type: Inject, args: [LOCALE_ID,] }] } ]; class MarkerManager { constructor(_mapsWrapper, _zone) { this._mapsWrapper = _mapsWrapper; this._zone = _zone; this._markers = new Map(); } convertAnimation(uiAnim) { return __awaiter(this, void 0, void 0, function* () { if (uiAnim === null) { return null; } else { return this._mapsWrapper.getNativeMap().then(() => google.maps.Animation[uiAnim]); } }); } deleteMarker(markerDirective) { const markerPromise = this._markers.get(markerDirective); if (markerPromise == null) { // marker already deleted return Promise.resolve(); } return markerPromise.then((marker) => { return this._zone.run(() => { marker.setMap(null); this._markers.delete(markerDirective); }); }); } updateMarkerPosition(marker) { return this._markers.get(marker).then((m) => m.setPosition({ lat: marker.latitude, lng: marker.longitude })); } updateTitle(marker) { return this._markers.get(marker).then((m) => m.setTitle(marker.title)); } updateLabel(marker) { return this._markers.get(marker).then((m) => { m.setLabel(marker.label); }); } updateDraggable(marker) { return this._markers.get(marker).then((m) => m.setDraggable(marker.draggable)); } updateIcon(marker) { return this._markers.get(marker).then((m) => m.setIcon(marker.iconUrl)); } updateOpacity(marker) { return this._markers.get(marker).then((m) => m.setOpacity(marker.opacity)); } updateVisible(marker) { return this._markers.get(marker).then((m) => m.setVisible(marker.visible)); } updateZIndex(marker) { return this._markers.get(marker).then((m) => m.setZIndex(marker.zIndex)); } updateClickable(marker) { return this._markers.get(marker).then((m) => m.setClickable(marker.clickable)); } updateAnimation(marker) { return __awaiter(this, void 0, void 0, function* () { const m = yield this._markers.get(marker); m.setAnimation(yield this.convertAnimation(marker.animation)); }); } addMarker(marker) { const markerPromise = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { return 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, animation: yield this.convertAnimation(marker.animation), }).then(resolve); })); this._markers.set(marker, markerPromise); } getNativeMarker(marker) { return this._markers.get(marker); } createEventObservable(eventName, marker) { return new Observable(observer => { this._markers.get(marker).then(m => m.addListener(eventName, e => this._zone.run(() => observer.next(e)))); }); } } MarkerManager.decorators = [ { type: Injectable } ]; MarkerManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; class InfoWindowManager { constructor(_mapsWrapper, _zone, _markerManager) { this._mapsWrapper = _mapsWrapper; this._zone = _zone; this._markerManager = _markerManager; this._infoWindows = new Map(); } deleteInfoWindow(infoWindow) { const iWindow = this._infoWindows.get(infoWindow); if (iWindow == null) { // info window already deleted return Promise.resolve(); } return iWindow.then((i) => { return this._zone.run(() => { i.close(); this._infoWindows.delete(infoWindow); }); }); } setPosition(infoWindow) { return this._infoWindows.get(infoWindow).then((i) => i.setPosition({ lat: infoWindow.latitude, lng: infoWindow.longitude, })); } setZIndex(infoWindow) { return this._infoWindows.get(infoWindow) .then((i) => i.setZIndex(infoWindow.zIndex)); } open(infoWindow) { return this._infoWindows.get(infoWindow).then((w) => { if (infoWindow.hostMarker != null) { return this._markerManager.getNativeMarker(infoWindow.hostMarker).then((marker) => { return this._mapsWrapper.getNativeMap().then((map) => w.open(map, marker)); }); } return this._mapsWrapper.getNativeMap().then((map) => w.open(map)); }); } close(infoWindow) { return this._infoWindows.get(infoWindow).then((w) => w.close()); } setOptions(infoWindow, options) { return this._infoWindows.get(infoWindow).then((i) => i.setOptions(options)); } addInfoWindow(infoWindow) { const options = { content: infoWindow.content, maxWidth: infoWindow.maxWidth, zIndex: infoWindow.zIndex, disableAutoPan: infoWindow.disableAutoPan, }; if (typeof infoWindow.latitude === 'number' && typeof infoWindow.longitude === 'number') { options.position = { lat: infoWindow.latitude, lng: infoWindow.longitude }; } const infoWindowPromise = this._mapsWrapper.createInfoWindow(options); this._infoWindows.set(infoWindow, infoWindowPromise); } /** * Creates a Google Maps event listener for the given InfoWindow as an Observable */ createEventObservable(eventName, infoWindow) { return new Observable((observer) => { this._infoWindows.get(infoWindow).then((i) => { i.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); }); } } InfoWindowManager.decorators = [ { type: Injectable } ]; InfoWindowManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone }, { type: MarkerManager } ]; /** * Manages all KML Layers for a Google Map instance. */ class KmlLayerManager { constructor(_wrapper, _zone) { this._wrapper = _wrapper; this._zone = _zone; this._layers = new Map(); } /** * Adds a new KML Layer to the map. */ addKmlLayer(layer) { const newLayer = this._wrapper.getNativeMap().then(m => { return new google.maps.KmlLayer({ clickable: layer.clickable, map: m, preserveViewport: layer.preserveViewport, screenOverlays: layer.screenOverlays, suppressInfoWindows: layer.suppressInfoWindows, url: layer.url, zIndex: layer.zIndex, }); }); this._layers.set(layer, newLayer); } setOptions(layer, options) { this._layers.get(layer).then(l => l.setOptions(options)); } deleteKmlLayer(layer) { this._layers.get(layer).then(l => { l.setMap(null); this._layers.delete(layer); }); } /** * Creates a Google Maps event listener for the given KmlLayer as an Observable */ createEventObservable(eventName, layer) { return new Observable((observer) => { this._layers.get(layer).then((m) => { m.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); }); } } KmlLayerManager.decorators = [ { type: Injectable } ]; KmlLayerManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; /** * This class manages Transit and Bicycling Layers for a Google Map instance. */ class LayerManager { constructor(_wrapper) { this._wrapper = _wrapper; this._layers = new Map(); } /** * Adds a transit layer to a map instance. * @param layer - a TransitLayer object * @param _options - TransitLayerOptions options * @returns void */ addTransitLayer(layer) { const newLayer = this._wrapper.createTransitLayer(); this._layers.set(layer, newLayer); } /** * Adds a bicycling layer to a map instance. * @param layer - a bicycling layer object * @param _options - BicyclingLayer options * @returns void */ addBicyclingLayer(layer) { const newLayer = this._wrapper.createBicyclingLayer(); this._layers.set(layer, newLayer); } /** * Deletes a map layer * @param layer - the layer to delete */ deleteLayer(layer) { return this._layers.get(layer).then(currentLayer => { currentLayer.setMap(null); this._layers.delete(layer); }); } } LayerManager.decorators = [ { type: Injectable } ]; LayerManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper } ]; /** * When using the NoOpMapsAPILoader, the Google Maps API must be added to the page via a `<script>` * Tag. * It's important that the Google Maps API script gets loaded first on the page. */ class NoOpMapsAPILoader { load() { if (!window.google || !window.google.maps) { throw new Error('Google Maps API not loaded on page. Make sure window.google.maps is available!'); } return Promise.resolve(); } } function createMVCEventObservable(array) { const eventNames = ['insert_at', 'remove_at', 'set_at']; return fromEventPattern(handler => eventNames.map(eventName => array.addListener(eventName, (index, previous) => handler.apply(array, [{ newArr: array.getArray(), eventName, index, previous }]))), (_handler, evListeners) => evListeners.forEach(evListener => evListener.remove())); } class MvcArrayMock { constructor() { this.vals = []; this.listeners = { remove_at: [], insert_at: [], set_at: [], }; } clear() { for (let i = this.vals.length - 1; i >= 0; i--) { this.removeAt(i); } } getArray() { return [...this.vals]; } getAt(i) { return this.vals[i]; } getLength() { return this.vals.length; } insertAt(i, elem) { this.vals.splice(i, 0, elem); this.listeners.insert_at.forEach(listener => listener(i)); } pop() { const deleted = this.vals.pop(); this.listeners.remove_at.forEach(listener => listener(this.vals.length, deleted)); return deleted; } push(elem) { this.vals.push(elem); this.listeners.insert_at.forEach(listener => listener(this.vals.length - 1)); return this.vals.length; } removeAt(i) { const deleted = this.vals.splice(i, 1)[0]; this.listeners.remove_at.forEach(listener => listener(i, deleted)); return deleted; } setAt(i, elem) { const deleted = this.vals[i]; this.vals[i] = elem; this.listeners.set_at.forEach(listener => listener(i, deleted)); } forEach(callback) { this.vals.forEach(callback); } addListener(eventName, handler) { const listenerArr = this.listeners[eventName]; listenerArr.push(handler); return { remove: () => { listenerArr.splice(listenerArr.indexOf(handler), 1); }, }; } bindTo() { throw new Error('Not implemented'); } changed() { throw new Error('Not implemented'); } get() { throw new Error('Not implemented'); } notify() { throw new Error('Not implemented'); } set() { throw new Error('Not implemented'); } setValues() { throw new Error('Not implemented'); } unbind() { throw new Error('Not implemented'); } unbindAll() { throw new Error('Not implemented'); } } class PolygonManager { constructor(_mapsWrapper, _zone) { this._mapsWrapper = _mapsWrapper; this._zone = _zone; this._polygons = new Map(); } addPolygon(path) { const polygonPromise = this._mapsWrapper.createPolygon({ clickable: path.clickable, draggable: path.draggable, editable: path.editable, fillColor: path.fillColor, fillOpacity: path.fillOpacity, geodesic: path.geodesic, paths: path.paths, strokeColor: path.strokeColor, strokeOpacity: path.strokeOpacity, strokeWeight: path.strokeWeight, visible: path.visible, zIndex: path.zIndex, }); this._polygons.set(path, polygonPromise); } updatePolygon(polygon) { const m = this._polygons.get(polygon); if (m == null) { return Promise.resolve(); } return m.then((l) => this._zone.run(() => { l.setPaths(polygon.paths); })); } setPolygonOptions(path, options) { return this._polygons.get(path).then((l) => { l.setOptions(options); }); } deletePolygon(paths) { const m = this._polygons.get(paths); if (m == null) { return Promise.resolve(); } return m.then((l) => { return this._zone.run(() => { l.setMap(null); this._polygons.delete(paths); }); }); } getPath(polygonDirective) { return this._polygons.get(polygonDirective) .then((polygon) => polygon.getPath().getArray()); } getPaths(polygonDirective) { return this._polygons.get(polygonDirective) .then((polygon) => polygon.getPaths().getArray().map((p) => p.getArray())); } createEventObservable(eventName, path) { return new Observable((observer) => { this._polygons.get(path).then((l) => { l.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); }); } createPathEventObservable(agmPolygon) { return __awaiter(this, void 0, void 0, function* () { const polygon = yield this._polygons.get(agmPolygon); const paths = polygon.getPaths(); const pathsChanges$ = createMVCEventObservable(paths); return pathsChanges$.pipe(startWith({ newArr: paths.getArray() }), // in order to subscribe to them all switchMap(parentMVEvent => merge(... // rest parameter parentMVEvent.newArr.map((chMVC, index) => createMVCEventObservable(chMVC) .pipe(map(chMVCEvent => ({ parentMVEvent, chMVCEvent, pathIndex: index }))))) .pipe(// start the merged ob with an event signinifing change to parent startWith({ parentMVEvent, chMVCEvent: null, pathIndex: null }))), skip(1), // skip the manually added event map(({ parentMVEvent, chMVCEvent, pathIndex }) => { let retVal; if (!chMVCEvent) { retVal = { newArr: parentMVEvent.newArr.map(subArr => subArr.getArray().map(latLng => latLng.toJSON())), eventName: parentMVEvent.eventName, index: parentMVEvent.index, }; if (parentMVEvent.previous) { retVal.previous = parentMVEvent.previous.getArray(); } } else { retVal = { newArr: parentMVEvent.newArr.map(subArr => subArr.getArray().map(latLng => latLng.toJSON())), pathIndex, eventName: chMVCEvent.eventName, index: chMVCEvent.index, }; if (chMVCEvent.previous) { retVal.previous = chMVCEvent.previous; } } return retVal; })); }); } } PolygonManager.decorators = [ { type: Injectable } ]; PolygonManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; class PolylineManager { constructor(_mapsWrapper, _zone) { this._mapsWrapper = _mapsWrapper; this._zone = _zone; this._polylines = new Map(); } static _convertPoints(line) { const path = line._getPoints().map((point) => { return { lat: point.latitude, lng: point.longitude }; }); return path; } static _convertPath(path) { const symbolPath = google.maps.SymbolPath[path]; if (typeof symbolPath === 'number') { return symbolPath; } else { return path; } } static _convertIcons(line) { const icons = line._getIcons().map(agmIcon => ({ fixedRotation: agmIcon.fixedRotation, offset: agmIcon.offset, repeat: agmIcon.repeat, icon: { anchor: new google.maps.Point(agmIcon.anchorX, agmIcon.anchorY), fillColor: agmIcon.fillColor, fillOpacity: agmIcon.fillOpacity, path: PolylineManager._convertPath(agmIcon.path), rotation: agmIcon.rotation, scale: agmIcon.scale, strokeColor: agmIcon.strokeColor, strokeOpacity: agmIcon.strokeOpacity, strokeWeight: agmIcon.strokeWeight, }, })); // prune undefineds; icons.forEach(icon => { Object.entries(icon).forEach(([key, val]) => { if (typeof val === 'undefined') { delete icon[key]; } }); if (typeof icon.icon.anchor.x === 'undefined' || typeof icon.icon.anchor.y === 'undefined') { delete icon.icon.anchor; } }); return icons; } addPolyline(line) { const polylinePromise = this._mapsWrapper.getNativeMap() .then(() => [PolylineManager._convertPoints(line), PolylineManager._convertIcons(line)]) .then(([path, icons]) => this._mapsWrapper.createPolyline({ clickable: line.clickable, draggable: line.draggable, editable: line.editable, geodesic: line.geodesic, strokeColor: line.strokeColor, strokeOpacity: line.strokeOpacity, strokeWeight: line.strokeWeight, visible: line.visible, zIndex: line.zIndex, path, icons, })); this._polylines.set(line, polylinePromise); } updatePolylinePoints(line) { const path = PolylineManager._convertPoints(line); const m = this._polylines.get(line); if (m == null) { return Promise.resolve(); } return m.then((l) => this._zone.run(() => l.setPath(path))); } updateIconSequences(line) { return __awaiter(this, void 0, void 0, function* () { yield this._mapsWrapper.getNativeMap(); const icons = PolylineManager._convertIcons(line); const m = this._polylines.get(line); if (m == null) { return; } return m.then(l => this._zone.run(() => l.setOptions({ icons }))); }); } setPolylineOptions(line, options) { return this._polylines.get(line).then((l) => { l.setOptions(options); }); } deletePolyline(line) { const m = this._polylines.get(line); if (m == null) { return Promise.resolve(); } return m.then((l) => { return this._zone.run(() => { l.setMap(null); this._polylines.delete(line); }); }); } getMVCPath(agmPolyline) { return __awaiter(this, void 0, void 0, function* () { const polyline = yield this._polylines.get(agmPolyline); return polyline.getPath(); }); } getPath(agmPolyline) { return __awaiter(this, void 0, void 0, function* () { return (yield this.getMVCPath(agmPolyline)).getArray(); }); } createEventObservable(eventName, line) { return new Observable((observer) => { this._polylines.get(line).then((l) => { l.addListener(eventName, (e) => this._zone.run(() => observer.next(e))); }); }); } createPathEventObservable(line) { return __awaiter(this, void 0, void 0, function* () { const mvcPath = yield this.getMVCPath(line); return createMVCEventObservable(mvcPath); }); } } PolylineManager.decorators = [ { type: Injectable } ]; PolylineManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; class RectangleManager { constructor(_apiWrapper, _zone) { this._apiWrapper = _apiWrapper; this._zone = _zone; this._rectangles = new Map(); } addRectangle(rectangle) { this._apiWrapper.getNativeMap().then(() => this._rectangles.set(rectangle, this._apiWrapper.createRectangle({ bounds: { north: rectangle.north, east: rectangle.east, south: rectangle.south, west: rectangle.west, }, clickable: rectangle.clickable, draggable: rectangle.draggable, editable: rectangle.editable, fillColor: rectangle.fillColor, fillOpacity: rectangle.fillOpacity, strokeColor: rectangle.strokeColor, strokeOpacity: rectangle.strokeOpacity, strokePosition: google.maps.StrokePosition[rectangle.strokePosition], strokeWeight: rectangle.strokeWeight, visible: rectangle.visible, zIndex: rectangle.zIndex, }))); } /** * Removes the given rectangle from the map. */ removeRectangle(rectangle) { return this._rectangles.get(rectangle).then((r) => { r.setMap(null); this._rectangles.delete(rectangle); }); } setOptions(rectangle, options) { return this._rectangles.get(rectangle).then((r) => { const actualStrokePosition = options.strokePosition; options.strokePosition = google.maps.StrokePosition[actualStrokePosition]; r.setOptions(options); }); } getBounds(rectangle) { return this._rectangles.get(rectangle).then((r) => r.getBounds()); } setBounds(rectangle) { return this._rectangles.get(rectangle).then((r) => { return r.setBounds({ north: rectangle.north, east: rectangle.east, south: rectangle.south, west: rectangle.west, }); }); } setEditable(rectangle) { return this._rectangles.get(rectangle).then((r) => { return r.setEditable(rectangle.editable); }); } setDraggable(rectangle) { return this._rectangles.get(rectangle).then((r) => { return r.setDraggable(rectangle.draggable); }); } setVisible(rectangle) { return this._rectangles.get(rectangle).then((r) => { return r.setVisible(rectangle.visible); }); } createEventObservable(eventName, rectangle) { return new Observable((subsrciber) => { let listener = null; this._rectangles.get(rectangle).then((r) => { listener = r.addListener(eventName, (e) => this._zone.run(() => subsrciber.next(e))); }); return () => { if (listener !== null) { listener.remove(); } }; }); } } RectangleManager.decorators = [ { type: Injectable } ]; RectangleManager.ctorParameters = () => [ { type: GoogleMapsAPIWrapper }, { type: NgZone } ]; let layerId = 0; /* * This directive adds a bicycling layer to a google map instance * <agm-bicycling-layer [visible]="true|false"> <agm-bicycling-layer> * */ class AgmBicyclingLayer { constructor(_manager) { this._manager = _manager; this._addedToManager = false; this._id = (layerId++).toString(); /** * Hide/show bicycling layer */ this.visible = true; } ngOnInit() { if (this._addedToManager) { return; } this._manager.addBicyclingLayer(this); this._addedToManager = true; } /** @internal */ id() { return this._id; } /** @internal */ toString() { return `AgmBicyclingLayer-${this._id.toString()}`; } /** @internal */ ngOnDestroy() { this._manager.deleteLayer(this); } } AgmBicyclingLayer.decorators = [ { type: Directive, args: [{ selector: 'agm-bicycling-layer', },] } ]; AgmBicyclingLayer.ctorParameters = () => [ { type: LayerManager } ]; AgmBicyclingLayer.propDecorators = { visible: [{ type: Input }] }; class AgmCircle { constructor(_manager) { this._manager = _manager; /** * Indicates whether this Circle handles mouse events. Defaults to true. */ this.clickable = true; /** * If set to true, the user can drag this circle over the map. Defaults to false. */ // tslint:disable-next-line:no-input-rename this.draggable = false; /** * If set to true, the user can edit this circle by dragging the control points shown at * the center and around the circumference of the circle. Defaults to false. */ this.editable = false; /** * The radius in meters on the Earth's surface. */ this.radius = 0; /** * The stroke position. Defaults to CENTER. * This property is not supported on Internet Explorer 8 and earlier. */ this.strokePosition = 'CENTER'; /** * The stroke width in pixels. */ this.strokeWeight = 0; /** * Whether this circle is visible on the map. Defaults to true. */ this.visible = true; /** * This event is fired when the circle's center is changed. */ this.centerChange = new EventEmitter(); /** * This event emitter gets emitted when the user clicks on the circle. */ this.circleClick = new EventEmitter(); /** * This event emitter gets emitted when the user clicks on the circle. */ this.circleDblClick = new EventEmitter(); /** * This event is repeatedly fired while the user drags the circle. */ // tslint:disable-next-line: no-output-native this.drag = new EventEmitter(); /** * This event is fired when the user stops dragging the circle. */ this.dragEnd = new EventEmitter(); /** * This event is fired when the user starts dragging the circle. */ this.dragStart = new EventEmitter(); /** * This event is fired when the DOM mousedown event is fired on the circle. */ this.mouseDown = new EventEmitter(); /** * This event is fired when the DOM mousemove event is fired on the circle. */ this.mouseMove = new EventEmitter(); /** * This event is fired on circle mouseout. */ this.mouseOut = new EventEmitter(); /** * This event is fired on circle mouseover. */ this.mouseOver = new EventEmitter(); /** * This event is fired when the DOM mouseup event is fired on the circle. */ this.mouseUp = new EventEmitter(); /** * This event is fired when the circle's radius is changed. */ this.radiusChange = new EventEmitter(); /** * This event is fired when the circle is right-clicked on. */ this.rightClick = new EventEmitter(); this._circleAddedToManager = false; this._eventSubscriptions = []; } /** @internal */ ngOnInit() { this._manager.addCircle(this); this._circleAddedToManager = true; this._registerEventListeners(); } /** @internal */ ngOnChanges(changes) { if (!this._circleAddedToManager)