Browse Source

Basically rewrote the whole thing using the new geodesy module

j433866 6 years ago
parent
commit
d00b0f4c0e
2 changed files with 350 additions and 270 deletions
  1. 336 122
      src/core/lib/ConvertCoordinates.mjs
  2. 14 148
      src/core/operations/ConvertCoordinateFormat.mjs

+ 336 - 122
src/core/lib/ConvertCoordinates.mjs

@@ -18,148 +18,240 @@ export const FORMATS = [
     "Decimal Degrees",
     "Decimal Degrees",
     "Geohash",
     "Geohash",
     "Military Grid Reference System",
     "Military Grid Reference System",
-    "Ordnance Survey National Grid"
+    "Ordnance Survey National Grid",
+    "Universal Transverse Mercator"
 ];
 ];
 
 
 /**
 /**
- * Formats that are made up of one string
- * These formats skip bits like filtering delimiters and
- * are outputted differently (only one output)
+ * Formats that should be passed to Geodesy module as-is
+ * Spaces are still removed
  */
  */
-export const STRING_FORMATS = [
+const NO_CHANGE = [
     "Geohash",
     "Geohash",
     "Military Grid Reference System",
     "Military Grid Reference System",
-    "Ordnance Survey National Grid"
+    "Ordnance Survey National Grid",
+    "Universal Transverse Mercator",
 ];
 ];
 
 
 /**
 /**
  * Convert a given latitude and longitude into a different format.
  * Convert a given latitude and longitude into a different format.
- * @param {string} inLat - Input latitude to be converted. Use this for supplying single values for conversion (e.g. geohash)
- * @param {string} inLong - Input longitude to be converted
+ * @param {string} input - Input string to be converted
  * @param {string} inFormat - Format of the input coordinates
  * @param {string} inFormat - Format of the input coordinates
+ * @param {string} inDelim - The delimiter splitting the lat/long of the input
  * @param {string} outFormat - Format to convert to
  * @param {string} outFormat - Format to convert to
+ * @param {string} outDelim - The delimiter to separate the output with
+ * @param {string} includeDir - Whether or not to include the compass direction in the output
  * @param {number} precision - Precision of the result
  * @param {number} precision - Precision of the result
- * @returns {string[]} Array containing the converted latitude and longitude
+ * @returns {string} A formatted string of the converted co-ordinates
  */
  */
