From 13b2c1b572406111128ffdd6c47f89a42920f6c1 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Mon, 26 Jul 2021 20:59:55 +0200 Subject: [PATCH] Finishing touches to export functionality, enable it in cycle_infra --- Logic/GeoOperations.ts | 49 +++++++++++++++++++++- UI/BigComponents/ExportDataButton.ts | 42 ++++++++++++++++--- Utils.ts | 14 +++---- assets/themes/cycle_infra/cycle_infra.json | 2 +- langs/en.json | 8 +++- 5 files changed, 100 insertions(+), 15 deletions(-) diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index 768a5fe24b..b1c66d12c4 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -273,14 +273,61 @@ export class GeoOperations { } return undefined; } + /** * Generates the closest point on a way from a given point * @param way The road on which you want to find a point * @param point Point defined as [lon, lat] */ - public static nearestPoint(way, point: [number, number]){ + public static nearestPoint(way, point: [number, number]) { return turf.nearestPointOnLine(way, point, {units: "kilometers"}); } + + public static toCSV(features: any[]): string { + + const headerValuesSeen = new Set(); + const headerValuesOrdered: string[] = [] + + function addH(key) { + if (!headerValuesSeen.has(key)) { + headerValuesSeen.add(key) + headerValuesOrdered.push(key) + } + } + + addH("_lat") + addH("_lon") + + const lines: string[] = [] + + for (const feature of features) { + const properties = feature.properties; + for (const key in properties) { + if (!properties.hasOwnProperty(key)) { + continue; + } + addH(key) + + } + } + headerValuesOrdered.sort() + for (const feature of features) { + const properties = feature.properties; + let line = "" + for (const key of headerValuesOrdered) { + const value = properties[key] + if (value === undefined) { + line += "," + } else { + line += JSON.stringify(value)+"," + } + } + lines.push(line) + } + + return headerValuesOrdered.map(v => JSON.stringify(v)).join(",") + "\n" + lines.join("\n") + } + } diff --git a/UI/BigComponents/ExportDataButton.ts b/UI/BigComponents/ExportDataButton.ts index 9a161de9fd..4e93503e87 100644 --- a/UI/BigComponents/ExportDataButton.ts +++ b/UI/BigComponents/ExportDataButton.ts @@ -5,17 +5,49 @@ import State from "../../State"; import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource"; 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"; -export class ExportDataButton extends Combine { +export class ExportDataButton extends Toggle { constructor() { const t = Translations.t.general.download - const button = new SubtleButton(Svg.floppy_ui(), t.downloadGeojson.Clone().SetClass("font-bold")) + const somethingLoaded = State.state.featurePipeline.features.map(features => features.length > 0); + 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")) .onClick(() => { - const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline) + const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline, {metadata: metaisIncluded.data}) const name = State.state.layoutToUse.data.id; - Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), `MapComplete_${name}_export_${new Date().toISOString().substr(0,19)}.geojson`); + Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), + `MapComplete_${name}_export_${new Date().toISOString().substr(0,19)}.geojson`); }) - super([button, t.licenseInfo.Clone().SetClass("link-underline")]) + const buttonCSV = new SubtleButton(Svg.floppy_ui(), new Combine( + [t.downloadCSV.Clone().SetClass("font-bold"), + t.downloadCSVHelper.Clone()]).SetClass("flex flex-col")) + .onClick(() => { + const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline, {metadata: 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(), + somethingLoaded) } } \ No newline at end of file diff --git a/Utils.ts b/Utils.ts index f16f987af2..30d413b17b 100644 --- a/Utils.ts +++ b/Utils.ts @@ -135,7 +135,7 @@ export class Utils { } return newArr; } - + public static MergeTags(a: any, b: any) { const t = {}; for (const k in a) { @@ -356,12 +356,12 @@ export class Utils { /** * Triggers a 'download file' popup which will download the contents - * @param contents - * @param fileName */ - public static offerContentsAsDownloadableFile(contents: string, fileName: string = "download.txt") { + public static offerContentsAsDownloadableFile(contents: string, fileName: string = "download.txt", options?: { + mimetype: string + }) { const element = document.createElement("a"); - const file = new Blob([contents], {type: 'text/plain'}); + const file = new Blob([contents], {type: options?.mimetype ?? 'text/plain'}); element.href = URL.createObjectURL(file); element.download = fileName; document.body.appendChild(element); // Required for this to work in FireFox @@ -449,8 +449,8 @@ export class Utils { } } - public static setDefaults(options, defaults){ - for (let key in defaults){ + public static setDefaults(options, defaults) { + for (let key in defaults) { if (!(key in options)) options[key] = defaults[key]; } return options; diff --git a/assets/themes/cycle_infra/cycle_infra.json b/assets/themes/cycle_infra/cycle_infra.json index 4925087d8d..f6bf2f4775 100644 --- a/assets/themes/cycle_infra/cycle_infra.json +++ b/assets/themes/cycle_infra/cycle_infra.json @@ -25,7 +25,7 @@ "startZoom": 11, "widenFactor": 0.05, "socialImage": "./assets/themes/cycle_infra/cycle-infra.svg", - "enableDownload": true, + "enableExportButton": true, "layers": [ { "id": "cycleways", diff --git a/langs/en.json b/langs/en.json index bc1098e34d..5eca70ca4c 100644 --- a/langs/en.json +++ b/langs/en.json @@ -150,8 +150,14 @@ "title": "Select layers" }, "download": { + "title": "Download visible data", "downloadGeojson": "Download visible data as geojson", - "licenseInfo": "

Copyright notice

The provided is available under ODbL. Reusing this data is free for any purpose, but . Please see the full copyright notice for details" + "downloadGeoJsonHelper": "Compatible with QGIS, OsmAnd, ArcGIS, ESRI, ...", + "downloadCSV": "Download as CSV", + "downloadCSVHelper": "Compatible with LibreOffice Calc, Excel, ...", + "includeMetaData": "Include metadata (last editor, calculated values, ...)", + "licenseInfo": "

Copyright notice

The provided is available under ODbL. Reusing this data is free for any purpose, but Please read the full copyright notice for details", + "noDataLoaded": "No data is loaded yet. Download will be available soon" }, "weekdays": { "abbreviations": {