'use strict'; const fs = require('fs'), { Errors } = require('./constants'), { AppInputError, AppParamError } = require('./app_error'); function splitDMS(dms) { if (!dms || dms.length !== 13) AppInputError.throw(Errors.INVALID_DMS); const fields = dms.trim().split(new RegExp('\\s+')).map(it => it.trim()); return { deg: fields[0], min: fields[1], sec: fields[2].slice(0, fields[2].length - 1), hem: fields[2].slice(-1) }; } function trimProps(objectToTrim) { for (const key in objectToTrim) { if (objectToTrim[key].constructor && objectToTrim[key].constructor == Object) trimProps(objectToTrim[key]); else if (objectToTrim[key].trim) objectToTrim[key] = objectToTrim[key].trim(); } } function readObstacles(filePath, cb) { //# .Dat files have fixed-width columns; the data is always in the same position within a row fs.readFile(filePath, 'utf8', function (err, data) { // fs.truncateSync(testRes); if (err) throw err; let failed = false; let lines = data.toString().split('\n'); let obs = []; let startLat = -1, startLatDeg = -1, startLonDeg = -1, startAGL = -1, startHT = -1, startType = -1, startAct = -1; // startASML = -1; try { for (let i = 0; i < lines.length; i++) { if (failed) break; let line = lines[i]; let latDms, lonDms, ob = {}; // Skip the first 4 rows (they include poorly formatted header information) if (i === 1) { startLat = line.toUpperCase().indexOf("LATITUDE"); startAGL = line.toUpperCase().indexOf("AGL"); // startASML = line.toUpperCase().indexOf("AMSL"); startAct = line.toUpperCase().indexOf("ACTION"); } else if (i === 2) { startLatDeg = line.toUpperCase().indexOf("DEG"); startLonDeg = line.toUpperCase().indexOf("DEG", startLatDeg + 1); startHT = line.toUpperCase().indexOf("HT"); startType = line.toUpperCase().indexOf("TYPE"); } if (i < 4 || line.trim().length === 0) continue; // Verify file format, then to know where to get important fields if (!(startLat >= startLatDeg && startAGL === startHT) || (!startLat || !startLatDeg || !startLonDeg || !startAGL || !startHT || !startType || !startAct)) AppParamError.throw(Errors.INVALID_OBSTACLE_FILE_FORMAT); ob.ors = line.slice(0, 9); // The first 9 characters of the row represent the Obstacle number ob.v = line.slice(10, 11); latDms = splitDMS(line.substring(startLatDeg, startLatDeg + 13)); ob.latDeg = latDms.deg; ob.latMin = latDms.min; ob.latSec = latDms.sec; ob.latHem = latDms.hem; lonDms = splitDMS(line.substring(startLonDeg, startLonDeg + 13)); ob.lonDeg = lonDms.deg; ob.lonMin = lonDms.min; ob.lonSec = lonDms.sec; ob.lonHem = lonDms.hem; ob.type = line.substring(startType, startType + 17); ob.agl = line.substring(startAGL, startAGL + 5); ob.amsl = line.substring(startAGL + 6, (startAGL + 6) + 5); ob.action = line.substring(startAct, startAct + 3); //Convert DMS to Decimal Degrees (N vs. S determines if the value is positive or negative) if (ob.latHem == "N") ob.latitude = (parseFloat(ob.latDeg) + (parseFloat(ob.latMin) / 60) + (parseFloat(ob.latSec) / 3600)); else ob.latitude = -1 * (parseFloat(ob.latDeg) + (parseFloat(ob.latMin) / 60) + (parseFloat(ob.latSec) / 3600)); // Convert DMS to Decimal Degrees (E vs. W determines if the value is positive or negative) if (ob.lonHem == "E") ob.longitude = (parseFloat(ob.lonDeg) + (parseFloat(ob.lonMin) / 60) + (parseFloat(ob.lonSec) / 3600)); else ob.longitude = -1 * (parseFloat(ob.lonDeg) + (parseFloat(ob.lonMin) / 60) + (parseFloat(ob.lonSec) / 3600)); trimProps(ob); ob.latitude = ob.latitude.toFixed(7); ob.longitude = ob.longitude.toFixed(7); const geoObstacle = { properties: { name: ob.ors, type: ob.type, agl: parseFloat(ob.agl), // Above ground level in feet amsl: parseFloat(ob.amsl), // Average mean sea level in feet }, geometry: { type: 'Point', coordinates: [ob.longitude, ob.latitude] } }; obs.push(geoObstacle); } return cb && cb(null, obs.slice(0)); } catch (err) { // debug(err.message, "\n", err.stack); failed = true; return cb && cb(err.message); } // fs.appendFileSync(testRes, JSON.stringify(obs, null, 2)); }); } module.exports = { readObstacles }