From 709e964579e198561fdf7a5b6734b19963a32eef Mon Sep 17 00:00:00 2001 From: Willem Dantuma Date: Thu, 13 Feb 2020 11:24:23 +0100 Subject: [PATCH] Add custom style for vnd.farmmaps.itemntype.layer --- .../aol/item-layers/item-layers.component.ts | 554 +++++++++--------- .../item-vector-source.component.ts | 14 +- .../fm-map/components/map/map.component.ts | 21 + 3 files changed, 309 insertions(+), 280 deletions(-) diff --git a/projects/common-map/src/fm-map/components/aol/item-layers/item-layers.component.ts b/projects/common-map/src/fm-map/components/aol/item-layers/item-layers.component.ts index 009e9e8..5c174f8 100644 --- a/projects/common-map/src/fm-map/components/aol/item-layers/item-layers.component.ts +++ b/projects/common-map/src/fm-map/components/aol/item-layers/item-layers.component.ts @@ -1,277 +1,277 @@ -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 } from '@farmmaps/common'; -import { AppConfig } from '@farmmaps/common'; -import { IItemLayer} 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 - }), - }); - } - - createLayer(itemLayer: IItemLayer): Layer { - var layer: Layer = null; - if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') { - let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); - layer = new Tile({ source: source }); - var data = itemLayer.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/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.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/${itemLayer.item.code}/mapimage?v=${itemLayer.item.updated.getTime()}` }); - layer = new Image({ source: source }); - } - } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') { - var data = itemLayer.item.data; - var layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index; - var l = itemLayer.item.data.layers[layerIndex]; - if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") { - var rt = itemLayer.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/${itemLayer.item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${itemLayer.item.updated.getTime()}` - }), - style: (feature) => { - return this.getColorFromGradient(l, feature); - } - }) - } 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/${itemLayer.item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.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(itemLayer.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]; - } - }); - } - } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') { - let data = itemLayer.item.data as ILayerData; - 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; - } - } - } - 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); - } - - updateLayers(itemLayers: IItemLayer[]) { - if (itemLayers) { - var olLayers = this.instance.getLayers(); - itemLayers.forEach((itemLayer, index) => { - - 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) { - itemLayer.layer = layer; - } - olLayers.insertAt(index, layer); - } else if (index !== olIndex) { - // layer has moved inside the layers list - olLayers.removeAt(olIndex); - olLayers.insertAt(index, layer); - } - layer.setOpacity(itemLayer.opacity); - layer.setVisible(itemLayer.visible); - }); - // Remove the layers that have disapeared from childrenLayers - if (olLayers.getLength() > itemLayers.length) { - for (let i = itemLayers.length; i < olLayers.getLength(); i++) { - olLayers.removeAt(i); - } - } - } - } - - 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([]); - } - } - } - } -} +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 } from '@farmmaps/common'; +import { AppConfig } from '@farmmaps/common'; +import { IItemLayer} 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 + }), + }); + } + + createLayer(itemLayer: IItemLayer): Layer { + var layer: Layer = null; + if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.geotiff.processed') { + let source = new XYZ({ maxZoom: 19, minZoom: 1, url: `${this._apiEndPoint}/api/v1/items/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.item.updated.getTime()}` }); + layer = new Tile({ source: source }); + var data = itemLayer.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/${itemLayer.item.code}/tiles/{z}/{x}/{y}.png?v=${itemLayer.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/${itemLayer.item.code}/mapimage?v=${itemLayer.item.updated.getTime()}` }); + layer = new Image({ source: source }); + } + } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.shape.processed') { + var data = itemLayer.item.data; + var layerIndex = itemLayer.layerIndex != -1 ? itemLayer.layerIndex : itemLayer.item.data.layers[0].index; + var l = itemLayer.item.data.layers[layerIndex]; + if (l && l.rendering && l.rendering.renderoutputType == "VectorTiles") { + var rt = itemLayer.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/${itemLayer.item.code}/vectortiles/{z}/{x}/{y}.pbf?v=${itemLayer.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/${itemLayer.item.code}/vectortiles/image_tiles/${layerIndex}/{z}/{x}/{y}.png?v=${itemLayer.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(itemLayer.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]; + } + }); + } + } else if (itemLayer.item.itemType == 'vnd.farmmaps.itemtype.layer') { + let data = itemLayer.item.data as ILayerData; + 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; + } + } + } + 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); + } + + updateLayers(itemLayers: IItemLayer[]) { + if (itemLayers) { + var olLayers = this.instance.getLayers(); + itemLayers.forEach((itemLayer, index) => { + + 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) { + itemLayer.layer = layer; + } + olLayers.insertAt(index, layer); + } else if (index !== olIndex) { + // layer has moved inside the layers list + olLayers.removeAt(olIndex); + olLayers.insertAt(index, layer); + } + layer.setOpacity(itemLayer.opacity); + layer.setVisible(itemLayer.visible); + }); + // Remove the layers that have disapeared from childrenLayers + if (olLayers.getLength() > itemLayers.length) { + for (let i = itemLayers.length; i < olLayers.getLength(); i++) { + olLayers.removeAt(i); + } + } + } + } + + 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([]); + } + } + } + } +} diff --git a/projects/common-map/src/fm-map/components/aol/item-vector-source/item-vector-source.component.ts b/projects/common-map/src/fm-map/components/aol/item-vector-source/item-vector-source.component.ts index 977042e..618cc11 100644 --- a/projects/common-map/src/fm-map/components/aol/item-vector-source/item-vector-source.component.ts +++ b/projects/common-map/src/fm-map/components/aol/item-vector-source/item-vector-source.component.ts @@ -54,19 +54,27 @@ export class ItemVectorSourceComponent extends SourceVectorComponent implements return geometry; } + getSelectedStyle(feature:Feature):style.Style { + let key = feature.get('itemType')+"_selected"; + if(this.stylesCache[key]) { + return this.stylesCache[key]; + } + return this.stylesCache["selected"]; + } + ngOnInit() { this.strategy = loadingstrategy.bbox; this.format = new GeoJSON(); this._select = new Select({ style: (feature) => { - return this.stylesCache['selected']; + return this.getSelectedStyle(feature); }, hitTolerance: 10, layers: [this.layer.instance as Layer] }); this._hoverSelect = new Select({ style: (feature) => { - return this.stylesCache['selected']; + return this.getSelectedStyle(feature); }, hitTolerance: 10, condition: (e: MapBrowserEvent) => { @@ -148,7 +156,7 @@ export class ItemVectorSourceComponent extends SourceVectorComponent implements for (const key in styles) { if (styles.hasOwnProperty(key)) { let style = styles[key]; - if(style.geometry == null) { + if(style.geometry_ == null) { style.setGeometry((feature) => this.geometry(feature)); } this.stylesCache[key]=style; diff --git a/projects/common-map/src/fm-map/components/map/map.component.ts b/projects/common-map/src/fm-map/components/map/map.component.ts index b95024b..9b51607 100644 --- a/projects/common-map/src/fm-map/components/map/map.component.ts +++ b/projects/common-map/src/fm-map/components/map/map.component.ts @@ -32,6 +32,7 @@ import {Extent,createEmpty,extend } from 'ol/extent'; import {transform} from 'ol/proj'; import { query } from '@angular/animations'; import { tassign } from 'tassign'; +import * as style from 'ol/style'; @Component({ @@ -156,6 +157,26 @@ export class MapComponent implements OnInit, OnDestroy,AfterViewInit { this.query$.pipe(withLatestFrom(this.mapState$)).subscribe((state) => { this.replaceUrl(state[1], state[0],false); }); + this.initCustomStyles(); + } + + initCustomStyles() { + this.store.dispatch(new mapActions.SetStyle('vnd.farmmaps.itemtype.layer',new style.Style({ + stroke: new style.Stroke({ + color: 'red', + lineDash: [ 5,5], + width: 1 + }), + geometry:(feature) =>feature.getGeometry() + }))); + this.store.dispatch(new mapActions.SetStyle('vnd.farmmaps.itemtype.layer_selected',new style.Style({ + stroke: new style.Stroke({ + color: 'red', + lineDash: [ 5,5], + width: 3 + }), + geometry:(feature) =>feature.getGeometry() + }))); } private stateSetCount: number = 0;