import { Button, HTMLWidget, IconBar, publish, Spacer, Widget } from "@hpcc-js/common"; import { LatLngBounds, Map } from "@hpcc-js/leaflet-shim"; import { AlbersLayer } from "./AlbersPR"; import { BlankLayer } from "./Blank"; import { GMapLayer } from "./GMap"; import { MapBoxLayer } from "./MapBox"; import { OpenStreetLayer } from "./OpenStreet"; import { ILayer, TileLayer } from "./TileLayer"; import "../../src/leaflet/Leaflet.css"; export class Leaflet extends HTMLWidget { protected _leafletElement; private _leafletMap: Map; protected _iconBar = new IconBar() .buttons([ new Button().faChar("fa-plus").tooltip("Zoom in") .on("click", () => { this.zoomPlus(); }), new Button().faChar("fa-minus").tooltip("Zoom out") .on("click", () => { this.zoomMinus(); }), new Spacer(), new Button().faChar("fa-arrows-alt").tooltip("Zoom to fit") .on("click", () => { this.zoomToFit(); }) ]) ; _blankLayer = new BlankLayer(); _albersLayer = new AlbersLayer(); _mapBoxLayer = new MapBoxLayer(); _openStreetLayer = new OpenStreetLayer(); _gmapLayer = new GMapLayer(); constructor() { super(); this.baseLayer(); } zoom() { return this._leafletMap.getZoom(); } zoomPlus() { this._leafletMap.zoomIn(); } zoomMinus() { this._leafletMap.zoomOut(); } zoomToDefault() { this._leafletMap.flyTo({ lat: this.defaultLat(), lng: this.defaultLong() }, this.defaultZoom(), { animate: false }); } zoomToFit() { let bounds: LatLngBounds; if (this.mapType() === "AlbersPR") { bounds = this._albersLayer.getBounds(); } else { this.layers().filter(l => l.hasBounds()).forEach(layer => { if (!bounds) { bounds = layer.getBounds(); } else { bounds.extend(layer.getBounds()); } }); } if (bounds && bounds.isValid()) { this._leafletMap.fitBounds(bounds, { maxZoom: 14 }); } else { this.zoomToDefault(); } } syncLayers(sLayers: ILayer[], domNode, element) { const added: ILayer[] = [...sLayers]; const updated: ILayer[] = []; const removed: ILayer[] = []; this._leafletMap.eachLayer(layer => { const idx = added.indexOf((layer as any).__hpcc_layer); if (idx >= 0) { updated.push((layer as any).__hpcc_layer); added.splice(idx, 1); } else if ((layer as any).__hpcc_layer) { removed.push((layer as any).__hpcc_layer); } }); added.forEach(sl => { sl.layerEnter(this._leafletMap); sl.layerUpdate(this._leafletMap); }); updated.forEach(sl => { sl.layerUpdate(this._leafletMap); }); removed.forEach(sl => { sl.layerExit(this._leafletMap); }); } protected _owner: HTMLWidget; isLayer(): boolean { return this._owner !== undefined && this._owner !== this; } baseLayer(): BlankLayer | AlbersLayer | MapBoxLayer | OpenStreetLayer | GMapLayer { switch (this.mapType()) { case "AlbersPR": this.map(this._albersLayer); break; case "MapBox": this.map(this._mapBoxLayer); break; case "OpenStreet": this.map(this._openStreetLayer); break; case "Google": this.map(this._gmapLayer); break; case "None": default: this.map(this._blankLayer); break; } return this.map(); } enter(domNode, element) { super.enter(domNode, element); element.style("position", "relative"); this._leafletElement = element.append("div") .style("position", "absolute") .style("width", `${this.width()}px`) .style("height", `${this.height()}px`) ; this._iconBar.target(domNode); } private _prevCRS; update(domNode, element) { super.update(domNode, element); this._leafletElement .style("width", `${this.width()}px`) .style("height", `${this.height()}px`) ; const baseLayer = this.baseLayer(); if (this._prevCRS !== baseLayer.crs()) { this._prevCRS = baseLayer.crs(); if (this._leafletMap) { this.syncLayers([], domNode, element); this._leafletMap.remove(); this._leafletElement.html(""); } this._leafletMap = new Map(this._leafletElement.node(), { trackResize: false, zoomControl: false, zoomSnap: this.mapType() === "AlbersPR" ? 0.1 : 1, crs: baseLayer.crs(), maxZoom: (baseLayer as any).getMaxZoom ? (baseLayer as any).getMaxZoom() : 18 }); this._leafletMap.setView([this.defaultLat(), this.defaultLong()], this.defaultZoom()); this._leafletMap["attributionControl"].setPrefix(baseLayer.attribution()); this._leafletMap.on("zoomend", e => this.layers().forEach(layer => layer.zoomEnd(e))); this._leafletMap.on("moveend", e => this.layers().forEach(layer => layer.moveEnd(e))); this._leafletMap.on("viewreset", e => this.layers().forEach(layer => layer.viewReset(e))); this._renderCount = 0; } this.syncLayers([baseLayer, ...this.layers()], domNode, element); this._leafletMap.invalidateSize(); if (this.autoZoomToFit()) { this._leafletMap.whenReady(function () { this.zoomToFit(); }, this); } this._iconBar .visible(this.showToolbar()) .render((w: IconBar) => { const bbox = w.getBBox(); w.element() .style("z-index", 5000) .style("left", `${this.width() - bbox.width - 4}px`) ; }) ; } exit(domNode, element) { this._iconBar.target(null); super.exit(domNode, element); } render(callback?: (w: Widget) => void): this { const promises = [this.baseLayer().init(), ...this.layers().map(l => l.init())]; Promise.all(promises).then(() => { super.render(callback); }); return this; } } Leaflet.prototype._class += " map_Leaflet"; export interface Leaflet { showToolbar(): boolean; showToolbar(_: boolean): this; mapType(): "None" | "AlbersPR" | "MapBox" | "OpenStreet" | "Google"; mapType(_: "None" | "AlbersPR" | "MapBox" | "OpenStreet" | "Google"): this; mapType_default(_: "None" | "AlbersPR" | "MapBox" | "OpenStreet" | "Google"): this; map(): TileLayer; map(_: TileLayer): this; layers(): ILayer[]; layers(_: ILayer[]): this; layers_default(_: ILayer[]): this; defaultLat(): number; defaultLat(_: number): this; defaultLong(): number; defaultLong(_: number): this; defaultZoom(): number; defaultZoom(_: number): this; autoZoomToFit: publish; } Leaflet.prototype.publish("showToolbar", true, "boolean", "Show toolbar", undefined, { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("mapType", "Google", "set", "Base Layer Type", ["None", "AlbersPR", "MapBox", "OpenStreet", "Google"], { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("map", null, "widget", "Base Layer", undefined, { internal: true }); Leaflet.prototype.publish("layers", [], "propertyArray", "Layers", undefined, { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("defaultLat", 42.877742, "number", "Center Latitude", undefined, { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("defaultLong", -97.380979, "number", "Center Longtitude", undefined, { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("defaultZoom", 4, "number", "Zoom Level", undefined, { hidden: (w: Leaflet) => w.isLayer() }); Leaflet.prototype.publish("autoZoomToFit", true, "boolean", "Auto zoom to fit Data", undefined, { hidden: (w: Leaflet) => w.isLayer() });