forked from MapComplete/MapComplete
		
	Exporting CSV/Geojson now respects filters + refactoring away State.state
This commit is contained in:
		
							parent
							
								
									5696b80a5e
								
							
						
					
					
						commit
						f0675a026b
					
				
					 4 changed files with 97 additions and 23 deletions
				
			
		|  | @ -345,6 +345,22 @@ export default class FeaturePipeline { | ||||||
|         return tiles; |         return tiles; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public GetAllFeaturesAndMetaWithin(bbox: BBox, layerIdWhitelist?: Set<string>): {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[][] { |     public GetFeaturesWithin(layerId: string, bbox: BBox): any[][] { | ||||||
|         if (layerId === "*") { |         if (layerId === "*") { | ||||||
|             return this.GetAllFeaturesWithin(bbox) |             return this.GetAllFeaturesWithin(bbox) | ||||||
|  |  | ||||||
|  | @ -1,4 +1,3 @@ | ||||||
| import State from "../../State"; |  | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
|  | @ -9,11 +8,39 @@ import {DownloadPanel} from "./DownloadPanel"; | ||||||
| import {SubtleButton} from "../Base/SubtleButton"; | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
| import Svg from "../../Svg"; | import Svg from "../../Svg"; | ||||||
| import ExportPDF from "../ExportPDF"; | 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<FilteredLayer[]> | ||||||
|  |     featurePipeline: FeaturePipeline, | ||||||
|  |     layoutToUse: LayoutConfig, | ||||||
|  |     currentBounds: UIEventSource<BBox>, | ||||||
|  |     backgroundLayer:UIEventSource<BaseLayer>, | ||||||
|  |     locationControl: UIEventSource<Loc>, | ||||||
|  |     featureSwitchExportAsPdf: UIEventSource<boolean>, | ||||||
|  |     featureSwitchEnableExport: UIEventSource<boolean>, | ||||||
|  | } | ||||||
|  |      | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| export default class AllDownloads extends ScrollableFullScreen { | export default class AllDownloads extends ScrollableFullScreen { | ||||||
| 
 | 
 | ||||||
|     constructor(isShown: UIEventSource<boolean>) { |     constructor(isShown: UIEventSource<boolean>,state: { | ||||||
|         super(AllDownloads.GenTitle, AllDownloads.GeneratePanel, "downloads", isShown); |         filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|  |         featurePipeline: FeaturePipeline, | ||||||
|  |         layoutToUse: LayoutConfig, | ||||||
|  |         currentBounds: UIEventSource<BBox>, | ||||||
|  |         backgroundLayer:UIEventSource<BaseLayer>, | ||||||
|  |         locationControl: UIEventSource<Loc>, | ||||||
|  |         featureSwitchExportAsPdf: UIEventSource<boolean>, | ||||||
|  |         featureSwitchEnableExport: UIEventSource<boolean>, | ||||||
|  |     }) { | ||||||
|  |         super(AllDownloads.GenTitle, () => AllDownloads.GeneratePanel(state), "downloads", isShown); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static GenTitle(): BaseUIElement { |     private static GenTitle(): BaseUIElement { | ||||||
|  | @ -22,17 +49,18 @@ export default class AllDownloads extends ScrollableFullScreen { | ||||||
|             .SetClass("text-2xl break-words font-bold p-2"); |             .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 isExporting = new UIEventSource(false, "Pdf-is-exporting") | ||||||
|         const generatePdf = () => { |         const generatePdf = () => { | ||||||
|             isExporting.setData(true) |             isExporting.setData(true) | ||||||
|             new ExportPDF( |             new ExportPDF( | ||||||
|                 { |                 { | ||||||
|                     freeDivId: "belowmap", |                     freeDivId: "belowmap", | ||||||
|                     background: State.state.backgroundLayer, |                     background: state.backgroundLayer, | ||||||
|                     location: State.state.locationControl, |                     location: state.locationControl, | ||||||
|                     features: State.state.featurePipeline, |                     features: state.featurePipeline, | ||||||
|                     layout: State.state.layoutToUse, |                     layout: state.layoutToUse, | ||||||
|                 }).isRunning.addCallbackAndRun(isRunning => isExporting.setData(isRunning)) |                 }).isRunning.addCallbackAndRun(isRunning => isExporting.setData(isRunning)) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -57,13 +85,13 @@ export default class AllDownloads extends ScrollableFullScreen { | ||||||
|                 text), |                 text), | ||||||
|             undefined, |             undefined, | ||||||
| 
 | 
 | ||||||
|             State.state.featureSwitchExportAsPdf |             state.featureSwitchExportAsPdf | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         const exportPanel = new Toggle( |         const exportPanel = new Toggle( | ||||||
|             new DownloadPanel(), |             new DownloadPanel(state), | ||||||
|             undefined, |             undefined, | ||||||
|             State.state.featureSwitchEnableExport |             state.featureSwitchEnableExport | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         return new Combine([pdf, exportPanel]).SetClass("flex flex-col"); |         return new Combine([pdf, exportPanel]).SetClass("flex flex-col"); | ||||||
|  |  | ||||||
|  | @ -13,15 +13,16 @@ import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
| import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; | import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"; | ||||||
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {BBox} from "../../Logic/BBox"; | import {BBox} from "../../Logic/BBox"; | ||||||
|  | import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | ||||||
| 
 | 
 | ||||||
| export class DownloadPanel extends Toggle { | export class DownloadPanel extends Toggle { | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor(state: { | ||||||
|         const state: { |         filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|             featurePipeline: FeaturePipeline, |         featurePipeline: FeaturePipeline, | ||||||
|             layoutToUse: LayoutConfig, |         layoutToUse: LayoutConfig, | ||||||
|             currentBounds: UIEventSource<BBox> |         currentBounds: UIEventSource<BBox> | ||||||
|         } = State.state |     }) { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const t = Translations.t.general.download |         const t = Translations.t.general.download | ||||||
|  | @ -34,7 +35,7 @@ export class DownloadPanel extends Toggle { | ||||||
|         const buttonGeoJson = new SubtleButton(Svg.floppy_ui(), |         const buttonGeoJson = new SubtleButton(Svg.floppy_ui(), | ||||||
|             new Combine([t.downloadGeojson.Clone().SetClass("font-bold"), |             new Combine([t.downloadGeojson.Clone().SetClass("font-bold"), | ||||||
|                 t.downloadGeoJsonHelper.Clone()]).SetClass("flex flex-col")) |                 t.downloadGeoJsonHelper.Clone()]).SetClass("flex flex-col")) | ||||||
|             .onClick(() => { |             .OnClickWithLoading(t.exporting,async () => { | ||||||
|                 const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) |                 const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) | ||||||
|                 Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, "  "), |                 Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson, null, "  "), | ||||||
|                     `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.geojson`, { |                     `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( |         const buttonCSV = new SubtleButton(Svg.floppy_ui(), new Combine( | ||||||
|             [t.downloadCSV.Clone().SetClass("font-bold"), |             [t.downloadCSV.Clone().SetClass("font-bold"), | ||||||
|                 t.downloadCSVHelper.Clone()]).SetClass("flex flex-col")) |                 t.downloadCSVHelper.Clone()]).SetClass("flex flex-col")) | ||||||
|             .onClick(() => { |             .OnClickWithLoading(t.exporting, async () => { | ||||||
|                 const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) |                 const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) | ||||||
|                 const csv = GeoOperations.toCSV(geojson.features) |                 const csv = GeoOperations.toCSV(geojson.features) | ||||||
| 
 | 
 | ||||||
|  | @ -72,13 +73,41 @@ export class DownloadPanel extends Toggle { | ||||||
| 
 | 
 | ||||||
|     private static getCleanGeoJson(state: { |     private static getCleanGeoJson(state: { | ||||||
|         featurePipeline: FeaturePipeline, |         featurePipeline: FeaturePipeline, | ||||||
|         currentBounds: UIEventSource<BBox> |         currentBounds: UIEventSource<BBox>, | ||||||
|  |         filteredLayers: UIEventSource<FilteredLayer[]> | ||||||
|     }, includeMetaData: boolean) { |     }, includeMetaData: boolean) { | ||||||
| 
 | 
 | ||||||
|         const resultFeatures = [] |         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 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 = { |                 const cleaned = { | ||||||
|                     type: feature.type, |                     type: feature.type, | ||||||
|                     geometry: feature.geometry, |                     geometry: feature.geometry, | ||||||
|  |  | ||||||
|  | @ -71,7 +71,8 @@ export default class LeftControls extends Combine { | ||||||
| 
 | 
 | ||||||
|         const toggledDownload = new Toggle( |         const toggledDownload = new Toggle( | ||||||
|             new AllDownloads( |             new AllDownloads( | ||||||
|                 guiState.downloadControlIsOpened |                 guiState.downloadControlIsOpened, | ||||||
|  |                 state | ||||||
|             ).SetClass("block p-1 rounded-full md:floating-element-width"), |             ).SetClass("block p-1 rounded-full md:floating-element-width"), | ||||||
|             new MapControlButton(Svg.download_svg()) |             new MapControlButton(Svg.download_svg()) | ||||||
|                 .onClick(() => guiState.downloadControlIsOpened.setData(true)), |                 .onClick(() => guiState.downloadControlIsOpened.setData(true)), | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue