import { Component, Host, Input, Output, EventEmitter, Optional, QueryList, OnInit, AfterViewInit, OnChanges, SimpleChanges, SkipSelf, forwardRef, Inject, InjectionToken } from '@angular/core'; import { HttpClient } from "@angular/common/http"; import { LayerVectorComponent, LayerTileComponent, LayerGroupComponent, MapComponent } from 'ngx-openlayers'; import { ItemService,IItem,AppConfig } from '@farmmaps/common'; import { IItemLayer,ItemLayer, ITemporalItemLayer} from '../../../models/item.layer'; import { ILayerData} from '../../../models/layer.data'; import { IRenderoutputTiles,IRenderoutputImage,IGradientstop,ILayer,IHistogram} from '../../../models/color.map'; import {Extent} from 'ol/extent'; import Projection from 'ol/proj/Projection'; import * as proj from 'ol/proj'; import * as loadingstrategy from 'ol/loadingstrategy'; import * as style from 'ol/style'; import {Tile,Layer,Image} from 'ol/layer'; import {XYZ,ImageStatic,OSM,BingMaps,TileWMS,TileArcGISRest} from 'ol/source'; import {Vector as VectorSource} from 'ol/source'; import { Vector as VectorLayer } from 'ol/layer'; import VectorTileSource from 'ol/source/VectorTile'; import VectorTileLayer from 'ol/layer/VectorTile'; import {GeoJSON,MVT} from 'ol/format'; import { from } from 'rxjs'; @Component({ selector: 'fm-map-item-layers', template: ``, providers: [ { provide: LayerGroupComponent, useExisting: forwardRef(() => ItemLayersComponent) } ] }) export class ItemLayersComponent extends LayerGroupComponent implements OnChanges, OnInit { @Input() itemLayers: IItemLayer[]; @Input() itemLayer: IItemLayer; private _apiEndPoint: string; constructor(private itemService: ItemService, @Host() private map: MapComponent, public appConfig: AppConfig) { super(map); this._apiEndPoint = appConfig.getConfig("apiEndPoint"); } private styleCache = {} componentToHex(c) { var hex = c.toString(16); return hex.length == 1 ? "0" + hex : hex; } rgbaToHex(r, g, b,a) { return "#" + this.componentToHex(r) + this.componentToHex(g) + this.componentToHex(b) + this.componentToHex(a); } getColorFromGradient(layer: ILayer, feature): style.Style { var value = feature.get(layer.name); var gradient: IGradientstop[] = layer.renderer.colorMap.gradient; var histogram: IHistogram = layer.renderer.band.histogram; var index = (value - histogram.min) / histogram.max; var min = gradient[0]; var max = gradient[gradient.length - 1]; for (var n = 0; n < gradient.length; n++) { var s = gradient[n]; if (s.relativestop <= index && min.relativestop < s.relativestop && n < gradient.length - 1) min = s; if (s.relativestop >= index && max.relativestop > s.relativestop && n > 0) max = s; } var i = index - min.relativestop; var size = max.relativestop - min.relativestop; var alpha = Math.round( min.color.alpha + ((max.color.alpha - min.color.alpha) * i / size)); var red = Math.round(min.color.red + ((max.color.red - min.color.red) * i / size)); var green = Math.round(min.color.green + ((max.color.green - min.color.green) * i / size)); var blue = Math.round(min.color.blue + ((max.color.blue - min.color.blue) * i / size)); return new style.Style( { image: new style.Circle({ fill: new style.Fill({ color: this.rgbaToHex(red,green,blue,alpha) }), radius: 3 }), fill: new style.Fill({ color: this.rgbaToHex(red, green, blue, alpha) }), stroke: new style.Stroke({ color: this.rgbaToHex(red, green, blue, alpha), width: 1.25 }), }); } createGeotiffLayer(item:IItem,itemLayer:IItemLayer):Layer { var layerIndex = -1; var layer: Layer = null; layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index; let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` }); layer = new Tile({ source: source }); var data = item.data; var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null; if (l && l.rendering && l.rendering.renderoutputType == "Tiles") { var rt = l.rendering as IRenderoutputTiles; let source = new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` }); layer = new Tile({ source: source }); } if (l && l.rendering && l.rendering.renderoutputType == "Image") { var ri = l.rendering as IRenderoutputImage; let projection = new Projection({ code: 'image', units: 'pixels', extent: ri.extent }); let source = new ImageStatic({ imageExtent: ri.extent, projection: projection, url: `${this._apiEndPoint}/api/v1/items/${item.code}/mapimage/${layerIndex}?v=${item.updated.getTime()}` }); layer = new Image({ source: source }); } return layer; } createShapeLayer(item:IItem,itemLayer:IItemLayer):Layer { var layerIndex = -1; var layer: Layer = null; layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : item.data.layers[0].index; var data = item.data; var l = (data && data.layers && data.layers.length > 0) ? data.layers[0] : null; if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") { var rt = item.data.layers[layerIndex].rendering as IRenderoutputTiles; layer = new VectorTileLayer({ declutter: true, source: new VectorTileSource({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, format: new MVT(), url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${item.updated.getTime()}` }), style: (feature) => { return this.getColorFromGradient(l, feature); } }) } else if (l && l.rendering && l.rendering.renderoutputType == "Tiles") { var rt = l.rendering as IRenderoutputTiles; layer = new Tile({ source: new XYZ({ maxZoom: rt.maxzoom, minZoom: rt.minzoom, url: `${this._apiEndPoint}/api/v1/items/${item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${item.updated.getTime()}` }) }); } else { let __this = this; let format = new GeoJSON(); let source = new VectorSource({ strategy: loadingstrategy.bbox, loader: function (extent: Extent, resolution: number, projection: Projection) { var source = this as VectorSource; __this.itemService.getItemFeatures(item.code, extent, projection.getCode(), layerIndex).subscribe(function (data) { var features = format.readFeatures(data); for (let f of features) { if (f.get("code")) { f.setId(f.get("code")); } } source.addFeatures(features); }); } }); layer = new VectorLayer({ source: source, style: (feature) => { var key = feature.get("color"); if (!this.styleCache[key]) { var color = feature.get("color"); this.styleCache[key] = new style.Style( { fill: new style.Fill({ color: color }), stroke: new style.Stroke({ color: color, width: 1.25 }), image: new style.Circle({ fill: new style.Fill({ color: color }), stroke: new style.Stroke({ color: color, width: 1.25 }), radius: 5 }), } ) } return this.styleCache[key]; } }); } return layer; } createExternalLayer(item:IItem,itemLayer:IItemLayer):Layer { let data = item.data as ILayerData; var layer: Layer = null; switch (data.interfaceType) { case 'OSM': { let source = new OSM(); layer = new Tile({ source: source }); break; } case 'BingMaps': { let source = new BingMaps(data.options); layer = new Tile({ source: source }); break; } case 'TileWMS': { let source = new TileWMS(data.options); layer = new Tile({ source: source }); break; } case 'TileArcGISRest': { let source = new TileArcGISRest(data.options); layer = new Tile({ source: source }); break; } default: { break; } } return layer; } createLayer(itemLayer: IItemLayer): Layer { var layer: Layer = null; var layerIndex = -1; if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') { layer = this.createGeotiffLayer(itemLayer.item,itemLayer); } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') { layer = this.createShapeLayer(itemLayer.item,itemLayer); } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') { layer = this.createExternalLayer(itemLayer.item,itemLayer); } if (layer) { let geometry = new GeoJSON().readGeometry(itemLayer.item.geometry); let extent = geometry ? proj.transformExtent(geometry.getExtent(), 'EPSG:4326', 'EPSG:3857') : null; if (extent) layer.setExtent(extent); } return layer; } ngOnInit() { super.ngOnInit(); this.updateLayers(this.itemLayers); } addOrUpdateOlLayer(itemLayer:IItemLayer,index:number):Layer { if(!itemLayer) return null; var olLayers = this.instance.getLayers(); var layer = itemLayer.layer; let olIndex = olLayers.getArray().indexOf(layer); if (olIndex < 0) { // New layer: we add it to the map layer = this.createLayer(itemLayer); if (layer) { olLayers.insertAt(index, layer); } } else if (index !== olIndex) { // layer has moved inside the layers list olLayers.removeAt(olIndex); olLayers.insertAt(index, layer); } if(layer) { itemLayer.layer = layer; layer.setOpacity(itemLayer.opacity); layer.setVisible(itemLayer.visible); } return layer; } updateLayers(itemLayers: IItemLayer[]) { let newLayers: IItemLayer[] = []; if (itemLayers) { itemLayers.forEach((itemLayer, index) => { if(itemLayer.item.itemType == 'vnd.farmmaps.itemtype.temporal') { let il = itemLayer as ITemporalItemLayer; let previousLayer = this.addOrUpdateOlLayer(il.previousItemLayer,newLayers.length); if(previousLayer) newLayers.push(previousLayer); let selectedLayer = this.addOrUpdateOlLayer(il.selectedItemLayer,newLayers.length); if(selectedLayer) newLayers.push(selectedLayer); let nextLayer = this.addOrUpdateOlLayer(il.nextItemLayer,newLayers.length); if(nextLayer) newLayers.push(nextLayer); } else { let layer = this.addOrUpdateOlLayer(itemLayer,newLayers.length); if(layer) newLayers.push(layer); } }); // Remove the layers that have disapeared from childrenLayers var olLayers = this.instance.getLayers(); while(olLayers.getLength() > newLayers.length) { olLayers.removeAt(newLayers.length); } } } ngOnChanges(changes: SimpleChanges) { if (this.instance) { if (changes['itemLayers']) { var itemLayers = changes['itemLayers'].currentValue as IItemLayer[]; this.updateLayers(itemLayers); } if (changes['itemLayer']) { var itemLayer = changes['itemLayer'].currentValue as IItemLayer; if(itemLayer) { this.updateLayers([itemLayer]); } else { this.updateLayers([]); } } } } }