| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import { SubtleButton } from "../Base/SubtleButton" | 
					
						
							|  |  |  | import Svg from "../../Svg" | 
					
						
							|  |  |  | import Translations from "../i18n/Translations" | 
					
						
							|  |  |  | import { Utils } from "../../Utils" | 
					
						
							|  |  |  | import Combine from "../Base/Combine" | 
					
						
							|  |  |  | import CheckBoxes from "../Input/Checkboxes" | 
					
						
							|  |  |  | import { GeoOperations } from "../../Logic/GeoOperations" | 
					
						
							|  |  |  | import Toggle from "../Input/Toggle" | 
					
						
							|  |  |  | import Title from "../Base/Title" | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  | import { Store } from "../../Logic/UIEventSource" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import SimpleMetaTagger from "../../Logic/SimpleMetaTagger" | 
					
						
							|  |  |  | import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" | 
					
						
							|  |  |  | import { BBox } from "../../Logic/BBox" | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  | import geojson2svg from "geojson2svg" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  | import { SpecialVisualizationState } from "../SpecialVisualization" | 
					
						
							|  |  |  | import { Feature, FeatureCollection } from "geojson" | 
					
						
							|  |  |  | import { GeoIndexedStoreForLayer } from "../../Logic/FeatureSource/Actors/GeoIndexedStore" | 
					
						
							|  |  |  | import LayerState from "../../Logic/State/LayerState" | 
					
						
							| 
									
										
										
										
											2021-07-16 01:42:09 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-26 21:03:27 +02:00
										 |  |  | export class DownloadPanel extends Toggle { | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |     constructor(state: SpecialVisualizationState) { | 
					
						
							| 
									
										
										
										
											2021-07-16 01:42:09 +02:00
										 |  |  |         const t = Translations.t.general.download | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         const name = state.layout.id | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 23:19:20 +01:00
										 |  |  |         const includeMetaToggle = new CheckBoxes([t.includeMetaData]) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const metaisIncluded = includeMetaToggle.GetValue().map((selected) => selected.length > 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const buttonGeoJson = new SubtleButton( | 
					
						
							|  |  |  |             Svg.floppy_ui(), | 
					
						
							|  |  |  |             new Combine([ | 
					
						
							|  |  |  |                 t.downloadGeojson.SetClass("font-bold"), | 
					
						
							|  |  |  |                 t.downloadGeoJsonHelper, | 
					
						
							|  |  |  |             ]).SetClass("flex flex-col") | 
					
						
							|  |  |  |         ).OnClickWithLoading(t.exporting, async () => { | 
					
						
							|  |  |  |             const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) | 
					
						
							|  |  |  |             Utils.offerContentsAsDownloadableFile( | 
					
						
							|  |  |  |                 JSON.stringify(geojson, null, "  "), | 
					
						
							|  |  |  |                 `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.geojson`, | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     mimetype: "application/vnd.geo+json", | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const buttonCSV = new SubtleButton( | 
					
						
							|  |  |  |             Svg.floppy_ui(), | 
					
						
							|  |  |  |             new Combine([t.downloadCSV.SetClass("font-bold"), t.downloadCSVHelper]).SetClass( | 
					
						
							|  |  |  |                 "flex flex-col" | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         ).OnClickWithLoading(t.exporting, async () => { | 
					
						
							|  |  |  |             const geojson = DownloadPanel.getCleanGeoJson(state, metaisIncluded.data) | 
					
						
							|  |  |  |             const csv = GeoOperations.toCSV(geojson.features) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             Utils.offerContentsAsDownloadableFile( | 
					
						
							|  |  |  |                 csv, | 
					
						
							|  |  |  |                 `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.csv`, | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     mimetype: "text/csv", | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const buttonSvg = new SubtleButton( | 
					
						
							|  |  |  |             Svg.floppy_ui(), | 
					
						
							|  |  |  |             new Combine([t.downloadAsSvg.SetClass("font-bold"), t.downloadAsSvgHelper]).SetClass( | 
					
						
							|  |  |  |                 "flex flex-col" | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         ).OnClickWithLoading(t.exporting, async () => { | 
					
						
							|  |  |  |             const geojson = DownloadPanel.getCleanGeoJsonPerLayer(state, metaisIncluded.data) | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |             const maindiv = document.getElementById("maindiv") | 
					
						
							|  |  |  |             const layers = state.layout.layers.filter((l) => l.source !== null) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const csv = DownloadPanel.asSvg(geojson, { | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |                 layers, | 
					
						
							|  |  |  |                 mapExtent: state.mapProperties.bounds.data, | 
					
						
							|  |  |  |                 width: maindiv.offsetWidth, | 
					
						
							|  |  |  |                 height: maindiv.offsetHeight, | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             Utils.offerContentsAsDownloadableFile( | 
					
						
							|  |  |  |                 csv, | 
					
						
							|  |  |  |                 `MapComplete_${name}_export_${new Date().toISOString().substr(0, 19)}.svg`, | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     mimetype: "image/svg+xml", | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const downloadButtons = new Combine([ | 
					
						
							|  |  |  |             new Title(t.title), | 
					
						
							|  |  |  |             buttonGeoJson, | 
					
						
							|  |  |  |             buttonCSV, | 
					
						
							|  |  |  |             buttonSvg, | 
					
						
							|  |  |  |             includeMetaToggle, | 
					
						
							|  |  |  |             t.licenseInfo.SetClass("link-underline"), | 
					
						
							| 
									
										
										
										
											2023-04-14 04:34:13 +02:00
										 |  |  |         ]).SetClass("w-full flex flex-col") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         super( | 
					
						
							|  |  |  |             downloadButtons, | 
					
						
							|  |  |  |             t.noDataLoaded, | 
					
						
							|  |  |  |             state.dataIsLoading.map((x) => !x) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Converts a geojson to an SVG | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * const feature = { | 
					
						
							|  |  |  |      *       "type": "Feature", | 
					
						
							|  |  |  |      *       "properties": {}, | 
					
						
							|  |  |  |      *       "geometry": { | 
					
						
							|  |  |  |      *         "type": "LineString", | 
					
						
							|  |  |  |      *         "coordinates": [ | 
					
						
							|  |  |  |      *           [-180, 80], | 
					
						
							|  |  |  |      *           [180, -80] | 
					
						
							|  |  |  |      *         ] | 
					
						
							|  |  |  |      *       } | 
					
						
							|  |  |  |      * } | 
					
						
							|  |  |  |      * const perLayer = new Map<string, any[]>([["testlayer", [feature]]]) | 
					
						
							|  |  |  |      * DownloadPanel.asSvg(perLayer).replace(/\n/g, "") // => `<svg width="1000px" height="1000px" viewBox="0 0 1000 1000">    <g id="testlayer" inkscape:groupmode="layer" inkscape:label="testlayer">        <path d="M0,27.77777777777778 1000,472.22222222222223" style="fill:none;stroke-width:1" stroke="#ff0000"/>    </g></svg>`
 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     public static asSvg( | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         perLayer: Map<string, Feature[]>, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         options?: { | 
					
						
							|  |  |  |             layers?: LayerConfig[] | 
					
						
							|  |  |  |             width?: 1000 | number | 
					
						
							|  |  |  |             height?: 1000 | number | 
					
						
							|  |  |  |             mapExtent?: BBox | 
					
						
							|  |  |  |             unit?: "px" | "mm" | string | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         options = options ?? {} | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         const width = options.width ?? 1000 | 
					
						
							|  |  |  |         const height = options.height ?? 1000 | 
					
						
							|  |  |  |         if (width <= 0 || height <= 0) { | 
					
						
							|  |  |  |             throw "Invalid width of height, they should be > 0" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         const unit = options.unit ?? "px" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const mapExtent = { left: -180, bottom: -90, right: 180, top: 90 } | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         if (options.mapExtent !== undefined) { | 
					
						
							|  |  |  |             const bbox = options.mapExtent | 
					
						
							|  |  |  |             mapExtent.left = bbox.minLon | 
					
						
							|  |  |  |             mapExtent.right = bbox.maxLon | 
					
						
							|  |  |  |             mapExtent.bottom = bbox.minLat | 
					
						
							|  |  |  |             mapExtent.top = bbox.maxLat | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         console.log("Generateing svg, extent:", { mapExtent, width, height }) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const elements: string[] = [] | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for (const layer of Array.from(perLayer.keys())) { | 
					
						
							|  |  |  |             const features = perLayer.get(layer) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             if (features.length === 0) { | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const layerDef = options?.layers?.find((l) => l.id === layer) | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |             const rendering = layerDef?.lineRendering[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const converter = geojson2svg({ | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |                 viewportSize: { width, height }, | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                 mapExtent, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 output: "svg", | 
					
						
							|  |  |  |                 attributes: [ | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                     { | 
					
						
							|  |  |  |                         property: "style", | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         type: "static", | 
					
						
							|  |  |  |                         value: "fill:none;stroke-width:1", | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                     }, | 
					
						
							|  |  |  |                     { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         property: "properties.stroke", | 
					
						
							|  |  |  |                         type: "dynamic", | 
					
						
							|  |  |  |                         key: "stroke", | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                 ], | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             for (const feature of features) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 const stroke = | 
					
						
							|  |  |  |                     rendering?.color?.GetRenderValue(feature.properties)?.txt ?? "#ff0000" | 
					
						
							|  |  |  |                 const color = Utils.colorAsHex(Utils.color(stroke)) | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                 feature.properties.stroke = color | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             const groupPaths: string[] = converter.convert({ type: "FeatureCollection", features }) | 
					
						
							|  |  |  |             const group = | 
					
						
							|  |  |  |                 `    <g id="${layer}" inkscape:groupmode="layer" inkscape:label="${layer}">\n` + | 
					
						
							|  |  |  |                 groupPaths.map((p) => "        " + p).join("\n") + | 
					
						
							|  |  |  |                 "\n    </g>" | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |             elements.push(group) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         const w = width | 
					
						
							|  |  |  |         const h = height | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         const header = `<svg width="${w}${unit}" height="${h}${unit}" viewBox="0 0 ${w} ${h}">` | 
					
						
							|  |  |  |         return header + "\n" + elements.join("\n") + "\n</svg>" | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private static getCleanGeoJson( | 
					
						
							|  |  |  |         state: { | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |             layout: LayoutConfig | 
					
						
							|  |  |  |             mapProperties: { bounds: Store<BBox> } | 
					
						
							|  |  |  |             perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | 
					
						
							|  |  |  |             layerState: LayerState | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         includeMetaData: boolean | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |     ): FeatureCollection { | 
					
						
							|  |  |  |         const featuresPerLayer = DownloadPanel.getCleanGeoJsonPerLayer(state, includeMetaData) | 
					
						
							|  |  |  |         const features = [].concat(...Array.from(featuresPerLayer.values())) | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         return { | 
					
						
							|  |  |  |             type: "FeatureCollection", | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             features, | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Returns a new feature of which all the metatags are deleted | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     private static cleanFeature(f: Feature): Feature { | 
					
						
							|  |  |  |         f = { | 
					
						
							|  |  |  |             type: f.type, | 
					
						
							|  |  |  |             geometry: { ...f.geometry }, | 
					
						
							|  |  |  |             properties: { ...f.properties }, | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (const key in f.properties) { | 
					
						
							|  |  |  |             if (key === "_lon" || key === "_lat") { | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |             if (key.startsWith("_")) { | 
					
						
							|  |  |  |                 delete f.properties[key] | 
					
						
							| 
									
										
										
										
											2022-03-18 01:21:00 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         const datedKeys = [].concat( | 
					
						
							|  |  |  |             SimpleMetaTagger.metatags | 
					
						
							|  |  |  |                 .filter((tagging) => tagging.includesDates) | 
					
						
							|  |  |  |                 .map((tagging) => tagging.keys) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         for (const key of datedKeys) { | 
					
						
							|  |  |  |             delete f.properties[key] | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return f | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |     private static getCleanGeoJsonPerLayer( | 
					
						
							|  |  |  |         state: { | 
					
						
							|  |  |  |             layout: LayoutConfig | 
					
						
							|  |  |  |             mapProperties: { bounds: Store<BBox> } | 
					
						
							|  |  |  |             perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | 
					
						
							|  |  |  |             layerState: LayerState | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         includeMetaData: boolean | 
					
						
							|  |  |  |     ): Map<string, Feature[]> { | 
					
						
							|  |  |  |         const featuresPerLayer = new Map<string, any[]>() | 
					
						
							|  |  |  |         const neededLayers = state.layout.layers.filter((l) => l.source !== null).map((l) => l.id) | 
					
						
							|  |  |  |         const bbox = state.mapProperties.bounds.data | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         for (const neededLayer of neededLayers) { | 
					
						
							|  |  |  |             const indexedFeatureSource = state.perLayer.get(neededLayer) | 
					
						
							|  |  |  |             let features = indexedFeatureSource.GetFeaturesWithin(bbox, true) | 
					
						
							|  |  |  |             // The 'indexedFeatureSources' contains _all_ features, they are not filtered yet
 | 
					
						
							|  |  |  |             const filter = state.layerState.filteredLayers.get(neededLayer) | 
					
						
							|  |  |  |             features = features.filter((f) => | 
					
						
							|  |  |  |                 filter.isShown(f.properties, state.layerState.globalFilters.data) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |             if (!includeMetaData) { | 
					
						
							|  |  |  |                 features = features.map((f) => DownloadPanel.cleanFeature(f)) | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |             featuresPerLayer.set(neededLayer, features) | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-14 04:33:06 +02:00
										 |  |  |         return featuresPerLayer | 
					
						
							| 
									
										
										
										
											2021-07-16 01:42:09 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | } |