Fix pdf export, fix feature switches
This commit is contained in:
parent
6cd75a8260
commit
ede67ca58c
19 changed files with 390 additions and 144 deletions
|
@ -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}]`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
|
16
State.ts
16
State.ts
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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
145
UI/ExportPDF.ts
Normal 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");
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 |
|
@ -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",
|
||||||
|
|
|
@ -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="
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
47
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<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))
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue