// TODO frha: Borde flyttas till common/componentDirectives/maps // Angular // import { Component, ElementRef, OnInit, OnDestroy, Input, ChangeDetectionStrategy, OnChanges, AfterViewInit, SimpleChanges } from '@angular/core'; import { downgradeComponent } from '@angular/upgrade/static'; declare const angular: angular.IAngularStatic; // Component // import { FbGoogleMapsService } from './fb-google-maps.service'; import { IMapElement, ICoordinateSet, IUniquePolygon } from './fb-google-maps.interface'; // Todo IMapOptions // ConfirmBox // import { FbConfirmBox } from './fb-google-maps-popover.class'; // Other // import * as statics from '@fb/statics'; // Notering: denna kodstil är extrem, och bör inte repliceras utan att veta vad man gör @Component({ selector: 'fb-google-maps', templateUrl: './fb-google-maps.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class FbGoogleMapsComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { @Input() centerCoordinates: ICoordinateSet; @Input() markerCoordinates: ICoordinateSet; @Input() polygonsCoordinatesList: ICoordinateSet[][]; @Input() hideControls: boolean = false; @Input() disableDefaultUi: boolean = false; @Input() enableDraw: boolean = false; @Input() storlek: 'stor' | 'liten' = 'stor'; hasMapError: boolean = false; private mapElement: IMapElement; private elm: HTMLElement; private confirmBox: FbConfirmBox; // private polygonColor: IColorSet = { fill: '#0097ed', stroke: '#0097ed' }; private preventMapClick: boolean = false; // input? private selectedPolygon: IUniquePolygon; private createdPolygonList: IUniquePolygon[] = []; private currentId: number = 0; constructor( private readonly elementRef: ElementRef, private readonly fbGoogleMapsService: FbGoogleMapsService ) { } // Lifecykle hooks // ngOnInit(): void { if (!this.fbGoogleMapsService.isGoogleMapsApiLoaded()) { console.error('Hittar inte google api, verkar som maps.googleapis.com/maps/api inte har hämtats'); this.hasMapError = true; return; } if (this.enableDraw) { this.centerMapToPolygon(); } this.setupMapCoordinates(); this.setupMapElement(); if (this.fbGoogleMapsService.isValidCoordinatesAsLatLgn(this.markerCoordinates)) { this.setupMapMarkers(); } if (this.enableDraw) { this.setupDrawingManager(); this.drawPolygons(); } this.setupEventListeners(); } ngOnChanges(changes: SimpleChanges): void { const markerPositionChange: boolean = statics.isDefined(changes.markerCoordinates) && !changes.markerCoordinates.firstChange; if (markerPositionChange) { if (this.fbGoogleMapsService.isValidCoordinates(this.markerCoordinates)) { this.setupMapMarkers(); this.centerMapToMarker(); } else { this.removeMarkerFromMap(); } this.updateMap(); } } ngAfterViewInit(): void { this.attachMapElementToView(); this.updateMap(); } ngOnDestroy(): void { this.mapElement.isInUse = false; this.fbGoogleMapsService.destroyMapMarker(this.mapElement); this.destroyEventListeners(); this.destroyConfirmBox(); this.destroyPolygons(); document.body.appendChild(this.mapElement.element); } deleteSelectedPolygon(): void { if (statics.isDefined(this.selectedPolygon)) { this.selectedPolygon.polygon.setMap(null); delete this.createdPolygonList[this.selectedPolygon.id]; this.selectedPolygon = null; this.syncStored(); } } // Setups // private setupEventListeners(): void { google.maps.event.addListenerOnce(this.mapElement.mapsInstance, 'idle', () => { this.updateMap(); }); if (!this.enableDraw) { let clickTimeout: any = null; google.maps.event.addListener(this.mapElement.mapsInstance, 'click', (evt) => { if (!this.preventMapClick) { clickTimeout = setTimeout(() => { this.updateMarkerCoordinatesFromGoogleMaps(evt.latLng); this.updateMapMarkers(); clickTimeout = null; }, 300); } else { this.preventMapClick = false; } }); google.maps.event.addListener(this.mapElement.mapsInstance, 'dblclick', () => { // Avbryter uppköad förflyttning av markören ifall användaren dubbelklickar if (clickTimeout) { clearTimeout(clickTimeout); clickTimeout = null; } }); } if (this.enableDraw) { google.maps.event.addListener(this.mapElement.drawingManagerInstance, 'polygoncomplete', (event: google.maps.Polygon) => { this.mapElement.drawingManagerInstance.setDrawingMode(null); const newShape: IUniquePolygon = { id: this.uniqueId(), polygon: event }; this.createdPolygonList[newShape.id] = newShape; google.maps.event.addListener(newShape.polygon, 'click', () => { this.setSelection(newShape); }); google.maps.event.addListener(newShape.polygon.getPath(), 'insert_at', () => { this.syncStored(); this.setSelection(newShape); }); google.maps.event.addListener(newShape.polygon.getPath(), 'set_at', () => { this.syncStored(); this.setSelection(newShape); }); this.setSelection(newShape); this.syncStored(); }); google.maps.event.addListener(this.mapElement.mapsInstance, 'click', () => { this.clearSelection(); }); } } private setupMapElement(): void { this.elm = this.elementRef.nativeElement; this.mapElement = this.fbGoogleMapsService.getMapElement(this.centerCoordinates, this.storlek); if (statics.isUndefined(this.mapElement)) { this.hasMapError = true; return; } this.mapElement.element.className = 'fb-map fb-map-google'; // Todo move to service? } private attachMapElementToView(): void { this.elm.insertBefore(this.mapElement.element, this.elm.lastChild); } private setupMapCoordinates(): void { if (this.fbGoogleMapsService.isValidCoordinates(this.markerCoordinates) && !this.fbGoogleMapsService.isValidCoordinates(this.centerCoordinates)) { this.centerCoordinates = this.markerCoordinates; } else if (!this.fbGoogleMapsService.isValidCoordinates(this.markerCoordinates) && !this.fbGoogleMapsService.isValidCoordinates(this.centerCoordinates)) { throw new Error('No coordinates provided for map instance'); } } private setupMapMarkers(): void { this.fbGoogleMapsService.createMapMarker(this.mapElement, this.markerCoordinates); google.maps.event.addListener(this.mapElement.mapMarkerInstance, 'dragend', (evt) => this.updateMarkerCoordinatesFromGoogleMaps(evt.latLng) ); // Todo setup with all other event listeners } private setupDrawingManager(): void { this.fbGoogleMapsService.createDrawingManager(this.mapElement); } private drawPolygons(): void { const bounds: google.maps.LatLngBounds = new google.maps.LatLngBounds(); angular.forEach(this.polygonsCoordinatesList, (polygonEdgeList: ICoordinateSet[]) => { const polygonPath: google.maps.MVCArray = this.transformEdgeListToPolygonPath(polygonEdgeList); const layer: IUniquePolygon = { id: this.uniqueId(), polygon: new google.maps.Polygon({ fillColor: '#0097ed', strokeColor: '#0097ed', paths: polygonPath, map: this.mapElement.mapsInstance }) }; google.maps.event.addListener(layer.polygon, 'click', () => { this.setSelection(layer); }); google.maps.event.addListener(layer.polygon.getPath(), 'insert_at', () => { this.syncStored(); this.setSelection(layer); }); google.maps.event.addListener(layer.polygon.getPath(), 'set_at', () => { this.syncStored(); this.setSelection(layer); }); angular.forEach(polygonPath, (latLng: google.maps.LatLng) => { bounds.extend(latLng); }); this.createdPolygonList[layer.id] = layer; layer.polygon.setMap(this.mapElement.mapsInstance); }); if (!bounds.isEmpty()) { this.mapElement.mapsInstance.fitBounds(bounds); } } // Create // private createConfirmBox(): void { if (statics.isDefined(this.confirmBox)) { this.destroyConfirmBox(); // Todo better to move existing confirmbox } const cancelCallback: () => void = () => { if (this.fbGoogleMapsService.isValidCoordinates(this.markerCoordinates)) { this.regretMarkerMove(); } else { this.removeMarkerFromMap(); } }; const okCallback: () => void = () => { console.log('ok'); }; this.confirmBox = new FbConfirmBox( this.mapElement.mapsInstance, this.fbGoogleMapsService.getLatLng(this.markerCoordinates), okCallback, cancelCallback ); } // Update // private updateMap(): void { this.fbGoogleMapsService.onMapResize(this.mapElement); this.updateMapOptions(); } private updateMapOptions(): void { const options: any = { // Todo IMapOption centerCoordinates: this.centerCoordinates, disableDefaultUI: this.disableDefaultUi }; this.fbGoogleMapsService.setOptions(this.mapElement.mapsInstance, options); } private updateMapMarkers(): void { this.fbGoogleMapsService.updateMapMarker(this.mapElement, this.markerCoordinates); this.createConfirmBox(); } private updateMarkerCoordinatesFromGoogleMaps(latLng: google.maps.LatLng): void { this.markerCoordinates.Latitud.value = latLng.lat(); this.markerCoordinates.Longitud.value = latLng.lng(); } // Destroy // private destroyMapMarker(): void { this.fbGoogleMapsService.destroyMapMarker(this.mapElement); } private destroyEventListeners(): void { if (statics.isDefined(this.mapElement.mapsInstance)) { google.maps.event.clearInstanceListeners(this.mapElement.mapsInstance); } if (this.mapElement.mapMarkerInstance) { google.maps.event.clearInstanceListeners(this.mapElement.mapMarkerInstance); } if (this.mapElement.drawingManagerInstance) { google.maps.event.clearInstanceListeners(this.mapElement.drawingManagerInstance); } angular.forEach(this.createdPolygonList, (uniquePolygon: IUniquePolygon) => { google.maps.event.clearInstanceListeners(uniquePolygon.polygon); }); } private destroyConfirmBox(): void { if (statics.isDefined(this.confirmBox)) { this.confirmBox.setMap(null); } } private destroyPolygons(): void { if (statics.isDefined(this.selectedPolygon)) { this.selectedPolygon.polygon.setMap(null); this.selectedPolygon = null; } angular.forEach(this.createdPolygonList, (createdPolygon: IUniquePolygon) => { createdPolygon.polygon.setMap(null); createdPolygon = null; }); } // Helpers // private centerMapToMarker(): void { this.centerCoordinates = this.markerCoordinates; } private centerMapToPolygon(): void { if (this.polygonsCoordinatesList.length > 0 && this.polygonsCoordinatesList[0].length > 0) { // Todo Inputen till komponenten borde vara ICoordinateset men är inte alltid det, vilket är anledningen till den här röran const coordinates: any = this.polygonsCoordinatesList[0][0]; if (this.fbGoogleMapsService.isValidCoordinates(coordinates)) { this.centerCoordinates = coordinates; } else if (coordinates.X && coordinates.Y) { // TODO: Ibland är dessa ChangeTrack (exempelvis Bevakiningar) och ibland inte (exempelvis Närområden). En riktig soppa const latitud: fb.ChangeTrack = coordinates.X.value ? coordinates.X : new fb.ChangeTrack(coordinates.X); const longitud: fb.ChangeTrack = coordinates.X.value ? coordinates.Y : new fb.ChangeTrack(coordinates.Y); this.centerCoordinates = { Latitud: latitud, Longitud: longitud }; } } } private removeMarkerFromMap(): void { this.centerCoordinates.Latitud.value = this.centerCoordinates.Latitud.originalValue; this.centerCoordinates.Longitud.value = this.centerCoordinates.Longitud.originalValue; this.destroyMapMarker(); } private regretMarkerMove(): void { this.markerCoordinates.Longitud.setValue(this.markerCoordinates.Longitud.originalValue); this.markerCoordinates.Latitud.setValue(this.markerCoordinates.Latitud.originalValue); this.fbGoogleMapsService.updateMapMarker(this.mapElement, this.markerCoordinates); } private clearSelection(): void { if (statics.isDefined(this.selectedPolygon)) { this.selectedPolygon.polygon.setEditable(false); this.selectedPolygon = null; } } private setSelection(uniquePolygon: IUniquePolygon): void { this.clearSelection(); this.selectedPolygon = uniquePolygon; this.selectedPolygon.polygon.setEditable(true); } private syncStored(): void { this.polygonsCoordinatesList.splice(0, this.polygonsCoordinatesList.length); angular.forEach(this.createdPolygonList, (uniquePolygon: IUniquePolygon) => { const verticeList: google.maps.MVCArray = uniquePolygon.polygon.getPath(); const transformedPolygon: any[] = []; for (let i: number = 0; i < verticeList.getLength(); i++) { const xy: google.maps.LatLng = verticeList.getAt(i); transformedPolygon.push({ 'X': xy.lat(), 'Y': xy.lng() }); } this.polygonsCoordinatesList.push(transformedPolygon); }); } private uniqueId(): number { return ++this.currentId; } private transformEdgeListToPolygonPath(edgeList: any[]): google.maps.MVCArray { const polygonPath: google.maps.MVCArray = new google.maps.MVCArray(); angular.forEach(edgeList, (edge: any) => { const x: number = edge.X.value ? edge.X.value : edge.X; const y: number = edge.Y.value ? edge.Y.value : edge.Y; polygonPath.push(new google.maps.LatLng(x, y)); }); return polygonPath; } } // Angular downgrade //// angular.module('fasit') .directive('fbGoogleMaps', downgradeComponent({ component: FbGoogleMapsComponent, inputs: ['centerCoordinates', 'markerCoordinates', 'disableDefaultUi' ], outputs: [] }) as angular.IDirectiveFactory);