@masterportal/masterportalapi 2.63.0 → 2.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import {GPX} from "ol/format.js";
3
+ import {Circle} from "ol/geom.js";
4
+ import {fromCircle} from "ol/geom/Polygon.js";
5
+ import {WKT} from "ol/format.js";
6
+ import Feature from "ol/Feature.js";
7
+ import {isObject, transformPoint, transformGeometry} from "./utils.js";
8
+
9
+ /**
10
+ * Transforms the given line or polygon coordinates from a given source projection to EPSG:4326.
11
+ *
12
+ * @param {String} sourceProjectionCode Source projection code (e.g. "EPSG:25832").
13
+ * @param {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} coords Coordinates.
14
+ * @param {Boolean} isPolygon Determines whether the given coordinates are a polygon or a line.
15
+ * @returns {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} Transformed coordinates.
16
+ */
17
+ function transform (sourceProjectionCode, coords, isPolygon) {
18
+ const transCoords = [];
19
+
20
+ for (const value of coords) {
21
+ if (isPolygon) {
22
+ if (coords.length > 1) {
23
+ transCoords.push(transform(sourceProjectionCode, value, isPolygon)[0]);
24
+ }
25
+ else {
26
+ value.forEach(point => {
27
+ if (point.length > 2) {
28
+ transCoords.push(transform(sourceProjectionCode, point, isPolygon));
29
+ }
30
+ else {
31
+ transCoords.push(transformPoint(sourceProjectionCode, point));
32
+ }
33
+ });
34
+ continue;
35
+ }
36
+ }
37
+ else if (value.length > 2 && !isPolygon) {
38
+ transCoords.push(transform(sourceProjectionCode, value, isPolygon));
39
+ }
40
+ else {
41
+ transCoords.push(transformPoint(sourceProjectionCode, value));
42
+ }
43
+ }
44
+ return isPolygon ? [transCoords] : transCoords;
45
+ }
46
+
47
+ /**
48
+ * Transforms the given geometry from a source projection to EPSG:4326.
49
+ * If the geometry type is not supported (LineString, Point, or Polygon), an empty array is returned.
50
+ *
51
+ * @param {module:ol/geom/Geometry} geometry Geometry to be transformed.
52
+ * @param {String} projection The source EPSG projection code.
53
+ * @returns {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} The transformed coordinates or an empty array.
54
+ */
55
+ function transformCoordinates (geometry, projection) {
56
+ const coords = geometry.getCoordinates(),
57
+ type = geometry.getType();
58
+
59
+ switch (type) {
60
+ case "LineString":
61
+ return transform(projection, coords, false);
62
+ case "Point":
63
+ return transformPoint(projection, coords);
64
+ case "Polygon":
65
+ return transform(projection, coords, true);
66
+ default:
67
+ return [];
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Adds draw state extensions to GPX feature nodes.
73
+ * @param {String} gpxData The generated GPX document.
74
+ * @param {ol/Feature[]} features The features written to the document.
75
+ * @returns {String} The GPX document with draw state extensions.
76
+ */
77
+ function addGpxDrawStateExtensions (gpxData, features) {
78
+ const document = new DOMParser().parseFromString(gpxData, "text/xml"),
79
+ featureNodes = Array.from(document.getElementsByTagName("*"))
80
+ .filter(node => ["wpt", "rte", "trk"].includes(node.localName)),
81
+ drawStateNamespace = "https://masterportal.org/gpx";
82
+
83
+ featureNodes.forEach((featureNode, index) => {
84
+ const drawState = features[index].get("masterportal_attributes")?.drawState;
85
+
86
+ if (!isObject(drawState)) {
87
+ return;
88
+ }
89
+
90
+ const extensions = document.createElementNS(document.documentElement.namespaceURI, "extensions"),
91
+ drawStateNode = document.createElementNS(drawStateNamespace, "masterportal:drawState");
92
+
93
+ drawStateNode.textContent = JSON.stringify(drawState);
94
+ extensions.appendChild(drawStateNode);
95
+ featureNode.appendChild(extensions);
96
+ document.documentElement.setAttribute("xmlns:masterportal", drawStateNamespace);
97
+ });
98
+
99
+ return new XMLSerializer().serializeToString(document);
100
+ }
101
+
102
+ /**
103
+ * Converts the features from OpenLayers Features to features in the chosen format,
104
+ * transforming coordinates from the given projection to EPSG:4326.
105
+ *
106
+ * @param {ol/Feature[]} features The features to be converted.
107
+ * @param {module:ol/format} format Format in which the features should be written.
108
+ * @param {String} projection The source EPSG projection code.
109
+ * @returns {String} The features written in the chosen format as a String.
110
+ */
111
+ function convertFeatures (features, format, projection) {
112
+ const convertedFeatures = [];
113
+ let notSupportedGeometryType = false;
114
+
115
+ for (const feature of features) {
116
+ const cloned = feature.clone(),
117
+ transCoords = transformCoordinates(cloned.getGeometry(), projection);
118
+
119
+ if (transCoords.length === 3 && transCoords[2] === 0) {
120
+ transCoords.pop();
121
+ }
122
+
123
+ cloned.getGeometry().setCoordinates(transCoords, "XY");
124
+ convertedFeatures.push(cloned);
125
+
126
+ if (format instanceof GPX && feature.getGeometry().getType() === "Polygon") {
127
+ notSupportedGeometryType = true;
128
+ }
129
+ }
130
+
131
+ if (notSupportedGeometryType) {
132
+ console.warn("Some features could not be exported because their geometry type is not supported by the chosen format.");
133
+ }
134
+
135
+ const dataString = format.writeFeatures(convertedFeatures);
136
+
137
+ return format instanceof GPX ? addGpxDrawStateExtensions(dataString, features) : dataString;
138
+ }
139
+
140
+ /**
141
+ * Prepares features for download by cloning them and converting Circle geometries
142
+ * to Polygons, storing circle metadata in masterportal_attributes.
143
+ *
144
+ * @param {ol/Feature[]} features The features to prepare.
145
+ * @param {String} code The source EPSG projection code.
146
+ * @returns {ol/Feature[]} The prepared features.
147
+ */
148
+ function prepareFeatures (features, code) {
149
+ const downloadFeatures = [];
150
+
151
+ features.forEach((drawnFeature) => {
152
+ const feature = drawnFeature.clone(),
153
+ geometry = feature.getGeometry();
154
+
155
+ if (geometry instanceof Circle || geometry.getType() === "Circle") {
156
+ feature.setGeometry(fromCircle(geometry));
157
+ transformGeometry(code, geometry);
158
+ feature.set("masterportal_attributes", Object.assign(feature.get("masterportal_attributes") ?? {}, {
159
+ "isGeoCircle": true,
160
+ "geoCircleCenter": geometry.getCenter().join(","),
161
+ "geoCircleRadius": geometry.getRadius()
162
+ }));
163
+ }
164
+ downloadFeatures.push(feature);
165
+ });
166
+ return downloadFeatures;
167
+ }
168
+
169
+ /**
170
+ * Sets the geometry of each feature as WKT and the EPSG code to its csv_attributes.
171
+ *
172
+ * @param {ol/Feature[]} features An array of features.
173
+ * @param {String} code The EPSG code in which the features are coded.
174
+ * @returns {ol/Feature[]|Boolean} The features incl. the WKT geometries. False if the given parameter is not an array.
175
+ */
176
+ export function setCsvAttributes (features, code) {
177
+ if (!Array.isArray(features)) {
178
+ return false;
179
+ }
180
+ const wktParser = new WKT();
181
+
182
+ features.forEach(feature => {
183
+ if (feature instanceof Feature) {
184
+ let geom = feature.getGeometry();
185
+
186
+ if (geom instanceof Circle) {
187
+ geom = fromCircle(geom);
188
+ }
189
+ const wktGeometry = wktParser.writeGeometry(geom);
190
+
191
+ if (!isObject(feature.get("csv_attributes"))) {
192
+ feature.set("csv_attributes", {});
193
+ }
194
+ feature.get("csv_attributes").geometry = wktGeometry;
195
+ feature.get("csv_attributes").epsg = code;
196
+
197
+ Object.keys(feature.getProperties()).forEach(key => {
198
+ if (!["masterportal_attributes", "geometry", "csv_attributes"].includes(key)) {
199
+ feature.get("csv_attributes")[key] = feature.get(key);
200
+ }
201
+ });
202
+ }
203
+ });
204
+
205
+ return features;
206
+ }
207
+
208
+ export default {
209
+ convertFeatures,
210
+ transform,
211
+ transformPoint,
212
+ transformGeometry,
213
+ transformCoordinates,
214
+ prepareFeatures,
215
+ setCsvAttributes
216
+ };
@@ -0,0 +1,315 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import {KML} from "ol/format.js";
3
+ import Feature from "ol/Feature.js";
4
+ import {isObject, transformPoint} from "./utils.js";
5
+
6
+
7
+ /**
8
+ * Converts an RGBA color array to a KML hex color string (aabbggrr format).
9
+ *
10
+ * @param {Number[]} rgba The color as [r, g, b, a] where r/g/b are 0-255 and a is 0-1.
11
+ * @returns {String} The KML hex color string in aabbggrr format, or empty string if input is invalid.
12
+ */
13
+ function rgbaToKmlColorHex (rgba) {
14
+ if (!Array.isArray(rgba) || rgba.length < 3) {
15
+ return "";
16
+ }
17
+ const r = Math.round(rgba[0]).toString(16).padStart(2, "0"),
18
+ g = Math.round(rgba[1]).toString(16).padStart(2, "0"),
19
+ b = Math.round(rgba[2]).toString(16).padStart(2, "0"),
20
+ a = rgba.length >= 4 ? Math.round(rgba[3] * 255).toString(16).padStart(2, "0") : "ff";
21
+
22
+ return `${a}${b}${g}${r}`;
23
+ }
24
+
25
+ /**
26
+ * Creates the IconStyle-Part of a Point-KML. Contains the link to a SVG.
27
+ *
28
+ * @see https://developers.google.com/kml/documentation/kmlreference#iconstyle
29
+ * @param {String} url URL from where the Icon can be retrieved from.
30
+ * @param {Number} scale Scale of the Icon. NOTE: If this value is 0, the Icon is not displayed.
31
+ * @param {String} [color] Optional KML hex color string (aabbggrr format).
32
+ * @returns {String} The IconStyle-Part of a KML-File.
33
+ */
34
+ function createKmlIconStyle (url, scale, color) {
35
+ const scaleTag = `<scale>${scale}</scale>`,
36
+ href = `<href>${url}</href>`,
37
+ colorTag = color ? `<color>${color}</color>` : "";
38
+
39
+ return `<IconStyle>${colorTag}${scaleTag}<Icon>${href}</Icon></IconStyle>`;
40
+ }
41
+
42
+ /**
43
+ * Constructs the hotspot-tag (anchoring of the icon) of an IconStyle-Part of a Point-KML.
44
+ *
45
+ * @see https://developers.google.com/kml/documentation/kmlreference#hotspot
46
+ * @param {Object} anchor Values for the hotspot-tag are retrieved from this object.
47
+ * @returns {String} hotspot-Tag for a KML IconStyle.
48
+ */
49
+ function getKmlHotSpotOfIconStyle (anchor) {
50
+ const x = anchor.anchor[0],
51
+ y = anchor.anchor[1],
52
+ {xUnit, yUnit} = anchor;
53
+
54
+ return `<hotSpot x="${x}" y="${y}" xunits="${xUnit}" yunits="${yUnit}" />`;
55
+ }
56
+
57
+ /**
58
+ * Transforms the given coordinates from EPSG:25832 to EPSG:4326, regardless of their nesting depth.
59
+ * Handles single coordinates as well as the nested arrays of (multi) lines and (multi) polygons.
60
+ *
61
+ * @param {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} coords Coordinates.
62
+ * @param {String} projectionCode Source projection code (e.g. "EPSG:25832").
63
+ * @returns {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} Transformed coordinates.
64
+ */
65
+ function transform (coords, projectionCode) {
66
+ if (!Array.isArray(coords) || coords.length === 0) {
67
+ return [];
68
+ }
69
+ if (Array.isArray(coords[0])) {
70
+ return coords.map(nested => transform(nested, projectionCode));
71
+ }
72
+ return transformPoint(projectionCode, coords);
73
+ }
74
+
75
+
76
+ /**
77
+ * Transforms the given geometry from EPSG:25832 to EPSG:4326.
78
+ * If the geometry type is not supported, an empty array is returned.
79
+ *
80
+ * @param {module:ol/geom/Geometry} geometry Geometry to be transformed.
81
+ * @param {String} projectionCode Source projection code (e.g. "EPSG:25832").
82
+ * @returns {(Array<number>|Array<Array<number>>|Array<Array<Array<number>>>)} The transformed coordinates or an empty array.
83
+ */
84
+ function transformCoordinates (geometry, projectionCode) {
85
+ const type = geometry.getType();
86
+
87
+ if (!["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon"].includes(type)) {
88
+ console.warn(`Geometry type "${type}" is not supported for transformation.`);
89
+ return [];
90
+ }
91
+
92
+ if (!projectionCode) {
93
+ return geometry.getCoordinates();
94
+ }
95
+
96
+ return transform(geometry.getCoordinates(), projectionCode);
97
+ }
98
+
99
+ /**
100
+ * Gets a KML document which has custom attributes from given features added.
101
+ *
102
+ * @param {ol/Feature[]} features The features.
103
+ * @param {module:ol/format/KML} format The KML format instance.
104
+ * @returns {Document|null} The KML Document or null if no attributes are present.
105
+ */
106
+ function getKMLWithCustomAttributes (features, format) {
107
+ if (!Array.isArray(features) || !isObject(format) || !features.some(feature => typeof feature.get === "function" && feature.get("attributes"))) {
108
+ return null;
109
+ }
110
+ const kml = new DOMParser().parseFromString(format.writeFeatures(features), "text/xml"),
111
+ placemarks = kml.getElementsByTagName("Placemark");
112
+
113
+ if (!placemarks.length) {
114
+ return null;
115
+ }
116
+ features.forEach((feature, idx) => {
117
+ const attributes = feature.get("attributes"),
118
+ attributeKeys = isObject(attributes) ? Object.keys(attributes) : [];
119
+
120
+ if (!attributeKeys.length) {
121
+ return;
122
+ }
123
+ attributeKeys.forEach(attrKey => {
124
+ if (placemarks[idx]) {
125
+ const extendedData = placemarks[idx].querySelector("ExtendedData");
126
+
127
+ if (!(extendedData instanceof Element)) {
128
+ return;
129
+ }
130
+
131
+ // KML native tags like name or description are not written into ExtendedData, so no Data node exists for them.
132
+ const data = extendedData.querySelector(`Data[name='${attrKey}']`),
133
+ existingDataNode = extendedData.querySelector(`Data[name='custom-attribute____${attrKey}']`);
134
+
135
+ if (existingDataNode instanceof Element) {
136
+ existingDataNode.remove();
137
+ }
138
+
139
+ if (data instanceof Element) {
140
+ data.setAttribute("name", `custom-attribute____${attrKey}`);
141
+ }
142
+ }
143
+ });
144
+ });
145
+ return kml;
146
+ }
147
+
148
+ /**
149
+ * Converts features to a KML string, transforming coordinates from EPSG:25832 to EPSG:4326.
150
+ *
151
+ * @param {ol/Feature[]} features The features to be converted.
152
+ * @param {module:ol/format/KML} format The KML format instance.
153
+ * @param {String} projectionCode Source projection code (e.g. "EPSG:25832").
154
+ * @returns {String} The features converted to a KML string.
155
+ */
156
+ function convertFeatures (features, format, projectionCode) {
157
+ const convertedFeatures = [];
158
+ let kml = null;
159
+
160
+ for (const feature of features) {
161
+ const cloned = feature.clone(),
162
+ transCoords = transformCoordinates(cloned.getGeometry(), projectionCode);
163
+
164
+ if (transCoords.length === 3 && transCoords[2] === 0) {
165
+ transCoords.pop();
166
+ }
167
+
168
+ cloned.getGeometry().setCoordinates(transCoords, "XY");
169
+ convertedFeatures.push(cloned);
170
+ }
171
+
172
+ kml = getKMLWithCustomAttributes(convertedFeatures, format);
173
+ if (kml === null) {
174
+ return format.writeFeatures(convertedFeatures);
175
+ }
176
+ return new XMLSerializer().serializeToString(kml);
177
+ }
178
+
179
+ /**
180
+ * Converts the features to KML while also saving its style information.
181
+ * @param {ol.Feature[]} features - the used features
182
+ * @param {String} projectionCode Source projection code (e.g. "EPSG:25832").
183
+ * @returns {String} The features written in KML as a String.
184
+ */
185
+ async function convertFeaturesToKml (features, projectionCode) {
186
+ const featureCount = features.length,
187
+ anchors = Array(featureCount).fill(undefined),
188
+ format = new KML({extractStyles: true}),
189
+ hasIconUrl = Array(featureCount).fill(false),
190
+ pointColors = Array(featureCount).fill(undefined),
191
+ pointScales = Array(featureCount).fill(undefined),
192
+ skip = Array(featureCount).fill(false),
193
+ textFonts = Array(featureCount).fill(undefined),
194
+ textFontSize = Array(featureCount).fill(undefined),
195
+ convertedFeatures = new DOMParser().parseFromString(convertFeatures(features, format, projectionCode), "text/xml");
196
+
197
+ features.forEach((feature, i) => {
198
+ const type = feature.getGeometry().getType();
199
+ let color,
200
+ style,
201
+ styles;
202
+
203
+ if (type === "Point" && feature.values_.drawState && feature.values_.drawState.text !== undefined) {
204
+ // Imported KML with text, can be used as it is
205
+ skip[i] = true;
206
+ textFontSize[i] = feature.values_.drawState.fontSize;
207
+ }
208
+ else {
209
+ try {
210
+ styles = feature.getStyleFunction()(feature);
211
+ style = Array.isArray(styles) ? styles[0] : styles;
212
+ }
213
+ catch (err) {
214
+ // Only happens if an imported KML is exported, can be skipped
215
+ skip[i] = true;
216
+ }
217
+
218
+ if (type === "Point") {
219
+ if (style.getImage() !== null && style.getImage().iconImage_ !== undefined) {
220
+ // Imported KML with link to SVG icon, has iconUrl from previous import
221
+ hasIconUrl[i] = true;
222
+ const anchorXUnits = style.getImage().anchorXUnits_,
223
+ anchorYUnits = style.getImage().anchorYUnits_,
224
+ anchor = style.getImage().anchor_;
225
+
226
+ anchors[i] = {xUnit: anchorXUnits, yUnit: anchorYUnits, anchor: anchor};
227
+ }
228
+ else if (style.getText()) {
229
+ textFonts[i] = style.getText().getFont();
230
+ }
231
+ else {
232
+ color = style.getImage().getFill().getColor();
233
+ pointColors[i] = [color[0], color[1], color[2]];
234
+ pointScales[i] = Math.max(...style.getImage().getSize()) / 32;
235
+ }
236
+ }
237
+ }
238
+ });
239
+
240
+ Array.from(convertedFeatures.getElementsByTagName("Placemark")).forEach((placemark, i) => {
241
+ if (placemark.getElementsByTagName("Point").length > 0 && skip[i] === false) {
242
+ const style = placemark.getElementsByTagName("Style")[0];
243
+
244
+ if (hasIconUrl[i] === false && pointColors[i]) {
245
+ const drawStateColor = features[i].values_ && features[i].values_.drawState ? features[i].values_.drawState.color : undefined,
246
+ kmlColor = drawStateColor ? rgbaToKmlColorHex(drawStateColor) : "",
247
+ iconUrl = `${window.location.origin}/src/assets/img/tools/draw/circle_white.svg`,
248
+ iconStyle = createKmlIconStyle(iconUrl, pointScales[i], kmlColor);
249
+
250
+ style.innerHTML += iconStyle;
251
+ }
252
+ else if (hasIconUrl[i] === true && anchors[i] !== undefined) {
253
+ const iconStyle = placemark.getElementsByTagName("IconStyle")[0];
254
+
255
+ iconStyle.innerHTML += getKmlHotSpotOfIconStyle(anchors[i]);
256
+ }
257
+ }
258
+
259
+ // Setting format for text
260
+ if (placemark.getElementsByTagName("Point").length > 0 && skip[i] === true && !isNaN(textFontSize[i])) {
261
+ const scale = textFontSize[i] / 16,
262
+ style = placemark.getElementsByTagName("Style")[0],
263
+ iconUrl = `${window.location.origin}/src/assets/img/tools/draw/circle_blue.svg`,
264
+ maskIcon = new DOMParser().parseFromString("<IconStyle><scale>0</scale><Icon><href>" + iconUrl + "</href></Icon></IconStyle>", "text/xml"),
265
+ maskScale = new DOMParser().parseFromString("<scale>" + scale + "</scale>", "text/xml");
266
+
267
+ style.getElementsByTagName("LabelStyle")[0].appendChild(maskScale.getElementsByTagName("scale")[0]);
268
+ style.appendChild(maskIcon.getElementsByTagName("IconStyle")[0]);
269
+ }
270
+ });
271
+ return new XMLSerializer().serializeToString(convertedFeatures);
272
+ }
273
+
274
+ /**
275
+ * Sets the attributes of each feature as requested by the converting function.
276
+ *
277
+ * @param {ol/Feature[]} features An array of features.
278
+ * @returns {ol/Feature[]|Boolean} The features incl. the necessary attributes. False if the given parameter is not an array.
279
+ */
280
+ function setKmlAttributes (features) {
281
+ if (!Array.isArray(features)) {
282
+ return false;
283
+ }
284
+
285
+ features.forEach(feature => {
286
+ if (feature instanceof Feature && !Object.prototype.hasOwnProperty.call(feature.getProperties(), "attributes")) {
287
+ feature.set("attributes", {});
288
+
289
+ Object.keys(feature.getProperties()).forEach(key => {
290
+ if (!["masterportal_attributes", "geometry", "attributes"].includes(key)) {
291
+ feature.get("attributes")[key] = feature.get(key);
292
+ }
293
+ });
294
+
295
+ if (Object.prototype.hasOwnProperty.call(feature.getProperties(), "masterportal_attributes")) {
296
+ Object.keys(feature.get("masterportal_attributes")).forEach(key => {
297
+ const value = feature.get("masterportal_attributes")[key];
298
+
299
+ feature.set(key, key === "drawState" && isObject(value) ? JSON.stringify(value) : value);
300
+ });
301
+ }
302
+ }
303
+ });
304
+
305
+ return features;
306
+ }
307
+
308
+
309
+ export default {
310
+ convertFeatures,
311
+ convertFeaturesToKml,
312
+ transformCoordinates,
313
+ getKMLWithCustomAttributes,
314
+ setKmlAttributes
315
+ };
@@ -0,0 +1,60 @@
1
+ import proj4 from "proj4";
2
+
3
+ /**
4
+ * Checks if the passed parameter is an object.
5
+ *
6
+ * @param {*} value parameter to check.
7
+ * @returns {Boolean} true if the value is an object; false otherwise.
8
+ */
9
+ function isObject (value) {
10
+ return Object.prototype.toString.call(value) === "[object Object]";
11
+ }
12
+
13
+ /**
14
+ * Registers a source projection with proj4 and returns both source and destination projection objects.
15
+ *
16
+ * @param {String} sourceProj Source projection name (e.g. "EPSG:25832").
17
+ * @param {String} destProj Destination projection name (e.g. "EPSG:4326").
18
+ * @param {String} zone UTM zone of the source projection.
19
+ * @returns {Object} An object with proj4 projection instances for sourceProj and destProj.
20
+ */
21
+ function getProjections (sourceProj, destProj, zone) {
22
+ proj4.defs(sourceProj, "+proj=utm +zone=" + zone + " +ellps=WGS84 +towgs84=0,0,0,0,0,0,1 +units=m +no_defs");
23
+
24
+ return {
25
+ sourceProj: proj4(sourceProj),
26
+ destProj: proj4(destProj)
27
+ };
28
+ }
29
+
30
+ const projections = getProjections("EPSG:25832", "EPSG:4326", "32");
31
+
32
+ /**
33
+ * Transforms the given point coordinates from a given source projection to EPSG:4326.
34
+ *
35
+ * @param {String} sourceProjectionCode Source projection code (e.g. "EPSG:25832").
36
+ * @param {Number[]} coords Coordinates to transform.
37
+ * @returns {Number[]} Transformed coordinates in EPSG:4326.
38
+ */
39
+ function transformPoint (sourceProjectionCode, coords) {
40
+ return proj4(proj4(sourceProjectionCode), proj4("EPSG:4326"), coords);
41
+ }
42
+
43
+ /**
44
+ * Transforms the given geometry in-place from a given source projection to EPSG:4326.
45
+ *
46
+ * @param {String} sourceProjectionCode Source projection code (e.g. "EPSG:25832").
47
+ * @param {module:ol/geom/Geometry} geometry Geometry to transform.
48
+ * @returns {module:ol/geom/Geometry} The transformed geometry.
49
+ */
50
+ function transformGeometry (sourceProjectionCode, geometry) {
51
+ return geometry.transform(sourceProjectionCode, "EPSG:4326");
52
+ }
53
+
54
+ export {
55
+ isObject,
56
+ getProjections,
57
+ projections,
58
+ transformGeometry,
59
+ transformPoint
60
+ };
@@ -54,6 +54,16 @@ describe("src/utils/attributeMapper.js", () => {
54
54
  result: 123
55
55
  });
56
56
  });
57
+ it("should map object with 0 as valid numeric value", () => {
58
+ const mappingObj = {
59
+ random_int: "count"
60
+ };
61
+ const propsWithZero = Object.assign({}, props, {random_int: 0});
62
+
63
+ expect(mapAttributes(propsWithZero, mappingObj)).toEqual({
64
+ count: 0
65
+ });
66
+ });
57
67
  it("should map object with multiple attributes", () => {
58
68
  const mappingObj = {
59
69
  random_text: "text",