import { Label, Observable } from '@nativescript/core'; import { Position, Marker, Shape, Polyline, Polygon, Projection, Circle, Camera, MarkerEventData, ShapeEventData, VisibleRegion, CameraEventData, PositionEventData, Bounds, StyleBase, UISettings, IndoorBuilding, IndoorLevel, IndoorLevelActivatedEventData, BuildingFocusedEventData } from "./index"; import { Point } from "@nativescript/core/ui/core/view"; import { View, Template, KeyedTemplate, Image, LayoutBase, Property, Color, Builder, eachDescendant, ProxyViewContainer, StackLayout } from "@nativescript/core"; function onInfoWindowTemplatesChanged(mapView: GoogleMapsCommon) { let _infoWindowTemplates = new Array<KeyedTemplate>(); if (mapView.infoWindowTemplates && typeof mapView.infoWindowTemplates === "string") { _infoWindowTemplates = _infoWindowTemplates.concat(Builder.parseMultipleTemplates(mapView.infoWindowTemplates)); } else if (mapView.infoWindowTemplates) { _infoWindowTemplates = _infoWindowTemplates.concat(<KeyedTemplate[]>mapView.infoWindowTemplates); } mapView._infoWindowTemplates = _infoWindowTemplates; } function onMapPropertyChanged(mapView: GoogleMapsCommon) { if (!mapView.processingCameraEvent) mapView.updateCamera(); } function onSetMinZoomMaxZoom(mapView: GoogleMapsCommon) { mapView.setMinZoomMaxZoom(); } function onPaddingPropertyChanged(mapView: GoogleMapsCommon) { mapView.updatePadding(); } function paddingValueConverter(value: any) { if (!Array.isArray(value)) { value = String(value).split(','); } value = value.map((v) => parseInt(v, 10)); if (value.length >= 4) { return value; } else if (value.length === 3) { return [value[0], value[1], value[2], value[2]]; } else if (value.length === 2) { return [value[0], value[0], value[1], value[1]]; } else if (value.length === 1) { return [value[0], value[0], value[0], value[0]]; } else { return [0, 0, 0, 0]; } } function onDescendantsLoaded(view: View, callback: () => void) { if (!view) return callback(); let loadingCount = 1; let loadedCount = 0; const watchLoaded = (view, event) => { const onLoaded = () => { view.off(event, onLoaded); loadedCount++; if (view instanceof Image && view.isLoading) { loadingCount++; watchLoaded(view, 'isLoadingChange'); if (view.nativeView.onAttachedToWindow) { view.nativeView.onAttachedToWindow(); } } if (loadedCount === loadingCount) callback(); }; view.on(event, onLoaded); }; eachDescendant(view, (descendant) => { loadingCount++; watchLoaded(descendant, View.loadedEvent); return true; }); watchLoaded(view, View.loadedEvent); } export module knownTemplates { export const infoWindowTemplate = "infoWindowTemplate"; } export module knownMultiTemplates { export const infoWindowTemplates = "infoWindowTemplates"; } export function getColorHue(color: Color | string | number): number { if (typeof color === 'number') { while (color < 0) { color += 360; } return color % 360; } if (typeof color === 'string') color = new Color(color); if (!(color instanceof Color)) return color; let min, max, delta, hue; const r = Math.max(0, Math.min(1, color.r / 255)); const g = Math.max(0, Math.min(1, color.g / 255)); const b = Math.max(0, Math.min(1, color.b / 255)); min = Math.min(r, g, b); max = Math.max(r, g, b); delta = max - min; if (delta == 0) { // white, grey, black hue = 0; } else if (r == max) { hue = (g - b) / delta; // between yellow & magenta } else if (g == max) { hue = 2 + (b - r) / delta; // between cyan & yellow } else { hue = 4 + (r - g) / delta; // between magenta & cyan } hue = ((hue * 60) + 360) % 360; // degrees return hue; } export abstract class GoogleMapsCommon extends View { protected _gMap: any; protected _markers: Array<MarkerBase> = new Array<MarkerBase>(); protected _shapes: Array<ShapeBase> = new Array<ShapeBase>(); public _processingCameraEvent: boolean; public latitude: number; public longitude: number; public bearing: number; public zoom: number; public minZoom: number; public maxZoom: number; public tilt: number; public padding: number[]; public mapAnimationsEnabled: boolean; public infoWindowTemplate: string | Template; public infoWindowTemplates: string | Array<KeyedTemplate>; public _defaultInfoWindowTemplate: KeyedTemplate = { key: "", createView: () => { if (this.infoWindowTemplate) { return Builder.parse(this.infoWindowTemplate, this); } return undefined; } }; public _infoWindowTemplates = new Array<KeyedTemplate>(); public projection: Projection; public settings: UISettingsBase; public myLocationEnabled: boolean; public static mapReadyEvent: string = "mapReady"; public static markerSelectEvent: string = "markerSelect"; public static markerInfoWindowTappedEvent: string = "markerInfoWindowTapped"; public static markerInfoWindowClosedEvent: string = "markerInfoWindowClosed"; public static shapeSelectEvent: string = "shapeSelect"; public static markerBeginDraggingEvent: string = "markerBeginDragging"; public static markerEndDraggingEvent: string = "markerEndDragging"; public static markerDragEvent: string = "markerDrag"; public static coordinateTappedEvent: string = "coordinateTapped"; public static coordinateLongPressEvent: string = "coordinateLongPress"; public static cameraChangedEvent: string = "cameraChanged"; public static cameraMoveEvent: string = "cameraMove"; public static myLocationTappedEvent: string = "myLocationTapped"; public static indoorBuildingFocusedEvent: string = "indoorBuildingFocused"; public static indoorLevelActivatedEvent: string = "indoorLevelActivated"; public get gMap() { return this._gMap; } public get processingCameraEvent(): boolean { return this._processingCameraEvent; } public _getMarkerInfoWindowContent(marker: MarkerBase) { var view; if (marker && marker._infoWindowView) { view = marker._infoWindowView; return view; } const template: KeyedTemplate = this._getInfoWindowTemplate(marker); if (template) view = template.createView(); if (!view) return null; if (!(view instanceof LayoutBase) || view instanceof ProxyViewContainer) { let sp = new StackLayout(); sp.addChild(view); view = sp; } marker._infoWindowView = view; view.bindingContext = marker; onDescendantsLoaded(view, () => { marker.hideInfoWindow(); marker.showInfoWindow(); }); this._addView(view); view.onLoaded(); return view; } protected _unloadInfoWindowContent(marker: MarkerBase) { if (marker._infoWindowView) { marker._infoWindowView.onUnloaded(); marker._infoWindowView = null; } } public _getInfoWindowTemplate(marker: MarkerBase): KeyedTemplate { if(marker){ const templateKey = marker.infoWindowTemplate; for (let i = 0, length = this._infoWindowTemplates.length; i < length; i++) { if (this._infoWindowTemplates[i].key === templateKey) { return this._infoWindowTemplates[i]; } } } return this._defaultInfoWindowTemplate; } public abstract findMarker(callback: (marker: Marker) => boolean): Marker; public abstract addPolyline(shape: Polyline): void; public abstract addPolygon(shape: Polygon): void; public abstract addCircle(shape: Circle): void; public abstract removeShape(shape: Shape): void; public abstract findShape(callback: (shape: Shape) => boolean): Shape; public abstract setStyle(style: StyleBase): boolean; public abstract updateCamera(): void; public abstract setViewport(b: Bounds, p?: number): void; public abstract updatePadding(): void; public abstract setMinZoomMaxZoom(): void; public abstract addMarker(...markers: Marker[]): void; public abstract removeMarker(...markers: Marker[]): void; public abstract removeAllMarkers(): void; public abstract removeAllShapes(): void; public abstract clear(): void; public removeAllPolylines() { if(!this._shapes) return null; this._shapes.forEach(shape => { if (shape.shape === 'polyline') { this.removeShape(shape); } }); } public removeAllPolygons() { if(!this._shapes) return null; this._shapes.forEach(shape => { if (shape.shape === 'polygon') { this.removeShape(shape); } }); } public removeAllCircles() { if(!this._shapes) return null; this._shapes.forEach(shape => { if (shape.shape === 'circle') { this.removeShape(shape); } }); } notifyMapReady() { this.notify({ eventName: GoogleMapsCommon.mapReadyEvent, object: this, gMap: this.gMap }); } notifyMarkerEvent(eventName: any, marker: Marker) { let args: MarkerEventData = { eventName: eventName, object: this, marker: marker }; this.notify(args); } notifyShapeEvent(eventName: string, shape: Shape) { let args: ShapeEventData = { eventName: eventName, object: this, shape: shape }; this.notify(args); } notifyMarkerTapped(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerSelectEvent, marker); } notifyMarkerInfoWindowTapped(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerInfoWindowTappedEvent, marker); } notifyMarkerInfoWindowClosed(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerInfoWindowClosedEvent, marker); } notifyShapeTapped(shape: ShapeBase) { this.notifyShapeEvent(GoogleMapsCommon.shapeSelectEvent, shape); } notifyMarkerBeginDragging(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerBeginDraggingEvent, marker); } notifyMarkerEndDragging(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerEndDraggingEvent, marker); } notifyMarkerDrag(marker: MarkerBase) { this.notifyMarkerEvent(GoogleMapsCommon.markerDragEvent, marker); } notifyPositionEvent(eventName: string, position: Position) { let args: PositionEventData = { eventName: eventName, object: this, position: position }; this.notify(args); } notifyCameraEvent(eventName: string, camera: Camera) { let args: CameraEventData = { eventName: eventName, object: this, camera: camera }; this.notify(args); } notifyMyLocationTapped() { this.notify({ eventName: GoogleMapsCommon.myLocationTappedEvent, object: this }); } notifyBuildingFocusedEvent(indoorBuilding: IndoorBuilding) { let args: BuildingFocusedEventData = { eventName: GoogleMapsCommon.indoorBuildingFocusedEvent, object: this, indoorBuilding: indoorBuilding }; this.notify(args); } notifyIndoorLevelActivatedEvent(activateLevel: IndoorLevel) { let args: IndoorLevelActivatedEventData = { eventName: GoogleMapsCommon.indoorLevelActivatedEvent, object: this, activateLevel: activateLevel }; this.notify(args); } } export const infoWindowTemplateProperty = new Property<GoogleMapsCommon, string | Template>({ name: "infoWindowTemplate" }); infoWindowTemplateProperty.register(GoogleMapsCommon); export const infoWindowTemplatesProperty = new Property<GoogleMapsCommon, string | Array<KeyedTemplate>>({ name: "infoWindowTemplates", valueChanged: onInfoWindowTemplatesChanged }) infoWindowTemplatesProperty.register(GoogleMapsCommon); export const latitudeProperty = new Property<GoogleMapsCommon, number>({ name: 'latitude', defaultValue: 0, valueChanged: onMapPropertyChanged }); latitudeProperty.register(GoogleMapsCommon); export const longitudeProperty = new Property<GoogleMapsCommon, number>({ name: 'longitude', defaultValue: 0, valueChanged: onMapPropertyChanged }); longitudeProperty.register(GoogleMapsCommon); export const bearingProperty = new Property<GoogleMapsCommon, number>({ name: 'bearing', defaultValue: 0, valueChanged: onMapPropertyChanged }); bearingProperty.register(GoogleMapsCommon); export const zoomProperty = new Property<GoogleMapsCommon, number>({ name: 'zoom', defaultValue: 0, valueChanged: onMapPropertyChanged }); zoomProperty.register(GoogleMapsCommon); export const minZoomProperty = new Property<GoogleMapsCommon, number>({ name: 'minZoom', defaultValue: 0, valueChanged: onSetMinZoomMaxZoom }); minZoomProperty.register(GoogleMapsCommon); export const maxZoomProperty = new Property<GoogleMapsCommon, number>({ name: 'maxZoom', defaultValue: 22, valueChanged: onSetMinZoomMaxZoom }); maxZoomProperty.register(GoogleMapsCommon); export const tiltProperty = new Property<GoogleMapsCommon, number>({ name: 'tilt', defaultValue: 0, valueChanged: onMapPropertyChanged }); tiltProperty.register(GoogleMapsCommon); export const paddingProperty = new Property<GoogleMapsCommon, number[]>({ name: 'padding', valueChanged: onPaddingPropertyChanged, valueConverter: paddingValueConverter }); paddingProperty.register(GoogleMapsCommon); export const mapAnimationsEnabledProperty = new Property<GoogleMapsCommon, boolean>({ name: 'mapAnimationsEnabled', defaultValue: true }); mapAnimationsEnabledProperty.register(GoogleMapsCommon); export class UISettingsBase implements UISettings { compassEnabled: boolean; indoorLevelPickerEnabled: boolean; mapToolbarEnabled: boolean; myLocationButtonEnabled: boolean; rotateGesturesEnabled: boolean; scrollGesturesEnabled: boolean; tiltGesturesEnabled: boolean; zoomControlsEnabled: boolean; zoomGesturesEnabled: boolean; } export abstract class ProjectionBase implements Projection { public visibleRegion: VisibleRegion; public abstract fromScreenLocation(point: Point): Position; public abstract toScreenLocation(position: Position): Point; public ios: any; /* GMSProjection */ public android: any; } export class VisibleRegionBase implements VisibleRegion { public nearLeft: Position; public nearRight: Position; public farLeft: Position; public farRight: Position; public bounds: Bounds; } export class PositionBase implements Position { public latitude: number; public longitude: number; public ios: any; /* CLLocationCoordinate2D */ public android: any; } export class BoundsBase implements Bounds { public northeast: Position; public southwest: Position; public ios: any; /* GMSCoordinateBounds */ public android: any; } export abstract class MarkerBase implements Marker { public _infoWindowView: any; public position: Position; public rotation: number; public anchor: Array<number>; public title: string; public snippet: string; public color: Color | string | number; public icon: Image | string; public alpha: number; public flat: boolean; public draggable: boolean; public visible: boolean; public zIndex: number; public abstract showInfoWindow(): void; public abstract isInfoWindowShown(): boolean; public infoWindowTemplate: string; public abstract hideInfoWindow(): void; public userData: any; public _map: any; public ios: any; public android: any; } export class ShapeBase implements Shape { public shape: string; public visible: boolean; public zIndex: number; public userData: any; public _map: any; public ios: any; public android: any; public clickable: boolean; } export abstract class PolylineBase extends ShapeBase implements Polyline { public shape: string = 'polyline'; public _map: any; public _points: Array<PositionBase>; public width: number; public color: Color; public geodesic: boolean; addPoint(point: PositionBase): void { this._points.push(point); this.reloadPoints(); } addPoints(points: PositionBase[]): void { this._points = this._points.concat(points); this.reloadPoints(); } removePoint(point: PositionBase): void { var index = this._points.indexOf(point); if (index > -1) { this._points.splice(index, 1); this.reloadPoints(); } } removeAllPoints(): void { this._points.length = 0; this.reloadPoints(); } getPoints(): Array<PositionBase> { return this._points.slice(); } public abstract reloadPoints(): void; } export abstract class PolygonBase extends ShapeBase implements Polygon { public shape: string = 'polygon'; public _map: any; public _points: Array<PositionBase>; public _holes: Array<Array<PositionBase>>; public strokeWidth: number; public strokeColor: Color; public fillColor: Color; addPoint(point: PositionBase): void { this._points.push(point); this.reloadPoints(); } addPoints(points: PositionBase[]): void { this._points = this._points.concat(points); this.reloadPoints(); } removePoint(point: PositionBase): void { var index = this._points.indexOf(point); if (index > -1) { this._points.splice(index, 1); this.reloadPoints(); } } removeAllPoints(): void { this._points.length = 0; this.reloadPoints(); } getPoints(): Array<PositionBase> { return this._points.slice(); } addHole(hole: PositionBase[]): void { this._holes.push(hole); this.reloadHoles(); } addHoles(holes: PositionBase[][]): void { this._holes = this._holes.concat(holes); this.reloadHoles(); } removeHole(hole: PositionBase[]): void { var index = this._holes.indexOf(hole); if (index > -1) { this._holes.splice(index, 1); this.reloadHoles(); } } removeAllHoles(): void { this._holes.length = 0; this.reloadHoles(); } getHoles(): Array<Array<PositionBase>> { return this._holes.slice(); } public abstract reloadPoints(): void; public abstract reloadHoles(): void; } export class CircleBase extends ShapeBase implements Circle { public shape: string = 'circle'; public center: Position; public _map: any; public radius: number; public strokeWidth: number; public strokeColor: Color; public fillColor: Color; }