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..afbc0aebc 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/Customizations/JSON/TagRenderingConfig.ts b/Customizations/JSON/TagRenderingConfig.ts index 7b36dae44..c3bb99273 100644 --- a/Customizations/JSON/TagRenderingConfig.ts +++ b/Customizations/JSON/TagRenderingConfig.ts @@ -87,6 +87,10 @@ export default class TagRenderingConfig { if (this.freeform.key === undefined || this.freeform.key === "") { throw `Freeform.key is undefined or the empty string - this is not allowed; either fill out something or remove the freeform block alltogether. Error in ${context}` } + if(json.freeform["args"] !== undefined){ + throw `Freeform.args is defined. This should probably be 'freeform.helperArgs' (at ${context})` + + } if (ValidatedTextField.AllTypes[this.freeform.type] === undefined) { diff --git a/InitUiElements.ts b/InitUiElements.ts index 888dbb612..483ce6f6b 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -10,7 +10,6 @@ import SimpleAddUI from "./UI/BigComponents/SimpleAddUI"; import CenterMessageBox from "./UI/CenterMessageBox"; import UserBadge from "./UI/BigComponents/UserBadge"; import SearchAndGo from "./UI/BigComponents/SearchAndGo"; -import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {Utils} from "./Utils"; import Svg from "./Svg"; @@ -23,25 +22,22 @@ import UserDetails from "./Logic/Osm/OsmConnection"; import Attribution from "./UI/BigComponents/Attribution"; import LayerResetter from "./Logic/Actors/LayerResetter"; import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs"; -import LayerControlPanel from "./UI/BigComponents/LayerControlPanel"; import ShowDataLayer from "./UI/ShowDataLayer"; import Hash from "./Logic/Web/Hash"; import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; import ScrollableFullScreen from "./UI/Base/ScrollableFullScreen"; import Translations from "./UI/i18n/Translations"; import MapControlButton from "./UI/MapControlButton"; -import Combine from "./UI/Base/Combine"; import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler"; import LZString from "lz-string"; import {LayoutConfigJson} from "./Customizations/JSON/LayoutConfigJson"; -import AttributionPanel from "./UI/BigComponents/AttributionPanel"; -import ContributorCount from "./Logic/ContributorCount"; import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import AllKnownLayers from "./Customizations/AllKnownLayers"; import LayerConfig from "./Customizations/JSON/LayerConfig"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import {TagsFilter} from "./Logic/Tags/TagsFilter"; -import FilterView from "./UI/BigComponents/FilterView"; +import LeftControls from "./UI/BigComponents/LeftControls"; +import RightControls from "./UI/BigComponents/RightControls"; export class InitUiElements { static InitAll( @@ -189,38 +185,10 @@ export class InitUiElements { marker.addTo(State.state.leafletMap.data); }); - const geolocationButton = new Toggle( - new MapControlButton( - new GeoLocationHandler( - State.state.currentGPSLocation, - State.state.leafletMap, - State.state.layoutToUse - ), - { - dontStyle: true, - } - ), - undefined, - State.state.featureSwitchGeolocation - ); - - const plus = new MapControlButton(Svg.plus_zoom_svg()).onClick(() => { - State.state.locationControl.data.zoom++; - State.state.locationControl.ping(); - }); - - const min = new MapControlButton(Svg.min_zoom_svg()).onClick(() => { - State.state.locationControl.data.zoom--; - State.state.locationControl.ping(); - }); - - new Combine([plus, min, geolocationButton]) - .SetClass("flex flex-col") - .AttachTo("bottom-right"); - if (layoutToUse.id === personal.id) { updateFavs(); } + InitUiElements.setupAllLayerElements(); if (layoutToUse.id === personal.id) { @@ -328,82 +296,7 @@ export class InitUiElements { Hash.hash.data == "welcome" ); } - - private static InitLayerSelection(featureSource: FeatureSource) { - const copyrightNotice = new ScrollableFullScreen( - () => Translations.t.general.attribution.attributionTitle.Clone(), - () => - new AttributionPanel( - State.state.layoutToUse, - new ContributorCount(featureSource).Contributors - ), - "copyright" - ); - - const copyrightButton = new Toggle( - copyrightNotice, - new MapControlButton(Svg.copyright_svg()), - copyrightNotice.isShown - ) - .ToggleOnClick() - .SetClass("p-0.5"); - - const layerControlPanel = new LayerControlPanel( - State.state.layerControlIsOpened - ).SetClass("block p-1 rounded-full"); - - const layerControlButton = new Toggle( - layerControlPanel, - new MapControlButton(Svg.layers_svg()), - State.state.layerControlIsOpened - ).ToggleOnClick(); - - const layerControl = new Toggle( - layerControlButton, - "", - State.state.featureSwitchLayers - ); - - 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()); - - const filterButton = new Toggle( - filterView, - filterMapControlButton, - State.state.filterIsOpened - ).ToggleOnClick(); - - const filterControl = new Toggle( - filterButton, - undefined, - State.state.featureSwitchFilter - ); - - new Combine([copyrightButton, layerControl, filterControl]) - .SetClass("flex flex-col") - .AttachTo("bottom-left"); - - State.state.locationControl.addCallback(() => { - // Close the layer selection when the map is moved - layerControlButton.isEnabled.setData(false); - copyrightButton.isEnabled.setData(false); - }); - - State.state.selectedElement.addCallbackAndRunD((_) => { - layerControlButton.isEnabled.setData(false); - copyrightButton.isEnabled.setData(false); - }); - } - + private static InitBaseMap() { State.state.availableBackgroundLayers = AvailableBaseLayers.AvailableLayersAt(State.state.locationControl); @@ -537,7 +430,9 @@ export class InitUiElements { // ------------- Setup the layers ------------------------------- const source = InitUiElements.InitLayers(); - InitUiElements.InitLayerSelection(source); + + new LeftControls(source).AttachTo("bottom-left"); + new RightControls().AttachTo("bottom-right"); // ------------------ Setup various other UI elements ------------ diff --git a/Logic/Actors/ExportPDF.ts b/Logic/Actors/ExportPDF.ts deleted file mode 100644 index aebfa4594..000000000 --- a/Logic/Actors/ExportPDF.ts +++ /dev/null @@ -1,37 +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 Minimap from "../../UI/Base/Minimap"; -import { PDFLayout } from "./PDFLayout"; - -export default class ExportPDF { - constructor( - name: string, - layout: "natuurpunt" - ){ - const screenshotter = new SimpleMapScreenshoter(); - //let temporaryMap = new Minimap(); - //temporaryMap.SetStyle('visibility: hidden'); - //temporaryMap.AttachTo("tempScreenshotDiv"); - //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('l'); - screenshotter.takeScreen('image').then(image => { - let file = new PDFLayout(); - file.AddLayout(layout, doc, image); - doc.save(name); - }) - } -} \ No newline at end of file diff --git a/Logic/Actors/PDFLayout.ts b/Logic/Actors/PDFLayout.ts deleted file mode 100644 index 79237d42d..000000000 --- a/Logic/Actors/PDFLayout.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Adds a theme to the pdf - */ - -import jsPDF from "jspdf"; - -export class PDFLayout { - public AddLayout(layout: string, 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; - } -} \ No newline at end of file diff --git a/Logic/FeatureSource/FeatureSource.ts b/Logic/FeatureSource/FeatureSource.ts index 171db39f6..5b66e1650 100644 --- a/Logic/FeatureSource/FeatureSource.ts +++ b/Logic/FeatureSource/FeatureSource.ts @@ -24,13 +24,14 @@ export class FeatureSourceUtils { options = Utils.setDefaults(options, defaults); // Select all features, ignore the freshness and other data - let featureList: any[] = featurePipeline.features.data.map((feature) => feature.feature); + let featureList: any[] = featurePipeline.features.data.map((feature) => + JSON.parse(JSON.stringify((feature.feature)))); // Make a deep copy! if (!options.metadata) { for (let i = 0; i < featureList.length; i++) { let feature = featureList[i]; for (let property in feature.properties) { - if (property[0] == "_") { + if (property[0] == "_" && property !== "_lat" && property !== "_lon") { delete featureList[i]["properties"][property]; } } 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/Logic/Osm/Changes.ts b/Logic/Osm/Changes.ts index 806af3ba7..6c2acd0f9 100644 --- a/Logic/Osm/Changes.ts +++ b/Logic/Osm/Changes.ts @@ -27,7 +27,9 @@ export class Changes { private readonly previouslyCreated : OsmObject[] = [] constructor() { - + this.isUploading.addCallbackAndRun(uploading => { + console.trace("Is uploading changed:", uploading) + }) } private static createChangesetFor(csId: string, @@ -255,6 +257,9 @@ export class Changes { console.log("Needed ids", neededIds) OsmObject.DownloadAll(neededIds, true).addCallbackAndRunD(osmObjects => { console.log("Got the fresh objects!", osmObjects, "pending: ", pending) + try{ + + const changes: { newObjects: OsmObject[], modifiedObjects: OsmObject[] @@ -283,6 +288,11 @@ export class Changes { return self.isUploading.setData(false); } // Failed - mark to try again ) + }catch(e){ + console.error("Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", e) + self.pendingChanges.setData([]) + self.isUploading.setData(false) + } return true; }); diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index fcc3da888..8a97e9ba1 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -437,6 +437,11 @@ export class OsmWay extends OsmObject { for (const nodeId of element.nodes) { const node = nodeDict.get(nodeId) + if(node === undefined){ + console.error("Error: node ", nodeId, "not found in ", nodeDict) + // This is probably part of a relation which hasn't been fully downloaded + continue; + } const cp = node.centerpoint(); this.coordinates.push(cp); latSum = cp[0] diff --git a/Logic/SimpleMetaTagger.ts b/Logic/SimpleMetaTagger.ts index 2337c97f3..569e4645e 100644 --- a/Logic/SimpleMetaTagger.ts +++ b/Logic/SimpleMetaTagger.ts @@ -305,7 +305,7 @@ export default class SimpleMetaTagger { } else if (_otherParkingMode.matchesProperties(properties)) { parallelParkingCount = 0; } else { - console.log("No parking data for ", properties.name, properties.id, properties) + console.log("No parking data for ", properties.name, properties.id) } diff --git a/README.md b/README.md index be23b733d..c1c2e950d 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,6 @@ The core strings and builtin themes of MapComplete are translated on [Hosted Web You can easily make an account and start translating in their web-environment - no installation required. [![Translation status](https://hosted.weblate.org/widgets/mapcomplete/-/multi-blue.svg)](https://hosted.weblate.org/engage/mapcomplete/) -[![Translation status](https://hosted.weblate.org/widgets/mapcomplete/-/multi-blue.svg)](https://hosted.weblate.org/engage/mapcomplete/) ## Architecture diff --git a/State.ts b/State.ts index fe2e2fdf7..9b7775504 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 @@ -79,7 +79,7 @@ export default class State { public readonly featureSwitchUserbadge: UIEventSource; public readonly featureSwitchSearch: UIEventSource; - public readonly featureSwitchLayers: UIEventSource; + public readonly featureSwitchBackgroundSlection: UIEventSource; public readonly featureSwitchAddNew: UIEventSource; public readonly featureSwitchWelcomeMessage: UIEventSource; public readonly featureSwitchIframe: UIEventSource; @@ -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; @@ -125,11 +125,11 @@ export default class State { public layoutDefinition: string; public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - public layerControlIsOpened: UIEventSource = + public downloadControlIsOpened: UIEventSource = QueryParameters.GetQueryParameter( - "layer-control-toggle", + "download-control-toggle", "false", - "Whether or not the layer control is shown" + "Whether or not the download panel is shown" ).map( (str) => str !== "false", [], @@ -249,11 +249,12 @@ export default class State { (layoutToUse) => layoutToUse?.enableSearch ?? true, "Disables/Enables the search bar" ); - this.featureSwitchLayers = featSw( - "fs-layers", - (layoutToUse) => layoutToUse?.enableLayers ?? true, - "Disables/Enables the layer control" + this.featureSwitchBackgroundSlection = featSw( + "fs-background", + (layoutToUse) => layoutToUse?.enableBackgroundLayerSelection ?? true, + "Disables/Enables the background layer control" ); + this.featureSwitchFilter = featSw( "fs-filter", (layoutToUse) => layoutToUse?.enableLayers ?? true, @@ -295,6 +296,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 +339,6 @@ export default class State { ); - this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { if (!userbadge) { this.featureSwitchAddNew.setData(false) @@ -372,9 +383,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/AllDownloads.ts b/UI/BigComponents/AllDownloads.ts new file mode 100644 index 000000000..063fea9fb --- /dev/null +++ b/UI/BigComponents/AllDownloads.ts @@ -0,0 +1,56 @@ +import State from "../../State"; +import Combine from "../Base/Combine"; +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 {DownloadPanel} from "./DownloadPanel"; +import {SubtleButton} from "../Base/SubtleButton"; +import Svg from "../../Svg"; +import ExportPDF from "../ExportPDF"; + +export default class AllDownloads extends ScrollableFullScreen { + + constructor(isShown: UIEventSource) { + super(AllDownloads.GenTitle, AllDownloads.GeneratePanel, "layers", isShown); + } + + private static GenTitle(): BaseUIElement { + return Translations.t.general.download.title + .Clone() + .SetClass("text-2xl break-words font-bold p-2"); + } + + private static GeneratePanel(): BaseUIElement { + const generatePdf = () => { + new ExportPDF( + { + freeDivId: "belowmap", + background: State.state.backgroundLayer, + location: State.state.locationControl, + features: State.state.featurePipeline.features, + layout: State.state.layoutToUse, + }) + } + + const pdf = new Toggle( + new SubtleButton(Svg.floppy_ui(), + new Combine([Translations.t.general.download.downloadAsPdf.Clone().SetClass("font-bold"), + Translations.t.general.download.downloadAsPdfHelper.Clone()] + ).SetClass("flex flex-col")) + .onClick(generatePdf), + undefined, + + State.state.featureSwitchExportAsPdf + ) + + const exportPanel = new Toggle( + new DownloadPanel(), + undefined, + State.state.featureSwitchEnableExport + ) + + return new Combine([pdf, exportPanel]).SetClass("flex flex-col"); + } +} diff --git a/UI/BigComponents/Basemap.ts b/UI/BigComponents/Basemap.ts index a4afd6ec8..9d627b631 100644 --- a/UI/BigComponents/Basemap.ts +++ b/UI/BigComponents/Basemap.ts @@ -50,6 +50,8 @@ export class Basemap { } previousLayer = newLayer; self.map.addLayer(newLayer); + extraAttribution.AttachTo('leaflet-attribution') + }) diff --git a/UI/BigComponents/FilterView.ts b/UI/BigComponents/FilterView.ts index 17318a610..dfa486fd1 100644 --- a/UI/BigComponents/FilterView.ts +++ b/UI/BigComponents/FilterView.ts @@ -15,6 +15,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import BaseUIElement from "../BaseUIElement"; import State from "../../State"; import FilteredLayer from "../../Models/FilteredLayer"; +import BackgroundSelector from "./BackgroundSelector"; /** @@ -23,9 +24,14 @@ import FilteredLayer from "../../Models/FilteredLayer"; export default class FilterView extends VariableUiElement { constructor(filteredLayer: UIEventSource) { + const backgroundSelector = new Toggle( + new BackgroundSelector(), + undefined, + State.state.featureSwitchBackgroundSlection + ) super( filteredLayer.map((filteredLayers) => - filteredLayers?.map(l => FilterView.createOneFilteredLayerElement(l)) + filteredLayers?.map(l => FilterView.createOneFilteredLayerElement(l)).concat(backgroundSelector) ) ); } diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts deleted file mode 100644 index 656c7084f..000000000 --- a/UI/BigComponents/LayerControlPanel.ts +++ /dev/null @@ -1,52 +0,0 @@ -import State from "../../State"; -import BackgroundSelector from "./BackgroundSelector"; -import Combine from "../Base/Combine"; -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 {DownloadPanel} from "./DownloadPanel"; - -export default class LayerControlPanel extends ScrollableFullScreen { - - constructor(isShown: UIEventSource) { - super(LayerControlPanel.GenTitle, LayerControlPanel.GeneratePanel, "layers", isShown); - } - - private static GenTitle(): BaseUIElement { - return Translations.t.general.layerSelection.title - .Clone() - .SetClass("text-2xl break-words font-bold p-2"); - } - - private static GeneratePanel(): BaseUIElement { - const elements: BaseUIElement[] = []; - - if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { - const backgroundSelector = new BackgroundSelector(); - backgroundSelector.SetStyle("margin:1em"); - backgroundSelector.onClick(() => { - }); - elements.push(backgroundSelector) - } - - elements.push(new Toggle( - new DownloadPanel(), - undefined, - State.state.featureSwitchEnableExport - )) - - - - elements.push( - new Toggle( - new DownloadPanel(), - undefined, - State.state.featureSwitchEnableExport - ) - ); - - return new Combine(elements).SetClass("flex flex-col"); - } -} diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts new file mode 100644 index 000000000..92a012bbf --- /dev/null +++ b/UI/BigComponents/LeftControls.ts @@ -0,0 +1,96 @@ +import Combine from "../Base/Combine"; +import ScrollableFullScreen from "../Base/ScrollableFullScreen"; +import Translations from "../i18n/Translations"; +import AttributionPanel from "./AttributionPanel"; +import State from "../../State"; +import ContributorCount from "../../Logic/ContributorCount"; +import Toggle from "../Input/Toggle"; +import MapControlButton from "../MapControlButton"; +import Svg from "../../Svg"; +import AllDownloads from "./AllDownloads"; +import FilterView from "./FilterView"; +import FeatureSource from "../../Logic/FeatureSource/FeatureSource"; + +export default class LeftControls extends Combine { + + constructor(featureSource: FeatureSource) { + + const toggledCopyright = new ScrollableFullScreen( + () => Translations.t.general.attribution.attributionTitle.Clone(), + () => + new AttributionPanel( + State.state.layoutToUse, + new ContributorCount(featureSource).Contributors + ), + undefined + ); + + const copyrightButton = new Toggle( + toggledCopyright, + new MapControlButton(Svg.copyright_svg()) + .onClick(() => toggledCopyright.isShown.setData(true)), + toggledCopyright.isShown + ) + .SetClass("p-0.5"); + + const toggledDownload = new Toggle( + new AllDownloads( + State.state.downloadControlIsOpened + ).SetClass("block p-1 rounded-full"), + new MapControlButton(Svg.download_svg()) + .onClick(() => State.state.downloadControlIsOpened.setData(true)), + State.state.downloadControlIsOpened + ) + + const downloadButtonn = new Toggle( + toggledDownload, + undefined, + State.state.featureSwitchEnableExport.map(downloadEnabled => downloadEnabled || State.state.featureSwitchExportAsPdf.data, + [State.state.featureSwitchExportAsPdf]) + ); + + + const toggledFilter = new Toggle( + new ScrollableFullScreen( + () => Translations.t.general.layerSelection.title.Clone(), + () => + new FilterView(State.state.filteredLayers).SetClass( + "block p-1 rounded-full" + ), + undefined, + State.state.filterIsOpened + ), + new MapControlButton(Svg.filter_svg()) + .onClick(() => State.state.filterIsOpened.setData(true)), + State.state.filterIsOpened + ) + + const filterButton = new Toggle( + toggledFilter, + undefined, + State.state.featureSwitchFilter + ); + + + State.state.locationControl.addCallback(() => { + // Close the layer selection when the map is moved + toggledDownload.isEnabled.setData(false); + copyrightButton.isEnabled.setData(false); + toggledFilter.isEnabled.setData(false); + }); + + State.state.selectedElement.addCallbackAndRunD((_) => { + toggledDownload.isEnabled.setData(false); + copyrightButton.isEnabled.setData(false); + toggledFilter.isEnabled.setData(false); + }); + super([filterButton, + downloadButtonn, + copyrightButton]) + + this.SetClass("flex flex-col") + + } + + +} \ No newline at end of file diff --git a/UI/BigComponents/RightControls.ts b/UI/BigComponents/RightControls.ts new file mode 100644 index 000000000..dd4282b35 --- /dev/null +++ b/UI/BigComponents/RightControls.ts @@ -0,0 +1,43 @@ +import Combine from "../Base/Combine"; +import Toggle from "../Input/Toggle"; +import MapControlButton from "../MapControlButton"; +import GeoLocationHandler from "../../Logic/Actors/GeoLocationHandler"; +import State from "../../State"; +import Svg from "../../Svg"; + +export default class RightControls extends Combine { + + constructor() { + const geolocationButton = new Toggle( + new MapControlButton( + new GeoLocationHandler( + State.state.currentGPSLocation, + State.state.leafletMap, + State.state.layoutToUse + ), { + dontStyle: true + } + ), + undefined, + State.state.featureSwitchGeolocation + ); + + const plus = new MapControlButton( + Svg.plus_zoom_svg() + ).onClick(() => { + State.state.locationControl.data.zoom++; + State.state.locationControl.ping(); + }); + + const min = new MapControlButton( + Svg.min_zoom_svg() + ).onClick(() => { + State.state.locationControl.data.zoom--; + State.state.locationControl.ping(); + }); + + super([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1"))) + this.SetClass("flex flex-col") + } + +} \ No newline at end of file diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index 62ea506bb..7c59503d7 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -166,7 +166,7 @@ export default class SimpleAddUI extends Toggle { ]) ) - .onClick(() => State.state.layerControlIsOpened.setData(true)) + .onClick(() => State.state.filterIsOpened.setData(true)) const openLayerOrConfirm = new Toggle( confirmButton, @@ -238,7 +238,13 @@ export default class SimpleAddUI extends Toggle { const allButtons = []; for (const layer of State.state.filteredLayers.data) { - if (layer.isDisplayed.data === false && State.state.featureSwitchLayers) { + if (layer.isDisplayed.data === false && !State.state.featureSwitchFilter.data) { + // The layer is not displayed and we cannot enable the layer control -> we skip + continue; + } + + if(layer.layerDef.name === undefined){ + // this is a parlty hidden layer continue; } diff --git a/UI/ExportPDF.ts b/UI/ExportPDF.ts new file mode 100644 index 000000000..fd431eb91 --- /dev/null +++ b/UI/ExportPDF.ts @@ -0,0 +1,193 @@ +/** + * 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"; +import State from "../State"; +import Constants from "../Models/Constants"; + +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; + private _screenhotTaken = false; + + 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 l = options.location.data; + const loc = { + lat: l.lat, + lon: l.lon, + zoom: l.zoom + 1 + } + + const minimap = new Minimap({ + location: new UIEventSource(loc), // We remove the link between the old and the new UI-event source as moving the map while the export is running fucks up the screenshot + background: options.background, + allowMoving: false, + onFullyLoaded: leaflet => window.setTimeout(() => { + if (self._screenhotTaken) { + return; + } + 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) + this._screenhotTaken = true; + } + + 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, 10, 145, 25, 5, 5, 'FD') + + doc.setFontSize(20) + doc.textWithLink(layout.title.txt, 40, 18.5, { + maxWidth: 125, + url: window.location.href + }) + doc.setFontSize(10) + doc.text(t.generatedWith.txt, 40, 23, { + maxWidth: 125 + }) + const backgroundLayer : BaseLayer = State.state.backgroundLayer.data + const attribution = new FixedUiElement(backgroundLayer.layer().getAttribution() ?? backgroundLayer.name).ConstructElement().innerText + doc.textWithLink(t.attr.txt, 40, 26.5, { + maxWidth: 125, + url: "https://www.openstreetmap.org/copyright" + }) + + doc.text(t.attrBackground.Subs({ + background: attribution + }).txt, 40, 30) + + let date = new Date().toISOString().substr(0,16) + + doc.setFontSize(7) + doc.text(t.versionInfo.Subs({ + version: Constants.vNumber, + date: date + }).txt, 40, 34, { + maxWidth: 125 + }) + + // Add the logo of the layout + let img = document.createElement('img'); + const imgSource = layout.icon + const imgType = imgSource.substr(imgSource.lastIndexOf(".") + 1); + img.src = imgSource + console.log(imgType) + if (imgType.toLowerCase() === "svg") { + new FixedUiElement("").AttachTo(this.freeDivId) + + // This is an svg image, we use the canvas to convert it to a png + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d'); + canvas.width = 500 + canvas.height = 500 + img.style.width = "100%" + img.style.height = "100%" + ctx.drawImage(img, 0, 0, 500, 500); + const base64img = canvas.toDataURL("image/png") + doc.addImage(base64img, 'png', 15, 12, 20, 20); + + } else { + try { + doc.addImage(img, imgType, 15, 12, 20, 20); + } catch (e) { + console.error(e) + } + } + + doc.save(`MapComplete_${layout.title.txt}_${date}.pdf`); + + + } +} diff --git a/UI/Input/LengthInput.ts b/UI/Input/LengthInput.ts index 0558069b2..7b690f691 100644 --- a/UI/Input/LengthInput.ts +++ b/UI/Input/LengthInput.ts @@ -142,7 +142,7 @@ export default class LengthInput extends InputElement { if (leaflet) { const first = leaflet.layerPointToLatLng(firstClickXY) const last = leaflet.layerPointToLatLng([dx, dy]) - const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 100000) / 100 + const geoDist = Math.floor(GeoOperations.distanceBetween([first.lng, first.lat], [last.lng, last.lat]) * 10000) / 10 self.value.setData("" + geoDist) } diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index 1bd09cb14..3f663a4b9 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -63,13 +63,15 @@ export default class FeatureInfoBox extends ScrollableFullScreen { } return new EditableTagRendering(tags, tr, layerConfig.units); }); + + let editElements : BaseUIElement[] = [] if (!questionBoxIsUsed) { - renderings.push(questionBox); + editElements.push(questionBox); } if (layerConfig.deletion) { - renderings.push( + editElements.push( new VariableUiElement(tags.map(tags => tags.id).map(id => new DeleteWizard( id, @@ -79,7 +81,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { } if (layerConfig.allowSplit) { - renderings.push( + editElements.push( new VariableUiElement(tags.map(tags => tags.id).map(id => new SplitRoadWizard(id)) )) @@ -91,7 +93,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { renderings.push(new TagRenderingAnswer(tags, SharedTagRenderings.SharedTagRendering.get("minimap"))) } - renderings.push( + editElements.push( new VariableUiElement( State.state.osmConnection.userDetails .map(ud => ud.csCount) @@ -109,7 +111,7 @@ export default class FeatureInfoBox extends ScrollableFullScreen { ) - renderings.push( + editElements.push( new VariableUiElement( State.state.featureSwitchIsDebugging.map(isDebugging => { if (isDebugging) { @@ -119,6 +121,16 @@ export default class FeatureInfoBox extends ScrollableFullScreen { }) ) ) + + const editors = new VariableUiElement(State.state.featureSwitchUserbadge.map( + userbadge => { + if(!userbadge){ + return undefined + } + return new Combine(editElements) + } + )) + renderings.push(editors) return new Combine(renderings).SetClass("block") 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/contributors.json b/assets/contributors.json index a66dcc9ba..54a85eebc 100644 --- a/assets/contributors.json +++ b/assets/contributors.json @@ -1 +1 @@ -{"contributors":[{"contributor":"pietervdvn", "commits":835},{"contributor":"Pieter Vander Vennet", "commits":746},{"contributor":"Weblate", "commits":38},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":17},{"contributor":"Joost", "commits":17},{"contributor":"Marco", "commits":16},{"contributor":"Artem", "commits":16},{"contributor":"Allan Nordhøy", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"Supaplex", "commits":14},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Hosted Weblate", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"Wiktor Przybylski", "commits":3},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Erik Palm", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Damian Tokarski", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"liimee", "commits":1},{"contributor":"Jeff Huang", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file +{"contributors":[{"contributor":"pietervdvn", "commits":1088},{"contributor":"Pieter Vander Vennet", "commits":775},{"contributor":"Weblate", "commits":47},{"contributor":"Robin van der Linde", "commits":45},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"karelleketers", "commits":26},{"contributor":"Artem", "commits":22},{"contributor":"Ward", "commits":20},{"contributor":"Sebastian Kürten", "commits":19},{"contributor":"Arno Deceuninck", "commits":18},{"contributor":"pgm-chardelv1", "commits":17},{"contributor":"Joost", "commits":17},{"contributor":"Marco", "commits":16},{"contributor":"Allan Nordhøy", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"Supaplex", "commits":14},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"LiamSimons", "commits":10},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Hosted Weblate", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"Vinicius", "commits":5},{"contributor":"Irina", "commits":5},{"contributor":"Ward Beyens", "commits":4},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Jan Zabel", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"Fabio Bettani", "commits":4},{"contributor":"Wiktor Przybylski", "commits":3},{"contributor":"vankos", "commits":3},{"contributor":"seppesantens", "commits":3},{"contributor":"Nikolay Korotkiy", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Erik Palm", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Eduardo Addad de Oliveira", "commits":2},{"contributor":"Damian Tokarski", "commits":2},{"contributor":"Charlotte Delvaux", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Seppe Santens", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"root", "commits":1},{"contributor":"Rodrigo Tavares", "commits":1},{"contributor":"Raphael Das Gupta", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"liimee", "commits":1},{"contributor":"Jeff Huang", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]} \ No newline at end of file diff --git a/assets/layers/crossings/crossings.json b/assets/layers/crossings/crossings.json index 0dcfc9aa1..bc3cf5f70 100644 --- a/assets/layers/crossings/crossings.json +++ b/assets/layers/crossings/crossings.json @@ -132,7 +132,7 @@ "if": "crossing_ref=", "then": { "en": "This is not a zebra crossing", - "nl": "Dit is niet een zebrapad" + "nl": "Dit is geen zebrapad" } } ] @@ -155,7 +155,7 @@ "if": "bicycle=no", "then": { "en": "A cyclist can not use this crossing", - "nl": "Een fietser kan niet deze oversteekplaats gebruiken" + "nl": "Een fietser kan deze oversteekplaats niet gebruiken" } } ] @@ -178,7 +178,7 @@ "if": "crossing:island=no", "then": { "en": "This crossing does not have an island in the middle", - "nl": "Deze oversteekplaats heeft niet een verkeerseiland in het midden" + "nl": "Deze oversteekplaats heeft geen verkeerseiland in het midden" } } ] @@ -201,7 +201,7 @@ "if": "tactile_paving=no", "then": { "en": "This crossing does not have tactile paving", - "nl": "Deze oversteekplaats heeft niet een geleidelijn" + "nl": "Deze oversteekplaats heeft geen geleidelijn" } }, { @@ -237,7 +237,7 @@ "if": "button_operated=no", "then": { "en": "This traffic light does not have a button to request green light", - "nl": "Dit verkeerlicht heeft niet een knop voor groen licht" + "nl": "Dit verkeerlicht heeft geen knop voor groen licht" } } ] @@ -252,8 +252,8 @@ { "if": "red_turn:right:bicycle=yes", "then": { - "en": "A cyclist can turn right if the light is red ", - "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is " + "en": "A cyclist can turn right if the light is red ", + "nl": "Een fietser mag wel rechtsaf slaan als het licht rood is " }, "hideInAnswer": "_country!=be" }, @@ -284,8 +284,8 @@ { "if": "red_turn:straight:bicycle=yes", "then": { - "en": "A cyclist can go straight on if the light is red ", - "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is " + "en": "A cyclist can go straight on if the light is red ", + "nl": "Een fietser mag wel rechtdoor gaan als het licht rood is " }, "hideInAnswer": "_country!=be" }, diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index cbdd19d28..67ba9267a 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -30,7 +30,12 @@ "then": "./assets/layers/toilet/wheelchair.svg" }, { - "if": "toilets:position=urinals", + "if": { + "or": [ + "toilets:position=urinals", + "toilets:position=urinal" + ] + }, "then": "./assets/layers/toilet/urinal.svg" } ] @@ -260,7 +265,7 @@ } }, { - "if": "toilets:position=urinals", + "if": "toilets:position=urinal", "then": { "en": "There are only urinals here", "de": "Hier gibt es nur Pissoirs", @@ -280,7 +285,7 @@ } }, { - "if": "toilets:position=seated;urinals", + "if": "toilets:position=seated;urinal", "then": { "en": "Both seated toilets and urinals are available here", "de": "Sowohl Sitztoiletten als auch Pissoirs sind hier verfügbar", diff --git a/assets/layers/watermill/watermill.json b/assets/layers/watermill/watermill.json index 1a4b78629..f2bc4c425 100644 --- a/assets/layers/watermill/watermill.json +++ b/assets/layers/watermill/watermill.json @@ -169,19 +169,5 @@ }, "color": { "render": "#FFC0CB" - }, - "presets": [ - { - "tags": [ - "man_made=watermill", - "fixme=Toegevoegd met MapComplete, geometry nog uit te tekenen" - ], - "title": { - "nl": "Paden" - }, - "description": { - "nl": "Voeg een ontbrekend, erkend pad toe." - } - } - ] + } } 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 7131b6f9d..9be2f6700 100644 --- a/assets/themes/cycle_infra/cycle_infra.json +++ b/assets/themes/cycle_infra/cycle_infra.json @@ -23,9 +23,9 @@ "startLat": 51, "startLon": 3.75, "startZoom": 11, - "widenFactor": 0.05, + "widenFactor": 0, "socialImage": "./assets/themes/cycle_infra/cycle-infra.svg", - "enableExportButton": true, + "enableDownload": true, "layers": [ { "id": "cycleways", @@ -33,7 +33,7 @@ "en": "Cycleways", "nl": "Fietspaden" }, - "minzoom": 14, + "minzoom": 16, "source": { "osmTags": { "or": [ @@ -239,7 +239,7 @@ "if": "cyclestreet=", "then": { "en": "This is not a cyclestreet.", - "nl": "Dit is niet een fietstraat" + "nl": "Dit is geen fietsstraat" }, "addExtraTags": [ "overtaking:motor_vehicle=" @@ -427,49 +427,57 @@ { "if": "cycleway:smoothness=excellent", "then": { - "en": "Usable for thin rollers: rollerblade, skateboard" + "en": "Usable for thin rollers: rollerblade, skateboard", + "nl": "Geschikt voor fijne rollers: rollerblade, skateboard" } }, { "if": "cycleway:smoothness=good", "then": { - "en": "Usable for thin wheels: racing bike" + "en": "Usable for thin wheels: racing bike", + "nl": "Geschikt voor fijne wielen: racefiets" } }, { "if": "cycleway:smoothness=intermediate", "then": { - "en": "Usable for normal wheels: city bike, wheelchair, scooter" + "en": "Usable for normal wheels: city bike, wheelchair, scooter", + "nl": "Geschikt voor normale wielen: stadsfiets, rolstoel, scooter" } }, { "if": "cycleway:smoothness=bad", "then": { - "en": "Usable for robust wheels: trekking bike, car, rickshaw" + "en": "Usable for robust wheels: trekking bike, car, rickshaw", + "nl": "Geschikt voor brede wielen: trekfiets, auto, rickshaw" } }, { "if": "cycleway:smoothness=very_bad", "then": { - "en": "Usable for vehicles with high clearance: light duty off-road vehicle" + "en": "Usable for vehicles with high clearance: light duty off-road vehicle", + "nl": "Geschikt voor voertuigen met hoge banden: lichte terreinwagen" } }, { "if": "cycleway:smoothness=horrible", "then": { - "en": "Usable for off-road vehicles: heavy duty off-road vehicle" + "en": "Usable for off-road vehicles: heavy duty off-road vehicle", + "nl": "Geschikt voor terreinwagens: zware terreinwagen" } }, { "if": "cycleway:smoothness=very_horrible", "then": { - "en": "Usable for specialized off-road vehicles: tractor, ATV" + "en": "Usable for specialized off-road vehicles: tractor, ATV", + "nl": "Geschikt voor gespecialiseerde terreinwagens: tractor, alleterreinwagen" } }, { "if": "cycleway:smoothness=impassable", "then": { - "en": "Impassable / No wheeled vehicle" + "en": "Impassable / No wheeled vehicle", + "nl": "Niet geschikt voor voertuigen met wielen" } } ] @@ -625,8 +633,11 @@ }, "freeform": { "key": "width:carriageway", - "addExtraTags": [], - "type": "pfloat" + "type": "length", + "helperArgs": [ + "20", + "map" + ] }, "question": { "en": "What is the carriage width of this road (in meters)?", @@ -648,24 +659,24 @@ { "if": "cycleway:traffic_sign=BE:D7", "then": { - "en": "Compulsory cycleway ", - "nl": "Verplicht fietspad " + "en": "Compulsory cycleway ", + "nl": "Verplicht fietspad " }, "hideInAnswer": "_country!=be" }, { "if": "cycleway:traffic_sign~BE:D7;.*", "then": { - "en": "Compulsory cycleway (with supplementary sign) ", - "nl": "Verplicht fietspad (met onderbord)" + "en": "Compulsory cycleway (with supplementary sign)
", + "nl": "Verplicht fietspad (met onderbord)
" }, "hideInAnswer": true }, { "if": "cycleway:traffic_sign=BE:D9", "then": { - "en": "Segregated foot/cycleway ", - "nl": "Afgescheiden voet-/fietspad " + "en": "Segregated foot/cycleway ", + "nl": "Afgescheiden voet-/fietspad " }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -676,8 +687,8 @@ { "if": "cycleway:traffic_sign=BE:D10", "then": { - "en": "Unsegregated foot/cycleway ", - "nl": "Gedeeld voet-/fietspad " + "en": "Unsegregated foot/cycleway ", + "nl": "Gedeeld voet-/fietspad " }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -709,8 +720,8 @@ { "if": "traffic_sign=BE:D7", "then": { - "en": "Compulsory cycleway ", - "nl": "Verplicht fietspad " + "en": "Compulsory cycleway ", + "nl": "Verplicht fietspad " }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -723,16 +734,16 @@ { "if": "traffic_sign~BE:D7;.*", "then": { - "en": "Compulsory cycleway (with supplementary sign) ", - "nl": "Verplicht fietspad (met onderbord)" + "en": "Compulsory cycleway (with supplementary sign) ", + "nl": "Verplicht fietspad (met onderbord)" }, "hideInAnswer": true }, { "if": "traffic_sign=BE:D9", "then": { - "en": "Segregated foot/cycleway ", - "nl": "Afgescheiden voet-/fietspad " + "en": "Segregated foot/cycleway ", + "nl": "Afgescheiden voet-/fietspad " }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -747,8 +758,8 @@ { "if": "traffic_sign=BE:D10", "then": { - "en": "Unsegregated foot/cycleway ", - "nl": "Gedeeld voet-/fietspad " + "en": "Unsegregated foot/cycleway ", + "nl": "Gedeeld voet-/fietspad " }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -771,8 +782,8 @@ }, { "question": { - "en": "Does the traffic sign D7 () have a supplementary sign?", - "nl": "Heeft het verkeersbord D7 () een onderbord?" + "en": "Does the traffic sign D7 () have a supplementary sign?", + "nl": "Heeft het verkeersbord D7 () een onderbord?" }, "condition": { "or": [ @@ -784,8 +795,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M6", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -795,8 +806,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M13", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -806,8 +817,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M14", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -818,8 +829,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M7", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -829,8 +840,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M15", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -840,8 +851,8 @@ { "if": "cycleway:traffic_sign=BE:D7;BE:M16", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -860,8 +871,8 @@ }, { "question": { - "en": "Does the traffic sign D7 () have a supplementary sign?", - "nl": "Heeft het verkeersbord D7 () een onderbord?" + "en": "Does the traffic sign D7 () have a supplementary sign?", + "nl": "Heeft het verkeersbord D7 () een onderbord?" }, "condition": { "or": [ @@ -873,8 +884,8 @@ { "if": "traffic_sign=BE:D7;BE:M6", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -884,8 +895,8 @@ { "if": "traffic_sign=BE:D7;BE:M13", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -895,8 +906,8 @@ { "if": "traffic_sign=BE:D7;BE:M14", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -907,8 +918,8 @@ { "if": "traffic_sign=BE:D7;BE:M7", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -918,8 +929,8 @@ { "if": ":traffic_sign=BE:D7;BE:M15", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -929,8 +940,8 @@ { "if": "traffic_sign=BE:D7;BE:M16", "then": { - "en": "", - "nl": "" + "en": "", + "nl": "" }, "hideInAnswer": "_country!=be", "addExtraTags": [ @@ -964,7 +975,11 @@ }, "freeform": { "key": "cycleway:buffer", - "type": "pfloat" + "type": "length", + "helperArgs": [ + "20", + "map" + ] } }, { @@ -1151,7 +1166,7 @@ "calculatedTags": [ "_comfort_score=feat.score('https://raw.githubusercontent.com/pietervdvn/AspectedRouting/master/Examples/bicycle/aspects/bicycle.comfort.json')" ], - "minzoom": 14, + "minzoom": 16, "wayHandling": 0, "title": { "render": { @@ -1286,13 +1301,31 @@ "if": "cyclestreet=", "then": { "en": "This is not a cyclestreet.", - "nl": "Dit is niet een fietstraat" + "nl": "Dit is geen fietsstraat" }, "addExtraTags": [ "overtaking:motor_vehicle=" ] } ] + }, + { + "render": { + "en": "The carriage width of this road is {width:carriageway}m", + "nl": "De breedte van deze rijbaan in deze straat is {width:carriageway}m" + }, + "freeform": { + "key": "width:carriageway", + "type": "length", + "helperArgs": [ + "20", + "map" + ] + }, + "question": { + "en": "What is the carriage width of this road (in meters)?
This is measured from kerb to kerb, including parking lanes", + "nl": "Hoe breed is de rijbaan in deze straat (in meters)?
Gemeten van stoepsteen tot stoepsten, inclusief parkeerstroken" + } } ] }, @@ -1465,7 +1498,8 @@ { "if": "cycle_barrier:type=squeeze", "then": { - "en": "Squeeze gate, gap is smaller at top, than at the bottom " + "en": "Squeeze gate, gap is smaller at top, than at the bottom ", + "nl": "Knijppoort, ruimte is smaller aan de top, dan aan de bodem " } } ] @@ -1487,15 +1521,21 @@ }, "freeform": { "key": "maxwidth:physical", - "type": "pfloat" + "type": "length", + "helperArgs": [ + "20", + "map" + ] } }, { "render": { - "en": "Space between barriers (along the length of the road): {width:seperation} m" + "en": "Space between barriers (along the length of the road): {width:seperation} m", + "nl": "Ruimte tussen barrières (langs de lengte van de weg): {width:seperation} m" }, "question": { - "en": "How much space is there between the barriers (along the length of the road)?" + "en": "How much space is there between the barriers (along the length of the road)?", + "nl": "Hoeveel ruimte is er tussen de barrières (langs de lengte van de weg)?" }, "condition": { "or": [ @@ -1505,15 +1545,21 @@ }, "freeform": { "key": "width:seperation", - "type": "pfloat" + "type": "length", + "helperArgs": [ + "20", + "map" + ] } }, { "render": { - "en": "Width of opening: {width:opening} m" + "en": "Width of opening: {width:opening} m", + "nl": "Breedte van de opening: {width:opening} m" }, "question": { - "en": "How wide is the smallest opening next to the barriers?" + "en": "How wide is the smallest opening next to the barriers?", + "nl": "Hoe breed is de smalste opening naast de barrières?" }, "condition": { "or": [ @@ -1531,7 +1577,8 @@ "en": "Overlap: {overlap} m" }, "question": { - "en": "How much overlap do the barriers have?" + "en": "How much overlap do the barriers have?", + "nl": "Hoeveel overlappen de barrières?" }, "condition": { "or": [ diff --git a/assets/themes/natuurpunt/natuurpunt.json b/assets/themes/natuurpunt/natuurpunt.json index 9c6cf7ad0..306b86f4c 100644 --- a/assets/themes/natuurpunt/natuurpunt.json +++ b/assets/themes/natuurpunt/natuurpunt.json @@ -25,6 +25,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..eb870680e 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,3 @@ - @@ -24,8 +23,9 @@ - - + + @@ -74,7 +74,7 @@ Loading MapComplete, hang on... - +Below
diff --git a/langs/en.json b/langs/en.json index 94ec9719c..6dbde357d 100644 --- a/langs/en.json +++ b/langs/en.json @@ -61,6 +61,12 @@ "readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback" }, "general": { + "pdf": { + "generatedWith": "Generated with MapComplete.osm.be", + "attr": "Map data © OpenStreetMap Contributors, reusable under ODbL", + "attrBackground": "Background layer: {background}", + "versionInfo": "v{version} - generated on {date}" + }, "loginWithOpenStreetMap": "Login with OpenStreetMap", "welcomeBack": "You are logged in, welcome back!", "loginToStart": "Login to answer this question", @@ -159,9 +165,11 @@ }, "download": { "title": "Download visible data", + "downloadAsPdf": "Download a PDF of the current map", + "downloadAsPdfHelper": "Ideal to print the current map", "downloadGeojson": "Download visible data as geojson", - "downloadGeoJsonHelper": "Compatible with QGIS, OsmAnd, ArcGIS, ESRI, ...", - "downloadCSV": "Download as CSV", + "downloadGeoJsonHelper": "Compatible with QGIS, ArcGIS, ESRI, ...", + "downloadCSV": "Download visible data 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
  • the attribution © OpenStreetMap contributors is required
  • Any change to this data must be republished under the same license
Please read the full copyright notice for details", diff --git a/langs/layers/en.json b/langs/layers/en.json index 44c0b1495..e473112dc 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -802,7 +802,7 @@ "question": "Can a cyclist turn right when the light is red?", "mappings": { "0": { - "then": "A cyclist can turn right if the light is red " + "then": "A cyclist can turn right if the light is red " }, "1": { "then": "A cyclist can turn right if the light is red" @@ -816,7 +816,7 @@ "question": "Can a cyclist go straight on when the light is red?", "mappings": { "0": { - "then": "A cyclist can go straight on if the light is red " + "then": "A cyclist can go straight on if the light is red " }, "1": { "then": "A cyclist can go straight on if the light is red" diff --git a/langs/layers/nl.json b/langs/layers/nl.json index c736486bb..24f9088ce 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -837,7 +837,7 @@ "then": "Dit is een zebrapad" }, "1": { - "then": "Dit is niet een zebrapad" + "then": "Dit is geen zebrapad" } } }, @@ -848,7 +848,7 @@ "then": "Een fietser kan deze oversteekplaats gebruiken" }, "1": { - "then": "Een fietser kan niet deze oversteekplaats gebruiken" + "then": "Een fietser kan deze oversteekplaats niet gebruiken" } } }, @@ -859,7 +859,7 @@ "then": "Deze oversteekplaats heeft een verkeerseiland in het midden" }, "1": { - "then": "Deze oversteekplaats heeft niet een verkeerseiland in het midden" + "then": "Deze oversteekplaats heeft geen verkeerseiland in het midden" } } }, @@ -870,7 +870,7 @@ "then": "Deze oversteekplaats heeft een geleidelijn" }, "1": { - "then": "Deze oversteekplaats heeft niet een geleidelijn" + "then": "Deze oversteekplaats heeft geen geleidelijn" }, "2": { "then": "Deze oversteekplaats heeft een geleidelijn, die incorrect is." @@ -884,7 +884,7 @@ "then": "Dit verkeerslicht heeft een knop voor groen licht" }, "1": { - "then": "Dit verkeerlicht heeft niet een knop voor groen licht" + "then": "Dit verkeerlicht heeft geen knop voor groen licht" } } }, @@ -892,7 +892,7 @@ "question": "Mag een fietser rechtsaf slaan als het licht rood is?", "mappings": { "0": { - "then": "Een fietser mag wel rechtsaf slaan als het licht rood is " + "then": "Een fietser mag wel rechtsaf slaan als het licht rood is " }, "1": { "then": "Een fietser mag wel rechtsaf slaan als het licht rood is" @@ -906,7 +906,7 @@ "question": "Mag een fietser rechtdoor gaan als het licht rood is?", "mappings": { "0": { - "then": "Een fietser mag wel rechtdoor gaan als het licht rood is " + "then": "Een fietser mag wel rechtdoor gaan als het licht rood is " }, "1": { "then": "Een fietser mag wel rechtdoor gaan als het licht rood is" @@ -2364,12 +2364,6 @@ } } } - }, - "presets": { - "0": { - "title": "Paden", - "description": "Voeg een ontbrekend, erkend pad toe." - } } } } \ No newline at end of file diff --git a/langs/themes/en.json b/langs/themes/en.json index 94744363f..e8885b11b 100644 --- a/langs/themes/en.json +++ b/langs/themes/en.json @@ -703,7 +703,7 @@ "render": "Cycleways", "mappings": { "0": { - "then": "Bike road" + "then": "Cycleway" }, "1": { "then": "Shared lane" @@ -712,7 +712,7 @@ "then": "Bike lane" }, "3": { - "then": "Bike road next to the road" + "then": "Cycleway next to the road" }, "4": { "then": "Cyclestreet" @@ -933,16 +933,16 @@ "question": "What traffic sign does this cycleway have?", "mappings": { "0": { - "then": "Compulsory cycleway " + "then": "Compulsory cycleway " }, "1": { - "then": "Compulsory cycleway (with supplementary sign) " + "then": "Compulsory cycleway (with supplementary sign)
" }, "2": { - "then": "Segregated foot/cycleway " + "then": "Segregated foot/cycleway " }, "3": { - "then": "Unsegregated foot/cycleway " + "then": "Unsegregated foot/cycleway " }, "4": { "then": "No traffic sign present" @@ -953,16 +953,16 @@ "question": "What traffic sign does this cycleway have?", "mappings": { "0": { - "then": "Compulsory cycleway " + "then": "Compulsory cycleway " }, "1": { - "then": "Compulsory cycleway (with supplementary sign) " + "then": "Compulsory cycleway (with supplementary sign) " }, "2": { - "then": "Segregated foot/cycleway " + "then": "Segregated foot/cycleway " }, "3": { - "then": "Unsegregated foot/cycleway " + "then": "Unsegregated foot/cycleway " }, "4": { "then": "No traffic sign present" @@ -970,25 +970,25 @@ } }, "11": { - "question": "Does the traffic sign D7 () have a supplementary sign?", + "question": "Does the traffic sign D7 () have a supplementary sign?", "mappings": { "0": { - "then": "" + "then": "" }, "1": { - "then": "" + "then": "" }, "2": { - "then": "" + "then": "" }, "3": { - "then": "" + "then": "" }, "4": { - "then": "" + "then": "" }, "5": { - "then": "" + "then": "" }, "6": { "then": "No supplementary traffic sign present" @@ -996,25 +996,25 @@ } }, "12": { - "question": "Does the traffic sign D7 () have a supplementary sign?", + "question": "Does the traffic sign D7 () have a supplementary sign?", "mappings": { "0": { - "then": "" + "then": "" }, "1": { - "then": "" + "then": "" }, "2": { - "then": "" + "then": "" }, "3": { - "then": "" + "then": "" }, "4": { - "then": "" + "then": "" }, "5": { - "then": "" + "then": "" }, "6": { "then": "No supplementary traffic sign present" @@ -1108,6 +1108,10 @@ "then": "This is not a cyclestreet." } } + }, + "2": { + "render": "The carriage width of this road is {width:carriageway}m", + "question": "What is the carriage width of this road (in meters)?
This is measured from kerb to kerb, including parking lanes" } } }, diff --git a/langs/themes/nl.json b/langs/themes/nl.json index 5f3953046..2a30a77cf 100644 --- a/langs/themes/nl.json +++ b/langs/themes/nl.json @@ -652,13 +652,13 @@ "question": "Is dit een fietsstraat?", "mappings": { "0": { - "then": "Dit is een fietstraat, en dus een 30km/h zone" + "then": "Dit is een fietsstraat, en dus een 30km/h zone" }, "1": { - "then": "Dit is een fietstraat" + "then": "Dit is een fietsstraat" }, "2": { - "then": "Dit is niet een fietstraat" + "then": "Dit is geen fietsstraat" } } }, @@ -726,7 +726,33 @@ "question": "Waaruit is het oppervlak van het fietspad van gemaakt?" }, "5": { - "question": "Wat is de kwaliteit van dit fietspad?" + "question": "Wat is de kwaliteit van dit fietspad?", + "mappings": { + "0": { + "then": "Geschikt voor fijne rollers: rollerblade, skateboard" + }, + "1": { + "then": "Geschikt voor fijne wielen: racefiets" + }, + "2": { + "then": "Geschikt voor normale wielen: stadsfiets, rolstoel, scooter" + }, + "3": { + "then": "Geschikt voor brede wielen: trekfiets, auto, rickshaw" + }, + "4": { + "then": "Geschikt voor voertuigen met hoge banden: lichte terreinwagen" + }, + "5": { + "then": "Geschikt voor terreinwagens: zware terreinwagen" + }, + "6": { + "then": "Geschikt voor gespecialiseerde terreinwagens: tractor, alleterreinwagen" + }, + "7": { + "then": "Niet geschikt voor voertuigen met wielen" + } + } }, "6": { "render": "Deze weg is gemaakt van {surface}", @@ -769,16 +795,16 @@ "question": "Welk verkeersbord heeft dit fietspad?", "mappings": { "0": { - "then": "Verplicht fietspad " + "then": "Verplicht fietspad " }, "1": { - "then": "Verplicht fietspad (met onderbord)" + "then": "Verplicht fietspad (met onderbord)
" }, "2": { - "then": "Afgescheiden voet-/fietspad " + "then": "Afgescheiden voet-/fietspad " }, "3": { - "then": "Gedeeld voet-/fietspad " + "then": "Gedeeld voet-/fietspad " }, "4": { "then": "Geen verkeersbord aanwezig" @@ -789,16 +815,16 @@ "question": "Welk verkeersbord heeft dit fietspad?", "mappings": { "0": { - "then": "Verplicht fietspad " + "then": "Verplicht fietspad " }, "1": { - "then": "Verplicht fietspad (met onderbord)" + "then": "Verplicht fietspad (met onderbord)" }, "2": { - "then": "Afgescheiden voet-/fietspad " + "then": "Afgescheiden voet-/fietspad " }, "3": { - "then": "Gedeeld voet-/fietspad " + "then": "Gedeeld voet-/fietspad " }, "4": { "then": "Geen verkeersbord aanwezig" @@ -806,25 +832,25 @@ } }, "11": { - "question": "Heeft het verkeersbord D7 () een onderbord?", + "question": "Heeft het verkeersbord D7 () een onderbord?", "mappings": { "0": { - "then": "" + "then": "" }, "1": { - "then": "" + "then": "" }, "2": { - "then": "" + "then": "" }, "3": { - "then": "" + "then": "" }, "4": { - "then": "" + "then": "" }, "5": { - "then": "" + "then": "" }, "6": { "then": "Geen onderbord aanwezig" @@ -832,25 +858,25 @@ } }, "12": { - "question": "Heeft het verkeersbord D7 () een onderbord?", + "question": "Heeft het verkeersbord D7 () een onderbord?", "mappings": { "0": { - "then": "" + "then": "" }, "1": { - "then": "" + "then": "" }, "2": { - "then": "" + "then": "" }, "3": { - "then": "" + "then": "" }, "4": { - "then": "" + "then": "" }, "5": { - "then": "" + "then": "" }, "6": { "then": "Geen onderbord aanwezig" @@ -935,15 +961,19 @@ "question": "Is dit een fietsstraat?", "mappings": { "0": { - "then": "Dit is een fietstraat, en dus een 30km/h zone" + "then": "Dit is een fietsstraat, en dus een 30km/h zone" }, "1": { - "then": "Dit is een fietstraat" + "then": "Dit is een fietsstraat" }, "2": { - "then": "Dit is niet een fietstraat" + "then": "Dit is geen fietsstraat" } } + }, + "2": { + "render": "De breedte van deze rijbaan in deze straat is {width:carriageway}m", + "question": "Hoe breed is de rijbaan in deze straat (in meters)?
Gemeten van stoepsteen tot stoepsten, inclusief parkeerstroken" } } }, @@ -1014,12 +1044,26 @@ }, "2": { "then": "Drievoudig, drie hekjes achter elkaar " + }, + "3": { + "then": "Knijppoort, ruimte is smaller aan de top, dan aan de bodem " } } }, "3": { "render": "Maximumbreedte: {maxwidth:physical} m", "question": "Hoe breed is de ruimte naast de barrière?" + }, + "4": { + "render": "Ruimte tussen barrières (langs de lengte van de weg): {width:seperation} m", + "question": "Hoeveel ruimte is er tussen de barrières (langs de lengte van de weg)?" + }, + "5": { + "render": "Breedte van de opening: {width:opening} m", + "question": "Hoe breed is de smalste opening naast de barrières?" + }, + "6": { + "question": "Hoeveel overlappen de barrières?" } } } diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 9f957553e..b66c97cde 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -2,6 +2,7 @@ import * as fs from "fs"; import {readFileSync, writeFileSync} from "fs"; import {Utils} from "../Utils"; import ScriptUtils from "./ScriptUtils"; +import {Layer} from "leaflet"; const knownLanguages = ["en", "nl", "de", "fr", "es", "gl", "ca"]; @@ -40,12 +41,12 @@ class TranslationPart { } } - recursiveAdd(object: any) { + recursiveAdd(object: any, context: string) { const isProbablyTranslationObject = knownLanguages.map(l => object.hasOwnProperty(l)).filter(x => x).length > 0; if (isProbablyTranslationObject) { - this.addTranslationObject(object) + this.addTranslationObject(object, context) return; } @@ -68,7 +69,7 @@ class TranslationPart { this.contents.set(key, new TranslationPart()) } - (this.contents.get(key) as TranslationPart).recursiveAdd(v); + (this.contents.get(key) as TranslationPart).recursiveAdd(v, context + "." + key); } } @@ -190,7 +191,7 @@ function generateTranslationsObjectFrom(objects: { path: string, parsed: { id: s if (config === undefined) { throw "Got something not parsed! Path is " + layerFile.path } - layerTr.recursiveAdd(config) + layerTr.recursiveAdd(config, layerFile.path) tr.contents.set(config.id, layerTr) } @@ -301,7 +302,7 @@ function mergeThemeTranslations() { const oldLanguages = config.language; const allTranslations = new TranslationPart(); - allTranslations.recursiveAdd(config) + allTranslations.recursiveAdd(config, themeFile.path) const newLanguages = allTranslations.knownLanguages() const languageDiff = newLanguages.filter(l => oldLanguages.indexOf(l) < 0).join(", ") if (languageDiff !== "") { 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)) + +