Fix pdf export, fix feature switches

This commit is contained in:
pietervdvn 2021-07-28 02:51:07 +02:00
parent 6cd75a8260
commit ede67ca58c
19 changed files with 390 additions and 144 deletions

View file

@ -43,6 +43,8 @@ export default class LayoutConfig {
public readonly enableBackgroundLayerSelection: boolean; public readonly enableBackgroundLayerSelection: boolean;
public readonly enableShowAllQuestions: boolean; public readonly enableShowAllQuestions: boolean;
public readonly enableExportButton: boolean; public readonly enableExportButton: boolean;
public readonly enablePdfDownload: boolean;
public readonly customCss?: string; public readonly customCss?: string;
/* /*
How long is the cache valid, in seconds? How long is the cache valid, in seconds?
@ -153,7 +155,8 @@ export default class LayoutConfig {
this.enableAddNewPoints = json.enableAddNewPoints ?? true; this.enableAddNewPoints = json.enableAddNewPoints ?? true;
this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true; this.enableBackgroundLayerSelection = json.enableBackgroundLayerSelection ?? true;
this.enableShowAllQuestions = json.enableShowAllQuestions ?? false; 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.customCss = json.customCss;
this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60) this.cacheTimeout = json.cacheTimout ?? (60 * 24 * 60 * 60)
@ -176,7 +179,7 @@ export default class LayoutConfig {
return return
} }
} else { } 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}]`; throw `Unknown builtin layer ${layer} at ${context}.layers[${i}]`;
} }
} }

View file

@ -336,5 +336,7 @@ export interface LayoutConfigJson {
enableGeolocation?: boolean; enableGeolocation?: boolean;
enableBackgroundLayerSelection?: boolean; enableBackgroundLayerSelection?: boolean;
enableShowAllQuestions?: boolean; enableShowAllQuestions?: boolean;
enableExportButton?: boolean; enableDownload?: boolean;
enablePdfDownload: boolean;
} }

View file

@ -40,9 +40,9 @@ import FeatureSource from "./Logic/FeatureSource/FeatureSource";
import AllKnownLayers from "./Customizations/AllKnownLayers"; import AllKnownLayers from "./Customizations/AllKnownLayers";
import LayerConfig from "./Customizations/JSON/LayerConfig"; import LayerConfig from "./Customizations/JSON/LayerConfig";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import ExportPDF from "./Logic/Actors/ExportPDF";
import {TagsFilter} from "./Logic/Tags/TagsFilter"; import {TagsFilter} from "./Logic/Tags/TagsFilter";
import FilterView from "./UI/BigComponents/FilterView"; import FilterView from "./UI/BigComponents/FilterView";
import ExportPDF from "./UI/ExportPDF";
export class InitUiElements { export class InitUiElements {
static InitAll( static InitAll(
@ -198,7 +198,7 @@ export class InitUiElements {
State.state.leafletMap, State.state.leafletMap,
State.state.layoutToUse State.state.layoutToUse
), { ), {
dontStyle : true dontStyle: true
} }
), ),
undefined, undefined,
@ -206,28 +206,21 @@ export class InitUiElements {
); );
const plus = new MapControlButton( const plus = new MapControlButton(
Svg.plus_zoom_svg() Svg.plus_zoom_svg()
).onClick(() => { ).onClick(() => {
State.state.locationControl.data.zoom++; State.state.locationControl.data.zoom++;
State.state.locationControl.ping(); State.state.locationControl.ping();
}); });
const min = new MapControlButton( const min = new MapControlButton(
Svg.min_zoom_svg() Svg.min_zoom_svg()
).onClick(() => { ).onClick(() => {
State.state.locationControl.data.zoom--; State.state.locationControl.data.zoom--;
State.state.locationControl.ping(); State.state.locationControl.ping();
}); });
const screenshot = new MapControlButton(
Svg.bug_svg(),
).onClick(() => {
// Will already export
new ExportPDF("Screenshot", "natuurpunt");
}) new Combine([plus, min, geolocationButton].map(el => el.SetClass("m-0.5 md:m-1")))
new Combine([plus, min, geolocationButton, screenshot].map(el => el.SetClass("m-0.5 md:m-1")))
.SetClass("flex flex-col") .SetClass("flex flex-col")
.AttachTo("bottom-right"); .AttachTo("bottom-right");
@ -402,7 +395,26 @@ export class InitUiElements {
State.state.featureSwitchFilter 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") .SetClass("flex flex-col")
.AttachTo("bottom-left"); .AttachTo("bottom-left");

View file

@ -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);
})
}
}

View file

@ -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;
}
}

View file

@ -322,7 +322,7 @@ export class GeoOperations {
if (value === undefined) { if (value === undefined) {
line += "," line += ","
} else { } else {
line += JSON.stringify(value)+"," line += JSON.stringify(value) + ","
} }
} }
lines.push(line) lines.push(line)
@ -334,7 +334,7 @@ export class GeoOperations {
} }
class BBox { export class BBox {
readonly maxLat: number; readonly maxLat: number;
readonly maxLon: number; readonly maxLon: number;
@ -357,33 +357,20 @@ class BBox {
this.check(); this.check();
} }
static fromLeafletBounds(bounds) {
return new BBox([[bounds.getWest(), bounds.getNorth()], [bounds.getEast(), bounds.getSouth()]])
}
static get(feature) { static get(feature) {
if (feature.bbox?.overlapsWith === undefined) { if (feature.bbox?.overlapsWith === undefined) {
const turfBbox: number[] = turf.bbox(feature)
if (feature.geometry.type === "MultiPolygon") { feature.bbox = new BBox([[turfBbox[0], turfBbox[1]],[turfBbox[2], turfBbox[3]]]);
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;
}
} }
return feature.bbox; return feature.bbox;
} }
public overlapsWith(other: BBox) { public overlapsWith(other: BBox) {
this.check();
other.check();
if (this.maxLon < other.minLon) { if (this.maxLon < other.minLon) {
return false; 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() { private check() {
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) { if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
console.log(this); console.log(this);

View file

@ -61,7 +61,7 @@ export default class State {
public osmApiFeatureSource: OsmApiFeatureSource; public osmApiFeatureSource: OsmApiFeatureSource;
public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([],"filteredLayers"); public filteredLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>([], "filteredLayers");
/** /**
The latest element that was selected The latest element that was selected
@ -93,7 +93,7 @@ export default class State {
public readonly featureSwitchFilter: UIEventSource<boolean>; public readonly featureSwitchFilter: UIEventSource<boolean>;
public readonly featureSwitchEnableExport: UIEventSource<boolean>; public readonly featureSwitchEnableExport: UIEventSource<boolean>;
public readonly featureSwitchFakeUser: UIEventSource<boolean>; public readonly featureSwitchFakeUser: UIEventSource<boolean>;
public readonly featureSwitchExportAsPdf: UIEventSource<boolean>;
public featurePipeline: FeaturePipeline; public featurePipeline: FeaturePipeline;
@ -295,6 +295,17 @@ export default class State {
"Always show all questions" "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( this.featureSwitchIsTesting = QueryParameters.GetQueryParameter(
"test", "test",
"false", "false",
@ -327,7 +338,6 @@ export default class State {
); );
this.featureSwitchUserbadge.addCallbackAndRun(userbadge => { this.featureSwitchUserbadge.addCallbackAndRun(userbadge => {
if (!userbadge) { if (!userbadge) {
this.featureSwitchAddNew.setData(false) this.featureSwitchAddNew.setData(false)

View file

@ -17,12 +17,14 @@ export default class Minimap extends BaseUIElement {
private _isInited = false; private _isInited = false;
private _allowMoving: boolean; private _allowMoving: boolean;
private readonly _leafletoptions: any; private readonly _leafletoptions: any;
private readonly _onFullyLoaded: (leaflet: L.Map) => void
constructor(options?: { constructor(options?: {
background?: UIEventSource<BaseLayer>, background?: UIEventSource<BaseLayer>,
location?: UIEventSource<Loc>, location?: UIEventSource<Loc>,
allowMoving?: boolean, allowMoving?: boolean,
leafletOptions?: any leafletOptions?: any,
onFullyLoaded?: (leaflet: L.Map) => void
} }
) { ) {
super() super()
@ -32,6 +34,7 @@ export default class Minimap extends BaseUIElement {
this._id = "minimap" + Minimap._nextId; this._id = "minimap" + Minimap._nextId;
this._allowMoving = options.allowMoving ?? true; this._allowMoving = options.allowMoving ?? true;
this._leafletoptions = options.leafletOptions ?? {} this._leafletoptions = options.leafletOptions ?? {}
this._onFullyLoaded = options.onFullyLoaded
Minimap._nextId++ Minimap._nextId++
} }
@ -74,10 +77,10 @@ export default class Minimap extends BaseUIElement {
} }
this._isInited = true; this._isInited = true;
const location = this._location; const location = this._location;
const self = this;
let currentLayer = this._background.data.layer() let currentLayer = this._background.data.layer()
const options = { 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, zoom: location.data?.zoom ?? 2,
layers: [currentLayer], layers: [currentLayer],
zoomControl: false, zoomControl: false,
@ -94,6 +97,13 @@ export default class Minimap extends BaseUIElement {
Utils.Merge(this._leafletoptions, options) Utils.Merge(this._leafletoptions, options)
const map = L.map(this._id, 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( map.setMaxBounds(
[[-100, -200], [100, 200]] [[-100, -200], [100, 200]]
@ -105,6 +115,13 @@ export default class Minimap extends BaseUIElement {
map.removeLayer(currentLayer); map.removeLayer(currentLayer);
} }
currentLayer = newLayer; currentLayer = newLayer;
if (self._onFullyLoaded !== undefined) {
currentLayer.on("load", () => {
console.log("Fully loaded all tiles!")
self._onFullyLoaded(map)
})
}
map.addLayer(newLayer); map.addLayer(newLayer);
}) })

View file

@ -37,16 +37,6 @@ export default class LayerControlPanel extends ScrollableFullScreen {
State.state.featureSwitchEnableExport State.state.featureSwitchEnableExport
)) ))
return new Combine(elements).SetClass("flex flex-col");
}
elements.push(
new Toggle(
new DownloadPanel(),
undefined,
State.state.featureSwitchEnableExport
)
);
return new Combine(elements).SetClass("flex flex-col");
}
} }

145
UI/ExportPDF.ts Normal file
View file

@ -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<LayoutConfig>;
constructor(
options: {
freeDivId: string,
location: UIEventSource<Loc>,
background?: UIEventSource<BaseLayer>
features: UIEventSource<{ feature: any }[]>,
layout: UIEventSource<LayoutConfig>
}
) {
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");
}
}

View file

@ -45,8 +45,8 @@ export default class SplitRoadWizard extends Toggle {
const roadElement = State.state.allElements.ContainingFeatures.get(id) const roadElement = State.state.allElements.ContainingFeatures.get(id)
const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]); const roadEventSource = new UIEventSource([{feature: roadElement, freshness: new Date()}]);
// Datalayer displaying the road and the cut points (if any) // Datalayer displaying the road and the cut points (if any)
new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true, "splitRoadWay"); new ShowDataLayer(roadEventSource, miniMap.leafletMap, State.state.layoutToUse, false, true);
new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false, "splitRoad: splitpoints") new ShowDataLayer(splitPoints, miniMap.leafletMap, SplitRoadWizard.splitLayout, false, false)
/** /**
* Handles a click on the overleaf map. * Handles a click on the overleaf map.

View file

@ -16,14 +16,13 @@ export default class ShowDataLayer {
private readonly _leafletMap: UIEventSource<L.Map>; private readonly _leafletMap: UIEventSource<L.Map>;
private _cleanCount = 0; private _cleanCount = 0;
private readonly _enablePopups: boolean; 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<L.Map>, leafletMap: UIEventSource<L.Map>,
layoutToUse: UIEventSource<LayoutConfig>, layoutToUse: UIEventSource<LayoutConfig>,
enablePopups = true, enablePopups = true,
zoomToFeatures = false, zoomToFeatures = false) {
name?: string) {
this._leafletMap = leafletMap; this._leafletMap = leafletMap;
this._enablePopups = enablePopups; this._enablePopups = enablePopups;
this._features = features; this._features = features;

View file

@ -1,3 +1,67 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<path d="M8.08 8.86C8.13 8.53 8.24 8.24 8.38 7.99C8.52 7.74 8.72 7.53 8.97 7.37C9.21 7.22 9.51 7.15 9.88 7.14C10.11 7.15 10.32 7.19 10.51 7.27C10.71 7.36 10.89 7.48 11.03 7.63C11.17 7.78 11.28 7.96 11.37 8.16C11.46 8.36 11.5 8.58 11.51 8.8H13.3C13.28 8.33 13.19 7.9 13.02 7.51C12.85 7.12 12.62 6.78 12.32 6.5C12.02 6.22 11.66 6 11.24 5.84C10.82 5.68 10.36 5.61 9.85 5.61C9.2 5.61 8.63 5.72 8.15 5.95C7.67 6.18 7.27 6.48 6.95 6.87C6.63 7.26 6.39 7.71 6.24 8.23C6.09 8.75 6 9.29 6 9.87V10.14C6 10.72 6.08 11.26 6.23 11.78C6.38 12.3 6.62 12.75 6.94 13.13C7.26 13.51 7.66 13.82 8.14 14.04C8.62 14.26 9.19 14.38 9.84 14.38C10.31 14.38 10.75 14.3 11.16 14.15C11.57 14 11.93 13.79 12.24 13.52C12.55 13.25 12.8 12.94 12.98 12.58C13.16 12.22 13.27 11.84 13.28 11.43H11.49C11.48 11.64 11.43 11.83 11.34 12.01C11.25 12.19 11.13 12.34 10.98 12.47C10.83 12.6 10.66 12.7 10.46 12.77C10.27 12.84 10.07 12.86 9.86 12.87C9.5 12.86 9.2 12.79 8.97 12.64C8.72 12.48 8.52 12.27 8.38 12.02C8.24 11.77 8.13 11.47 8.08 11.14C8.03 10.81 8 10.47 8 10.14V9.87C8 9.52 8.03 9.19 8.08 8.86V8.86ZM10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18Z" fill="white"/> <!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 600 600"
enable-background="new 0 0 600 600"
xml:space="preserve"
sodipodi:docname="download.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
id="metadata17"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs15">
</defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview13"
showgrid="false"
inkscape:zoom="0.78666667"
inkscape:cx="257.94125"
inkscape:cy="387.47074"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1" />
<rect
display="none"
fill="#020202"
stroke="#000000"
stroke-width="1.1344"
stroke-miterlimit="10"
width="600"
height="600"
id="rect2" />
<polygon
display="none"
fill="#FFFFFF"
points="0,0 0,600 600,300 "
id="polygon4" />
<path
d="M 449.2,197.8 H 349.4 V 44.5 c 0,-14 -11.2,-25.5 -25,-25.5 h -49.9 c -13.7,0 -25,11.5 -25,25.5 v 153.3 h -99.8 l 149.7,204.4 z m 112.4,76.7 c -20.7,0 -37.4,17.2 -37.4,38.3 V 504.4 H 74.9 V 312.8 C 74.9,291.6 58.1,274.5 37.5,274.5 16.8,274.5 0,291.6 0,312.8 V 542.7 C 0,563.8 16.8,581 37.4,581 h 524.1 c 20.7,0 37.4,-17.2 37.4,-38.3 V 312.8 c 0.1,-21.2 -16.7,-38.3 -37.3,-38.3 z"
id="path6"
style="fill:#000000;fill-opacity:1"
inkscape:connector-curvature="0" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -216,6 +216,16 @@
"license": "CC0; trivial", "license": "CC0; trivial",
"sources": [] "sources": []
}, },
{
"authors": [
"Engr.eponce"
],
"path": "download.svg",
"license": "CC-BY-SA 4.0",
"sources": [
"https://commons.wikimedia.org/wiki/File:Download-icon.svg"
]
},
{ {
"authors": [], "authors": [],
"path": "down.svg", "path": "down.svg",

View file

@ -25,7 +25,7 @@
"startZoom": 11, "startZoom": 11,
"widenFactor": 0.05, "widenFactor": 0.05,
"socialImage": "./assets/themes/cycle_infra/cycle-infra.svg", "socialImage": "./assets/themes/cycle_infra/cycle-infra.svg",
"enableExportButton": true, "enableDownload": true,
"layers": [ "layers": [
{ {
"id": "cycleways", "id": "cycleways",
@ -69,7 +69,7 @@
}, },
"then": { "then": {
"nl": "Fietsweg", "nl": "Fietsweg",
"en": "Cycleway" "en": "Bike road"
} }
}, },
{ {
@ -89,7 +89,7 @@
{ {
"if": "cycleway=track", "if": "cycleway=track",
"then": { "then": {
"en": "Cycleway next to the road", "en": "Bike road next to the road",
"nl": "Fietsweg naast de weg" "nl": "Fietsweg naast de weg"
} }
}, },
@ -219,7 +219,7 @@
"if": "cyclestreet=yes", "if": "cyclestreet=yes",
"then": { "then": {
"en": "This is a cyclestreet, and a 30km/h zone.", "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": [ "addExtraTags": [
"overtaking:motor_vehicle=no", "overtaking:motor_vehicle=no",
@ -231,7 +231,7 @@
"if": "cyclestreet=yes", "if": "cyclestreet=yes",
"then": { "then": {
"en": "This is a cyclestreet", "en": "This is a cyclestreet",
"nl": "Dit is een fietsstraat" "nl": "Dit is een fietstraat"
}, },
"hideInAnswer": "_country=be" "hideInAnswer": "_country=be"
}, },
@ -239,7 +239,7 @@
"if": "cyclestreet=", "if": "cyclestreet=",
"then": { "then": {
"en": "This is not a cyclestreet.", "en": "This is not a cyclestreet.",
"nl": "Dit is niet een fietsstraat" "nl": "Dit is niet een fietstraat"
}, },
"addExtraTags": [ "addExtraTags": [
"overtaking:motor_vehicle=" "overtaking:motor_vehicle="
@ -1266,7 +1266,7 @@
"if": "cyclestreet=yes", "if": "cyclestreet=yes",
"then": { "then": {
"en": "This is a cyclestreet, and a 30km/h zone.", "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": [ "addExtraTags": [
"overtaking:motor_vehicle=no", "overtaking:motor_vehicle=no",
@ -1278,7 +1278,7 @@
"if": "cyclestreet=yes", "if": "cyclestreet=yes",
"then": { "then": {
"en": "This is a cyclestreet", "en": "This is a cyclestreet",
"nl": "Dit is een fietsstraat" "nl": "Dit is een fietstraat"
}, },
"hideInAnswer": "_country=be" "hideInAnswer": "_country=be"
}, },
@ -1286,7 +1286,7 @@
"if": "cyclestreet=", "if": "cyclestreet=",
"then": { "then": {
"en": "This is not a cyclestreet.", "en": "This is not a cyclestreet.",
"nl": "Dit is niet een fietsstraat" "nl": "Dit is niet een fietstraat"
}, },
"addExtraTags": [ "addExtraTags": [
"overtaking:motor_vehicle=" "overtaking:motor_vehicle="

View file

@ -26,6 +26,8 @@
"widenFactor": 0.05, "widenFactor": 0.05,
"socialImage": "", "socialImage": "",
"defaultBackgroundId": "CartoDB.Positron", "defaultBackgroundId": "CartoDB.Positron",
"enablePdfDownload": true,
"enableDownload": true,
"layers": [ "layers": [
{ {
"#": "Nature reserve with geometry, z>=13", "#": "Nature reserve with geometry, z>=13",

View file

@ -74,7 +74,10 @@
Loading MapComplete, hang on... Loading MapComplete, hang on...
</div> </div>
<span id="belowmap"
class="absolute"
style="z-index: -1"
>Below</span>
<div id="leafletDiv"></div> <div id="leafletDiv"></div>
<script src="./index.ts"></script> <script src="./index.ts"></script>

View file

@ -61,6 +61,9 @@
"readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback" "readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback"
}, },
"general": { "general": {
"pdf": {
"attr": "Generated with MapComplete.osm.be - map data © OpenStreetMap Contributors, reusable under ODbL"
},
"loginWithOpenStreetMap": "Login with OpenStreetMap", "loginWithOpenStreetMap": "Login with OpenStreetMap",
"welcomeBack": "You are logged in, welcome back!", "welcomeBack": "You are logged in, welcome back!",
"loginToStart": "Login to answer this question", "loginToStart": "Login to answer this question",

47
test.ts
View file

@ -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<LayoutConfig>(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))