Feature: add SVG for 3D-printing

This commit is contained in:
Pieter Vander Vennet 2023-11-06 12:44:58 +01:00
parent 8685ec8ccc
commit 8a9650c737
4 changed files with 69 additions and 15 deletions

View file

@ -173,7 +173,9 @@
"downloadAsPng": "Download as image", "downloadAsPng": "Download as image",
"downloadAsPngHelper": "Ideal to include in reports", "downloadAsPngHelper": "Ideal to include in reports",
"downloadAsSvg": "Download an SVG of the current map", "downloadAsSvg": "Download an SVG of the current map",
"downloadAsSvgHelper": "Compatible Inkscape or Adobe Illustrator; will need further processing", "downloadAsSvgHelper": "Compatible with Inkscape or Adobe Illustrator; will need further processing",
"downloadAsSvgLinesOnly": "Download an SVG of the current map only containing lines",
"downloadAsSvgLinesOnlyHelper": "Self-intersecting lines are broken up, can be used with some 3D-software",
"downloadCSV": "Download visible data as CSV", "downloadCSV": "Download visible data as CSV",
"downloadCSVHelper": "Compatible with LibreOffice Calc, Excel, …", "downloadCSVHelper": "Compatible with LibreOffice Calc, Excel, …",
"downloadFeatureAsGeojson": "Download as GeoJSON-file", "downloadFeatureAsGeojson": "Download as GeoJSON-file",

View file

@ -261,16 +261,16 @@ export class GeoOperations {
} }
/** /**
* Generates the closest point on a way from a given point. * Generates the closest point on a way from a given point.
* If the passed-in geojson object is a polygon, the outer ring will be used as linestring * If the passed-in geojson object is a polygon, the outer ring will be used as linestring
* *
* The properties object will contain three values: * The properties object will contain three values:
// - `index`: closest point was found on nth line part, // - `index`: closest point was found on nth line part,
// - `dist`: distance between pt and the closest point (in kilometer), // - `dist`: distance between pt and the closest point (in kilometer),
// `location`: distance along the line between start (of the line) and the closest point. // `location`: distance along the line between start (of the line) and the closest point.
* @param way The road on which you want to find a point * @param way The road on which you want to find a point
* @param point Point defined as [lon, lat] * @param point Point defined as [lon, lat]
*/ */
public static nearestPoint( public static nearestPoint(
way: Feature<LineString>, way: Feature<LineString>,
point: [number, number] point: [number, number]
@ -449,6 +449,7 @@ export class GeoOperations {
return perBbox return perBbox
} }
public static toGpx( public static toGpx(
locations: locations:
| Feature<LineString> | Feature<LineString>
@ -1052,4 +1053,40 @@ export class GeoOperations {
} }
throw "CalculateIntersection fallthrough: can not calculate an intersection between features" throw "CalculateIntersection fallthrough: can not calculate an intersection between features"
} }
public static SplitSelfIntersectingWays(features: Feature[]): Feature[] {
const result: Feature[] = []
for (const feature of features) {
if (feature.geometry.type === "LineString") {
let coors = feature.geometry.coordinates
for (let i = coors.length - 1; i >= 0; i--) {
// Go back, to nick of the back when needed
const ci = coors[i]
for (let j = i + 1; j < coors.length; j++) {
const cj = coors[j]
if (
Math.abs(ci[0] - cj[0]) <= 0.000001 &&
Math.abs(ci[1] - cj[1]) <= 0.0000001
) {
// Found a self-intersecting way!
console.debug("SPlitting way", feature.properties.id)
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors.slice(i + 1) },
})
coors = coors.slice(0, i + 1)
break
}
}
}
result.push({
...feature,
geometry: { ...feature.geometry, coordinates: coors },
})
}
}
return result
}
} }

View file

@ -5,6 +5,7 @@ import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { Utils } from "../../Utils" import { Utils } from "../../Utils"
import SimpleMetaTagger from "../../Logic/SimpleMetaTagger" import SimpleMetaTagger from "../../Logic/SimpleMetaTagger"
import geojson2svg from "geojson2svg" import geojson2svg from "geojson2svg"
import { GeoOperations } from "../../Logic/GeoOperations"
/** /**
* Exposes the download-functionality * Exposes the download-functionality
@ -82,6 +83,7 @@ export default class DownloadHelper {
height?: 1000 | number height?: 1000 | number
mapExtent?: BBox mapExtent?: BBox
unit?: "px" | "mm" | string unit?: "px" | "mm" | string
noSelfIntersectingLines?: boolean
}) { }) {
const perLayer = this._state.perLayer const perLayer = this._state.perLayer
options = options ?? {} options = options ?? {}
@ -103,7 +105,7 @@ export default class DownloadHelper {
const elements: string[] = [] const elements: string[] = []
for (const layer of Array.from(perLayer.keys())) { for (const layer of Array.from(perLayer.keys())) {
const features = perLayer.get(layer).features.data let features = perLayer.get(layer).features.data
if (features.length === 0) { if (features.length === 0) {
continue continue
} }
@ -128,7 +130,9 @@ export default class DownloadHelper {
}, },
], ],
}) })
if (options.noSelfIntersectingLines) {
features = GeoOperations.SplitSelfIntersectingWays(features)
}
for (const feature of features) { for (const feature of features) {
const stroke = const stroke =
rendering?.color?.GetRenderValue(feature.properties)?.txt ?? "#ff0000" rendering?.color?.GetRenderValue(feature.properties)?.txt ?? "#ff0000"

View file

@ -19,7 +19,7 @@
let metaIsIncluded = false let metaIsIncluded = false
const name = state.layout.id const name = state.layout.id
function offerSvg(): string { function offerSvg(noSelfIntersectingLines: boolean): string {
const maindiv = document.getElementById("maindiv") const maindiv = document.getElementById("maindiv")
const layers = state.layout.layers.filter((l) => l.source !== null) const layers = state.layout.layers.filter((l) => l.source !== null)
return downloadHelper.asSvg({ return downloadHelper.asSvg({
@ -27,6 +27,7 @@
mapExtent: state.mapProperties.bounds.data, mapExtent: state.mapProperties.bounds.data,
width: maindiv.offsetWidth, width: maindiv.offsetWidth,
height: maindiv.offsetHeight, height: maindiv.offsetHeight,
noSelfIntersectingLines: true
}) })
} }
</script> </script>
@ -71,9 +72,19 @@
mimetype="image/svg+xml" mimetype="image/svg+xml"
mainText={t.downloadAsSvg} mainText={t.downloadAsSvg}
helperText={t.downloadAsSvgHelper} helperText={t.downloadAsSvgHelper}
construct={offerSvg} construct={() => offerSvg(false)}
/> />
<DownloadButton
{state}
{metaIsIncluded}
extension="svg"
mimetype="image/svg+xml"
mainText={t.downloadAsSvgLinesOnly}
helperText={t.downloadAsSvgLinesOnlyHelper}
construct={() => offerSvg(true)}
/>
<DownloadButton <DownloadButton
{state} {state}
{metaIsIncluded} {metaIsIncluded}