mobility-toolbox-js
Version:
Toolbox for JavaScript applications in the domains of mobility and logistics.
658 lines (657 loc) • 36.1 kB
JavaScript
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 {Object} formatOptions used to read and writes features. It extends the ol KML format read options with some custom options.
* @param {boolean} [formatOptions.applyMinMaxZoom=true] Generate a style function to apply the minZoom and maxZoom properties of features if they are defined and if the getResolutionForZoom function is provided in options. The style function will return undefined for features that are out of the zoom range defined by minZoom and maxZoom, so they will not be displayed on the map.
* @param {boolean} [formatOptions.doNotRevert32pxScaling=false] 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.
* @param {function} [formatOptions.getResolutionForZoom] A function to get the resolution for a given zoom level. Mandatory if applyMinMaxZoom is true. It can be the map.getView().getResolutionForZoom function.
*/
readFeatures(kmlString, formatOptions = {}) {
// 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 { featureProjection = EPSG_4326 } = formatOptions;
const features = new KML().readFeatures(kmlString, formatOptions);
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), 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, formatOptions);
});
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, formatOptions) {
var _a, _b, _c, _d, _e, _f, _g;
const { applyMinMaxZoom = true, doNotRevert32pxScaling = false, getResolutionForZoom, } = formatOptions;
const geom = feature.getGeometry();
let styles = feature.getStyleFunction();
// Store maxZoom in properties
const maxZoom = parseFloat(feature.get('maxZoom'));
if (!Number.isNaN(maxZoom)) {
feature.set('maxZoom', maxZoom);
}
// Store minZoom in properties
const minZoom = parseFloat(feature.get('minZoom'));
if (!Number.isNaN(minZoom)) {
feature.set('minZoom', 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((feature.get('textArray') || '').replace(/\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));
}
}
// Apply minZoom and maxZoom properties if they are defined on the feature
// and if the getResolutionForZoom function is provided in options
let styleFunction = undefined;
if (applyMinMaxZoom &&
getResolutionForZoom &&
(!Number.isNaN(feature.get('minZoom')) ||
!Number.isNaN(feature.get('maxZoom')))) {
styleFunction = (feat, resolution) => {
const minRes = getResolutionForZoom(feature.get('minZoom') || -Infinity);
const maxRes = getResolutionForZoom(feature.get('maxZoom') || Infinity);
// We test if the resolution exists because you could call the styleFuntion
// with an undefined resolution like in mapset to get the actula styles
// without applying the min max zoom filter.
if (!Number.isNaN(resolution) &&
(resolution > minRes || maxRes > resolution)) {
return;
}
if (typeof styles === 'function') {
return styles(feat, resolution);
}
return styles;
};
}
feature.setStyle(styleFunction !== null && styleFunction !== void 0 ? styleFunction : styles);
}
/**
* Create a KML string.
* @param {VectorLayer} layer A openlayers VectorLayer.
* @param {<ol.Projection|String>} featureProjection The current projection used by the features.
* @param {<boolean>} mapResolution The current map resolution.
*/
writeFeatures(layer, featureProjection, mapResolution) {
var _a, _b;
let featString;
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
const maxZoom = parseFloat(feature.get('maxZoom'));
if (!Number.isNaN(maxZoom)) {
clone.set('maxZoom', maxZoom);
}
// minZoom: minimum zoom level at which the feature is displayed
const minZoom = parseFloat(feature.get('minZoom'));
if (!Number.isNaN(minZoom)) {
clone.set('minZoom', 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;