dasf-web
Version:
Web frontend components for the data analytics software framework (DASF)
269 lines (218 loc) • 8.05 kB
text/typescript
import IHasFilter, { isFilterable } from "./HasFilter";
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import { Observable } from 'typescript-observable/dist/observable';
import { IObservableEvent } from 'typescript-observable/dist/interfaces/observable-event';
// TODO: the filter needs to be observable!!!
export default class Filter extends Observable {
private readonly minChangeEvent: IObservableEvent = {
parent: null,
name: 'changed:min'
};
private readonly maxChangeEvent: IObservableEvent = {
parent: null,
name: 'changed:max'
};
private readonly thresholdChangeEvent: IObservableEvent = {
parent: null,
name: 'changed:threshold'
};
private readonly attributeChangeEvent: IObservableEvent = {
parent: null,
name: 'changed:attribute'
};
private readonly useTimeseriesChangeEvent: IObservableEvent = {
parent: null,
name: 'changed:useTimeseries'
};
private static readonly ORIGINAL_FEATURES_KEY = 'originalFeatures';
private static readonly FILTER_KEY = "filter";
public static readonly MEDIAN_TOP = "Median (top)";
public static readonly MEDIAN_BOTTOM = "Median (bottom)";
public static readonly QUARTILE_R1 = "Quartile (R1)";
public static readonly QUARTILE_R2 = "Quartile (R2)";
public static readonly QUARTILE_R3 = "Quartile (R3)";
public static readonly QUARTILE_R4 = "Quartile (R4)";
private _attribute: string;
private _useTimeseries = true;
private _threshold: [number, number] = [Number.NaN, Number.NaN];
private _min: number = 0;
private _max: number = 0;
private _median: number = NaN;
private _quartileQ1: number = NaN;
private _quartileQ3: number = NaN;
private _protected: boolean = false;
public get isProtected(): boolean {
return this._protected;
}
public set isProtected(value: boolean) {
this._protected = value;
}
public get attribute(): string {
return this._attribute;
}
public set attribute(value: string) {
let changed = value != this._attribute;
this._attribute = value;
if (changed) {
this.notify(this.attributeChangeEvent, value);
}
}
public get useTimeseries(): boolean {
return this._useTimeseries;
}
public set useTimeseries(value: boolean) {
let changed = value != this._useTimeseries;
this._useTimeseries = value;
if (changed) {
this.notify(this.useTimeseriesChangeEvent, value);
}
}
public set min(value: number) {
let changed = value != this._min;
this._min = value;
if (changed) {
this.notify(this.minChangeEvent, value);
}
}
public get min(): number {
return this._min;
}
public set max(value: number) {
let changed = value != this._max;
this._max = value;
if (changed) {
this.notify(this.maxChangeEvent, value);
}
}
public get max(): number {
return this._max;
}
public set median(value:number){
this._median = value;
}
public get median():number{
return this._median;
}
public set quartileQ1(value:number){
this._quartileQ1 = value;
}
public get quartileQ1():number{
return this._quartileQ1;
}
public set quartileQ3(value:number){
this._quartileQ3 = value;
}
public get quartileQ3():number{
return this._quartileQ3;
}
public setThreshold(lower: number, upper: number): void {
let changed = lower != this.threshold[0] || upper != this.threshold[1];
this._threshold[0] = lower;
this._threshold[1] = upper;
if (changed) {
this.notify(this.thresholdChangeEvent, this._threshold);
}
}
public get threshold(): [number, number] {
return this._threshold;
}
public reset(): void {
this.setThreshold(this._min, this._max);
}
public isValid(): boolean {
return this.attribute !== undefined && this.attribute.length > 0 && this.threshold !== undefined && this.threshold.length > 0;
}
/**
* Applies this filter to the given object
* @param obj
*/
public apply(obj: object): void {
// console.log('applying filter', this, obj);
if (isFilterable(obj)) {
let filterable: IHasFilter = obj as IHasFilter;
filterable.setFilter(this);
} else if (Filter.isVectorLayerAndVectorSource(obj)) {
// we can filter any vector layer with a vector source
let vectorLayer: VectorLayer = obj as VectorLayer;
let vectorSource: VectorSource = vectorLayer.getSource() as VectorSource;
// store the filter in the layer
vectorLayer.set(Filter.FILTER_KEY, this);
// get original feature collection
let origFeatures = vectorLayer.get(Filter.ORIGINAL_FEATURES_KEY);
if (origFeatures === undefined) {
// get original features from original source
origFeatures = vectorSource.getFeatures();
// store original features in layer
vectorLayer.set(Filter.ORIGINAL_FEATURES_KEY, origFeatures);
}
let filteredFeatures: Feature[] = [];
for (let feature of origFeatures) {
var featureValue = feature.get(this.attribute);
if (this.filter(featureValue)) {
filteredFeatures.push(feature);
}
}
// clear the source and add features satisfying the filter
vectorSource.clear();
vectorSource.addFeatures(filteredFeatures);
} else {
console.warn('ignoring attempt to apply a filter to an unsupported object', this, obj);
}
}
/**
* Extracts a filter from the given object
* @param obj
*/
public static extract(obj: object): Filter | undefined {
if (isFilterable(obj)) {
return (obj as IHasFilter).getFilter();
} else if (Filter.isVectorLayerAndVectorSource(obj)) {
return (obj as VectorLayer).get(Filter.FILTER_KEY);
} else {
console.warn('attempt to extract filter from unsupported object', obj);
}
return undefined;
}
public static canFilter(obj: object): boolean {
return isFilterable(obj) || Filter.isVectorLayerAndVectorSource(obj);
}
private static isVectorLayerAndVectorSource(obj: object): boolean {
return obj instanceof VectorLayer && (obj as VectorLayer).getSource() instanceof VectorSource;
}
/**
* @param value -> check value
* @returns automatic filter
*/
public filter(value: number): boolean {
return value >= this._threshold[0] && value <= this._threshold[1];
}
/**
* @returns cql filter
*/
public getQuery(): string {
if (this.attribute != "" && this._threshold[0] != NaN && this._threshold[1] != NaN) {
if (this._threshold[0] == this._threshold[1]) {
return this.attribute + " = " + this._threshold[0];
} else {
return this.attribute + " BETWEEN " + this._threshold[0] + " AND " + this._threshold[1];
}
}
return "";
}
public getPossibleThresholds(): string[]{
let options: string[] = [];
if(this.median!=NaN){
options.push(Filter.MEDIAN_BOTTOM);
options.push(Filter.MEDIAN_TOP);
}
if(this.quartileQ1!=NaN && this.quartileQ3!=NaN){
options.push(Filter.QUARTILE_R1);
options.push(Filter.QUARTILE_R2);
options.push(Filter.QUARTILE_R3);
options.push(Filter.QUARTILE_R4);
}
return options;
}
}