From ede67ca58c53951a95d85d2af0ef4407a3723c32 Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Wed, 28 Jul 2021 02:51:07 +0200 Subject: [PATCH] Fix pdf export, fix feature switches --- Customizations/JSON/LayoutConfig.ts | 7 +- Customizations/JSON/LayoutConfigJson.ts | 4 +- InitUiElements.ts | 44 ++++--- Logic/Actors/ExportPDF.ts | 38 ------ Logic/Actors/PDFLayout.ts | 26 ---- Logic/GeoOperations.ts | 45 ++++--- State.ts | 20 ++- UI/Base/Minimap.ts | 27 +++- UI/BigComponents/LayerControlPanel.ts | 14 +- UI/ExportPDF.ts | 145 +++++++++++++++++++++ UI/Popup/SplitRoadWizard.ts | 4 +- UI/ShowDataLayer.ts | 7 +- assets/svg/download.svg | 68 +++++++++- assets/svg/license_info.json | 10 ++ assets/themes/cycle_infra/cycle_infra.json | 18 +-- assets/themes/natuurpunt/natuurpunt.json | 2 + index.html | 5 +- langs/en.json | 3 + test.ts | 47 +++++++ 19 files changed, 390 insertions(+), 144 deletions(-) delete mode 100644 Logic/Actors/ExportPDF.ts delete mode 100644 Logic/Actors/PDFLayout.ts create mode 100644 UI/ExportPDF.ts diff --git a/Customizations/JSON/LayoutConfig.ts b/Customizations/JSON/LayoutConfig.ts index bda08db56..8032701ea 100644 --- a/Customizations/JSON/LayoutConfig.ts +++ b/Customizations/JSON/LayoutConfig.ts @@ -43,6 +43,8 @@ export default class LayoutConfig { public readonly enableBackgroundLayerSelection: boolean; public readonly enableShowAllQuestions: boolean; public readonly enableExportButton: boolean; + public readonly enablePdfDownload: boolean; + public readonly customCss?: string; /* How long is the cache valid, in seconds? @@ -153,7 +155,8 @@ 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.enableExportButton = json.enableDownload ?? false; + this.enablePdfDownload = json.enablePdfDownload ?? false; this.customCss = json.customCss; this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) @@ -176,7 +179,7 @@ export default class LayoutConfig { return } } else { - console.log("Layer ", layer," not kown, try one of", Array.from(AllKnownLayers.sharedLayers.keys()).join(", ")) + console.log("Layer ", layer, " not kown, try one of", Array.from(AllKnownLayers.sharedLayers.keys()).join(", ")) throw `Unknown builtin layer ${layer} at ${context}.layers[${i}]`; } } diff --git a/Customizations/JSON/LayoutConfigJson.ts b/Customizations/JSON/LayoutConfigJson.ts index d36a8463d..c7dda8468 100644 --- a/Customizations/JSON/LayoutConfigJson.ts +++ b/Customizations/JSON/LayoutConfigJson.ts @@ -336,5 +336,7 @@ export interface LayoutConfigJson { enableGeolocation?: boolean; enableBackgroundLayerSelection?: boolean; enableShowAllQuestions?: boolean; - enableExportButton?: boolean; + enableDownload?: boolean; + enablePdfDownload: boolean; + } diff --git a/InitUiElements.ts b/InitUiElements.ts index 494b00014..389171d2d 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -40,9 +40,9 @@ import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; -import ExportPDF from "./Logic/Actors/ExportPDF"; import {TagsFilter} from "./Logic/Tags/TagsFilter"; import FilterView from "./UI/BigComponents/FilterView"; +import ExportPDF from "./UI/ExportPDF"; export class InitUiElements { static InitAll( @@ -198,7 +198,7 @@ export class InitUiElements { State.state.leafletMap, State.state.layoutToUse ), { - dontStyle : true + dontStyle: true } ), undefined, @@ -206,28 +206,21 @@ export class InitUiElements { ); const plus = new MapControlButton( - Svg.plus_zoom_svg() + Svg.plus_zoom_svg() ).onClick(() => { State.state.locationControl.data.zoom++; State.state.locationControl.ping(); }); const min = new MapControlButton( - Svg.min_zoom_svg() + Svg.min_zoom_svg() ).onClick(() => { State.state.locationControl.data.zoom--; State.state.locationControl.ping(); }); - const screenshot = new MapControlButton( - Svg.bug_svg(), - ).onClick(() => { - // Will already export - new ExportPDF("Screenshot", "natuurpunt"); - - }) - new Combine([plus, min, geolocationButton, screenshot].map(el => el.SetClass("m-0.5 md:m-1"))) + new Combine([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) .SetClass("flex flex-col") .AttachTo("bottom-right"); @@ -374,17 +367,17 @@ export class InitUiElements { ); - const filterView = + const filterView = new ScrollableFullScreen( () => Translations.t.general.layerSelection.title.Clone(), - () => + () => new FilterView(State.state.filteredLayers).SetClass( "block p-1 rounded-full" ), undefined, State.state.filterIsOpened ); - + const filterMapControlButton = new MapControlButton( Svg.filter_svg() @@ -402,7 +395,26 @@ export class InitUiElements { State.state.featureSwitchFilter ); - new Combine([copyrightButton, layerControl, filterControl]) + const screenshot = + new Toggle( + new MapControlButton( + Svg.download_svg(), + ).onClick(() => { + // Will already export + new ExportPDF( + { + freeDivId: "belowmap", + background: State.state.backgroundLayer, + location: State.state.locationControl, + features: State.state.featurePipeline.features, + layout: State.state.layoutToUse, + } + ); + + }), undefined, State.state.featureSwitchExportAsPdf) + + + new Combine([filterControl, layerControl, screenshot, copyrightButton,]) .SetClass("flex flex-col") .AttachTo("bottom-left"); diff --git a/Logic/Actors/ExportPDF.ts b/Logic/Actors/ExportPDF.ts deleted file mode 100644 index 78316be72..000000000 --- a/Logic/Actors/ExportPDF.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Creates screenshoter to take png screenshot - * Creates jspdf and downloads it - * - landscape pdf - * - * To add new layout: - * - add new possible layout name in constructor - * - add new layout in "PDFLayout" - * -> in there are more instructions - */ - - import jsPDF from "jspdf"; - import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter"; - import State from "../../State"; - import { PDFLayout } from "./PDFLayout"; - - export default class ExportPDF { - constructor( - name: string, - layout: "natuurpunt" - ){ - const screenshotter = new SimpleMapScreenshoter(); - //minimap op index.html -> hidden daar alles op doen en dan weg - //minimap - leaflet map ophalen - boundaries ophalen - State.state.featurePipeline - screenshotter.addTo(State.state.leafletMap.data); - let doc = new jsPDF('landscape'); - console.log("Taking screenshot") - screenshotter.takeScreen('image').then(image => { - if(!(image instanceof Blob)){ - alert("Exporting failed :(") - return; - } - let file = new PDFLayout(); - file.AddLayout(layout, doc, image); - doc.save(name); - }) - } - } diff --git a/Logic/Actors/PDFLayout.ts b/Logic/Actors/PDFLayout.ts deleted file mode 100644 index db4ddd66d..000000000 --- a/Logic/Actors/PDFLayout.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Adds a theme to the pdf - * - * To add new layout: (first check ExportPDF.ts) - * - in AddLayout() -> add new name for your layout - * AddLayout(layout: "natuurpunt" ...) => AddLayout(layout: "natuurpunt" | "newlayout" ...) - * - add if statement that checks which layout you want - * - add new function to change the pdf layout - */ - - import jsPDF from "jspdf"; - - export class PDFLayout { - public AddLayout(layout: "natuurpunt", doc: jsPDF, image: Blob){ - if(layout === "natuurpunt") this.AddNatuurpuntLayout(doc, image); - } - public AddNatuurpuntLayout(doc: jsPDF, image: Blob){ - // Add Natuurpunt layout - const screenRatio = screen.width/screen.height; - let img = document.createElement('img'); - img.src = './assets/themes/natuurpunt/natuurpunt.png'; - doc.addImage(img, 'PNG', 15, 5, 20, 20); - doc.addImage(image, 'PNG', 15, 30, 150*screenRatio, 150); - return doc; - } - } diff --git a/Logic/GeoOperations.ts b/Logic/GeoOperations.ts index a513bb8e0..be0c1d526 100644 --- a/Logic/GeoOperations.ts +++ b/Logic/GeoOperations.ts @@ -322,7 +322,7 @@ export class GeoOperations { if (value === undefined) { line += "," } else { - line += JSON.stringify(value)+"," + line += JSON.stringify(value) + "," } } lines.push(line) @@ -334,7 +334,7 @@ export class GeoOperations { } -class BBox { +export class BBox { readonly maxLat: number; readonly maxLon: number; @@ -357,33 +357,20 @@ class BBox { this.check(); } + static fromLeafletBounds(bounds) { + return new BBox([[bounds.getWest(), bounds.getNorth()], [bounds.getEast(), bounds.getSouth()]]) + } + static get(feature) { if (feature.bbox?.overlapsWith === undefined) { - - if (feature.geometry.type === "MultiPolygon") { - let coordinates = []; - for (const coorlist of feature.geometry.coordinates) { - coordinates = coordinates.concat(coorlist[0]); - } - feature.bbox = new BBox(coordinates); - } else if (feature.geometry.type === "Polygon") { - feature.bbox = new BBox(feature.geometry.coordinates[0]); - } else if (feature.geometry.type === "LineString") { - feature.bbox = new BBox(feature.geometry.coordinates); - } else if (feature.geometry.type === "Point") { - // Point - feature.bbox = new BBox([feature.geometry.coordinates]); - } else { - throw "Cannot calculate bbox, unknown type " + feature.geometry.type; - } + const turfBbox: number[] = turf.bbox(feature) + feature.bbox = new BBox([[turfBbox[0], turfBbox[1]],[turfBbox[2], turfBbox[3]]]); } return feature.bbox; } public overlapsWith(other: BBox) { - this.check(); - other.check(); if (this.maxLon < other.minLon) { return false; } @@ -397,6 +384,22 @@ class BBox { } + public isContainedIn(other: BBox) { + if (this.maxLon > other.maxLon) { + return false; + } + if (this.maxLat > other.maxLat) { + return false; + } + if (this.minLon < other.minLon) { + return false; + } + if (this.minLat < other.minLat) { + return false + } + return true; + } + private check() { if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) { console.log(this); diff --git a/State.ts b/State.ts index fe2e2fdf7..ac0c4a390 100644 --- a/State.ts +++ b/State.ts @@ -61,7 +61,7 @@ export default class State { public osmApiFeatureSource: OsmApiFeatureSource; - public filteredLayers: UIEventSource = new UIEventSource([],"filteredLayers"); + public filteredLayers: UIEventSource = new UIEventSource([], "filteredLayers"); /** The latest element that was selected @@ -93,7 +93,7 @@ export default class State { public readonly featureSwitchFilter: UIEventSource; public readonly featureSwitchEnableExport: UIEventSource; public readonly featureSwitchFakeUser: UIEventSource; - + public readonly featureSwitchExportAsPdf: UIEventSource; public featurePipeline: FeaturePipeline; @@ -295,6 +295,17 @@ export default class State { "Always show all questions" ); + this.featureSwitchEnableExport = featSw( + "fs-export", + (layoutToUse) => layoutToUse?.enableExportButton ?? false, + "Enable the export as GeoJSON and CSV button" + ); + this.featureSwitchExportAsPdf = featSw( + "fs-pdf", + (layoutToUse) => layoutToUse?.enablePdfDownload ?? false, + "Enable the PDF download button" + ); + this.featureSwitchIsTesting = QueryParameters.GetQueryParameter( "test", "false", @@ -327,7 +338,6 @@ export default class State { ); - this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { if (!userbadge) { this.featureSwitchAddNew.setData(false) @@ -372,9 +382,9 @@ export default class State { this.allElements = new ElementStorage(); this.changes = new Changes(); - + new ChangeToElementsActor(this.changes, this.allElements) - + this.osmApiFeatureSource = new OsmApiFeatureSource() new PendingChangesUploader(this.changes, this.selectedElement); diff --git a/UI/Base/Minimap.ts b/UI/Base/Minimap.ts index 0c063b672..871a14021 100644 --- a/UI/Base/Minimap.ts +++ b/UI/Base/Minimap.ts @@ -17,12 +17,14 @@ export default class Minimap extends BaseUIElement { private _isInited = false; private _allowMoving: boolean; private readonly _leafletoptions: any; + private readonly _onFullyLoaded: (leaflet: L.Map) => void constructor(options?: { background?: UIEventSource, location?: UIEventSource, allowMoving?: boolean, - leafletOptions?: any + leafletOptions?: any, + onFullyLoaded?: (leaflet: L.Map) => void } ) { super() @@ -32,6 +34,7 @@ export default class Minimap extends BaseUIElement { this._id = "minimap" + Minimap._nextId; this._allowMoving = options.allowMoving ?? true; this._leafletoptions = options.leafletOptions ?? {} + this._onFullyLoaded = options.onFullyLoaded Minimap._nextId++ } @@ -74,10 +77,10 @@ export default class Minimap extends BaseUIElement { } this._isInited = true; const location = this._location; - + const self = this; let currentLayer = this._background.data.layer() const options = { - center: <[number, number]> [location.data?.lat ?? 0, location.data?.lon ?? 0], + center: <[number, number]>[location.data?.lat ?? 0, location.data?.lon ?? 0], zoom: location.data?.zoom ?? 2, layers: [currentLayer], zoomControl: false, @@ -90,10 +93,17 @@ export default class Minimap extends BaseUIElement { // Disabling this breaks the geojson layer - don't ask me why! zoomAnimation: this._allowMoving, fadeAnimation: this._allowMoving } - + Utils.Merge(this._leafletoptions, options) - + const map = L.map(this._id, options); + if (self._onFullyLoaded !== undefined) { + + currentLayer.on("load", () => { + console.log("Fully loaded all tiles!") + self._onFullyLoaded(map) + }) + } map.setMaxBounds( [[-100, -200], [100, 200]] @@ -105,6 +115,13 @@ export default class Minimap extends BaseUIElement { map.removeLayer(currentLayer); } currentLayer = newLayer; + if (self._onFullyLoaded !== undefined) { + + currentLayer.on("load", () => { + console.log("Fully loaded all tiles!") + self._onFullyLoaded(map) + }) + } map.addLayer(newLayer); }) diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts index 656c7084f..968902ee3 100644 --- a/UI/BigComponents/LayerControlPanel.ts +++ b/UI/BigComponents/LayerControlPanel.ts @@ -36,17 +36,7 @@ export default class LayerControlPanel extends ScrollableFullScreen { undefined, State.state.featureSwitchEnableExport )) - - - elements.push( - new Toggle( - new DownloadPanel(), - undefined, - State.state.featureSwitchEnableExport - ) - ); - - return new Combine(elements).SetClass("flex flex-col"); - } + return new Combine(elements).SetClass("flex flex-col"); + } } diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts new file mode 100644 index 000000000..5422f38de --- /dev/null +++ b/UI/ExportPDF.ts @@ -0,0 +1,145 @@ +/** + * Creates screenshoter to take png screenshot + * Creates jspdf and downloads it + * - landscape pdf + * + * To add new layout: + * - add new possible layout name in constructor + * - add new layout in "PDFLayout" + * -> in there are more instructions + */ + +import jsPDF from "jspdf"; +import {SimpleMapScreenshoter} from "leaflet-simple-map-screenshoter"; +import {UIEventSource} from "../Logic/UIEventSource"; +import Minimap from "./Base/Minimap"; +import Loc from "../Models/Loc"; +import {BBox} from "../Logic/GeoOperations"; +import ShowDataLayer from "./ShowDataLayer"; +import BaseLayer from "../Models/BaseLayer"; +import LayoutConfig from "../Customizations/JSON/LayoutConfig"; +import {FixedUiElement} from "./Base/FixedUiElement"; +import Translations from "./i18n/Translations"; + +export default class ExportPDF { + // dimensions of the map in milimeter + // A4: 297 * 210mm + private readonly mapW = 297; + private readonly mapH = 210; + private readonly scaling = 2 + private readonly freeDivId: string; + private readonly _layout: UIEventSource; + + constructor( + options: { + freeDivId: string, + location: UIEventSource, + background?: UIEventSource + features: UIEventSource<{ feature: any }[]>, + layout: UIEventSource + } + ) { + + this.freeDivId = options.freeDivId; + this._layout = options.layout; + const self = this; + + // We create a minimap at the given location and attach it to the given 'hidden' element + + + + const minimap = new Minimap({ + location: options.location.map(l => ({ + lat : l.lat, + lon: l.lon, + zoom: l.zoom + 1 + })), + background: options.background, + allowMoving: true, + onFullyLoaded: leaflet => window.setTimeout(() => { + try{ + self.CreatePdf(leaflet) + .then(() => self.cleanup()) + .catch(() => self.cleanup()) + }catch(e){ + console.error(e) + self.cleanup() + } + + }, 500) + }) + + minimap.SetStyle(`width: ${this.mapW * this.scaling}mm; height: ${this.mapH * this.scaling}mm;`) + minimap.AttachTo(options.freeDivId) + + // Next: we prepare the features. Only fully contained features are shown + const bounded = options.features.map(feats => { + + const leaflet = minimap.leafletMap.data; + if (leaflet === undefined) { + return feats + } + const bounds = BBox.fromLeafletBounds(leaflet.getBounds().pad(0.2)) + return feats.filter(f => BBox.get(f.feature).isContainedIn(bounds)) + + }, [minimap.leafletMap]) + + // Add the features to the minimap + new ShowDataLayer( + bounded, + minimap.leafletMap, + options.layout, + false + ) + + } + + private cleanup(){ + new FixedUiElement("Screenshot taken!").AttachTo(this.freeDivId) + } + + private async CreatePdf(leaflet: L.Map) { + const t = Translations.t.general.pdf; + const layout = this._layout.data + const screenshotter = new SimpleMapScreenshoter(); + //minimap op index.html -> hidden daar alles op doen en dan weg + //minimap - leaflet map ophalen - boundaries ophalen - State.state.featurePipeline + screenshotter.addTo(leaflet); + console.log("Taking screenshot") + + + let doc = new jsPDF('landscape'); + + + const image = (await screenshotter.takeScreen('image')) + // @ts-ignore + doc.addImage(image, 'PNG', 0, 0, this.mapW, this.mapH); + + + doc.setDrawColor(255, 255, 255) + doc.setFillColor(255, 255, 255) + doc.roundedRect(12, 5, 125, 30, 5, 5, 'FD') + + doc.setFontSize(20) + doc.text(layout.title.txt, 40, 20, { maxWidth: 100 + }) + doc.setFontSize(10) + doc.text(t.attr.txt, 40, 25, { + maxWidth: 100 + }) + // Add the logo of the layout + let img = document.createElement('img'); + const imgSource = layout.icon + img.src = imgSource + try { + doc.addImage(img, imgSource.substr(imgSource.lastIndexOf(".")), 15, 12, 20, 20); + } catch (e) { + // TODO: support svg rendering... + console.error(e) + } + + doc.save("MapComplete_export.pdf"); + + + } +} diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index f445263e3..f54126be6 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -45,8 +45,8 @@ export default class SplitRoadWizard extends Toggle { const roadElement = State.state.allElements.ContainingFeatures.get(id) const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]); // Datalayer displaying the road and the cut points (if any) - new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true, "splitRoadWay"); - new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false, "splitRoad: splitpoints") + new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true); + new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false) /** * Handles a click on the overleaf map. diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts index 335826628..05a720b0e 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer.ts @@ -16,14 +16,13 @@ export default class ShowDataLayer { private readonly _leafletMap: UIEventSource; private _cleanCount = 0; private readonly _enablePopups: boolean; - private readonly _features: UIEventSource<{ feature: any, freshness: Date }[]> + private readonly _features: UIEventSource<{ feature: any}[]> - constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, + constructor(features: UIEventSource<{ feature: any}[]>, leafletMap: UIEventSource, layoutToUse: UIEventSource, enablePopups = true, - zoomToFeatures = false, - name?: string) { + zoomToFeatures = false) { this._leafletMap = leafletMap; this._enablePopups = enablePopups; this._features = features; diff --git a/assets/svg/download.svg b/assets/svg/download.svg index bfde05980..98921dec5 100644 --- a/assets/svg/download.svg +++ b/assets/svg/download.svg @@ -1,3 +1,67 @@ - - + + + +image/svg+xml + + + + + + + \ No newline at end of file diff --git a/assets/svg/license_info.json b/assets/svg/license_info.json index 0ea2d3a2d..377b4d2ba 100644 --- a/assets/svg/license_info.json +++ b/assets/svg/license_info.json @@ -216,6 +216,16 @@ "license": "CC0; trivial", "sources": [] }, + { + "authors": [ + "Engr.eponce" + ], + "path": "download.svg", + "license": "CC-BY-SA 4.0", + "sources": [ + "https://commons.wikimedia.org/wiki/File:Download-icon.svg" + ] + }, { "authors": [], "path": "down.svg", diff --git a/assets/themes/cycle_infra/cycle_infra.json b/assets/themes/cycle_infra/cycle_infra.json index 8d4183a7c..3193f27b1 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", - "enableExportButton": true, + "enableDownload": true, "layers": [ { "id": "cycleways", @@ -69,7 +69,7 @@ }, "then": { "nl": "Fietsweg", - "en": "Cycleway" + "en": "Bike road" } }, { @@ -89,7 +89,7 @@ { "if": "cycleway=track", "then": { - "en": "Cycleway next to the road", + "en": "Bike road next to the road", "nl": "Fietsweg naast de weg" } }, @@ -219,7 +219,7 @@ "if": "cyclestreet=yes", "then": { "en": "This is a cyclestreet, and a 30km/h zone.", - "nl": "Dit is een fietsstraat, en dus een 30km/h zone" + "nl": "Dit is een fietstraat, en dus een 30km/h zone" }, "addExtraTags": [ "overtaking:motor_vehicle=no", @@ -231,7 +231,7 @@ "if": "cyclestreet=yes", "then": { "en": "This is a cyclestreet", - "nl": "Dit is een fietsstraat" + "nl": "Dit is een fietstraat" }, "hideInAnswer": "_country=be" }, @@ -239,7 +239,7 @@ "if": "cyclestreet=", "then": { "en": "This is not a cyclestreet.", - "nl": "Dit is niet een fietsstraat" + "nl": "Dit is niet een fietstraat" }, "addExtraTags": [ "overtaking:motor_vehicle=" @@ -1266,7 +1266,7 @@ "if": "cyclestreet=yes", "then": { "en": "This is a cyclestreet, and a 30km/h zone.", - "nl": "Dit is een fietsstraat, en dus een 30km/h zone" + "nl": "Dit is een fietstraat, en dus een 30km/h zone" }, "addExtraTags": [ "overtaking:motor_vehicle=no", @@ -1278,7 +1278,7 @@ "if": "cyclestreet=yes", "then": { "en": "This is a cyclestreet", - "nl": "Dit is een fietsstraat" + "nl": "Dit is een fietstraat" }, "hideInAnswer": "_country=be" }, @@ -1286,7 +1286,7 @@ "if": "cyclestreet=", "then": { "en": "This is not a cyclestreet.", - "nl": "Dit is niet een fietsstraat" + "nl": "Dit is niet een fietstraat" }, "addExtraTags": [ "overtaking:motor_vehicle=" diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index 4d66fada5..4287536c4 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -26,6 +26,8 @@ "widenFactor": 0.05, "socialImage": "", "defaultBackgroundId": "CartoDB.Positron", + "enablePdfDownload": true, + "enableDownload": true, "layers": [ { "#": "Nature reserve with geometry, z>=13", diff --git a/index.html b/index.html index 22113fa9d..a2192f281 100644 --- a/index.html +++ b/index.html @@ -74,7 +74,10 @@ Loading MapComplete, hang on... - +Below
diff --git a/langs/en.json b/langs/en.json index 94ec9719c..f8453d507 100644 --- a/langs/en.json +++ b/langs/en.json @@ -61,6 +61,9 @@ "readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback" }, "general": { + "pdf": { + "attr": "Generated with MapComplete.osm.be - map data © OpenStreetMap Contributors, reusable under ODbL" + }, "loginWithOpenStreetMap": "Login with OpenStreetMap", "welcomeBack": "You are logged in, welcome back!", "loginToStart": "Login to answer this question", diff --git a/test.ts b/test.ts index e69de29bb..609529cbd 100644 --- a/test.ts +++ b/test.ts @@ -0,0 +1,47 @@ +import {UIEventSource} from "./Logic/UIEventSource"; +import LayoutConfig from "./Customizations/JSON/LayoutConfig"; +import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; +import State from "./State"; + +const layout = new UIEventSource(AllKnownLayouts.allKnownLayouts.get("bookcases")) +State.state = new State(layout.data) + +const features = new UIEventSource<{ feature: any }[]>([ + { + feature: { + "type": "Feature", + "properties": {"amenity": "public_bookcase", "id": "node/123"}, + + id: "node/123", + _matching_layer_id: "public_bookcase", + "geometry": { + "type": "Point", + "coordinates": [ + 3.220506906509399, + 51.215009243433094 + ] + } + } + }, { + feature: { + "type": "Feature", + "properties": { + amenity: "public_bookcase", + id: "node/456" + }, + _matching_layer_id: "public_bookcase", + id: "node/456", + "geometry": { + "type": "Point", + "coordinates": [ + 3.4243011474609375, + 51.138432319543924 + ] + } + } + } +]) + +features.data.map(f => State.state.allElements.addOrGetElement(f.feature)) + +