Finetuning of the filter functionality

This commit is contained in:
Pieter Vander Vennet 2021-07-27 19:39:57 +02:00
parent 31d2bd83b9
commit 79569f5119
17 changed files with 219 additions and 309 deletions

View file

@ -1,6 +1,6 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import FilteredLayer from "../../Models/FilteredLayer";
/**
@ -13,7 +13,7 @@ export default class FeatureDuplicatorPerLayer implements FeatureSource {
public readonly name;
constructor(layers: UIEventSource<{ layerDef: LayerConfig }[]>, upstream: FeatureSource) {
constructor(layers: UIEventSource<FilteredLayer[]>, upstream: FeatureSource) {
this.name = "FeatureDuplicator of "+upstream.name;
this.features = upstream.features.map(features => {
const newFeatures: { feature: any, freshness: Date }[] = [];

View file

@ -13,6 +13,7 @@ import Loc from "../../Models/Loc";
import GeoJsonSource from "./GeoJsonSource";
import MetaTaggingFeatureSource from "./MetaTaggingFeatureSource";
import RegisteringFeatureSource from "./RegisteringFeatureSource";
import FilteredLayer from "../../Models/FilteredLayer";
export default class FeaturePipeline implements FeatureSource {
@ -20,7 +21,7 @@ export default class FeaturePipeline implements FeatureSource {
public readonly name = "FeaturePipeline"
constructor(flayers: UIEventSource<{ isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>,
constructor(flayers: UIEventSource<FilteredLayer[]>,
updater: FeatureSource,
fromOsmApi: FeatureSource,
layout: UIEventSource<LayoutConfig>,

View file

@ -1,173 +1,169 @@
import FeatureSource from "./FeatureSource";
import { UIEventSource } from "../UIEventSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
import Loc from "../../Models/Loc";
import Hash from "../Web/Hash";
import { TagsFilter } from "../Tags/TagsFilter";
import {TagsFilter} from "../Tags/TagsFilter";
export default class FilteringFeatureSource implements FeatureSource {
public features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name = "FilteringFeatureSource";
public features: UIEventSource<{ feature: any; freshness: Date }[]> =
new UIEventSource<{ feature: any; freshness: Date }[]>([]);
public readonly name = "FilteringFeatureSource";
constructor(
layers: UIEventSource<
{
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>;
}[]
>,
location: UIEventSource<Loc>,
selectedElement: UIEventSource<any>,
upstream: FeatureSource
) {
const self = this;
function update() {
const layerDict = {};
if (layers.data.length == 0) {
console.warn("No layers defined!");
return;
}
for (const layer of layers.data) {
layerDict[layer.layerDef.id] = layer;
}
const features: { feature: any; freshness: Date }[] =
upstream.features.data;
const missingLayers = new Set<string>();
const newFeatures = features.filter((f) => {
const layerId = f.feature._matching_layer_id;
if (
selectedElement.data?.id === f.feature.id ||
f.feature.id === Hash.hash.data
) {
// This is the selected object - it gets a free pass even if zoom is not sufficient
return true;
}
if (layerId !== undefined) {
const layer: {
constructor(
layers: UIEventSource<{
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>;
} = layerDict[layerId];
if (layer === undefined) {
missingLayers.add(layerId);
return true;
}
}[]>,
location: UIEventSource<Loc>,
selectedElement: UIEventSource<any>,
upstream: FeatureSource
) {
const self = this;
const isShown = layer.layerDef.isShown;
const tags = f.feature.properties;
if (isShown.IsKnown(tags)) {
const result = layer.layerDef.isShown.GetRenderValue(
f.feature.properties
).txt;
if (result !== "yes") {
return false;
function update() {
const layerDict = {};
if (layers.data.length == 0) {
console.warn("No layers defined!");
return;
}
}
if (FilteringFeatureSource.showLayer(layer, location)) {
const tagsFilter = layer.appliedFilters.data;
if (tagsFilter) {
const properties = f.feature.properties;
if (!tagsFilter.matchesProperties(properties)) {
return false;
}
for (const layer of layers.data) {
const prev = layerDict[layer.layerDef.id]
if (prev !== undefined) {
// We have seen this layer before!
// We prefer the one which has a name
if (layer.layerDef.name === undefined) {
// This one is hidden, so we skip it
console.log("Ignoring layer selection from ", layer)
continue;
}
}
layerDict[layer.layerDef.id] = layer;
}
return true;
}
const features: { feature: any; freshness: Date }[] =
upstream.features.data;
const missingLayers = new Set<string>();
const newFeatures = features.filter((f) => {
const layerId = f.feature._matching_layer_id;
if (
selectedElement.data?.id === f.feature.id ||
f.feature.id === Hash.hash.data) {
// This is the selected object - it gets a free pass even if zoom is not sufficient or it is filtered away
return true;
}
if (layerId === undefined) {
return false;
}
const layer: {
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
appliedFilters: UIEventSource<TagsFilter>;
} = layerDict[layerId];
if (layer === undefined) {
missingLayers.add(layerId);
return false;
}
const isShown = layer.layerDef.isShown;
const tags = f.feature.properties;
if (isShown.IsKnown(tags)) {
const result = layer.layerDef.isShown.GetRenderValue(
f.feature.properties
).txt;
if (result !== "yes") {
return false;
}
}
const tagsFilter = layer.appliedFilters.data;
if (tagsFilter) {
if (!tagsFilter.matchesProperties(f.feature.properties)) {
// Hidden by the filter on the layer itself - we want to hide it no matter wat
return false;
}
}
if (!FilteringFeatureSource.showLayer(layer, location)) {
// The layer itself is either disabled or hidden due to zoom constraints
// We should return true, but it might still match some other layer
return false;
}
return true;
});
console.log(
"Filtering layer source: input: ",
upstream.features.data?.length,
"output:",
newFeatures.length
);
self.features.setData(newFeatures);
if (missingLayers.size > 0) {
console.error(
"Some layers were not found: ",
Array.from(missingLayers)
);
}
}
// Does it match any other layer - e.g. because of a switch?
for (const toCheck of layers.data) {
if (!FilteringFeatureSource.showLayer(toCheck, location)) {
continue;
}
if (
toCheck.layerDef.source.osmTags.matchesProperties(
f.feature.properties
)
) {
return true;
}
}
return false;
});
console.log(
"Filtering layer source: input: ",
upstream.features.data?.length,
"output:",
newFeatures.length
);
self.features.setData(newFeatures);
if (missingLayers.size > 0) {
console.error(
"Some layers were not found: ",
Array.from(missingLayers)
);
}
upstream.features.addCallback(() => {
update();
});
location
.map((l) => {
// We want something that is stable for the shown layers
const displayedLayerIndexes = [];
for (let i = 0; i < layers.data.length; i++) {
const layer = layers.data[i];
if (l.zoom < layer.layerDef.minzoom) {
continue;
}
if (!layer.isDisplayed.data) {
continue;
}
displayedLayerIndexes.push(i);
}
return displayedLayerIndexes.join(",");
})
.addCallback(() => {
update();
});
layers.addCallback(update);
const registered = new Set<UIEventSource<boolean>>();
layers.addCallbackAndRun((layers) => {
for (const layer of layers) {
if (registered.has(layer.isDisplayed)) {
continue;
}
registered.add(layer.isDisplayed);
layer.isDisplayed.addCallback(() => update());
layer.appliedFilters.addCallback(() => update());
}
});
update();
}
upstream.features.addCallback(() => {
update();
});
location
.map((l) => {
// We want something that is stable for the shown layers
const displayedLayerIndexes = [];
for (let i = 0; i < layers.data.length; i++) {
const layer = layers.data[i];
if (l.zoom < layer.layerDef.minzoom) {
continue;
}
if (l.zoom > layer.layerDef.maxzoom) {
continue;
}
if (!layer.isDisplayed.data) {
continue;
}
displayedLayerIndexes.push(i);
}
return displayedLayerIndexes.join(",");
})
.addCallback(() => {
update();
});
layers.addCallback(update);
const registered = new Set<UIEventSource<boolean>>();
layers.addCallbackAndRun((layers) => {
for (const layer of layers) {
if (registered.has(layer.isDisplayed)) {
continue;
}
registered.add(layer.isDisplayed);
layer.isDisplayed.addCallback(() => update());
layer.appliedFilters.addCallback(() => update());
}
});
update();
}
private static showLayer(
layer: {
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
},
location: UIEventSource<Loc>
) {
return (
layer.isDisplayed.data &&
layer.layerDef.minzoom <= location.data.zoom &&
layer.layerDef.maxzoom >= location.data.zoom
);
}
private static showLayer(
layer: {
isDisplayed: UIEventSource<boolean>;
layerDef: LayerConfig;
},
location: UIEventSource<Loc>
) {
return (
layer.isDisplayed.data &&
layer.layerDef.minzoomVisible <= location.data.zoom
);
}
}

View file

@ -50,7 +50,7 @@ export default class GeoJsonSource implements FeatureSource {
* @param locationControl
* @constructor
*/
public static ConstructMultiSource(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], locationControl: UIEventSource<Loc>): FeatureSource[] {
public static ConstructMultiSource(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], locationControl: UIEventSource<Loc>): GeoJsonSource[] {
const flayersPerSource = new Map<string, { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>();
for (const flayer of flayers) {
@ -65,7 +65,7 @@ export default class GeoJsonSource implements FeatureSource {
flayersPerSource.get(url).push(flayer)
}
const sources: FeatureSource[] = []
const sources: GeoJsonSource[] = []
flayersPerSource.forEach((flayers, key) => {
if (flayers.length == 1) {
@ -118,8 +118,7 @@ export default class GeoJsonSource implements FeatureSource {
return undefined;
}
if (location.zoom < flayer.layerDef.minzoom ||
location.zoom > flayer.layerDef.maxzoom) {
if (location.zoom < flayer.layerDef.minzoom) {
// No need to download! - the layer is disabled
return undefined;
}

View file

@ -1,25 +0,0 @@
import FeatureSource from "./FeatureSource";
import {UIEventSource} from "../UIEventSource";
import LayerConfig from "../../Customizations/JSON/LayerConfig";
export default class ZoomRespectingFeatureSource implements FeatureSource{
public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>;
public readonly name: string;
constructor(layerConfig: LayerConfig, location: UIEventSource<{zoom: number}>, upstream: FeatureSource) {
this.name = "zoomrespecting("+upstream.name+")"
const empty = []
this.features = upstream.features.map(
features => {
const z = location.data.zoom
if(layerConfig.minzoom < z || layerConfig.maxzoom > z){
return empty
}
return features
},[location]
)
}
}