blob: 8e30601e79fd1b4db2b50b0f8e6e950263455b45 [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
********************************************************************************/
import {AfterViewInit, Directive, ElementRef, forwardRef, Inject, Input, NgZone, OnDestroy, OnInit, Optional, Output} from "@angular/core";
import {InvalidateSizeOptions, LatLngLiteral, LeafletMouseEvent, Map, PopupEvent, TileLayer} from "leaflet";
import {defer, Observable} from "rxjs";
import {debounceTime, delay, map, takeUntil, throttleTime} from "rxjs/operators";
import {runInZone} from "../../../../util/rxjs";
import {ILeafletConfiguration, LEAFLET_CONFIGURATION_TOKEN, LEAFLET_RESIZE_TOKEN} from "../../leaflet-configuration.token";
import {latLngZoomToString} from "../../util";
import {LeafletHandler} from "./LeafletHandler";
export interface ILeafletBounds {
northWest: LatLngLiteral;
northEast: LatLngLiteral;
southEast: LatLngLiteral;
southWest: LatLngLiteral;
center: LatLngLiteral;
zoom: number;
}
@Directive({
selector: "[appLeaflet]",
exportAs: "appLeaflet",
providers: [
{
provide: LeafletHandler,
useExisting: forwardRef(() => LeafletDirective)
}
]
})
export class LeafletDirective extends LeafletHandler implements OnInit, OnDestroy, AfterViewInit {
public readonly instance: Map;
@Output()
public appClick = defer(() => this.on<LeafletMouseEvent>("click"));
@Output()
public appPopupClose = defer(() => this.on<PopupEvent>("popupclose")).pipe(delay(0));
@Output()
public appUnload$ = defer(() => this.on("unload"));
@Output()
public appLatLngZoomChange = defer(() => this.on("moveend zoomend", true)).pipe(
debounceTime(10),
map(() => latLngZoomToString(this.instance.getCenter(), this.instance.getZoom())),
runInZone(this.ngZone)
);
public constructor(
public readonly elementRef: ElementRef<HTMLElement>,
public readonly ngZone: NgZone,
@Inject(LEAFLET_CONFIGURATION_TOKEN) public readonly configuration: ILeafletConfiguration,
@Optional() @Inject(LEAFLET_RESIZE_TOKEN) public resize$: Observable<any>
) {
super();
this.instance = this.ngZone.runOutsideAngular(() => {
const tileLayer = new TileLayer(this.configuration.urlTemplate, {
attribution: this.configuration.attribution
});
const result = new Map(this.elementRef.nativeElement, {layers: [tileLayer]});
result.setView({lat: configuration.lat, lng: configuration.lng}, configuration.zoom);
return result;
});
}
@Input()
public set appCenter(value: { lat: number, lng: number, zoom: number }) {
if (value != null) {
this.ngZone.runOutsideAngular(() => this.instance.setView(value, value.zoom));
}
}
@Input()
public set appDisabled(value: boolean) {
const handlers = [
this.instance.dragging,
this.instance.touchZoom,
this.instance.doubleClickZoom,
this.instance.scrollWheelZoom,
this.instance.boxZoom,
this.instance.keyboard
];
handlers.forEach((handler) => value ? handler.disable() : handler.enable());
value ? this.instance.removeControl(this.instance.zoomControl) : this.instance.addControl(this.instance.zoomControl);
}
public ngOnInit() {
if (this.resize$ instanceof Observable) {
this.resize$.pipe(throttleTime(10), takeUntil(this.appUnload$))
.subscribe((_) => this.invalidateSize());
}
}
public ngAfterViewInit() {
setTimeout(() => this.invalidateSize());
}
public ngOnDestroy() {
this.ngZone.runOutsideAngular(() => this.instance.remove());
}
public getZoom(): number {
return this.ngZone.runOutsideAngular(() => this.instance.getZoom());
}
public getBounds(): ILeafletBounds {
return this.ngZone.runOutsideAngular(() => {
const bounds = this.instance.getBounds();
const zoom = this.getZoom();
const result: ILeafletBounds = {
northWest: bounds.getNorthWest(),
northEast: bounds.getNorthEast(),
southEast: bounds.getSouthEast(),
southWest: bounds.getSouthWest(),
center: bounds.getCenter(),
zoom
};
return this.ngZone.run(() => result);
});
}
public invalidateSize(options?: boolean | InvalidateSizeOptions) {
this.ngZone.runOutsideAngular(() => this.instance.invalidateSize(options));
}
}