diff --git a/service/src/routes/imports.ts b/service/src/routes/imports.ts index 7aac6f3e0..284f562b2 100644 --- a/service/src/routes/imports.ts +++ b/service/src/routes/imports.ts @@ -5,8 +5,7 @@ import { AnyPermission } from '../entities/authorization/entities.permissions' import fs from 'fs-extra'; import Zip from 'adm-zip'; import { defaultHandler as upload } from '../upload'; -import { DOMParser, Document } from '@xmldom/xmldom'; -import toGeoJson from '../utilities/togeojson'; +import { KmlFeature, kml } from '../utilities/transformKML'; interface SecurityConfig { authentication: { @@ -17,7 +16,7 @@ interface LayerRequest extends Request { layer: { type: string; }; - kml?: Document; + features?: any[]; file?: Express.Multer.File; } @@ -29,76 +28,90 @@ interface ImportResponse { }>; } -function importRoutes(app: Express, security: SecurityConfig): void { - const passport = security.authentication.passport; +const getMimeType = (filename: string): string => { + const ext = filename.toLowerCase().split('.').pop() || ''; + const mimeTypes: { [key: string]: string } = { + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + 'bmp': 'image/bmp' + }; + return mimeTypes[ext] || 'application/octet-stream'; +} - function validate(req: Request, res: Response, next: NextFunction): void | Response { - const layRequest = req as LayerRequest; - if (layRequest.layer.type !== 'Feature') { - return res.status(400).send('Cannot import data, layer type is not "Static".'); - } +const kmlToGeoJSON = (kmlPathname: string, isKMZ: boolean): KmlFeature[] => { + let kmlString: string; + let images: Record = {}; - if (!layRequest.file) { - return res.status(400).send('Invalid file, please upload a KML or KMZ file.'); - } + if (isKMZ) { + // TODO: Update how images are handled in KMZ files to prevent duplication. Move images to a separate directory and store their paths in the KML. + const zip = new Zip(kmlPathname); + const zipEntries = zip.getEntries(); + const kmlEntry = zipEntries.find(entry => entry.entryName.toLowerCase().endsWith('.kml')); - const fileExtension: string = layRequest.file.originalname.toLowerCase().split('.').pop() || ''; + if (!kmlEntry) { + throw new Error('No KML file found inside KMZ.'); + } - if (fileExtension === 'kmz') { + zipEntries.forEach(entry => { + const entryName = entry.entryName; try { - const zip = new Zip(layRequest.file.path); - const zipEntries = zip.getEntries(); - const kmlEntry = zipEntries.find(entry => entry.entryName.toLowerCase().endsWith('.kml')); - - if (!kmlEntry) { - return res.status(400).send('No KML file found inside.'); + if (!entry.isDirectory && /\.(png|jpg|jpeg|gif|bmp)$/i.test(entryName)) { + const buffer = entry.getData(); + const base64 = buffer.toString('base64'); + const mimeType = getMimeType(entryName); + images[entryName] = `data:${mimeType};base64,${base64}`; } - - const kmlData: string = kmlEntry.getData().toString('utf8'); - processKmlData(kmlData, layRequest, res, next); - } catch (err) { - return res.status(400).send('Unable to extract contents from KMZ file.'); + } catch (error) { + console.error(`Error processing entry ${entryName}:`, error); } - } else if (fileExtension === 'kml') { - fs.readFile(layRequest.file.path, 'utf8', function (err: Error | null, data: string) { - if (err) return next(err); - processKmlData(data, layRequest, res, next); - }); - } else { - return res.status(400).send('Invalid file, please upload a KML or KMZ file.'); - } + }); + + kmlString = kmlEntry.getData().toString('utf8'); + } else { + kmlString = fs.readFileSync(kmlPathname, 'utf8'); } - function processKmlData(data: string, req: LayerRequest, res: Response, next: NextFunction): void | Response { - const parser = new DOMParser(); - const kml: Document = parser.parseFromString(data, "application/xml"); - const parseError = kml.getElementsByTagName("parsererror"); + try { + return kml(kmlString, images); + } catch (error) { + throw new Error('Failed to transform KML: ' + error); + } +} - if (parseError.length > 0) { - console.error("KML Parsing Error:", parseError[0].textContent); - } else { - console.log("Parsed KML successfully"); - } +const validate = async (req: Request, res: Response, next: NextFunction): Promise => { + const layRequest = req as LayerRequest; + if (layRequest.layer.type !== 'Feature') { + return res.status(400).send('Cannot import data, layer type is not "Static".'); + } - if (!kml || kml.documentElement?.nodeName !== 'kml') { - return res.status(400).send('Invalid file, please upload a KML or KMZ file.'); - } + const fileExtension: string = layRequest.file?.originalname?.toLowerCase().split('.').pop() || ''; - req.kml = kml; - return next(); + if (!['kml', 'kmz'].includes(fileExtension)) { + return res.status(400).send('Invalid file, please upload a KML or KMZ file.'); } + try { + layRequest.features = kmlToGeoJSON(layRequest.file!.path, fileExtension === 'kmz'); + } catch (err) { + return res.status(400).send('Unable to extract contents from KMZ file.' + err); + } + return next(); +} + +function importRoutes(app: Express, security: SecurityConfig): void { + const passport = security.authentication.passport; + app.post( '/api/layers/:layerId/kml', passport.authenticate('bearer'), access.authorize('CREATE_LAYER' as AnyPermission), upload.single('file'), validate, - function (req: Request, res: Response, next: NextFunction): void { + (req: Request, res: Response, next: NextFunction) => { const layerRequest = req as LayerRequest; - console.log('Importing KML file:', layerRequest.file?.originalname); - const features = toGeoJson.kml(layerRequest.kml!); - new api.Feature(layerRequest.layer).createFeatures(features) + new api.Feature(layerRequest.layer).createFeatures(layerRequest.features) .then((newFeatures: any[]) => { const response: ImportResponse = { files: [{ diff --git a/service/src/utilities/togeojson.js b/service/src/utilities/togeojson.js deleted file mode 100644 index a305d7d94..000000000 --- a/service/src/utilities/togeojson.js +++ /dev/null @@ -1,378 +0,0 @@ -const xpath = require('xpath') - , log = require('winston'); - -exports.kml = kml; - -function kml(document) { - log.info('Generate KML'); - - const styleIndex = getStyles(document); - - // Pull all placemarks regards of depth level - let placemarks = xpath.select("//*[local-name()='Placemark']", document); - - log.info('Style index ', styleIndex); - log.info('Found ' + placemarks.length + ' placemarks in the KML document'); - - let features = []; - placemarks.forEach(placemark => { - features = features.concat(getPlacemark(placemark, styleIndex)); - }); - - return features; -} - -function getProperties(node, styleIndex) { - let properties = {}; - - let name = nodeVal(get1(node, 'name')); - if (name) properties.name = name; - - let description = nodeVal(get1(node, 'description')); - if (description) properties.description = description; - - const extendedData = get1(node, 'ExtendedData'); - if (extendedData) { - const datas = get(extendedData, 'Data'), - simpleDatas = get(extendedData, 'SimpleData'); - - for (let i = 0; i < datas.length; i++) { - properties[datas[i].getAttribute('name')] = nodeVal(get1(datas[i], 'value')); - } - - for (let i = 0; i < simpleDatas.length; i++) { - properties[simpleDatas[i].getAttribute('name')] = nodeVal(simpleDatas[i]); - } - } - - let styleUrl = nodeVal(get1(node, 'styleUrl')); - if (styleUrl && styleIndex[styleUrl]) { - properties.style = styleIndex[styleUrl]; - } else { - // Check if placemark has style - const styleElement = get1(node, 'Style'); - if (styleElement) { - properties.style = { - iconStyle: getIconStyle(styleElement), - lineStyle: getLineStyle(styleElement), - labelStyle: getLabelStyle(styleElement), - polyStyle: getPolygonStyle(styleElement) - }; - } - } - - return properties; -} - -function getPlacemark(node, styleIndex) { - const geometries = getGeometry(node); - if (!geometries.length) return []; - - const properties = getProperties(node, styleIndex); - - return geometries.map(geometry => { - return { - type: 'Feature', - geometry: geometry, - properties: properties - }; - }); -} - -function getGeometry(node) { - if (get1(node, 'MultiGeometry')){ - return getGeometry(get1(node, 'MultiGeometry')); - } - - if (get1(node, 'MultiTrack')) { - return getGeometry(get1(node, 'MultiTrack')); - } - - let geometries = []; - ['Polygon', 'LineString', 'Point', 'Track'].forEach(geometryType => { - let geometryNodes = get(node, geometryType); - if (geometryNodes) { - for (let i = 0; i < geometryNodes.length; i++) { - let geometryNode = geometryNodes[i]; - - switch(geometryType) { - case 'Point': - geometries.push(getPoint(geometryNode)); - break; - case 'LineString': - geometries.push(getLineString(geometryNode)); - break; - case 'Track': - geometries.push(getTrack(geometryNode)); - break; - case 'Polygon': - geometries.push(getPolygon(geometryNode)); - break; - } - } - } - }); - - return geometries; -} - -function getPoint(node) { - return { - type: 'Point', - coordinates: coord1(nodeVal(get1(node, 'coordinates'))) - }; -} - -function getLineString(node) { - return { - type: 'LineString', - coordinates: coord(nodeVal(get1(node, 'coordinates'))) - }; -} - -function getTrack(node) { - return { - type: 'LineString', - coordinates: gxCoords(node) - }; -} - -function getPolygon(node) { - const rings = get(node, 'LinearRing'); - - let coords = []; - for (let i = 0; i < rings.length; i++) { - coords.push(coord(nodeVal(get1(rings[i], 'coordinates')))); - } - - return { - type: 'Polygon', - coordinates: coords - }; -} - -function gxCoords(node) { - let elems = get(node, 'coord', 'gx'); - if (elems.length === 0) { - elems = get(node, 'gx:coord'); - } - - let coords = []; - for (let i = 0; i < elems.length; i++) { - coords.push(gxCoord(nodeVal(elems[i]))); - } - - let times = []; - const timeElems = get(node, 'when'); - for (let i = 0; i < timeElems.length; i++) { - times.push(nodeVal(timeElems[i])); - } - - return { - coords: coords, - times: times - }; -} - -function gxCoord(v) { - return numarray(v.split(' ')); -} - -function getStyles(node) { - let styleIndex = {}; - - const styles = get(node, 'Style'); - for (let i = 0; i < styles.length; i++) { - const kmlStyle = styles[i]; - const styleId = '#' + attr(kmlStyle, 'id'); - styleIndex[styleId] = { - iconStyle: getIconStyle(kmlStyle), - lineStyle: getLineStyle(kmlStyle), - labelStyle: getLabelStyle(kmlStyle), - polyStyle: getPolygonStyle(kmlStyle) - }; - } - - const styleMaps = get(node, 'StyleMap'); - for (let i = 0; i < styleMaps.length; i++) { - const styleMap = styleMaps[i]; - const pairs = xpath.select("*[local-name()='Pair']", styleMap); - for (let p = 0; p < pairs.length; p++) { - const key = get(pairs[p], 'key'); - if (key) { - const keyName = nodeVal(key[0]); - if (keyName === 'normal') { - const styleUrl = get(pairs[p], 'styleUrl'); - if (styleUrl) { - const styleUrlName = nodeVal(styleUrl[0]); - const styleMapId = '#' + attr(styleMap, 'id'); - styleIndex[styleMapId] = styleIndex[styleUrlName]; - } - } - } - } - } - - return styleIndex; -} - -function getIconStyle(node) { - const iconStyle = get(node, 'IconStyle'); - if (iconStyle[0]) { - let style = {}; - - const iconScale = get(iconStyle[0], 'scale'); - if (iconScale && iconScale[0]) { - style.scale = nodeVal(iconScale[0]); - } - - const icon = get(iconStyle[0], 'Icon'); - if (icon && icon[0]) { - style.icon = {}; - - const href = get(icon[0], 'href'); - if (href[0]) { - style.icon.href = nodeVal(href[0]); - } - } - - return style; - } -} - -function getLineStyle(node) { - const lineStyle = get(node, 'LineStyle'); - if (lineStyle[0]) { - let style = {}; - - const lineColor = get(lineStyle[0], 'color'); - if (lineColor[0]) { - style.color = parseColor(nodeVal(lineColor[0])); - } - - const width = get(lineStyle[0], 'width'); - if (width[0]) { - style.width = nodeVal(width[0]); - } - - return style; - } -} - -function getLabelStyle(node) { - const labelStyle = get(node, 'LabelStyle'); - if (labelStyle[0]) { - let style = {}; - - const labelColor = get(labelStyle[0], 'color'); - if (labelColor[0]) { - style.color = parseColor(nodeVal(labelColor[0])); - } - - const labelScale = get(labelStyle[0], 'scale'); - if (labelScale[0]) { - style.color = nodeVal(labelScale[0]); - } - - return style; - } -} - -function getPolygonStyle(node) { - - const polyStyle = get(node, 'PolyStyle'); - if (polyStyle[0]) { - let style = {}; - - const color = get(polyStyle[0], 'color'); - if (color[0]) { - style.color = parseColor(nodeVal(color[0])); - } - - return style; - } -} - -// all Y children of X -function get(x, y) { - return x.getElementsByTagNameNS("*", y); -} - -function attr(x, y) { return x.getAttribute(y); } - -// one Y child of X, if any, otherwise null -function get1(x, y) { - let n = get(x, y); - return n.length ? n[0] : null; -} - -// https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize -function norm(el) { - if (el.normalize) { - el.normalize(); - } - - return el; -} - -// cast array x into numbers -function numarray(x) { - let o = []; - for (let i = 0; i < x.length; i++) { - o[i] = parseFloat(x[i]); - } - - return o; -} - -// cast array x into numbers -function coordinateArray(x) { - let o = []; - for (let i = 0; i < x.length; i++) { - o[i] = parseFloat(x[i]); - } - - return o.splice(0,2); -} - -// get the content of a text node, if any -function nodeVal(x) { - if (x) { - norm(x); - } - - return x && x.firstChild && x.firstChild.nodeValue; -} - -// get one coordinate from a coordinate array, if any -function coord1(v) { - const removeSpace = (/\s*/g); - return coordinateArray(v.replace(removeSpace, '').split(',')); -} - -// get all coordinates from a coordinate array as [[],[]] -function coord(v) { - const trimSpace = (/^\s*|\s*$/g); - const splitSpace = (/\s+/); - - let coords = v.replace(trimSpace, '').split(splitSpace), - o = []; - - for (let i = 0; i < coords.length; i++) { - o.push(coord1(coords[i])); - } - - return o; -} - -function parseColor(color) { - const r = color.slice(6,8); - const g = color.slice(4,6); - const b = color.slice(2,4); - const a = color.slice(0,2); - - return { - rgb: '#' + r + g + b, - opacity: parseInt(a, 16) - }; -} diff --git a/service/src/utilities/transformKML.ts b/service/src/utilities/transformKML.ts new file mode 100644 index 000000000..b67c0674e --- /dev/null +++ b/service/src/utilities/transformKML.ts @@ -0,0 +1,388 @@ +var xpath = require('xpath'); +import { DOMParser, Document } from '@xmldom/xmldom'; + +interface KmlStyle { + iconStyle?: any; + lineStyle?: any; + labelStyle?: any; + polyStyle?: any; +} + +interface Color { + rgb: string; + opacity: number; +} + +interface KmlFeature { + type: 'Feature'; + geometry: any; + properties: any; +} + +function kml(docString: string, images: Record = {}): KmlFeature[] { + const parser = new DOMParser(); + const document = parser.parseFromString(docString, 'application/xml'); + if (!document || document.documentElement?.nodeName !== 'kml') { + throw new Error('KML file is not valid or does not contain a root element.'); + } + + const styleIndex = getStyles(document, images); + + // Pull all placemarks regards of depth level + const placemarks = xpath.select("//*[local-name()='Placemark']", document) as Element[]; + + return placemarks.map((placemark) => { + return getPlacemark(placemark, styleIndex, images); + }).flat(); +} + +function getProperties(node: Element, styleIndex: Record, images: Record = {}): any { + const properties: any = {}; + + const name = nodeVal(getFirstElementByNamespace(node, 'name')); + if (name) properties.name = name; + + const description = nodeVal(getFirstElementByNamespace(node, 'description')); + if (description) properties.description = description; + + const extendedData = getFirstElementByNamespace(node, 'ExtendedData'); + if (extendedData) { + const datas = getElementsByNamespace(extendedData, 'Data'), + simpleDatas = getElementsByNamespace(extendedData, 'SimpleData'); + + for (let i = 0; i < datas.length; i++) { + properties[datas[i].getAttribute('name') as string] = nodeVal(getFirstElementByNamespace(datas[i], 'value')); + } + + for (let i = 0; i < simpleDatas.length; i++) { + properties[simpleDatas[i].getAttribute('name') as string] = nodeVal(simpleDatas[i]); + } + } + + const styleUrl = nodeVal(getFirstElementByNamespace(node, 'styleUrl')); + if (styleUrl && styleIndex[styleUrl]) { + properties.style = styleIndex[styleUrl]; + } else { + // Check if placemark has style + const styleElement = getFirstElementByNamespace(node, 'Style'); + if (styleElement) { + properties.style = { + iconStyle: getIconStyle(styleElement, images), + lineStyle: getLineStyle(styleElement), + labelStyle: getLabelStyle(styleElement), + polyStyle: getPolygonStyle(styleElement) + }; + } + } + + return properties; +} + +function getPlacemark(node: Element, styleIndex: Record, images: Record = {}): KmlFeature[] { + const geometries = getGeometry(node); + if (!geometries.length) return []; + + const properties = getProperties(node, styleIndex, images); + + return geometries.map(geometry => { + return { + type: 'Feature', + geometry: geometry, + properties: properties + }; + }); +} + +function getGeometry(node: Element): any[] { + if (getFirstElementByNamespace(node, 'MultiGeometry')) { + return getGeometry(getFirstElementByNamespace(node, 'MultiGeometry') as Element); + } + + if (getFirstElementByNamespace(node, 'MultiTrack')) { + return getGeometry(getFirstElementByNamespace(node, 'MultiTrack') as Element); + } + const geometries: any[] = []; + ['Polygon', 'LineString', 'Point', 'Track'].forEach(geometryType => { + const geometryNodes = getElementsByNamespace(node, geometryType); + if (geometryNodes) { + for (let i = 0; i < geometryNodes.length; i++) { + const geometryNode = geometryNodes[i]; + + switch (geometryType) { + case 'Point': + geometries.push({ + type: 'Point', + coordinates: coord1(nodeVal(getFirstElementByNamespace(geometryNode, 'coordinates'))) + }); + break; + case 'LineString': + geometries.push({ + type: 'LineString', + coordinates: coord(nodeVal(getFirstElementByNamespace(geometryNode, 'coordinates'))) + }); + break; + case 'Track': + geometries.push({ + type: 'LineString', + coordinates: gxCoords(geometryNode) + }); + break; + case 'Polygon': + const rings = getElementsByNamespace(geometryNode, 'LinearRing'); + + const coords: (number[])[][] = []; + for (let i = 0; i < rings.length; i++) { + coords.push(coord(nodeVal(getFirstElementByNamespace(rings[i], 'coordinates')))); + } + geometries.push({ + type: 'Polygon', + coordinates: coords + }); + } + } + } + }); + + return geometries; +} + +function gxCoords(node: Element): any { + let elems = getElementsByNamespace(node, 'coord'); + if (elems.length === 0) { + elems = getElementsByNamespace(node, 'gx:coord'); + } + + const coords: number[][] = []; + for (let i = 0; i < elems.length; i++) { + coords.push(gxCoord(nodeVal(elems[i]))); + } + + const times: (string | null)[] = []; + const timeElems = getElementsByNamespace(node, 'when'); + for (let i = 0; i < timeElems.length; i++) { + times.push(nodeVal(timeElems[i])); + } + + return { + coords: coords, + times: times + }; +} + +function gxCoord(v: string | null): number[] { + if (!v) return []; + return numarray(v.split(' ')); +} + +function getStyles(node: Document, images: Record = {}): Record { + const styleIndex: Record = {}; + + const styles = getElementsByNamespace(node, 'Style'); + for (let i = 0; i < styles.length; i++) { + const kmlStyle = styles[i]; + const styleId = '#' + attr(kmlStyle, 'id'); + styleIndex[styleId] = { + iconStyle: getIconStyle(kmlStyle, images), + lineStyle: getLineStyle(kmlStyle), + labelStyle: getLabelStyle(kmlStyle), + polyStyle: getPolygonStyle(kmlStyle) + }; + } + + const styleMaps = getElementsByNamespace(node, 'StyleMap'); + for (let i = 0; i < styleMaps.length; i++) { + const styleMap = styleMaps[i]; + const pairs = xpath.select("*[local-name()='Pair']", styleMap) as Element[]; + for (let p = 0; p < pairs.length; p++) { + const key = getElementsByNamespace(pairs[p], 'key'); + if (key) { + const keyName = nodeVal(key[0]); + if (keyName === 'normal') { + const styleUrl = getElementsByNamespace(pairs[p], 'styleUrl'); + if (styleUrl) { + const styleUrlName = nodeVal(styleUrl[0]); + if (!styleUrlName) continue; + const styleMapId = '#' + attr(styleMap, 'id'); + styleIndex[styleMapId] = styleIndex[styleUrlName]; + } + } + } + } + } + + return styleIndex; +} + +function getIconStyle(node: Element, images: Record = {}): any { + const iconStyle = getElementsByNamespace(node, 'IconStyle'); + if (iconStyle[0]) { + const style: any = {}; + + const iconScale = getElementsByNamespace(iconStyle[0], 'scale'); + if (iconScale && iconScale[0]) { + style.scale = nodeVal(iconScale[0]); + } + + const icon = getElementsByNamespace(iconStyle[0], 'Icon'); + if (icon && icon[0]) { + const href = getElementsByNamespace(icon[0], 'href'); + if (href[0]) { + const hrefValue = nodeVal(href[0]); + if (hrefValue) { + const isWebUrl = /^(http|https):/.test(hrefValue); + + if (isWebUrl || images[hrefValue]) { + style.icon = { + href: images[hrefValue] || hrefValue + }; + } + } + } + } + + return style; + } +} + +function getLineStyle(node: Element): any { + const lineStyle = getElementsByNamespace(node, 'LineStyle'); + if (lineStyle[0]) { + const style: any = {}; + + const lineColor = getElementsByNamespace(lineStyle[0], 'color'); + if (lineColor[0]) { + style.color = parseColor(nodeVal(lineColor[0])); + } + + const width = getElementsByNamespace(lineStyle[0], 'width'); + if (width[0]) { + style.width = nodeVal(width[0]); + } + + return style; + } +} + +function getLabelStyle(node: Element): any { + const labelStyle = getElementsByNamespace(node, 'LabelStyle'); + if (labelStyle[0]) { + const style: any = {}; + + const labelColor = getElementsByNamespace(labelStyle[0], 'color'); + if (labelColor[0]) { + style.color = parseColor(nodeVal(labelColor[0])); + } + + const labelScale = getElementsByNamespace(labelStyle[0], 'scale'); + if (labelScale[0]) { + style.scale = nodeVal(labelScale[0]); + } + + return style; + } +} + +function getPolygonStyle(node: Element): any { + + const polyStyle = getElementsByNamespace(node, 'PolyStyle'); + if (polyStyle[0]) { + const style: any = {}; + + const color = getElementsByNamespace(polyStyle[0], 'color'); + if (color[0]) { + style.color = parseColor(nodeVal(color[0])); + } + + return style; + } +} + +function getElementsByNamespace(x: Document | Element, y: string): HTMLCollectionOf { + return (x as Element).getElementsByTagNameNS("*", y); +} + +function attr(x: Element, y: string): string | null { return x.getAttribute(y); } + +function getFirstElementByNamespace(x: Document | Element, y: string): Element | null { + const n = getElementsByNamespace(x, y); + return n.length ? n[0] : null; +} + +// https://developer.mozilla.org/en-US/docs/Web/API/Node.normalize +function norm(el: Node | null): Node | null { + if (el && el.normalize) { + el.normalize(); + } + + return el; +} + +// cast array x into numbers +function numarray(x: string[]): number[] { + const o: number[] = []; + for (let i = 0; i < x.length; i++) { + o[i] = parseFloat(x[i]); + } + + return o; +} + +// cast array x into numbers +function coordinateArray(x: string[]): number[] { + const o: number[] = []; + for (let i = 0; i < x.length; i++) { + o[i] = parseFloat(x[i]); + } + + return o.splice(0, 2); +} + +// get the content of a text node, if any +function nodeVal(x: Node | null): string | null { + if (x) { + norm(x); + } + + return (x && x.firstChild && x.firstChild.nodeValue) ? x.firstChild.nodeValue : null; +} + +// get one coordinate from a coordinate array, if any +function coord1(v: string | null): number[] { + if (!v) return []; + const removeSpace = (/\s*/g); + return coordinateArray(v.replace(removeSpace, '').split(',')); +} + +// get all coordinates from a coordinate array as [[],[]] +function coord(v: string | null): (number[])[] { + if (!v) return []; + const trimSpace = (/^\s*|\s*$/g); + const splitSpace = (/\s+/); + + const coords = v.replace(trimSpace, '').split(splitSpace), + o: (number[])[] = []; + + coords.forEach(coordStr => { + if (coordStr) { + o.push(coord1(coordStr)); + } + }); + + return o; +} + +function parseColor(color: string | null): Color | undefined { + if (!color) return; + + const r = color.slice(6, 8); + const g = color.slice(4, 6); + const b = color.slice(2, 4); + const a = color.slice(0, 2); + + return { + rgb: '#' + r + g + b, + opacity: parseInt(a, 16) + }; +} + +export { kml, KmlFeature }; \ No newline at end of file diff --git a/web-app/src/app/map/marker/FixedWidthMarker.ts b/web-app/src/app/map/marker/FixedWidthMarker.ts index ae397c6e5..69ac7efc2 100644 --- a/web-app/src/app/map/marker/FixedWidthMarker.ts +++ b/web-app/src/app/map/marker/FixedWidthMarker.ts @@ -17,9 +17,7 @@ export class FixedWidthMarker extends Marker { const img = new Image() img.src = options.iconUrl - this.setOpacity(0) img.onload = (event: any) => { - this.setOpacity(1) const scale = this.iconWidth / event.srcElement.width this.setIcon( icon({