forked from MapComplete/MapComplete
Finish the export functionality: move logic around a bit, add license information for reusers, wire the functionality as feature switch
This commit is contained in:
parent
3001a563d2
commit
abd7db100d
10 changed files with 98 additions and 65 deletions
|
@ -42,6 +42,7 @@ export default class LayoutConfig {
|
|||
public readonly enableGeolocation: boolean;
|
||||
public readonly enableBackgroundLayerSelection: boolean;
|
||||
public readonly enableShowAllQuestions: boolean;
|
||||
public readonly enableExportButton: boolean;
|
||||
public readonly customCss?: string;
|
||||
/*
|
||||
How long is the cache valid, in seconds?
|
||||
|
@ -152,6 +153,7 @@ export default class LayoutConfig {
|
|||
this.enableAddNewPoints = json.enableAddNewPoints ?? true;
|
||||
this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true;
|
||||
this.enableShowAllQuestions = json.enableShowAllQuestions ?? false;
|
||||
this.enableExportButton = json.enableExportButton ?? false;
|
||||
this.customCss = json.customCss;
|
||||
this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60)
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import UnitConfigJson from "./UnitConfigJson";
|
|||
* General remark: a type (string | any) indicates either a fixed or a translatable string.
|
||||
*/
|
||||
export interface LayoutConfigJson {
|
||||
|
||||
/**
|
||||
* The id of this layout.
|
||||
*
|
||||
|
@ -335,4 +336,5 @@ export interface LayoutConfigJson {
|
|||
enableGeolocation?: boolean;
|
||||
enableBackgroundLayerSelection?: boolean;
|
||||
enableShowAllQuestions?: boolean;
|
||||
enableExportButton?: boolean;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,45 @@
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
export default interface FeatureSource {
|
||||
features: UIEventSource<{feature: any, freshness: Date}[]>;
|
||||
features: UIEventSource<{ feature: any, freshness: Date }[]>;
|
||||
/**
|
||||
* Mainly used for debuging
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class FeatureSourceUtils {
|
||||
|
||||
/**
|
||||
* Exports given featurePipeline as a geojson FeatureLists (downloads as a json)
|
||||
* @param featurePipeline The FeaturePipeline you want to export
|
||||
* @param options The options object
|
||||
* @param options.metadata True if you want to include the MapComplete metadata, false otherwise
|
||||
*/
|
||||
public static extractGeoJson(featurePipeline: FeatureSource, options: { metadata?: boolean } = {}) {
|
||||
let defaults = {
|
||||
metadata: false,
|
||||
}
|
||||
options = Utils.setDefaults(options, defaults);
|
||||
|
||||
// Select all features, ignore the freshness and other data
|
||||
let featureList: any[] = featurePipeline.features.data.map((feature) => feature.feature);
|
||||
|
||||
if (!options.metadata) {
|
||||
for (let i = 0; i < featureList.length; i++) {
|
||||
let feature = featureList[i];
|
||||
for (let property in feature.properties) {
|
||||
if (property[0] == "_") {
|
||||
delete featureList[i]["properties"][property];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {type: "FeatureCollection", features: featureList}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import FeaturePipeline from "./FeaturePipeline";
|
||||
import {Utils} from "../../Utils";
|
||||
|
||||
/**
|
||||
* Exports given featurePipeline as a geojson FeatureLists (downloads as a json)
|
||||
* @param featurePipeline The FeaturePipeline you want to export
|
||||
* @param options The options object
|
||||
* @param options.metadata True if you want to include the MapComplete metadata, false otherwise
|
||||
*/
|
||||
export function exportAsGeoJson(featurePipeline: FeaturePipeline, options: { metadata?: boolean} = {}) {
|
||||
let defaults = {
|
||||
metadata: false,
|
||||
}
|
||||
options = Utils.setDefaults(options, defaults);
|
||||
|
||||
// Select all features, ignore the freshness and other data
|
||||
let featureList: JSON[] = featurePipeline ? featurePipeline.features.data.map((feature) => feature.feature) : ["I'm empty"];
|
||||
|
||||
/**
|
||||
* Removes the metadata of MapComplete (all properties starting with an underscore)
|
||||
* @param featureList JsonList containing features, output object
|
||||
*/
|
||||
function removeMetaData(featureList: JSON[]) {
|
||||
for (let i=0; i < featureList.length; i++) {
|
||||
let feature = featureList[i];
|
||||
for (let property in feature.properties) {
|
||||
if (property[0] == "_") {
|
||||
delete featureList[i]["properties"][property];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.metadata) removeMetaData(featureList);
|
||||
|
||||
let geojson = {type: "FeatureCollection", features: featureList}
|
||||
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), "Geodata.json");
|
||||
}
|
10
State.ts
10
State.ts
|
@ -96,6 +96,10 @@ export default class State {
|
|||
public readonly featureSwitchIsDebugging: UIEventSource<boolean>;
|
||||
public readonly featureSwitchShowAllQuestions: UIEventSource<boolean>;
|
||||
public readonly featureSwitchApiURL: UIEventSource<string>;
|
||||
public readonly featureSwitchEnableExport: UIEventSource<boolean>;
|
||||
|
||||
|
||||
|
||||
public readonly featurePipeline: FeaturePipeline;
|
||||
|
||||
|
||||
|
@ -127,7 +131,7 @@ export default class State {
|
|||
public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map<number>(
|
||||
str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n
|
||||
);
|
||||
|
||||
|
||||
constructor(layoutToUse: LayoutConfig) {
|
||||
const self = this;
|
||||
|
||||
|
@ -201,6 +205,8 @@ export default class State {
|
|||
"Disables/Enables the geolocation button");
|
||||
this.featureSwitchShowAllQuestions = featSw("fs-all-questions", (layoutToUse) => layoutToUse?.enableShowAllQuestions ?? false,
|
||||
"Always show all questions");
|
||||
this.featureSwitchEnableExport = featSw("fs-export",(layoutToUse) => layoutToUse?.enableExportButton ?? false,
|
||||
"If set, enables the 'download'-button to download everything as geojson")
|
||||
|
||||
this.featureSwitchIsTesting = QueryParameters.GetQueryParameter("test", "false",
|
||||
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org")
|
||||
|
@ -212,7 +218,7 @@ export default class State {
|
|||
|
||||
this.featureSwitchApiURL = QueryParameters.GetQueryParameter("backend","osm",
|
||||
"The OSM backend to use - can be used to redirect mapcomplete to the testing backend when using 'osm-test'")
|
||||
|
||||
|
||||
}
|
||||
{
|
||||
// Some other feature switches
|
||||
|
|
2
Svg.ts
2
Svg.ts
|
@ -94,7 +94,7 @@ export default class Svg {
|
|||
public static crosshair_empty_svg() { return new Img(Svg.crosshair_empty, true);}
|
||||
public static crosshair_empty_ui() { return new FixedUiElement(Svg.crosshair_empty_img);}
|
||||
|
||||
public static crosshair_locked = " <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" width=\"100\" height=\"100\" viewBox=\"0 0 26.458333 26.458334\" version=\"1.1\" id=\"svg8\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\" sodipodi:docname=\"crosshair-locked.svg\"> <defs id=\"defs2\" /> <sodipodi:namedview id=\"base\" pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1.0\" inkscape:pageopacity=\"0.0\" inkscape:pageshadow=\"2\" inkscape:zoom=\"2.8284271\" inkscape:cx=\"67.47399\" inkscape:cy=\"29.788021\" inkscape:document-units=\"px\" inkscape:current-layer=\"layer1\" showgrid=\"false\" units=\"px\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:snap-global=\"false\"> <sodipodi:guide position=\"13.229167,23.859748\" orientation=\"1,0\" id=\"guide815\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"14.944824,13.229167\" orientation=\"0,1\" id=\"guide817\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata5\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title /> </cc:Work> </rdf:RDF> </metadata> <g inkscape:label=\"Layer 1\" inkscape:groupmode=\"layer\" id=\"layer1\" transform=\"translate(0,-270.54165)\"> <circle style=\"fill: none !important;fill-opacity:1;stroke:#5555ec;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529\" id=\"path815\" cx=\"13.16302\" cy=\"283.77081\" r=\"8.8715391\" /> <path style=\"fill: none !important;stroke:#5555ec;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" d=\"M 3.2841366,283.77082 H 1.0418969\" id=\"path817\" inkscape:connector-curvature=\"0\" /> <path style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" d=\"M 25.405696,283.77082 H 23.286471\" id=\"path817-3\" inkscape:connector-curvature=\"0\" /> <path style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" d=\"m 13.229167,295.9489 v -2.11763\" id=\"path817-3-6\" inkscape:connector-curvature=\"0\" /> <path style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" d=\"m 13.229167,275.05759 v -3.44507\" id=\"path817-3-6-7\" inkscape:connector-curvature=\"0\" /> <g id=\"g824\" transform=\"matrix(0.63953151,0,0,0.64343684,6.732623,276.71502)\" style=\"fill:#ffcc33\"> <path id=\"path822\" d=\"M 16.07,8 H 15 V 5 C 15,5 15,0 10,0 5,0 5,5 5,5 V 8 H 3.93 A 1.93,1.93 0 0 0 2,9.93 v 8.15 A 1.93,1.93 0 0 0 3.93,20 H 16.07 A 1.93,1.93 0 0 0 18,18.07 V 9.93 A 1.93,1.93 0 0 0 16.07,8 Z M 10,16 a 2,2 0 1 1 2,-2 2,2 0 0 1 -2,2 z M 13,8 H 7 V 5.5 C 7,4 7,2 10,2 c 3,0 3,2 3,3.5 z\" inkscape:connector-curvature=\"0\" /> </g> </g> </svg> "
|
||||
public static crosshair_locked = " <!-- Created with Inkscape (http://www.inkscape.org/) --> <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" width=\"100\" height=\"100\" viewBox=\"0 0 26.458333 26.458334\" version=\"1.1\" id=\"svg8\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\" sodipodi:docname=\"crosshair-locked.svg\"> <defs id=\"defs2\" /> <sodipodi:namedview id=\"base\" pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1.0\" inkscape:pageopacity=\"0.0\" inkscape:pageshadow=\"2\" inkscape:zoom=\"5.6568542\" inkscape:cx=\"27.044982\" inkscape:cy=\"77.667126\" inkscape:document-units=\"px\" inkscape:current-layer=\"layer1\" showgrid=\"false\" units=\"px\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:snap-global=\"false\"> <sodipodi:guide position=\"13.229167,23.859748\" orientation=\"1,0\" id=\"guide815\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"14.944824,13.229167\" orientation=\"0,1\" id=\"guide817\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata5\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title /> </cc:Work> </rdf:RDF> </metadata> <g inkscape:label=\"Layer 1\" inkscape:groupmode=\"layer\" id=\"layer1\" transform=\"translate(0,-270.54165)\"> <g id=\"g827\"> <circle r=\"8.8715391\" cy=\"283.77081\" cx=\"13.16302\" id=\"path815\" style=\"fill: none !important;fill-opacity:1;stroke:#5555ec;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529\" /> <path inkscape:connector-curvature=\"0\" id=\"path817\" d=\"M 3.2841366,283.77082 H 1.0418969\" style=\"fill: none !important;stroke:#5555ec;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" /> <path inkscape:connector-curvature=\"0\" id=\"path817-3\" d=\"M 25.405696,283.77082 H 23.286471\" style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" /> <path inkscape:connector-curvature=\"0\" id=\"path817-3-6\" d=\"m 13.229167,295.9489 v -2.11763\" style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" /> <path inkscape:connector-curvature=\"0\" id=\"path817-3-6-7\" d=\"m 13.229167,275.05759 v -3.44507\" style=\"fill: none !important;stroke:#5555ec;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529\" /> </g> <path style=\"fill:#5555ec;fill-opacity:0.98823529;stroke-width:0.6151033\" inkscape:connector-curvature=\"0\" d=\"m 16.850267,281.91543 h -0.65616 v -1.85094 c 0,0 0,-3.08489 -3.066169,-3.08489 -3.066169,0 -3.066169,3.08489 -3.066169,3.08489 v 1.85094 H 9.4056091 a 1.1835412,1.1907685 0 0 0 -1.1835412,1.19077 v 5.02838 a 1.1835412,1.1907685 0 0 0 1.1835412,1.1846 h 7.4446579 a 1.1835412,1.1907685 0 0 0 1.183541,-1.19078 v -5.0222 a 1.1835412,1.1907685 0 0 0 -1.183541,-1.19077 z m -3.722329,4.93583 a 1.2264675,1.233957 0 1 1 1.226468,-1.23395 1.2264675,1.233957 0 0 1 -1.226468,1.23395 z m 1.839702,-4.93583 h -3.679403 v -1.54245 c 0,-0.92546 0,-2.15942 1.839701,-2.15942 1.839702,0 1.839702,1.23396 1.839702,2.15942 z\" id=\"path822\" /> </g> </svg> "
|
||||
public static crosshair_locked_img = Img.AsImageElement(Svg.crosshair_locked)
|
||||
public static crosshair_locked_svg() { return new Img(Svg.crosshair_locked, true);}
|
||||
public static crosshair_locked_ui() { return new FixedUiElement(Svg.crosshair_locked_img);}
|
||||
|
|
21
UI/BigComponents/ExportDataButton.ts
Normal file
21
UI/BigComponents/ExportDataButton.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import Svg from "../../Svg";
|
||||
import Translations from "../i18n/Translations";
|
||||
import State from "../../State";
|
||||
import {FeatureSourceUtils} from "../../Logic/FeatureSource/FeatureSource";
|
||||
import {Utils} from "../../Utils";
|
||||
import Combine from "../Base/Combine";
|
||||
|
||||
export class ExportDataButton extends Combine {
|
||||
constructor() {
|
||||
const t = Translations.t.general.download
|
||||
const button = new SubtleButton(Svg.floppy_ui(), t.downloadGeojson.Clone().SetClass("font-bold"))
|
||||
.onClick(() => {
|
||||
const geojson = FeatureSourceUtils.extractGeoJson(State.state.featurePipeline)
|
||||
const name = State.state.layoutToUse.data.id;
|
||||
Utils.offerContentsAsDownloadableFile(JSON.stringify(geojson), `MapComplete_${name}_export_${new Date().toISOString().substr(0,19)}.geojson`);
|
||||
})
|
||||
|
||||
super([button, t.licenseInfo.Clone().SetClass("link-underline")])
|
||||
}
|
||||
}
|
|
@ -2,11 +2,12 @@ import State from "../../State";
|
|||
import BackgroundSelector from "./BackgroundSelector";
|
||||
import LayerSelection from "./LayerSelection";
|
||||
import Combine from "../Base/Combine";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import {ExportDataButton} from "./ExportDataButton";
|
||||
|
||||
export default class LayerControlPanel extends ScrollableFullScreen {
|
||||
|
||||
|
@ -14,27 +15,34 @@ export default class LayerControlPanel extends ScrollableFullScreen {
|
|||
super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown);
|
||||
}
|
||||
|
||||
private static GenTitle():BaseUIElement {
|
||||
private static GenTitle(): BaseUIElement {
|
||||
return Translations.t.general.layerSelection.title.Clone().SetClass("text-2xl break-words font-bold p-2")
|
||||
}
|
||||
|
||||
private static GeneratePanel() : BaseUIElement {
|
||||
let layerControlPanel: BaseUIElement = new FixedUiElement("");
|
||||
private static GeneratePanel(): BaseUIElement {
|
||||
const elements: BaseUIElement[] = []
|
||||
|
||||
if (State.state.layoutToUse.data.enableBackgroundLayerSelection) {
|
||||
layerControlPanel = new BackgroundSelector();
|
||||
layerControlPanel.SetStyle("margin:1em");
|
||||
layerControlPanel.onClick(() => {
|
||||
const backgroundSelector = new BackgroundSelector();
|
||||
backgroundSelector.SetStyle("margin:1em");
|
||||
backgroundSelector.onClick(() => {
|
||||
});
|
||||
elements.push(backgroundSelector)
|
||||
}
|
||||
|
||||
if (State.state.filteredLayers.data.length > 1) {
|
||||
const layerSelection = new LayerSelection(State.state.filteredLayers);
|
||||
layerSelection.onClick(() => {
|
||||
});
|
||||
layerControlPanel = new Combine([layerSelection, "<br/>", layerControlPanel]);
|
||||
}
|
||||
elements.push(new Toggle(
|
||||
new LayerSelection(State.state.filteredLayers),
|
||||
undefined,
|
||||
State.state.filteredLayers.map(layers => layers.length > 1)
|
||||
))
|
||||
|
||||
return layerControlPanel;
|
||||
elements.push(new Toggle(
|
||||
new ExportDataButton(),
|
||||
undefined,
|
||||
State.state.featureSwitchEnableExport
|
||||
))
|
||||
|
||||
return new Combine(elements).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,6 @@ import Translations from "../i18n/Translations";
|
|||
import LayerConfig from "../../Customizations/JSON/LayerConfig";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import {Translation} from "../i18n/Translation";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import {exportAsGeoJson} from "../../Logic/FeatureSource/GeoJsonExport";
|
||||
|
||||
/**
|
||||
* Shows the panel with all layers and a toggle for each of them
|
||||
|
@ -76,10 +74,6 @@ export default class LayerSelection extends Combine {
|
|||
);
|
||||
}
|
||||
|
||||
const downloadButton = new SubtleButton("./assets/svg/floppy.svg", Translations.t.general.layerSelection.downloadGeojson.Clone())
|
||||
downloadButton.onClick(() => exportAsGeoJson(State.state.featurePipeline))
|
||||
checkboxes.push(downloadButton)
|
||||
|
||||
super(checkboxes)
|
||||
this.SetStyle("display:flex;flex-direction:column;")
|
||||
|
||||
|
|
|
@ -147,8 +147,11 @@
|
|||
"loginOnlyNeededToEdit": "if you want to edit the map",
|
||||
"layerSelection": {
|
||||
"zoomInToSeeThisLayer": "Zoom in to see this layer",
|
||||
"title": "Select layers",
|
||||
"downloadGeojson": "Download layer features as geojson"
|
||||
"title": "Select layers"
|
||||
},
|
||||
"download": {
|
||||
"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"
|
||||
},
|
||||
"weekdays": {
|
||||
"abbreviations": {
|
||||
|
|
Loading…
Reference in a new issue