Add new Convert co-ordinate format module.
Also added autodetect of co-ordinate format / delimitermaster
parent
abdd70c6fa
commit
68fbbb64db
|
@ -210,6 +210,7 @@
|
|||
"Convert mass",
|
||||
"Convert speed",
|
||||
"Convert data units",
|
||||
"Convert co-ordinate format",
|
||||
"Parse UNIX file permissions",
|
||||
"Swap endianness",
|
||||
"Parse colour code",
|
||||
|
|
|
@ -208,3 +208,98 @@ function convDDToDDM (decDegrees, precision) {
|
|||
converted.string = degrees + "° " + decMinutes + "'";
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} input - The input data whose format we need to detect
|
||||
* @param {string} delim - The delimiter separating the data in input
|
||||
* @returns {string} The input format
|
||||
*/
|
||||
export function findFormat (input, delim) {
|
||||
input = input.trim();
|
||||
let testData;
|
||||
if (delim.includes("Direction")) {
|
||||
const split = input.split(/[NnEeSsWw]/);
|
||||
if (split.length > 0) {
|
||||
if (split[0] === "") {
|
||||
// Direction Preceding
|
||||
testData = split[1];
|
||||
} else {
|
||||
// Direction Following
|
||||
testData = split[0];
|
||||
}
|
||||
}
|
||||
} else if (delim !== "") {
|
||||
const split = input.split(delim);
|
||||
if (!input.includes(delim)) {
|
||||
testData = input;
|
||||
}
|
||||
if (split.length > 0) {
|
||||
if (split[0] !== "") {
|
||||
testData = split[0];
|
||||
} else if (split.length > 1) {
|
||||
testData = split[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test MGRS and Geohash
|
||||
if (input.split(" ").length === 1) {
|
||||
const mgrsPattern = new RegExp(/^[0-9]{2}[C-HJ-NP-X]{2}[A-Z]+/);
|
||||
const geohashPattern = new RegExp(/^[0123456789bcdefghjkmnpqrstuvwxyz]+$/);
|
||||
if (mgrsPattern.test(input.toUpperCase())) {
|
||||
return "Military Grid Reference System";
|
||||
} else if (geohashPattern.test(input.toLowerCase())) {
|
||||
return "Geohash";
|
||||
}
|
||||
}
|
||||
|
||||
// Test DMS/DDM/DD formats
|
||||
if (testData !== undefined) {
|
||||
const split = splitInput(testData);
|
||||
if (split.length === 3) {
|
||||
// DMS
|
||||
return "Degrees Minutes Seconds";
|
||||
} else if (split.length === 2) {
|
||||
// DDM
|
||||
return "Degrees Decimal Minutes";
|
||||
} else if (split.length === 1) {
|
||||
return "Decimal Degrees";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically find the delimeter type from the given input
|
||||
* @param {string} input
|
||||
* @returns {string} Delimiter type
|
||||
*/
|
||||
export function findDelim (input) {
|
||||
input = input.trim();
|
||||
const delims = [",", ";", ":"];
|
||||
// Direction
|
||||
const testDir = input.match(/[NnEeSsWw]/g);
|
||||
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
|
||||
// Possible direction
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
// One of the splits should be an empty string
|
||||
if (splitInput[0] === "") {
|
||||
return "Direction Preceding";
|
||||
} else if (splitInput[splitInput.length - 1] === "") {
|
||||
return "Direction Following";
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < delims.length; i++) {
|
||||
const delim = delims[i];
|
||||
if (input.includes(delim)) {
|
||||
const splitInput = input.split(delim);
|
||||
if (splitInput.length <= 3 && splitInput.length > 0) {
|
||||
return delim;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
* @author j433866 [j433866@gmail.com]
|
||||
* @copyright Crown Copyright 2019
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation";
|
||||
import OperationError from "../errors/OperationError";
|
||||
import {FORMATS, convertCoordinates, convertSingleCoordinate, findDelim, findFormat} from "../lib/ConvertCoordinates";
|
||||
import Utils from "../Utils";
|
||||
|
||||
/**
|
||||
* Convert co-ordinate format operation
|
||||
*/
|
||||
class ConvertCoordinateFormat extends Operation {
|
||||
|
||||
/**
|
||||
* ConvertCoordinateFormat constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Convert co-ordinate format";
|
||||
this.module = "Hashing";
|
||||
this.description = "Convert geographical coordinates between different formats.<br><br>Currently supported formats:<ul><li>Degrees Minutes Seconds (DMS)</li><li>Degrees Decimal Minutes (DDM)</li><li>Decimal Degrees (DD)</li><li>Geohash</li><li>Military Grid Reference System (MGRS)</li></ul>";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Input Format",
|
||||
"type": "option",
|
||||
"value": ["Auto"].concat(FORMATS)
|
||||
},
|
||||
{
|
||||
"name": "Input Delimiter",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Auto",
|
||||
"Direction Preceding", // Need better names
|
||||
"Direction Following",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
"Colon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Output Format",
|
||||
"type": "option",
|
||||
"value": FORMATS
|
||||
},
|
||||
{
|
||||
"name": "Output Delimiter",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"Space",
|
||||
"Direction Preceding", // Need better names
|
||||
"Direction Following",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
"Colon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Precision",
|
||||
"type": "number",
|
||||
"value": 3
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const outFormat = args[2],
|
||||
outDelim = args[3],
|
||||
precision = args[4];
|
||||
let inFormat = args[0],
|
||||
inDelim = args[1],
|
||||
inLat,
|
||||
inLong,
|
||||
outLat,
|
||||
outLong,
|
||||
latDir = "",
|
||||
longDir = "",
|
||||
outSeparator = " ";
|
||||
|
||||
// Autodetect input delimiter
|
||||
if (inDelim === "Auto") {
|
||||
inDelim = findDelim(input);
|
||||
log.error("DATA: " + input + " DELIM: " + inDelim);
|
||||
if (inDelim === null) {
|
||||
inDelim = "";
|
||||
// throw new OperationError("Could not automatically detect the input delimiter.");
|
||||
}
|
||||
} else if (!inDelim.includes("Direction")) {
|
||||
// Get the actual delimiter from the regex
|
||||
inDelim = String(Utils.regexRep(inDelim)).slice(1, 2);
|
||||
}
|
||||
if (inFormat === "Auto") {
|
||||
inFormat = findFormat(input, inDelim);
|
||||
log.error("DATA: " + input + " FORMAT: " + inFormat);
|
||||
if (inFormat === null) {
|
||||
throw new OperationError("Could not automatically detect the input");
|
||||
}
|
||||
}
|
||||
|
||||
if (inDelim === "" && (inFormat !== "Geohash" && inFormat !== "Military Grid Reference System")) {
|
||||
throw new OperationError("Could not automatically detect the input delimiter.");
|
||||
}
|
||||
|
||||
// Prepare input data
|
||||
if (inFormat === "Geohash" || inFormat === "Military Grid Reference System") {
|
||||
// Geohash only has one value, so just use the input
|
||||
inLat = input;
|
||||
} else if (inDelim === "Direction Preceding") {
|
||||
// Split on the compass directions
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
const dir = input.match(/[NnEeSsWw]/g);
|
||||
if (splitInput.length > 1) {
|
||||
inLat = splitInput[1];
|
||||
if (dir !== null) {
|
||||
latDir = dir[0];
|
||||
}
|
||||
if (splitInput.length > 2) {
|
||||
inLong = splitInput[2];
|
||||
if (dir !== null && dir.length > 1) {
|
||||
longDir = dir[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (inDelim === "Direction Following") {
|
||||
// Split on the compass directions
|
||||
const splitInput = input.split(/[NnEeSsWw]/);
|
||||
if (splitInput.length >= 1) {
|
||||
inLat = splitInput[0];
|
||||
if (splitInput.length >= 2) {
|
||||
inLong = splitInput[1];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Split on the delimiter
|
||||
const splitInput = input.split(inDelim);
|
||||
log.error(splitInput);
|
||||
if (splitInput.length > 0) {
|
||||
inLat = splitInput[0];
|
||||
if (splitInput.length >= 2) {
|
||||
inLong = splitInput[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inFormat !== "Geohash" && inFormat !== "Military Grid Reference System" && outDelim.includes("Direction")) {
|
||||
// Match on compass directions, and store the first 2 matches for the output
|
||||
const dir = input.match(/[NnEeSsWw]/g);
|
||||
if (dir !== null) {
|
||||
latDir = dir[0];
|
||||
if (dir.length > 1) {
|
||||
longDir = dir[1];
|
||||
}
|
||||
}
|
||||
} else if (outDelim === "\\n") {
|
||||
outSeparator = "\n";
|
||||
} else if (outDelim === "Space") {
|
||||
outSeparator = " ";
|
||||
} else if (!outDelim.includes("Direction")) {
|
||||
// Cut out the regex syntax (/) from the delimiter
|
||||
outSeparator = String(Utils.regexRep(outDelim)).slice(1, 2);
|
||||
}
|
||||
|
||||
// Convert the co-ordinates
|
||||
if (inLat !== undefined) {
|
||||
if (inLong === undefined) {
|
||||
if (inFormat !== "Geohash" && inFormat !== "Military Grid Reference System") {
|
||||
if (outFormat === "Geohash" || outFormat === "Military Grid Reference System"){
|
||||
throw new OperationError(`${outFormat} needs both a latitude and a longitude to be calculated`);
|
||||
}
|
||||
}
|
||||
if (inFormat === "Geohash" || inFormat === "Military Grid Reference System") {
|
||||
// Geohash conversion is in convertCoordinates despite needing
|
||||
// only one input as it needs to output two values
|
||||
[outLat, outLong] = convertCoordinates(inLat, inLat, inFormat, outFormat, precision);
|
||||
} else {
|
||||
outLat = convertSingleCoordinate(inLat, inFormat, outFormat, precision);
|
||||
}
|
||||
} else {
|
||||
[outLat, outLong] = convertCoordinates(inLat, inLong, inFormat, outFormat, precision);
|
||||
}
|
||||
} else {
|
||||
throw new OperationError("No co-ordinates were detected in the input.");
|
||||
}
|
||||
|
||||
// Output conversion results if successful
|
||||
if (outLat !== undefined) {
|
||||
let output = "";
|
||||
if (outDelim === "Direction Preceding" && outFormat !== "Geohash" && outFormat !== "Military Grid Reference System") {
|
||||
output += latDir += " ";
|
||||
}
|
||||
output += outLat;
|
||||
if (outDelim === "Direction Following" && outFormat !== "Geohash" && outFormat !== "Military Grid Reference System") {
|
||||
output += " " + latDir;
|
||||
}
|
||||
output += outSeparator;
|
||||
|
||||
if (outLong !== undefined && outFormat !== "Geohash" && outFormat !== "Military Grid Reference System") {
|
||||
if (outDelim === "Direction Preceding") {
|
||||
output += longDir + " ";
|
||||
}
|
||||
output += outLong;
|
||||
if (outDelim === "Direction Following") {
|
||||
output += " " + longDir;
|
||||
}
|
||||
output += outSeparator;
|
||||
}
|
||||
return output;
|
||||
} else {
|
||||
throw new OperationError("Co-ordinate conversion failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ConvertCoordinateFormat;
|
Loading…
Reference in New Issue