forked from MapComplete/MapComplete
Finetuning of the filter functionality
This commit is contained in:
parent
31d2bd83b9
commit
79569f5119
17 changed files with 219 additions and 309 deletions
|
@ -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 }[] = [];
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue