UNPKG

mobility-toolbox-js

Version:

Toolbox for JavaScript applications in the domains of mobility and logistics.

630 lines (629 loc) 34.2 kB
import { replace } from 'lodash'; import { Feature, getUid } from 'ol'; import { asString } from 'ol/color'; import KML from 'ol/format/KML'; import CircleGeom from 'ol/geom/Circle'; import GeometryCollection from 'ol/geom/GeometryCollection'; import MultiPoint from 'ol/geom/MultiPoint'; import Point from 'ol/geom/Point'; import { fromCircle } from 'ol/geom/Polygon'; import { get, transform } from 'ol/proj'; import { Circle, Fill, Icon, Stroke, Style, Text } from 'ol/style'; import { parse } from 'ol/xml'; import getPolygonPattern from './getMapsetPolygonPattern'; const CIRCLE_GEOMETRY_CENTER = 'circleGeometryCenter'; const CIRCLE_GEOMETRY_RADIUS = 'circleGeometryRadius'; const EPSG_4326 = get('EPSG:4326'); // Default style for KML layer const kmlFill = new Fill({ color: [255, 0, 0, 0.7], }); const kmlStroke = new Stroke({ color: [255, 0, 0, 1], width: 1.5, }); const kmlcircle = new Circle({ fill: kmlFill, radius: 7, stroke: kmlStroke, }); const kmlStyle = new Style({ fill: kmlFill, image: kmlcircle, stroke: kmlStroke, text: new Text({ fill: kmlFill, font: 'normal 16px Helvetica', stroke: new Stroke({ color: [255, 255, 255, 1], width: 3, }), }), }); // Comes from ol >= 6.7, // https://github.com/openlayers/openlayers/blob/main/src/ol/format/KML.js#L320 const scaleForSize = (size) => { return 32 / Math.min(size[0], size[1]); }; const applyTextStyleForIcon = (olIcon, olText) => { const size = olIcon.getSize() || [48, 48]; const scale = (olIcon.getScale() || 1); const anchor = olIcon.getAnchor() || [ (size[0] * scale) / 2, (size[1] * scale) / 2, ]; const offset = [ scale * (size[0] - anchor[0]) + 5, scale * (size[1] / 2 - anchor[1]), ]; olText.setOffsetX(offset[0]); olText.setOffsetY(offset[1]); olText.setTextAlign('left'); }; const getVertexCoord = (geom, start = true, index = 0) => { const coords = geom === null || geom === void 0 ? void 0 : geom.getCoordinates(); if (!coords) { return undefined; } const len = coords.length - 1; return start ? coords[index] : coords[len - index]; }; const getLineIcon = (feature, icon, color, start = true) => { const geom = feature.getGeometry(); const coordA = getVertexCoord(geom, start, 1); const coordB = getVertexCoord(geom, start); if (!coordA || !coordB) { return new Style(); } const dx = start ? coordA[0] - coordB[0] : coordB[0] - coordA[0]; const dy = start ? coordA[1] - coordB[1] : coordB[1] - coordA[1]; const rotation = Math.atan2(dy, dx); return new Style({ geometry: (feat) => { const ge = feat.getGeometry(); return new Point(getVertexCoord(ge, start)); }, image: new Icon({ color, rotateWithView: true, rotation: -rotation, scale: icon.scale, size: icon.size, // ie 11 src: icon.url, }), zIndex: icon.zIndex, }); }; class MapsetKmlFormat { constructor() { /** * Write the <Camera> tag into a KML string. Returns the KML string with added <Camera> tag. * @param {String} kmlString A string representing a KML file. * @param {Object} cameraAttributes Object containing the camera tags (longitude, latitude, altitude, heading, tilt, altitudeMode, roll) * as keys with corresponding values. See https://developers.google.com/kml/documentation/kmlreference#camera */ this.writeDocumentCamera = (kmlString, cameraAttributes) => { const kmlDoc = parse(this.removeDocumentCamera(kmlString)); if (cameraAttributes) { // Create Camera node with child attributes if the cameraAttributes object is defined const cameraNode = kmlDoc.createElement('Camera'); Object.keys(cameraAttributes).forEach((key) => { const cameraAttribute = kmlDoc.createElement(`${key.charAt(0).toUpperCase() + key.slice(1)}`); cameraAttribute.innerHTML = cameraAttributes[key]; cameraNode.appendChild(cameraAttribute); }); const documentNode = kmlDoc.getElementsByTagName('Document')[0]; documentNode.appendChild(cameraNode); } return new XMLSerializer().serializeToString(kmlDoc); }; } /** * Read a KML string. * @param {String} kmlString A string representing a KML file. * @param {<ol.Projection|String>} featureProjection The projection used by the map. * @param {<boolean>} doNotRevert32pxScaling Set it to true if you use ol < 6.7 and last version of react-spatial, Fix the 32px scaling, introduced by (ol >= 6.7), see https://github.com/openlayers/openlayers/pull/12695. */ readFeatures(kmlString, featureProjection, doNotRevert32pxScaling = false) { // Since ol 6.7, the KML follows better the spec and GoogleEarth interpretation, see https://github.com/openlayers/openlayers/pull/12695. // so the <scale> value is interpreted using an image size of 32px. // So when revert32pxScaling is true we fix back the scale, to use only, if you use an OL < 6.7. // Now the writeFeatures function use the iconScale extended data to set the image's scale. // If the extended data is not found it will look at this boolean to define if we must revert the scale or not. const features = new KML().readFeatures(kmlString, { featureProjection, }); features.forEach((feature) => { var _a, _b; // Transform back polygon to circle geometry const { [CIRCLE_GEOMETRY_CENTER]: circleGeometryCenter, [CIRCLE_GEOMETRY_RADIUS]: circleGeometryRadius, } = (feature === null || feature === void 0 ? void 0 : feature.getProperties()) || {}; if (feature && circleGeometryCenter && circleGeometryRadius) { const circle = new CircleGeom(transform(JSON.parse(circleGeometryCenter), EPSG_4326, featureProjection || EPSG_4326), parseFloat(circleGeometryRadius)); circle.setProperties((_b = (_a = feature === null || feature === void 0 ? void 0 : feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getProperties()) !== null && _b !== void 0 ? _b : {}); feature.setGeometry(circle); } this.sanitizeFeature(feature, doNotRevert32pxScaling); }); return features; } /** * Removes the <Camera> tag from a KML string. Returns the KML string with removed <Camera> tag. * @param {String} kmlString A string representing a KML file. */ removeDocumentCamera(kmlString) { const kmlDoc = parse(kmlString); // Remove old Camera node const oldCameraNode = kmlDoc.getElementsByTagName('Camera')[0]; if (oldCameraNode) { oldCameraNode.remove(); } return new XMLSerializer().serializeToString(kmlDoc); } sanitizeFeature(feature, doNotRevert32pxScaling = false) { var _a, _b, _c, _d, _e, _f, _g; const geom = feature.getGeometry(); let styles = feature.getStyleFunction(); // Store maxZoom in properties if (feature.get('maxZoom')) { feature.set('maxZoom', parseFloat(feature.get('maxZoom'))); } // Store minZoom in properties if (feature.get('minZoom')) { feature.set('minZoom', parseFloat(feature.get('minZoom'))); } // The use of clone is part of the scale fix for OL > 6.7 // If an IconStyle has no gx:w and gx:h defined, a scale factor is applied // after the image is loaded. To avoided having the scale factor applied we // clone the style and keep the scale as it is. // Having gx:w and gx:h not defined should not happen, using the last version of the parser/reader. const tmpStyles = styles === null || styles === void 0 ? void 0 : styles(feature, 1); const style = (_a = (Array.isArray(tmpStyles) ? tmpStyles[0] : tmpStyles)) === null || _a === void 0 ? void 0 : _a.clone(); let stroke = style === null || style === void 0 ? void 0 : style.getStroke(); if (feature.get('lineCap')) { stroke === null || stroke === void 0 ? void 0 : stroke.setLineCap(feature.get('lineCap')); } if (feature.get('lineJoin')) { stroke === null || stroke === void 0 ? void 0 : stroke.setLineJoin(feature.get('lineJoin')); } if (feature.get('lineDash')) { stroke === null || stroke === void 0 ? void 0 : stroke.setLineDash((feature === null || feature === void 0 ? void 0 : feature.get('lineDash')).split(',').map((l) => { return parseInt(l, 10); })); } if (feature.get('lineDashOffset')) { stroke === null || stroke === void 0 ? void 0 : stroke.setLineDashOffset(parseInt(feature.get('lineDashOffset'), 10)); } if (feature.get('miterLimit')) { stroke === null || stroke === void 0 ? void 0 : stroke.setMiterLimit(parseInt(feature.get('miterLimit'), 10)); } // The canvas draws a stroke width=1 by default if width=0, so we // remove the stroke style in that case. if (stroke && stroke.getWidth() === 0) { stroke = undefined; } if (feature.get('zIndex')) { style === null || style === void 0 ? void 0 : style.setZIndex(parseInt(feature.get('zIndex'), 10)); } // if the feature is a Point and we are offline, we use default vector // style. // if the feature is a Point and has a name with a text style, we // create a correct text style. // TODO Handle GeometryCollection displaying name on the first Point // geometry. if (style && (geom instanceof Point || geom instanceof MultiPoint)) { let image = style.getImage(); let text = null; let fill = style.getFill(); // If the feature has name we display it on the map as Google does if (feature.get('name') && style.getText() && ((_b = style.getText()) === null || _b === void 0 ? void 0 : _b.getScale()) !== 0) { if (image && image.getScale() === 0) { // transparentCircle is used to allow selection image = new Circle({ fill: new Fill({ color: [0, 0, 0, 0] }), radius: 1, stroke: new Stroke({ color: [0, 0, 0, 0] }), }); } // We replace empty white spaces used to keep normal spaces before and after the name. let name = feature.get('name'); if (/\u200B/g.test(name)) { name = name.replace(/\u200B/g, ''); feature.set('name', name); } // For backward compatibility we translate the bold and italic textFont property to a textArray prop const font = feature.get('textFont') || 'normal 16px Arial'; // Since we use rich text in mapset editor we use a text array instead, // it's only necessary when there is new lines in the text // Manage new lines if (name.includes('\n')) { const array = []; const split = name.split('\n'); split.forEach((txt, idx) => { array.push(txt || '\u200B', txt ? font : ''); if (idx < split.length - 1) { array.push('\n', ''); } }); name = array; } else { name = [name, font]; } text = new Text({ fill: style.getText().getFill(), font: `${font.replace(/bold/g, 'normal')}, Arial, sans-serif`, // We manage bold in textArray // rotation unsupported by KML, taken instead from custom field. rotation: feature.get('textRotation') || 0, // stroke: style.getText().getStroke(), scale: (_c = style.getText()) === null || _c === void 0 ? void 0 : _c.getScale(), // since ol 6.3.1 : https://github.com/openlayers/openlayers/pull/10613/files#diff-1883da8b57e690db7ea0c35ce53c880aR925 // a default textstroke is added to mimic google earth. // it was not the case before, the stroke was always null. So to keep // the same behavior we don't copy the stroke style. // TODO : maybe we should use this functionnality in the futur. text: name, }); if (feature.get('textArray')) { try { const textArray = JSON.parse(replace(feature.get('textArray'), /\r?\n/g, '\\n')); text.setText(textArray); } catch (err) { // eslint-disable-next-line no-console console.error('Error parsing textArray', feature.get('textArray'), err); } } if (feature.get('textStrokeColor') && feature.get('textStrokeWidth')) { text.setStroke(new Stroke({ color: feature.get('textStrokeColor'), width: parseFloat(feature.get('textStrokeWidth')), })); } if (feature.get('textAlign')) { text.setTextAlign(feature.get('textAlign')); } if (feature.get('textOffsetX')) { text.setOffsetX(parseFloat(feature.get('textOffsetX'))); } if (feature.get('textOffsetY')) { text.setOffsetY(parseFloat(feature.get('textOffsetY'))); } if (feature.get('textBackgroundFillColor')) { text.setBackgroundFill(new Fill({ color: feature.get('textBackgroundFillColor'), })); } if (feature.get('textPadding')) { text.setPadding((_d = feature.get('textPadding')) === null || _d === void 0 ? void 0 : _d.split(',').map((n) => { return parseFloat(n); })); } if (image instanceof Icon) { applyTextStyleForIcon(image, text); } } if (image instanceof Icon) { /* Apply icon rotation if defined (by default only written as * <heading> tag, which is not read as rotation value by the ol KML module) */ image.setRotation(parseFloat(feature.get('iconRotation')) || 0); if (feature.get('iconScale')) { image.setScale(parseFloat(feature.get('iconScale')) || 0); // We fix the 32px scaling introduced by OL 6.7 only if the image has a size defined. } else if (!doNotRevert32pxScaling && image.getSize()) { const resizeScale = scaleForSize(image.getSize()); image.setScale(image.getScaleArray()[0] / resizeScale); } } fill = null; stroke = null; styles = (feat, resolution) => { /* Options to be used for picture scaling with map, should have at least * a resolution attribute (this is the map resolution at the zoom level when * the picture is created), can take an optional constant for further scale * adjustment. * e.g. { resolution: 0.123, defaultScale: 1 / 6 } */ var _a; if (feat.get('pictureOptions')) { let pictureOptions = feat.get('pictureOptions'); if (typeof pictureOptions === 'string') { pictureOptions = JSON.parse(pictureOptions); } feat.set('pictureOptions', pictureOptions); if (pictureOptions.resolution) { image === null || image === void 0 ? void 0 : image.setScale((pictureOptions.resolution / resolution) * ((_a = pictureOptions === null || pictureOptions === void 0 ? void 0 : pictureOptions.defaultScale) !== null && _a !== void 0 ? _a : 1)); } } return new Style({ fill: fill !== null && fill !== void 0 ? fill : undefined, image: image !== null && image !== void 0 ? image : undefined, stroke: stroke !== null && stroke !== void 0 ? stroke : undefined, text: text !== null && text !== void 0 ? text : undefined, zIndex: style.getZIndex(), }); }; } // Remove image and text styles for polygons and lines if (!(geom instanceof Point || geom instanceof MultiPoint || geom instanceof GeometryCollection)) { styles = [ new Style({ fill: (_e = style === null || style === void 0 ? void 0 : style.getFill()) !== null && _e !== void 0 ? _e : undefined, image: undefined, stroke: stroke !== null && stroke !== void 0 ? stroke : undefined, text: undefined, zIndex: style === null || style === void 0 ? void 0 : style.getZIndex(), }), ]; // Parse the fillPattern json string and store parsed object const fillPattern = feature.get('fillPattern'); if (fillPattern) { const fillPatternOptions = JSON.parse(fillPattern); feature.set('fillPattern', fillPatternOptions); /* We set the fill pattern for polygons */ if (!(style === null || style === void 0 ? void 0 : style.getFill())) { styles[0].setFill(new Fill()); } const patternOrColor = (fillPatternOptions === null || fillPatternOptions === void 0 ? void 0 : fillPatternOptions.empty) ? [0, 0, 0, 0] : getPolygonPattern(fillPatternOptions.id, fillPatternOptions.color); (_g = (_f = styles[0]) === null || _f === void 0 ? void 0 : _f.getFill()) === null || _g === void 0 ? void 0 : _g.setColor(patternOrColor); } // Add line's icons styles if (feature.get('lineStartIcon')) { styles.push(getLineIcon(feature, JSON.parse(feature.get('lineStartIcon')), stroke === null || stroke === void 0 ? void 0 : stroke.getColor())); } if (feature.get('lineEndIcon')) { styles.push(getLineIcon(feature, JSON.parse(feature.get('lineEndIcon')), stroke === null || stroke === void 0 ? void 0 : stroke.getColor(), false)); } } feature.setStyle(styles); } /** * Create a KML string. * @param {VectorLayer} layer A react-spatial VectorLayer. * @param {<ol.Projection|String>} featureProjection The current projection used by the features. * @param {<boolean>} fixGxyAndGxh If the KML contains gx:w and gx:h, (ol >= 6.7), it will fix the bug introduced by https://github.com/openlayers/openlayers/pull/12695. */ writeFeatures(layer, featureProjection, mapResolution) { var _a, _b; let featString; // const olLayer = layer.olLayer || layer.get('olLayer') || layer; const exportFeatures = []; [...((_b = (_a = layer === null || layer === void 0 ? void 0 : layer.getSource()) === null || _a === void 0 ? void 0 : _a.getFeatures()) !== null && _b !== void 0 ? _b : [])] .sort((a, b) => { // The order of features must be kept. // We could use the useSpatialIndex = false property on the layer // but we prefer to sort feature by ol uid because ol uid is an integer // increased on each creation of a feature. // So we will keep the order of creation made by the the KML parser. // Ideally we should order by the zIndex of the style only. if (getUid(a) <= getUid(b)) { return -1; } return 1; }) .forEach((feature) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6; const clone = feature.clone(); if (((_a = clone.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType()) === 'Circle') { // We transform circle elements into polygons // because circle not supported in KML spec and in ol KML parser const circleGeom = feature.getGeometry(); clone.setGeometry(fromCircle(circleGeom, 100)); clone.set(CIRCLE_GEOMETRY_CENTER, JSON.stringify(transform(circleGeom.getCenter(), featureProjection, EPSG_4326))); clone.set(CIRCLE_GEOMETRY_RADIUS, circleGeom.getRadius()); } clone.setId(feature.getId()); (_b = clone.getGeometry()) === null || _b === void 0 ? void 0 : _b.transform(featureProjection, EPSG_4326); // We remove all ExtendedData not related to style. Object.keys(feature.getProperties()).forEach((key) => { if (![ CIRCLE_GEOMETRY_CENTER, CIRCLE_GEOMETRY_RADIUS, 'description', 'geometry', 'name', ].includes(key)) { clone.unset(key, true); } }); let styles; if (feature.getStyleFunction()) { styles = (_c = feature.getStyleFunction()) === null || _c === void 0 ? void 0 : _c(feature, mapResolution); } else if (layer === null || layer === void 0 ? void 0 : layer.getStyleFunction()) { styles = (_d = layer.getStyleFunction()) === null || _d === void 0 ? void 0 : _d(feature, mapResolution); } const mainStyle = Array.isArray(styles) ? styles[0] : styles; const newStyle = { fill: (_e = mainStyle === null || mainStyle === void 0 ? void 0 : mainStyle.getFill()) !== null && _e !== void 0 ? _e : undefined, image: (_f = mainStyle === null || mainStyle === void 0 ? void 0 : mainStyle.getImage()) !== null && _f !== void 0 ? _f : undefined, stroke: (_g = mainStyle === null || mainStyle === void 0 ? void 0 : mainStyle.getStroke()) !== null && _g !== void 0 ? _g : undefined, text: (_h = mainStyle === null || mainStyle === void 0 ? void 0 : mainStyle.getText()) !== null && _h !== void 0 ? _h : undefined, zIndex: (_j = mainStyle === null || mainStyle === void 0 ? void 0 : mainStyle.getZIndex()) !== null && _j !== void 0 ? _j : undefined, }; if (newStyle.zIndex) { clone.set('zIndex', newStyle.zIndex); } const text = (_k = newStyle.text) === null || _k === void 0 ? void 0 : _k.getText(); if (text) { let kmlText = ''; if (Array.isArray(text)) { // text can be a string or an array of strings clone.set('textArray', JSON.stringify(text)); const textArray = text; // in the KML we just add the text without the bold or italic information kmlText = textArray .map((t, idx) => { return idx % 2 === 0 ? t : ''; }) .join('') .replace(/\u200B/g, ''); } // We add the current text as features's name so it will be added as Placemark's name in the kml if (kmlText) { // If we see spaces at the beginning or at the end we add a empty // white space at the beginning and at the end. if (/^(\s|\n)|(\n|\s)$/g.test(kmlText)) { clone.set('name', `\u200B${kmlText}\u200B`); } else { clone.set('name', kmlText); } } } // Set custom properties to be converted in extendedData in KML. if ((_l = newStyle.text) === null || _l === void 0 ? void 0 : _l.getRotation()) { clone.set('textRotation', newStyle.text.getRotation()); } if ((_m = newStyle.text) === null || _m === void 0 ? void 0 : _m.getFont()) { clone.set('textFont', newStyle.text.getFont()); } if ((_o = newStyle.text) === null || _o === void 0 ? void 0 : _o.getTextAlign()) { clone.set('textAlign', newStyle.text.getTextAlign()); } if ((_p = newStyle.text) === null || _p === void 0 ? void 0 : _p.getOffsetX()) { clone.set('textOffsetX', newStyle.text.getOffsetX()); } if ((_q = newStyle.text) === null || _q === void 0 ? void 0 : _q.getOffsetY()) { clone.set('textOffsetY', newStyle.text.getOffsetY()); } if ((_r = newStyle.text) === null || _r === void 0 ? void 0 : _r.getStroke()) { if ((_s = newStyle.text.getStroke()) === null || _s === void 0 ? void 0 : _s.getColor()) { clone.set('textStrokeColor', asString((_t = newStyle.text.getStroke()) === null || _t === void 0 ? void 0 : _t.getColor())); } if ((_u = newStyle.text.getStroke()) === null || _u === void 0 ? void 0 : _u.getWidth()) { clone.set('textStrokeWidth', (_v = newStyle.text.getStroke()) === null || _v === void 0 ? void 0 : _v.getWidth()); } } if ((_w = newStyle.text) === null || _w === void 0 ? void 0 : _w.getBackgroundFill()) { clone.set('textBackgroundFillColor', asString((_x = newStyle.text.getBackgroundFill()) === null || _x === void 0 ? void 0 : _x.getColor())); } if ((_y = newStyle.text) === null || _y === void 0 ? void 0 : _y.getPadding()) { clone.set('textPadding', (_z = newStyle.text.getPadding()) === null || _z === void 0 ? void 0 : _z.join()); } if ((_0 = newStyle.stroke) === null || _0 === void 0 ? void 0 : _0.getLineCap()) { clone.set('lineCap', newStyle.stroke.getLineCap()); } if ((_1 = newStyle.stroke) === null || _1 === void 0 ? void 0 : _1.getLineJoin()) { clone.set('lineJoin', newStyle.stroke.getLineJoin()); } if ((_2 = newStyle.stroke) === null || _2 === void 0 ? void 0 : _2.getLineDash()) { clone.set('lineDash', (_3 = newStyle.stroke.getLineDash()) === null || _3 === void 0 ? void 0 : _3.join(',')); } if ((_4 = newStyle.stroke) === null || _4 === void 0 ? void 0 : _4.getLineDashOffset()) { clone.set('lineDashOffset', newStyle.stroke.getLineDashOffset()); } if ((_5 = newStyle.stroke) === null || _5 === void 0 ? void 0 : _5.getMiterLimit()) { clone.set('miterLimit', newStyle.stroke.getMiterLimit()); } if (newStyle.image instanceof Circle) { newStyle.image = undefined; } if (newStyle.image) { const imgSource = newStyle.image.getSrc(); if (!/(http(s?)):\/\//gi.test(imgSource)) { // eslint-disable-next-line no-console console.log('Local image source not supported for KML export.' + 'Should use remote web server'); } if (newStyle.image.getRotation()) { // We set the icon rotation as extended data clone.set('iconRotation', newStyle.image.getRotation()); } if (newStyle.image.getScale()) { // We set the scale as extended metadata because the <scale> in the KML is related to a 32px img, since ol >= 6.10. clone.set('iconScale', newStyle.image.getScale()); } // Set map resolution to use for icon-to-map proportional scaling if (feature.get('pictureOptions')) { clone.set('pictureOptions', JSON.stringify(feature.get('pictureOptions'))); } } // In case a fill pattern should be applied (use fillPattern attribute to store pattern id, color etc) if (feature.get('fillPattern')) { clone.set('fillPattern', JSON.stringify(feature.get('fillPattern'))); newStyle.fill = undefined; } // maxZoom: maximum zoom level at which the feature is displayed if (feature.get('maxZoom')) { clone.set('maxZoom', parseFloat(feature.get('maxZoom'))); } // minZoom: minimum zoom level at which the feature is displayed if (feature.get('minZoom')) { clone.set('minZoom', parseFloat(feature.get('minZoom'))); } // If only text is displayed we must specify an // image style with scale=0 if (newStyle.text && !newStyle.image) { newStyle.image = new Icon({ scale: 0, src: 'noimage', }); } // In case we use line's icon . const extraLineStyles = (Array.isArray(styles) && styles.slice(1)) || []; extraLineStyles.forEach((extraLineStyle) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j; if (extraLineStyle && extraLineStyle.getImage() instanceof Icon && extraLineStyle.getGeometry()) { const coord = (_b = (_a = extraLineStyle === null || extraLineStyle === void 0 ? void 0 : extraLineStyle.getGeometry()) === null || _a === void 0 ? void 0 : _a(feature)) === null || _b === void 0 ? void 0 : _b.getCoordinates(); const startCoord = (_c = feature.getGeometry()) === null || _c === void 0 ? void 0 : _c.getFirstCoordinate(); if ((coord === null || coord === void 0 ? void 0 : coord[0]) === (startCoord === null || startCoord === void 0 ? void 0 : startCoord[0]) && (coord === null || coord === void 0 ? void 0 : coord[1]) === (startCoord === null || startCoord === void 0 ? void 0 : startCoord[1])) { clone.set('lineStartIcon', JSON.stringify({ scale: (_d = extraLineStyle === null || extraLineStyle === void 0 ? void 0 : extraLineStyle.getImage()) === null || _d === void 0 ? void 0 : _d.getScale(), size: (_e = extraLineStyle === null || extraLineStyle === void 0 ? void 0 : extraLineStyle.getImage()) === null || _e === void 0 ? void 0 : _e.getSize(), url: (_f = extraLineStyle === null || extraLineStyle === void 0 ? void 0 : extraLineStyle.getImage()) === null || _f === void 0 ? void 0 : _f.getSrc(), zIndex: extraLineStyle === null || extraLineStyle === void 0 ? void 0 : extraLineStyle.getZIndex(), })); } else { clone.set('lineEndIcon', JSON.stringify({ scale: (_g = extraLineStyle.getImage()) === null || _g === void 0 ? void 0 : _g.getScale(), size: (_h = extraLineStyle.getImage()) === null || _h === void 0 ? void 0 : _h.getSize(), url: (_j = extraLineStyle.getImage()) === null || _j === void 0 ? void 0 : _j.getSrc(), zIndex: extraLineStyle.getZIndex(), })); } } }); const olStyle = new Style(newStyle); clone.setStyle(olStyle); if (!(clone.getGeometry() instanceof Point && olStyle.getText() && !((_6 = olStyle.getText()) === null || _6 === void 0 ? void 0 : _6.getText()))) { exportFeatures.push(clone); } }); if (exportFeatures.length > 0) { if (exportFeatures.length === 1) { // force the add of a <Document> node exportFeatures.push(new Feature()); } featString = new KML({ defaultStyle: [kmlStyle], extractStyles: true, }).writeFeatures(exportFeatures); // Remove no image hack featString = featString.replace(/<Icon>\s*<href>noimage<\/href>\s*<\/Icon>/g, ''); // Remove empty placemark added to have // <Document> tag featString = featString.replace(/<Placemark\/>/g, ''); // Add KML document name if (layer.get('name')) { featString = featString.replace(/<Document>/, `<Document><name>${layer.get('name')}</name>`); } } return featString; } } export default MapsetKmlFormat;