-export function convertCoordinates (inLat, inLong, inFormat, outFormat, precision) {
-    let convLat = inLat;
-    let convLong = inLong;
+export function convertCoordinates (input, inFormat, inDelim, outFormat, outDelim, includeDir, precision) {
+    let isPair = false,
+        split,
+        latlon,
+        conv,
+        inLatDir,
+        inLongDir;
+
+    if (inDelim === "Auto") {
+        inDelim = findDelim(input);
+    } else {
+        inDelim = realDelim(inDelim);
+    }
+    if (inFormat === "Auto") {
+        inFormat = findFormat(input, inDelim);
+        if (inFormat === null) {
+            throw "Unable to detect the input format automatically.";
+        }
+    }
+    if (inDelim === null && !inFormat.includes("Direction")) {
+        throw "Unable to detect the input delimiter automatically.";
+    }
+    outDelim = realDelim(outDelim);
+
+    if (!NO_CHANGE.includes(inFormat)) {
+        split = input.split(inDelim);
+        if (split.length > 1) {
+            isPair = true;
+        }
+    } else {
+        input = input.replace(inDelim, "");
+        isPair = true;
+    }
+
+    if (inFormat.includes("Degrees")) {
+        [inLatDir, inLongDir] = findDirs(input, inDelim);
+    }
+
     if (inFormat === "Geohash") {
     if (inFormat === "Geohash") {
-        const hash = geohash.decode(inLat);
-        convLat = hash.latitude.toString();
-        convLong = hash.longitude.toString();
+        const hash = geohash.decode(input.replace(/[^A-Za-z0-9]/g, ""));
+        latlon = new geodesy.LatLonEllipsoidal(hash.latitude, hash.longitude);
     } else if (inFormat === "Military Grid Reference System") {
     } else if (inFormat === "Military Grid Reference System") {
-        const utm = geodesy.Mgrs.parse(inLat).toUtm();
-        const result = utm.toLatLonE().toString("d", 4).replace(/[^0-9.,]/g, "");
-        const splitResult = result.split(",");
-        if (splitResult.length === 2) {
-            convLat = splitResult[0];
-            convLong = splitResult[1];
-        }
+        const utm = geodesy.Mgrs.parse(input.replace(/[^A-Za-z0-9]/g, "")).toUtm();
+        latlon = utm.toLatLonE();
     } else if (inFormat === "Ordnance Survey National Grid") {
     } else if (inFormat === "Ordnance Survey National Grid") {
-        const osng = geodesy.OsGridRef.parse(inLat);
-        const latlon = geodesy.OsGridRef.osGridToLatLon(osng, geodesy.LatLonEllipsoidal.datum.WGS84);
-        const result = latlon.toString("d", 4).replace(/[^0-9.,]/g, "");
-        const splitResult = result.split(",");
-        if (splitResult.length === 2) {
-            convLat = splitResult[0];
-            convLong = splitResult[1];
+        const osng = geodesy.OsGridRef.parse(input.replace(/[^A-Za-z0-9]/g, ""));
+        latlon = geodesy.OsGridRef.osGridToLatLon(osng);
+    } else if (inFormat === "Universal Transverse Mercator") {
+        if (/^[\d]{2}[A-Za-z]/.test(input)) {
+            input = input.slice(0, 2) + " " + input.slice(2);
+        }
+        const utm = geodesy.Utm.parse(input);
+        latlon = utm.toLatLonE();
+    } else if (inFormat === "Degrees Minutes Seconds") {
+        if (isPair) {
+            split[0] = split[0].replace(/[NnEeSsWw]/g, "").trim();
+            split[1] = split[1].replace(/[NnEeSsWw]/g, "").trim();
+            const splitLat = split[0].split(/[°′″'"\s]/g),
+                splitLong = split[1].split(/[°′″'"\s]/g);
+
+            if (splitLat.length >= 3 && splitLong.length >= 3) {
+                const lat = convDMSToDD(parseFloat(splitLat[0]), parseFloat(splitLat[1]), parseFloat(splitLat[2]), 10);
+                const long = convDMSToDD(parseFloat(splitLong[0]), parseFloat(splitLong[1]), parseFloat(splitLong[2]), 10);
+                latlon = new geodesy.LatLonEllipsoidal(lat.degrees, long.degrees);
+            }
+        } else {
+            // Create a new latlon object anyway, but we can ignore the lon value
+            split[0] = split[0].replace(/[NnEeSsWw]/g, "").trim();
+            const splitLat = split[0].split(/[°′″'"\s]/g);
+            if (splitLat.length >= 3) {
+                const lat = convDMSToDD(parseFloat(splitLat[0]), parseFloat(splitLat[1]), parseFloat(splitLat[2]));
+                latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
+            }
+        }
+    } else if (inFormat === "Degrees Decimal Minutes") {
+        if (isPair) {
+            const splitLat = splitInput(split[0]);
+            const splitLong = splitInput(split[1]);
+            if (splitLat.length !== 2 || splitLong.length !== 2) {
+                throw "Invalid co-ordinate format for Degrees Decimal Minutes.";
+            }
+            const lat = convDDMToDD(splitLat[0], splitLat[1], 10);
+            const long = convDDMToDD(splitLong[0], splitLong[1], 10);
+            latlon = new geodesy.LatLonEllipsoidal(lat.degrees, long.degrees);
+        } else {
+            const splitLat = splitInput(input);
+            if (splitLat.length !== 2) {
+                throw "Invalid co-ordinate format for Degrees Decimal Minutes.";
+            }
+            const lat = convDDMToDD(splitLat[0], splitLat[1], 10);
+            latlon = new geodesy.LatLonEllipsoidal(lat.degrees, lat.degrees);
+        }
+    } else if (inFormat === "Decimal Degrees") {
+        if (isPair) {
+            const splitLat =  splitInput(split[0]);
+            const splitLong = splitInput(split[1]);
+            if (splitLat.length !== 1 || splitLong.length !== 1) {
+                throw "Invalid co-ordinate format for Decimal Degrees.";
+            }
+            latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLong[0]);
+        } else {
+            const splitLat = splitInput(split[0]);
+            if (splitLat.length !== 1) {
+                throw "Invalid co-ordinate format for Decimal Degrees.";
+            }
+            latlon = new geodesy.LatLonEllipsoidal(splitLat[0], splitLat[0]);
         }
         }
     } else {
     } else {
-        convLat = convertSingleCoordinate(inLat, inFormat, "Decimal Degrees", 15).split("°");
-        convLong = convertSingleCoordinate(inLong, inFormat, "Decimal Degrees", 15).split("°");
+        throw "Invalid input co-ordinate format selected.";
     }
     }
 
 
-    // Convert Geohash and MGRS here, as they need both the lat and long values
-    if (outFormat === "Geohash") {
-        convLat = geohash.encode(parseFloat(convLat), parseFloat(convLong), precision);
+    // Everything is now a geodesy latlon object
+    if (outFormat === "Decimal Degrees") {
+        conv = latlon.toString("d", precision);
+        if (!isPair) {
+            conv = conv.split(",")[0];
+        }
+    } else if (outFormat === "Degrees Decimal Minutes") {
+        conv = latlon.toString("dm", precision);
+        if (!isPair) {
+            conv = conv.split(",")[0];
+        }
+    } else if (outFormat === "Degrees Minutes Seconds") {
+        conv = latlon.toString("dms", precision);
+        if (!isPair) {
+            conv = conv.split(",")[0];
+        }
+    } else if (outFormat === "Geohash") {
+        conv = geohash.encode(latlon.lat.toString(), latlon.lon.toString(), precision);
     } else if (outFormat === "Military Grid Reference System") {
     } else if (outFormat === "Military Grid Reference System") {
-        const utm = new geodesy.LatLonEllipsoidal(parseFloat(convLat), parseFloat(convLong)).toUtm();
+        const utm = latlon.toUtm();
         const mgrs = utm.toMgrs();
         const mgrs = utm.toMgrs();
-        convLat = mgrs.toString();
+        conv = mgrs.toString(precision);
     } else if (outFormat === "Ordnance Survey National Grid") {
     } else if (outFormat === "Ordnance Survey National Grid") {
-        const latlon = new geodesy.LatLonEllipsoidal(parseFloat(convLat), parseFloat(convLong));
         const osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
         const osng = geodesy.OsGridRef.latLonToOsGrid(latlon);
-        convLat = osng.toString();
-        if (convLat === "") {
-            throw "Couldn't convert co-ordinates to Ordnance Survey National Grid. Are they out of range?";
+        if (osng.toString() === "") {
+            throw "Could not convert co-ordinates to OS National Grid. Are the co-ordinates in range?";
         }
         }
-    } else {
-        convLat = convertSingleCoordinate(convLat.toString(), "Decimal Degrees", outFormat, precision);
-        convLong = convertSingleCoordinate(convLong.toString(), "Decimal Degrees", outFormat, precision);
+        conv = osng.toString(precision);
+    } else if (outFormat === "Universal Transverse Mercator") {
+        const utm = latlon.toUtm();
+        conv = utm.toString(precision);
     }
     }
 
 
-    return [convLat, convLong];
-}
-
-/**
- * @param {string} input - The input co-ordinate to be converted
- * @param {string} inFormat - The format of the input co-ordinates
- * @param {string} outFormat - The format which input should be converted to
- * @param {boolean} returnRaw - When true, returns the raw float instead of a String
- * @returns {string|{Object}} The converted co-ordinate result, as either the raw object or a formatted string
- */
-export function convertSingleCoordinate (input, inFormat, outFormat, precision, returnRaw = false){
-    let converted;
-    precision = Math.pow(10, precision);
-    const convData = splitInput(input);
-    // Convert everything to decimal degrees first
-    switch (inFormat) {
-        case "Degrees Minutes Seconds":
-            if (convData.length < 3) {
-                throw "Invalid co-ordinates format.";
-            }
-            converted = convDMSToDD(convData[0], convData[1], convData[2], precision);
-            break;
-        case "Degrees Decimal Minutes":
-            if (convData.length < 2) {
-                throw "Invalid co-ordinates format.";
-            }
-            converted = convDDMToDD(convData[0], convData[1], precision);
-            break;
-        case "Decimal Degrees":
-            if (convData.length < 1) {
-                throw "Invalid co-ordinates format.";
+    if (conv === undefined) {
+        throw "Error converting co-ordinates.";
+    }
+    if (outFormat.includes("Degrees")) {
+        let [latDir, longDir] = findDirs(conv, outDelim);
+        if (inLatDir !== undefined) {
+            latDir = inLatDir;
+        }
+        if (inLongDir !== undefined) {
+            longDir = inLongDir;
+        }
+        // DMS/DDM/DD
+        conv = conv.replace(", ", outDelim);
+        // Remove any directions from the current string,
+        // so we can put them where we want them
+        conv = conv.replace(/[NnEeSsWw]/g, "");
+        if (includeDir !== "None") {
+            let outConv = "";
+            if (!isPair) {
+                if (includeDir === "Before") {
+                    outConv += latDir + " " + conv;
+                } else {
+                    outConv += conv + " " + latDir;
+                }
+            } else {
+                const splitConv = conv.split(outDelim);
+                if (splitConv.length === 2) {
+                    if (includeDir === "Before") {
+                        outConv += latDir + " ";
+                    }
+                    outConv += splitConv[0];
+                    if (includeDir === "After") {
+                        outConv += " " + latDir;
+                    }
+                    outConv += outDelim;
+                    if (includeDir === "Before") {
+                        outConv += longDir + " ";
+                    }
+                    outConv += splitConv[1];
+                    if (includeDir === "After") {
+                        outConv += " " + longDir;
+                    }
+                }
             }
             }
-            converted = convDDToDD(convData[0], precision);
-            break;
-        default:
-            throw "Unknown input format selection.";
+            conv = outConv;
+        }
     }
     }
 
 
-    // Convert from decimal degrees to the output format
-    switch (outFormat) {
-        case "Decimal Degrees":
-            break;
-        case "Degrees Minutes Seconds":
-            converted = convDDToDMS(converted.degrees);
-            break;
-        case "Degrees Decimal Minutes":
-            converted = convDDToDDM(converted.degrees, precision);
-            break;
-        default:
-            throw "Unknown output format selection.";
-    }
-    if (returnRaw) {
-        return converted;
-    } else {
-        return converted.string;
-    }
+    return conv;
 }
 }
 
 
 /**
 /**
- * Split up the input using a space, and sanitise the result
+ * Split up the input using a space or degrees signs, and sanitise the result
  * @param {string} input - The input data to be split
  * @param {string} input - The input data to be split
  * @returns {number[]} An array of the different items in the string, stored as floats
  * @returns {number[]} An array of the different items in the string, stored as floats
  */
  */
 function splitInput (input){
 function splitInput (input){
     const split = [];
     const split = [];
 
 
-    input.split(" ").forEach(item => {
+    input.split(/[°′″'"\s]/).forEach(item => {
         // Remove any character that isn't a digit
         // Remove any character that isn't a digit
         item = item.replace(/[^0-9.-]/g, "");
         item = item.replace(/[^0-9.-]/g, "");
         if (item.length > 0){
         if (item.length > 0){
-            split.push(parseFloat(item, 10));
+            split.push(parseFloat(item));
         }
         }
     });
     });
     return split;
     return split;
@@ -245,47 +337,153 @@ function convDDToDDM (decDegrees, precision) {
 }
 }
 
 
 /**
 /**
- * 
+ * Finds and returns the compass directions in an input string
+ * @param {string} input - The input co-ordinates containing the direction
+ * @param {string} delim - The delimiter separating latitide and longitude
+ * @returns {string[]} String array containing the latitude and longitude directions
+ */
+export function findDirs(input, delim) {
+    const upperInput = input.toUpperCase();
+    const dirExp = new RegExp(/[NESW]/g);
+
+    const dirs = upperInput.match(dirExp);
+
+    if (dirExp.test(upperInput)) {
+        // If there's actually compass directions in the string
+        if (dirs.length <= 2 && dirs.length >= 1) {
+            if (dirs.length === 2) {
+                return [dirs[0], dirs[1]];
+            } else {
+                return [dirs[0], ""];
+            }
+        }
+    }
+    // Nothing was returned, so guess the directions
+    let lat = upperInput,
+        long,
+        latDir = "",
+        longDir = "";
+    if (!delim.includes("Direction")) {
+        if (upperInput.includes(delim)) {
+            const split = upperInput.split(delim);
+            if (split.length > 1) {
+                if (split[0] === "") {
+                    lat = split[1];
+                } else {
+                    lat = split[0];
+                }
+                if (split.length > 2) {
+                    if (split[2] !== "") {
+                        long = split[2];
+                    }
+                }
+            }
+        }
+    } else {
+        const split = upperInput.split(dirExp);
+        if (split.length > 1) {
+            if (split[0] === "") {
+                lat = split[1];
+            } else {
+                lat = split[0];
+            }
+            if (split.length > 2) {
+                if (split[2] !== "") {
+                    long = split[2];
+                }
+            }
+        }
+    }
+    if (lat) {
+        lat = parseFloat(lat);
+        if (lat < 0) {
+            latDir = "S";
+        } else {
+            latDir = "N";
+        }
+    }
+    if (long) {
+        long = parseFloat(long);
+        if (long < 0) {
+            longDir = "W";
+        } else {
+            longDir = "E";
+        }
+    }
+
+    return [latDir, longDir];
+}
+
+/**
+ * Detects the co-ordinate format of the input data
  * @param {string} input - The input data whose format we need to detect
  * @param {string} input - The input data whose format we need to detect
  * @param {string} delim - The delimiter separating the data in input
  * @param {string} delim - The delimiter separating the data in input
  * @returns {string} The input format
  * @returns {string} The input format
  */
  */
 export function findFormat (input, delim) {
 export function findFormat (input, delim) {
-    input = input.trim();
     let testData;
     let testData;
-    if (delim.includes("Direction")) {
+    const mgrsPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]{1}\s?[A-HJ-NP-Z][A-HJ-NP-V]\s?[0-9\s]+/),
+        osngPattern = new RegExp(/^[STNHO][A-HJ-Z][0-9]+$/),
+        geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/),
+        utmPattern = new RegExp(/^[0-9]{2}\s?[C-HJ-NP-X]\s[0-9\.]+\s?[0-9\.]+$/),
+        degPattern = new RegExp(/[°'"]/g);
+    input = input.trim();
+    if (delim !== null && delim.includes("Direction")) {
         const split = input.split(/[NnEeSsWw]/);
         const split = input.split(/[NnEeSsWw]/);
-        if (split.length > 0) {
+        if (split.length > 1) {
             if (split[0] === "") {
             if (split[0] === "") {
-                // Direction Preceding
                 testData = split[1];
                 testData = split[1];
             } else {
             } else {
-                // Direction Following
                 testData = split[0];
                 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];
+    } else if (delim !== null && delim !== "") {
+        if (input.includes(delim)) {
+            const split = input.split(delim);
+            if (split.length > 1) {
+                if (split[0] === "") {
+                    testData = split[1];
+                } else {
+                    testData = split[0];
+                }
             }
             }
+        } else {
+            testData = input;
         }
         }
     }
     }
 
 
     // Test MGRS and Geohash
     // Test MGRS and Geohash
-    if (input.split(" ").length <= 1) {
-        const filteredInput = input.replace(/[^A-Za-z0-9]/, "").toUpperCase();
-        const mgrsPattern = new RegExp(/^[0-9]{2}[C-HJ-NP-X]{2}[A-Z]+/);
-        const geohashPattern = new RegExp(/^[0123456789BCDEFGHJKMNPQRSTUVWXYZ]+$/);
-        if (mgrsPattern.test(filteredInput)) {
+    if (!degPattern.test(input)) {
+        const filteredInput = input.toUpperCase();
+        const isMgrs = mgrsPattern.test(filteredInput);
+        const isOsng = osngPattern.test(filteredInput);
+        const isGeohash = geohashPattern.test(filteredInput);
+        const isUtm = utmPattern.test(filteredInput);
+        if (isMgrs && (isOsng || isGeohash)) {
+            if (filteredInput.includes("I")) {
+                // Only MGRS can have an i!
+                return "Military Grid Reference System";
+            }
+        }
+        if (isUtm) {
+            return "Universal Transverse Mercator";
+        }
+        if (isOsng && isGeohash) {
+            // Geohash doesn't have A, L or O, but OSNG does.
+            const testExp = new RegExp(/[ALO]/g);
+            if (testExp.test(filteredInput)) {
+                return "Ordnance Survey National Grid";
+            } else {
+                return "Geohash";
+            }
+        }
+        if (isMgrs) {
             return "Military Grid Reference System";
             return "Military Grid Reference System";
-        } else if (geohashPattern.test(filteredInput)) {
+        }
+        if (isOsng) {
+            return "Ordnance Survey National Grid";
+        }
+        if (isGeohash) {
             return "Geohash";
             return "Geohash";
         }
         }
     }
     }
@@ -312,7 +510,7 @@ export function findFormat (input, delim) {
  */
  */
 export function findDelim (input) {
 export function findDelim (input) {
     input = input.trim();
     input = input.trim();
-    const delims = [",", ";", ":"];
+    const delims = [",", ";", ":", " "];
     const testDir = input.match(/[NnEeSsWw]/g);
     const testDir = input.match(/[NnEeSsWw]/g);
     if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
     if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
         // Possibly contains a direction
         // Possibly contains a direction
@@ -340,3 +538,19 @@ export function findDelim (input) {
     }
     }
     return null;
     return null;
 }
 }
+
+/**
+ * Gets the real string for a delimiter name.
+ * @param {string} delim The delimiter to be matched
+ * @returns {string}
+ */
+export function realDelim (delim) {
+    return {
+        "Auto":         "Auto",
+        "Space":        " ",
+        "\\n":          "\n",
+        "Comma":        ",",
+        "Semi-colon":   ";",
+        "Colon":        ":"
+    }[delim];
+}

+ 14 - 148
src/core/operations/ConvertCoordinateFormat.mjs

@@ -5,9 +5,7 @@
  */
  */
 
 
 import Operation from "../Operation";
 import Operation from "../Operation";
-import OperationError from "../errors/OperationError";
-import {FORMATS, STRING_FORMATS, convertCoordinates, convertSingleCoordinate, findDelim, findFormat} from "../lib/ConvertCoordinates";
-import Utils from "../Utils";
+import {FORMATS, convertCoordinates} from "../lib/ConvertCoordinates";
 
 
 /**
 /**
  * Convert co-ordinate format operation
  * Convert co-ordinate format operation
@@ -22,7 +20,7 @@ class ConvertCoordinateFormat extends Operation {
 
 
         this.name = "Convert co-ordinate format";
         this.name = "Convert co-ordinate format";
         this.module = "Hashing";
         this.module = "Hashing";
-        this.description = "Convert geographical coordinates between different formats.<br><br>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><li>Ordnance Survey National Grid (OSNG)</li></ul>";
+        this.description = "Convert geographical coordinates between different formats.<br><br>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><li>Ordnance Survey National Grid (OSNG)</li><li>Universal Transverse Mercator (UTM)</li></ul><br>The operation can try to detect the input co-ordinate format and delimiter automatically, but this may not always work correctly.";
         this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
         this.infoURL = "https://wikipedia.org/wiki/Geographic_coordinate_conversion";
         this.inputType = "string";
         this.inputType = "string";
         this.outputType = "string";
         this.outputType = "string";
@@ -39,6 +37,7 @@ class ConvertCoordinateFormat extends Operation {
                     "Auto",
                     "Auto",
                     "Direction Preceding",
                     "Direction Preceding",
                     "Direction Following",
                     "Direction Following",
+                    "Space",
                     "\\n",
                     "\\n",
                     "Comma",
                     "Comma",
                     "Semi-colon",
                     "Semi-colon",
@@ -55,14 +54,21 @@ class ConvertCoordinateFormat extends Operation {
                 "type": "option",
                 "type": "option",
                 "value": [
                 "value": [
                     "Space",
                     "Space",
-                    "Direction Preceding",
-                    "Direction Following",
                     "\\n",
                     "\\n",
                     "Comma",
                     "Comma",
                     "Semi-colon",
                     "Semi-colon",
                     "Colon"
                     "Colon"
                 ]
                 ]
             },
             },
+            {
+                "name": "Include Compass Directions",
+                "type": "option",
+                "value": [
+                    "None",
+                    "Before",
+                    "After"
+                ]
+            },
             {
             {
                 "name": "Precision",
                 "name": "Precision",
                 "type": "number",
                 "type": "number",
@@ -77,148 +83,8 @@ class ConvertCoordinateFormat extends Operation {
      * @returns {string}
      * @returns {string}
      */
      */
     run(input, args) {
     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);
-            if (inDelim === null) {
-                inDelim = "";
-            }
-        } 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);
-            if (inFormat === null) {
-                throw new OperationError("Could not automatically detect the input format.");
-            }
-        }
-
-        if (inDelim === "" && (!STRING_FORMATS.includes(inFormat))) {
-            throw new OperationError("Could not automatically detect the input delimiter.");
-        }
-
-        // Prepare input data
-        if (STRING_FORMATS.includes(inFormat)) {
-            // Geohash only has one value, so just use the input
-            // Replace anything that isn't a valid character in Geohash / MGRS / OSNG
-            inLat = input.replace(/[^A-Za-z0-9]/, "");
-        } 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);
-            if (splitInput.length > 0) {
-                inLat = splitInput[0];
-                if (splitInput.length >= 2) {
-                    inLong = splitInput[1];
-                }
-            }
-        }
-
-        if (!STRING_FORMATS.includes(inFormat) && 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 (!STRING_FORMATS.includes(inFormat)) {
-                    if (STRING_FORMATS.includes(outFormat)){
-                        throw new OperationError(`${outFormat} needs both a latitude and a longitude to be calculated`);
-                    }
-                }
-                if (STRING_FORMATS.includes(inFormat)) {
-                    // Geohash conversion is in convertCoordinates despite needing
-                    // only one input as it needs to output two values
-                    inLat = inLat.replace(/[^A-Za-z0-9]/g, "");
-                    [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" && !STRING_FORMATS.includes(outFormat)) {
-                output += latDir += " ";
-            }
-            output += outLat;
-            if (outDelim === "Direction Following" && !STRING_FORMATS.includes(outFormat)) {
-                output += " " + latDir;
-            }
-            output += outSeparator;
-
-            if (outLong !== undefined && !STRING_FORMATS.includes(outFormat)) {
-                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.");
-        }
+        const [inFormat, inDelim, outFormat, outDelim, incDirection, precision] = args;
+        return convertCoordinates(input, inFormat, inDelim, outFormat, outDelim, incDirection, precision);
     }
     }
 }
 }