From f0675a026b14e0bafad9b32d9e94690e61d1bdd8 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 16 Feb 2022 01:34:28 +0100 Subject: [PATCH] Exporting CSV/Geojson now respects filters + refactoring away State.state --- Logic/FeatureSource/FeaturePipeline.ts | 16 ++++++++ UI/BigComponents/AllDownloads.ts | 50 +++++++++++++++++++------ UI/BigComponents/DownloadPanel.ts | 51 ++++++++++++++++++++------ UI/BigComponents/LeftControls.ts | 3 +- 4 files changed, 97 insertions(+), 23 deletions(-) diff --git a/Logic/FeatureSource/FeaturePipeline.ts b/Logic/FeatureSource/FeaturePipeline.ts index af63b314f..bb2382aff 100644 --- a/Logic/FeatureSource/FeaturePipeline.ts +++ b/Logic/FeatureSource/FeaturePipeline.ts @@ -345,6 +345,22 @@ export default class FeaturePipeline { return tiles; } + public GetAllFeaturesAndMetaWithin(bbox: BBox, layerIdWhitelist?: Set): {features: any[], layer: string}[] { + const self = this + const tiles :{features: any[], layer: string}[]= [] + Array.from(this.perLayerHierarchy.keys()) + .forEach(key => { + if(layerIdWhitelist !== undefined && !layerIdWhitelist.has(key)){ + return; + } + return tiles.push({ + layer: key, + features: [].concat(...self.GetFeaturesWithin(key, bbox)) + }); + }) + return tiles; + } + public GetFeaturesWithin(layerId: string, bbox: BBox): any[][] { if (layerId === "*") { return this.GetAllFeaturesWithin(bbox) diff --git a/UI/BigComponents/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts index 2586fef3f..2d2fc6698 100644 --- a/UI/BigComponents/AllDownloads.ts +++ b/UI/BigComponents/AllDownloads.ts @@ -1,4 +1,3 @@ -import State from "../../State"; import Combine from "../Base/Combine"; import ScrollableFullScreen from "../Base/ScrollableFullScreen"; import Translations from "../i18n/Translations"; @@ -9,11 +8,39 @@ import {DownloadPanel} from "./DownloadPanel"; import {SubtleButton} from "../Base/SubtleButton"; import Svg from "../../Svg"; import ExportPDF from "../ExportPDF"; +import FilteredLayer from "../../Models/FilteredLayer"; +import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline"; +import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; +import {BBox} from "../../Logic/BBox"; +import BaseLayer from "../../Models/BaseLayer"; +import Loc from "../../Models/Loc"; + +interface DownloadState { + filteredLayers: UIEventSource + featurePipeline: FeaturePipeline, + layoutToUse: LayoutConfig, + currentBounds: UIEventSource, + backgroundLayer:UIEventSource, + locationControl: UIEventSource, + featureSwitchExportAsPdf: UIEventSource, + featureSwitchEnableExport: UIEventSource, +} + + export default class AllDownloads extends ScrollableFullScreen { - constructor(isShown: UIEventSource) { - super(AllDownloads.GenTitle, AllDownloads.GeneratePanel, "downloads", isShown); + constructor(isShown: UIEventSource,state: { + filteredLayers: UIEventSource + featurePipeline: FeaturePipeline, + layoutToUse: LayoutConfig, + currentBounds: UIEventSource, + backgroundLayer:UIEventSource, + locationControl: UIEventSource, + featureSwitchExportAsPdf: UIEventSource, + featureSwitchEnableExport: UIEventSource, + }) { + super(AllDownloads.GenTitle, () => AllDownloads.GeneratePanel(state), "downloads", isShown); } private static GenTitle(): BaseUIElement { @@ -22,17 +49,18 @@ export default class AllDownloads extends ScrollableFullScreen { .SetClass("text-2xl break-words font-bold p-2"); } - private static GeneratePanel(): BaseUIElement { + private static GeneratePanel(state: DownloadState): BaseUIElement { + const isExporting = new UIEventSource(false, "Pdf-is-exporting") const generatePdf = () => { isExporting.setData(true) new ExportPDF( { freeDivId: "belowmap", - background: State.state.backgroundLayer, - location: State.state.locationControl, - features: State.state.featurePipeline, - layout: State.state.layoutToUse, + background: state.backgroundLayer, + location: state.locationControl, + features: state.featurePipeline, + layout: state.layoutToUse, }).isRunning.addCallbackAndRun(isRunning => isExporting.setData(isRunning)) } @@ -57,13 +85,13 @@ export default class AllDownloads extends ScrollableFullScreen { text), undefined, - State.state.featureSwitchExportAsPdf + state.featureSwitchExportAsPdf ) const exportPanel = new Toggle( - new DownloadPanel(), + new DownloadPanel(state), undefined, - State.state.featureSwitchEnableExport + state.featureSwitchEnableExport ) return new Combine([pdf, exportPanel]).SetClass("flex flex-col"); diff --git a/UI/BigComponents/DownloadPanel.ts b/UI/BigComponents/DownloadPanel.ts index 520473625..fefdeb64a 100644 --- a/UI/BigComponents/DownloadPanel.ts +++ b/UI/BigComponents/DownloadPanel.ts @@ -13,15 +13,16 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; import {BBox} from "../../Logic/BBox"; +import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; export class DownloadPanel extends Toggle { - constructor() { - const state: { - featurePipeline: FeaturePipeline, - layoutToUse: LayoutConfig, - currentBounds: UIEventSource - } = State.state + constructor(state: { + filteredLayers: UIEventSource + featurePipeline: FeaturePipeline, + layoutToUse: LayoutConfig, + currentBounds: UIEventSource + }) { const t = Translations.t.general.download @@ -34,7 +35,7 @@ export class DownloadPanel extends Toggle { const buttonGeoJson = new SubtleButton(Svg.floppy_ui(), new Combine([t.downloadGeojson.Clone().SetClass("font-bold"), t.downloadGeoJsonHelper.Clone()]).SetClass("flex flex-col")) - .onClick(() => { + .OnClickWithLoading(t.exporting,async () => { const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, " "), `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.geojson`, { @@ -46,7 +47,7 @@ export class DownloadPanel extends Toggle { const buttonCSV = new SubtleButton(Svg.floppy_ui(), new Combine( [t.downloadCSV.Clone().SetClass("font-bold"), t.downloadCSVHelper.Clone()]).SetClass("flex flex-col")) - .onClick(() => { + .OnClickWithLoading(t.exporting, async () => { const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) const csv = GeoOperations.toCSV(geojson.features) @@ -72,13 +73,41 @@ export class DownloadPanel extends Toggle { private static getCleanGeoJson(state: { featurePipeline: FeaturePipeline, - currentBounds: UIEventSource + currentBounds: UIEventSource, + filteredLayers: UIEventSource }, includeMetaData: boolean) { const resultFeatures = [] - const featureList = state.featurePipeline.GetAllFeaturesWithin(state.currentBounds.data); + const neededLayers = state.filteredLayers.data.map(l => l.layerDef.id) + const bbox = state.currentBounds.data + const featureList = state.featurePipeline.GetAllFeaturesAndMetaWithin(bbox, new Set(neededLayers)); for (const tile of featureList) { - for (const feature of tile) { + const layer = state.filteredLayers.data.find(fl => fl.layerDef.id === tile.layer) + const filters = layer.appliedFilters.data + for (const feature of tile.features) { + + if(!bbox.overlapsWith(BBox.get(feature))){ + continue + } + + + if (filters !== undefined) { + let featureDoesMatchAllFilters = true; + for (let key of Array.from(filters.keys())) { + const filter: FilterState = filters.get(key) + if(filter?.currentFilter === undefined){ + continue + } + if (!filter.currentFilter.matchesProperties(feature.properties)) { + featureDoesMatchAllFilters = false; + break + } + } + if(!featureDoesMatchAllFilters){ + continue; // the outer loop + } + } + const cleaned = { type: feature.type, geometry: feature.geometry, diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 0681322d4..ea29a3e5a 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -71,7 +71,8 @@ export default class LeftControls extends Combine { const toggledDownload = new Toggle( new AllDownloads( - guiState.downloadControlIsOpened + guiState.downloadControlIsOpened, + state ).SetClass("block p-1 rounded-full md:floating-element-width"), new MapControlButton(Svg.download_svg()) .onClick(() => guiState.downloadControlIsOpened.setData(true)),