dasf-web
Version:
Web frontend components for the data analytics software framework (DASF)
255 lines (215 loc) • 8.32 kB
text/typescript
import Gradient, { Spectral, ColorRamp } from '../../colors/Gradient';
import NumericalParameter, { NoParam } from '../../map/model/Parameter';
import Layer from 'ol/layer/Layer';
// import VectorLayer from 'ol/layer/Vector';
import ImageLayer from 'ol/layer/Image';
import BaseVectorLayer from 'ol/layer/BaseVector';
import { FeatureLike } from 'ol/Feature';
import Style, { StyleFunction } from 'ol/style/Style'
import Fill from 'ol/style/Fill';
import CircleStyle from 'ol/style/Circle';
import Stroke from 'ol/style/Stroke';
import GeometryType from 'ol/geom/GeometryType';
import { Observable } from 'typescript-observable/dist/observable';
import { IObservableEvent } from 'typescript-observable/dist/interfaces/observable-event';
import ColorUtils from '../../colors/ColorUtils';
import TemporalImageLayer from './TemporalImageLayer';
export interface HasColorScale {
setLayerColorScale(colorScale: LayerColorScale): void;
getLayerColorScale(): LayerColorScale;
updateLayerColorScale(parameters: NumericalParameter[]): void;
hasLayerColorScale(): boolean;
getAvailableParameters(): NumericalParameter[];
isAutoLayerColorScale(): boolean;
setAutoLayerColorScale(auto: boolean): void;
}
export default class LayerColorScale extends Observable {
public static KEY: string = 'colorScale';
public static AUTO_COLOR_SCALE_KEY: string = 'autoColorScale';
public static AUTO_COLOR_SCALE_DISABLE_VALUE: string = 'disable';
private _selectedParameter: NumericalParameter = NoParam;
private availableParameters: NumericalParameter[];
private colorGradient: Gradient = new Gradient(Spectral, 0, 1);
private registeredLayer: Layer[] = [];
private readonly parameterChangeEvent: IObservableEvent = {
parent: null,
name: 'parameterChanged'
};
private readonly availableParametersChangeEvent: IObservableEvent = {
parent: null,
name: 'availableParametersChanged'
};
private customStyle!: ((feature: FeatureLike, colorScale: LayerColorScale) => Style[]);
public constructor(availableParameters: NumericalParameter[]) {
super();
this.availableParameters = availableParameters;
this.selectedParameter = availableParameters[0];
}
public registerImageLayer(layer: ImageLayer): void {
this.registeredLayer.push(layer);
// set color scale to layer
layer.set(LayerColorScale.KEY, this);
layer.dispatchEvent(LayerColorScale.KEY);
}
/**
* Assoziates the given vector layer with this color scale.<br>
* @param layer
*/
public registerVectorLayer(layer: BaseVectorLayer): void {
let self: LayerColorScale = this;
this.registeredLayer.push(layer);
// set color scale to layer and set style function
layer.set(LayerColorScale.KEY, this);
layer.setStyle(this.getStyleFunction());
layer.dispatchEvent(LayerColorScale.KEY);
// register a post render hook, to capture the case that color gradient was updated during the last run
layer.on('postcompose', () => {
if (self.colorGradient.getMin() != self.selectedParameter.getMin() || self.colorGradient.getMax() != self.selectedParameter.getMax()) {
// the color gradient adapted it self - update the parameter bounds
self.selectedParameter.setMin(self.colorGradient.getMin());
self.selectedParameter.setMax(self.colorGradient.getMax());
// we need to redraw since the bounds of the gradient changed on the fly
self.triggerLayerChanged();
}
});
}
public getSelectedParameter(): NumericalParameter {
return this.selectedParameter;
}
public getAvailableParameters(): NumericalParameter[] {
return this.availableParameters;
}
public getAvailableParameter(name: string): NumericalParameter | undefined {
return this.availableParameters.find(parameter => parameter.getName() === name);
}
public getGradient(): Gradient {
return this.colorGradient;
}
public get colorRamp(): ColorRamp {
return this.colorGradient.getColorRamp();
}
public set colorRamp(colorRamp: ColorRamp) {
this.colorGradient.setColorRamp(colorRamp);
// trigger redraw of the layers
this.triggerLayerChanged();
}
public get selectedParameter(): NumericalParameter {
return this._selectedParameter;
}
public set selectedParameter(param: NumericalParameter) {
this._selectedParameter = param;
// the parameter changed - update the gradient with the new min max values
this.colorGradient.updateMinMax(param.getMin(), param.getMax());
// trigger a redraw of the layers
this.triggerLayerChanged();
// notify observers
this.notify(this.parameterChangeEvent, param);
}
public updateParameters(parameters: NumericalParameter[]): void {
this.availableParameters = parameters;
// notify observers
this.notify(this.availableParametersChangeEvent, this.availableParameters);
// exchange selected parameter with same name
let parameterFound = false;
for (let p of parameters) {
if (p.getName() === this.selectedParameter.getName()) {
parameterFound = true;
this.selectedParameter = p;
break;
}
}
if (!parameterFound) {
// the previously selected parameter doesn't exist anymore - select first
this.selectedParameter = parameters[0];
}
}
private triggerLayerChanged(): void {
for (let layer of this.registeredLayer) {
// console.log('LayerColorScale::trigger layer changed: ' + layer.get('title'));
if (layer instanceof TemporalImageLayer) {
layer.updateSourceColorScale()
} else {
layer.changed();
}
layer.dispatchEvent(LayerColorScale.KEY);
}
}
public setCustomStyleFunction(customStyle: (feature: FeatureLike, colorScale: LayerColorScale) => Style[]): void {
this.customStyle = customStyle;
if (this.registeredLayer.length > 0) {
// update style functions for already registered layers
for (let layer of this.registeredLayer) {
if (layer instanceof BaseVectorLayer) {
layer.setStyle(this.getStyleFunction());
layer.dispatchEvent(LayerColorScale.KEY);
}
}
}
}
/**
* Returns the style function (custom or default) for this color scale
*/
public getStyleFunction(): StyleFunction {
let self = this;
// return custom style if any
if (this.customStyle) {
return (feature: FeatureLike): Style[] => {
return self.customStyle(feature, self);
}
} else {
// return default style
return (feature: FeatureLike): Style[] => {
// get value of the currently selected parameter
let value: number = feature.get(self.selectedParameter.getName());
// get color for the value
let color: string = isNaN(value) ? '#77777777' : self.colorGradient.getColor(value);
switch (feature.getGeometry().getType()) {
case GeometryType.POINT:
case GeometryType.MULTI_POINT:
let circle = new CircleStyle({
radius: 4,
fill: new Fill({
color: color
})
});
// fixme: workaround for missing function exception
circle['getPixelRatio'] = function (arg) {
return 1;
};
circle['getScaleArray'] = function () {
return [1, 1];
}
return [
new Style({
image: circle
})];
case GeometryType.LINE_STRING:
case GeometryType.MULTI_LINE_STRING:
return [
new Style({
stroke: new Stroke({
color: color,
width: 4
})
})];
case GeometryType.LINEAR_RING:
case GeometryType.POLYGON:
case GeometryType.MULTI_POLYGON:
return [
new Style({
stroke: new Stroke({
color: color,
width: 4
}),
fill: new Fill({
color: ColorUtils.setAlpha(color, 0.4)
})
})];
default:
console.error('unsupported geometry', feature.getGeometry().getType());
return [];
}
};
}
}
}