Преглед изворни кода

Add new Convert co-ordinate format module.
Also added autodetect of co-ordinate format / delimiter

j433866 пре 6 година
родитељ
комит
68fbbb64db

+ 1 - 0
src/core/config/Categories.json

@@ -210,6 +210,7 @@
             "Convert mass",
             "Convert speed",
             "Convert data units",
+            "Convert co-ordinate format",
             "Parse UNIX file permissions",
             "Swap endianness",
             "Parse colour code",

+ 95 - 0
src/core/lib/ConvertCoordinates.mjs

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

+ 227 - 0
src/core/operations/ConvertCoordinateFormat.mjs

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