@@ -5,8 +5,7 @@ import { AnyPermission } from '../entities/authorization/entities.permissions'
55import fs from 'fs-extra' ;
66import Zip from 'adm-zip' ;
77import { defaultHandler as upload } from '../upload' ;
8- import { DOMParser , Document } from '@xmldom/xmldom' ;
9- import toGeoJson from '../utilities/togeojson' ;
8+ import { KmlFeature , kml } from '../utilities/transformKML' ;
109
1110interface SecurityConfig {
1211 authentication : {
@@ -17,7 +16,7 @@ interface LayerRequest extends Request {
1716 layer : {
1817 type : string ;
1918 } ;
20- kml ?: Document ;
19+ features ?: any [ ] ;
2120 file ?: Express . Multer . File ;
2221}
2322
@@ -29,76 +28,90 @@ interface ImportResponse {
2928 } > ;
3029}
3130
32- function importRoutes ( app : Express , security : SecurityConfig ) : void {
33- const passport = security . authentication . passport ;
31+ const getMimeType = ( filename : string ) : string => {
32+ const ext = filename . toLowerCase ( ) . split ( '.' ) . pop ( ) || '' ;
33+ const mimeTypes : { [ key : string ] : string } = {
34+ 'png' : 'image/png' ,
35+ 'jpg' : 'image/jpeg' ,
36+ 'jpeg' : 'image/jpeg' ,
37+ 'gif' : 'image/gif' ,
38+ 'bmp' : 'image/bmp'
39+ } ;
40+ return mimeTypes [ ext ] || 'application/octet-stream' ;
41+ }
3442
35- function validate ( req : Request , res : Response , next : NextFunction ) : void | Response {
36- const layRequest = req as LayerRequest ;
37- if ( layRequest . layer . type !== 'Feature' ) {
38- return res . status ( 400 ) . send ( 'Cannot import data, layer type is not "Static".' ) ;
39- }
43+ const kmlToGeoJSON = ( kmlPathname : string , isKMZ : boolean ) : KmlFeature [ ] => {
44+ let kmlString : string ;
45+ let images : Record < string , string > = { } ;
4046
41- if ( ! layRequest . file ) {
42- return res . status ( 400 ) . send ( 'Invalid file, please upload a KML or KMZ file.' ) ;
43- }
47+ if ( isKMZ ) {
48+ // 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.
49+ const zip = new Zip ( kmlPathname ) ;
50+ const zipEntries = zip . getEntries ( ) ;
51+ const kmlEntry = zipEntries . find ( entry => entry . entryName . toLowerCase ( ) . endsWith ( '.kml' ) ) ;
4452
45- const fileExtension : string = layRequest . file . originalname . toLowerCase ( ) . split ( '.' ) . pop ( ) || '' ;
53+ if ( ! kmlEntry ) {
54+ throw new Error ( 'No KML file found inside KMZ.' ) ;
55+ }
4656
47- if ( fileExtension === 'kmz' ) {
57+ zipEntries . forEach ( entry => {
58+ const entryName = entry . entryName ;
4859 try {
49- const zip = new Zip ( layRequest . file . path ) ;
50- const zipEntries = zip . getEntries ( ) ;
51- const kmlEntry = zipEntries . find ( entry => entry . entryName . toLowerCase ( ) . endsWith ( '.kml' ) ) ;
52-
53- if ( ! kmlEntry ) {
54- return res . status ( 400 ) . send ( 'No KML file found inside.' ) ;
60+ if ( ! entry . isDirectory && / \. ( p n g | j p g | j p e g | g i f | b m p ) $ / i. test ( entryName ) ) {
61+ const buffer = entry . getData ( ) ;
62+ const base64 = buffer . toString ( 'base64' ) ;
63+ const mimeType = getMimeType ( entryName ) ;
64+ images [ entryName ] = `data:${ mimeType } ;base64,${ base64 } ` ;
5565 }
56-
57- const kmlData : string = kmlEntry . getData ( ) . toString ( 'utf8' ) ;
58- processKmlData ( kmlData , layRequest , res , next ) ;
59- } catch ( err ) {
60- return res . status ( 400 ) . send ( 'Unable to extract contents from KMZ file.' ) ;
66+ } catch ( error ) {
67+ console . error ( `Error processing entry ${ entryName } :` , error ) ;
6168 }
62- } else if ( fileExtension === 'kml' ) {
63- fs . readFile ( layRequest . file . path , 'utf8' , function ( err : Error | null , data : string ) {
64- if ( err ) return next ( err ) ;
65- processKmlData ( data , layRequest , res , next ) ;
66- } ) ;
67- } else {
68- return res . status ( 400 ) . send ( 'Invalid file, please upload a KML or KMZ file.' ) ;
69- }
69+ } ) ;
70+
71+ kmlString = kmlEntry . getData ( ) . toString ( 'utf8' ) ;
72+ } else {
73+ kmlString = fs . readFileSync ( kmlPathname , 'utf8' ) ;
7074 }
7175
72- function processKmlData ( data : string , req : LayerRequest , res : Response , next : NextFunction ) : void | Response {
73- const parser = new DOMParser ( ) ;
74- const kml : Document = parser . parseFromString ( data , "application/xml" ) ;
75- const parseError = kml . getElementsByTagName ( "parsererror" ) ;
76+ try {
77+ return kml ( kmlString , images ) ;
78+ } catch ( error ) {
79+ throw new Error ( 'Failed to transform KML: ' + error ) ;
80+ }
81+ }
7682
77- if ( parseError . length > 0 ) {
78- console . error ( "KML Parsing Error:" , parseError [ 0 ] . textContent ) ;
79- } else {
80- console . log ( "Parsed KML successfully" ) ;
81- }
83+ const validate = async ( req : Request , res : Response , next : NextFunction ) : Promise < void | Response > => {
84+ const layRequest = req as LayerRequest ;
85+ if ( layRequest . layer . type !== 'Feature' ) {
86+ return res . status ( 400 ) . send ( 'Cannot import data, layer type is not "Static".' ) ;
87+ }
8288
83- if ( ! kml || kml . documentElement ?. nodeName !== 'kml' ) {
84- return res . status ( 400 ) . send ( 'Invalid file, please upload a KML or KMZ file.' ) ;
85- }
89+ const fileExtension : string = layRequest . file ?. originalname ?. toLowerCase ( ) . split ( '.' ) . pop ( ) || '' ;
8690
87- req . kml = kml ;
88- return next ( ) ;
91+ if ( ! [ 'kml' , 'kmz' ] . includes ( fileExtension ) ) {
92+ return res . status ( 400 ) . send ( 'Invalid file, please upload a KML or KMZ file.' ) ;
8993 }
9094
95+ try {
96+ layRequest . features = kmlToGeoJSON ( layRequest . file ! . path , fileExtension === 'kmz' ) ;
97+ } catch ( err ) {
98+ return res . status ( 400 ) . send ( 'Unable to extract contents from KMZ file.' + err ) ;
99+ }
100+ return next ( ) ;
101+ }
102+
103+ function importRoutes ( app : Express , security : SecurityConfig ) : void {
104+ const passport = security . authentication . passport ;
105+
91106 app . post (
92107 '/api/layers/:layerId/kml' ,
93108 passport . authenticate ( 'bearer' ) ,
94109 access . authorize ( 'CREATE_LAYER' as AnyPermission ) ,
95110 upload . single ( 'file' ) ,
96111 validate ,
97- function ( req : Request , res : Response , next : NextFunction ) : void {
112+ ( req : Request , res : Response , next : NextFunction ) => {
98113 const layerRequest = req as LayerRequest ;
99- console . log ( 'Importing KML file:' , layerRequest . file ?. originalname ) ;
100- const features = toGeoJson . kml ( layerRequest . kml ! ) ;
101- new api . Feature ( layerRequest . layer ) . createFeatures ( features )
114+ new api . Feature ( layerRequest . layer ) . createFeatures ( layerRequest . features )
102115 . then ( ( newFeatures : any [ ] ) => {
103116 const response : ImportResponse = {
104117 files : [ {
0 commit comments