landxml/lib/converter.js

142 lines
4.2 KiB
JavaScript

import xml2js from 'xml2js';
import { featureCollection } from '@turf/helpers';
import area from '@turf/area';
import proj4 from 'proj4';
import { Proj4Defs } from './projection';
import { getProp } from './util';
import { convertSurfaces } from './surface';
import { convertParcels } from './parcel';
import { convertPipeNetworks } from './pipe-network';
import { convertCGPoints } from './cg-point';
export const PROJECTION_AUTO_DETECT = 'auto_detect';
export const CONVERT_SURFACES = 'surfaces';
export const CONVERT_PARCELS = 'parcels';
export const CONVERT_PIPE_NETWORKS = 'pipe_networks';
export const CONVERT_CG_POINTS = 'cg_points';
const converters = {
[CONVERT_PARCELS]: convertParcels,
[CONVERT_SURFACES]: convertSurfaces,
[CONVERT_PIPE_NETWORKS]: convertPipeNetworks,
[CONVERT_CG_POINTS]: convertCGPoints,
}
// Based on alexgleith's preliminary work
// github.com/alexgleith/land-xml-to-geojson
export class Converter {
defaultOptions = {
projection: PROJECTION_AUTO_DETECT,
enabledConverters: [ CONVERT_SURFACES, CONVERT_PARCELS, CONVERT_PIPE_NETWORKS, CONVERT_CG_POINTS ],
parser: {
normalize: true
},
proj4Defs: [
[
"EPSG:28355",
"+proj=utm +zone=55 +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs",
],
...Proj4Defs,
],
featureMapper: (feature, opts) => feature,
colorOpts: {
hue: 'blue',
luminosity: 'dark',
seed: 1,
},
}
toGeoJSON(xml, opts = {}) {
const parserOptions = Object.assign({}, this.defaultOptions.parser, opts.parser);
const parser = new xml2js.Parser(parserOptions);
this.configureProj4(opts.proj4Defs);
return new Promise((resolve, reject) => {
parser.parseString(xml, (err, xmlObj) => {
if (err) return reject(err);
let geojson;
try {
geojson = this.convertToGeoJSON(xmlObj, opts);
} catch(err) {
return reject(err);
}
return resolve(geojson);
});
});
}
convertToGeoJSON(xmlObj, opts = {}) {
const options = Object.assign({}, this.defaultOptions, opts);
// Use specified projection or try to find one in the LandXML file
const projection = this.getLandXMLProjection(xmlObj, options.projection || this.defaultOptions.projection);
if (!projection) throw new Error("Source projection can not be found !");
// List enabled converters
const enabledConverters = Array.isArray(opts.enabledConverters) ?
opts.enabledConverters :
this.defaultOptions.enabledConverters
;
let features = [];
// Convert LandXML entities to GeoJSON features
enabledConverters.forEach(c => {
if (!(c in converters)) return;
console.log("Executing convert '%s'...", c);
const newFeatures = converters[c](xmlObj, projection, options);
features.push(...newFeatures);
});
// Apply features mapper function if defined
if (typeof options.featureMapper === 'function') {
features = features.map(f => {
return options.featureMapper(f, options);
});
}
// Sort feature by reverse area
// to ease rendering
features.sort((a, b) => {
if (area(a) > area(b)) return -1;
if (area(a) < area(b)) return 1;
return 0;
});
// Return extracted features as a feature collection
return featureCollection(features);
}
configureProj4(additionalProj4Defs) {
const proj4Defs = this.defaultOptions.proj4Defs.slice(0)
if (Array.isArray(additionalProj4Defs)) proj4Defs.push(...additionalProj4Defs);
proj4.defs(proj4Defs);
}
getLandXMLProjection(xmlObj, manualProjection) {
if (manualProjection !== PROJECTION_AUTO_DETECT) return manualProjection;
let projection = null;
if(!projection) projection = this.findUTMProjection(xmlObj);
return projection;
}
findUTMProjection(xmlObj) {
const projection = getProp(xmlObj, "LandXML", "CoordinateSystem", 0, "$", "datum");
const zone = getProp(xmlObj, "LandXML", "CgPoints", 0, "$", "zoneNumber");
let utmProjection = null
if (projection && zone) {
utmProjection = `+proj=utm +zone=${zone} +south +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs `;
}
return utmProjection;
}
}