forked from MapComplete/MapComplete
Finishing touches to export functionality, enable it in cycle_infra
This commit is contained in:
parent
e0b71ca53e
commit
13b2c1b572
5 changed files with 100 additions and 15 deletions
|
@ -273,14 +273,61 @@ export class GeoOperations {
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the closest point on a way from a given point
|
* Generates the closest point on a way from a given point
|
||||||
* @param way The road on which you want to find a point
|
* @param way The road on which you want to find a point
|
||||||
* @param point Point defined as [lon, lat]
|
* @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"});
|
return turf.nearestPointOnLine(way, point, {units: "kilometers"});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static toCSV(features: any[]): string {
|
||||||
|
|
||||||
|
const headerValuesSeen = new Set<string>();
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,49 @@ import State from "../../State";
|
||||||
import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource";
|
import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import Combine from "../Base/Combine";
|
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() {
|
constructor() {
|
||||||
const t = Translations.t.general.download
|
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(() => {
|
.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;
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
14
Utils.ts
14
Utils.ts
|
@ -135,7 +135,7 @@ export class Utils {
|
||||||
}
|
}
|
||||||
return newArr;
|
return newArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MergeTags(a: any, b: any) {
|
public static MergeTags(a: any, b: any) {
|
||||||
const t = {};
|
const t = {};
|
||||||
for (const k in a) {
|
for (const k in a) {
|
||||||
|
@ -356,12 +356,12 @@ export class Utils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a 'download file' popup which will download the contents
|
* 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 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.href = URL.createObjectURL(file);
|
||||||
element.download = fileName;
|
element.download = fileName;
|
||||||
document.body.appendChild(element); // Required for this to work in FireFox
|
document.body.appendChild(element); // Required for this to work in FireFox
|
||||||
|
@ -449,8 +449,8 @@ export class Utils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static setDefaults(options, defaults){
|
public static setDefaults(options, defaults) {
|
||||||
for (let key in defaults){
|
for (let key in defaults) {
|
||||||
if (!(key in options)) options[key] = defaults[key];
|
if (!(key in options)) options[key] = defaults[key];
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
"startZoom": 11,
|
"startZoom": 11,
|
||||||
"widenFactor": 0.05,
|
"widenFactor": 0.05,
|
||||||
"socialImage": "./assets/themes/cycle_infra/cycle-infra.svg",
|
"socialImage": "./assets/themes/cycle_infra/cycle-infra.svg",
|
||||||
"enableDownload": true,
|
"enableExportButton": true,
|
||||||
"layers": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"id": "cycleways",
|
"id": "cycleways",
|
||||||
|
|
|
@ -150,8 +150,14 @@
|
||||||
"title": "Select layers"
|
"title": "Select layers"
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
|
"title": "Download visible data",
|
||||||
"downloadGeojson": "Download visible data as geojson",
|
"downloadGeojson": "Download visible data as geojson",
|
||||||
"licenseInfo": "<h3>Copyright notice</h3>The provided is available under ODbL. Reusing this data is free for any purpose, but <ul><li>the attribution <b>© OpenStreetMap contributors</b></li><li>Any change to this data must be republished under the same license</li></ul>. Please see the full <a href='https://www.openstreetmap.org/copyright' target='_blank'>copyright notice</a> 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": "<h3>Copyright notice</h3>The provided is available under ODbL. Reusing this data is free for any purpose, but <ul><li>the attribution <b>© OpenStreetMap contributors</b> is required</li><li>Any change to this data must be republished under the same license</li></ul> Please read the full <a href='https://www.openstreetmap.org/copyright' target='_blank'>copyright notice</a> for details",
|
||||||
|
"noDataLoaded": "No data is loaded yet. Download will be available soon"
|
||||||
},
|
},
|
||||||
"weekdays": {
|
"weekdays": {
|
||||||
"abbreviations": {
|
"abbreviations": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue