Skip to content

Commit 0c283db

Browse files
authored
Allow KMZ filetype uploads for Layers (#291)
* initial commit * update imports to typescript * update KML text * updated another text field
1 parent 2669b44 commit 0c283db

File tree

6 files changed

+351
-297
lines changed

6 files changed

+351
-297
lines changed

service/npm-shrinkwrap.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
},
8383
"devDependencies": {
8484
"@fluffy-spoon/substitute": "1.208.0",
85+
"@types/adm-zip": "0.5.7",
8586
"@types/archiver": "5.3.4",
8687
"@types/async": "3.2.24",
8788
"@types/bson": "1.0.11",

service/src/routes/imports.js

Lines changed: 0 additions & 65 deletions
This file was deleted.

service/src/routes/imports.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Express, Request, Response, NextFunction } from 'express';
2+
import api from '../api';
3+
import access from '../access';
4+
import { AnyPermission } from '../entities/authorization/entities.permissions'
5+
import fs from 'fs-extra';
6+
import Zip from 'adm-zip';
7+
import { defaultHandler as upload } from '../upload';
8+
import { DOMParser, Document } from '@xmldom/xmldom';
9+
import toGeoJson from '../utilities/togeojson';
10+
11+
interface SecurityConfig {
12+
authentication: {
13+
passport: any;
14+
};
15+
}
16+
interface LayerRequest extends Request {
17+
layer: {
18+
type: string;
19+
};
20+
kml?: Document;
21+
file?: Express.Multer.File;
22+
}
23+
24+
interface ImportResponse {
25+
files: Array<{
26+
name: string;
27+
size: number;
28+
features: number;
29+
}>;
30+
}
31+
32+
function importRoutes(app: Express, security: SecurityConfig): void {
33+
const passport = security.authentication.passport;
34+
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+
}
40+
41+
if (!layRequest.file) {
42+
return res.status(400).send('Invalid file, please upload a KML or KMZ file.');
43+
}
44+
45+
const fileExtension: string = layRequest.file.originalname.toLowerCase().split('.').pop() || '';
46+
47+
if (fileExtension === 'kmz') {
48+
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.');
55+
}
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.');
61+
}
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+
}
70+
}
71+
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+
77+
if (parseError.length > 0) {
78+
console.error("KML Parsing Error:", parseError[0].textContent);
79+
} else {
80+
console.log("Parsed KML successfully");
81+
}
82+
83+
if (!kml || kml.documentElement?.nodeName !== 'kml') {
84+
return res.status(400).send('Invalid file, please upload a KML or KMZ file.');
85+
}
86+
87+
req.kml = kml;
88+
return next();
89+
}
90+
91+
app.post(
92+
'/api/layers/:layerId/kml',
93+
passport.authenticate('bearer'),
94+
access.authorize('CREATE_LAYER' as AnyPermission),
95+
upload.single('file'),
96+
validate,
97+
function (req: Request, res: Response, next: NextFunction): void {
98+
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)
102+
.then((newFeatures: any[]) => {
103+
const response: ImportResponse = {
104+
files: [{
105+
name: Buffer.from(layerRequest.file!.originalname, 'latin1').toString('utf-8'),
106+
size: layerRequest.file!.size,
107+
features: newFeatures ? newFeatures.length : 0
108+
}]
109+
};
110+
111+
res.json(response);
112+
})
113+
.catch((err: Error) => next(err));
114+
}
115+
);
116+
}
117+
118+
export = importRoutes;

0 commit comments

Comments
 (0)