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; } }