forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			143 lines
		
	
	
		
			No EOL
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
		
			No EOL
		
	
	
		
			5.6 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import {SubtleButton} from "../Base/SubtleButton";
 | 
						|
import Svg from "../../Svg";
 | 
						|
import Translations from "../i18n/Translations";
 | 
						|
import State from "../../State";
 | 
						|
import {Utils} from "../../Utils";
 | 
						|
import Combine from "../Base/Combine";
 | 
						|
import CheckBoxes from "../Input/Checkboxes";
 | 
						|
import {GeoOperations} from "../../Logic/GeoOperations";
 | 
						|
import Toggle from "../Input/Toggle";
 | 
						|
import Title from "../Base/Title";
 | 
						|
import FeaturePipeline from "../../Logic/FeatureSource/FeaturePipeline";
 | 
						|
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(state: {
 | 
						|
        filteredLayers: UIEventSource<FilteredLayer[]>
 | 
						|
        featurePipeline: FeaturePipeline,
 | 
						|
        layoutToUse: LayoutConfig,
 | 
						|
        currentBounds: UIEventSource<BBox>
 | 
						|
    }) {
 | 
						|
 | 
						|
 | 
						|
        const t = Translations.t.general.download
 | 
						|
        const name = State.state.layoutToUse.id;
 | 
						|
 | 
						|
        const includeMetaToggle = new CheckBoxes([t.includeMetaData.Clone()])
 | 
						|
        const metaisIncluded = includeMetaToggle.GetValue().map(selected => selected.length > 0)
 | 
						|
 | 
						|
 | 
						|
        const buttonGeoJson = new SubtleButton(Svg.floppy_ui(),
 | 
						|
            new Combine([t.downloadGeojson.Clone().SetClass("font-bold"),
 | 
						|
                t.downloadGeoJsonHelper.Clone()]).SetClass("flex flex-col"))
 | 
						|
            .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`, {
 | 
						|
                        mimetype: "application/vnd.geo+json"
 | 
						|
                    });
 | 
						|
            })
 | 
						|
 | 
						|
 | 
						|
        const buttonCSV = new SubtleButton(Svg.floppy_ui(), new Combine(
 | 
						|
            [t.downloadCSV.Clone().SetClass("font-bold"),
 | 
						|
                t.downloadCSVHelper.Clone()]).SetClass("flex flex-col"))
 | 
						|
            .OnClickWithLoading(t.exporting, async () => {
 | 
						|
                const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data)
 | 
						|
                const csv = GeoOperations.toCSV(geojson.features)
 | 
						|
 | 
						|
                Utils.offerContentsAsDownloadableFile(csv,
 | 
						|
                    `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.csv`, {
 | 
						|
                        mimetype: "text/csv"
 | 
						|
                    });
 | 
						|
            })
 | 
						|
 | 
						|
        const downloadButtons = new Combine(
 | 
						|
            [new Title(t.title),
 | 
						|
                buttonGeoJson,
 | 
						|
                buttonCSV,
 | 
						|
                includeMetaToggle,
 | 
						|
                t.licenseInfo.Clone().SetClass("link-underline")])
 | 
						|
            .SetClass("w-full flex flex-col border-4 border-gray-300 rounded-3xl p-4")
 | 
						|
 | 
						|
        super(
 | 
						|
            downloadButtons,
 | 
						|
            t.noDataLoaded.Clone(),
 | 
						|
            state.featurePipeline.somethingLoaded)
 | 
						|
    }
 | 
						|
 | 
						|
    private static getCleanGeoJson(state: {
 | 
						|
        featurePipeline: FeaturePipeline,
 | 
						|
        currentBounds: UIEventSource<BBox>,
 | 
						|
        filteredLayers: UIEventSource<FilteredLayer[]>
 | 
						|
    }, includeMetaData: boolean) {
 | 
						|
 | 
						|
        const resultFeatures = []
 | 
						|
        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) {
 | 
						|
            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,
 | 
						|
                    properties: {...feature.properties}
 | 
						|
                }
 | 
						|
 | 
						|
                if (!includeMetaData) {
 | 
						|
                    for (const key in cleaned.properties) {
 | 
						|
                        if (key === "_lon" || key === "_lat") {
 | 
						|
                            continue;
 | 
						|
                        }
 | 
						|
                        if (key.startsWith("_")) {
 | 
						|
                            delete feature.properties[key]
 | 
						|
                        }
 | 
						|
                    }
 | 
						|
                }
 | 
						|
 | 
						|
                const datedKeys = [].concat(SimpleMetaTagger.metatags.filter(tagging => tagging.includesDates).map(tagging => tagging.keys))
 | 
						|
                for (const key of datedKeys) {
 | 
						|
                    delete feature.properties[key]
 | 
						|
                }
 | 
						|
 | 
						|
                resultFeatures.push(feature)
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return {
 | 
						|
            type: "FeatureCollection",
 | 
						|
            features: resultFeatures
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
} |