Basically rewrote the whole thing using the new geodesy module
parent
5e68959c03
commit
d00b0f4c0e
|
@ -18,148 +18,240 @@ export const FORMATS = [
|
|||
"Decimal Degrees",
|
||||
"Geohash",
|
||||
"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",
|
||||
"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.
|
||||
* @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} inDelim - The delimiter splitting the lat/long of the input
|
||||
* @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
|
||||
* @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") {
|
||||
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") {
|
||||
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") {
|
||||
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 {
|
||||
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") {
|
||||
const utm = new geodesy.LatLonEllipsoidal(parseFloat(convLat), parseFloat(convLong)).toUtm();
|
||||
const utm = latlon.toUtm();
|
||||
const mgrs = utm.toMgrs();
|
||||
convLat = mgrs.toString();
|
||||
conv = mgrs.toString(precision);
|
||||
} else if (outFormat === "Ordnance Survey National Grid") {
|
||||
const latlon = new geodesy.LatLonEllipsoidal(parseFloat(convLat), parseFloat(convLong));
|
||||
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];
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
conv = outConv;
|
||||
}
|
||||
}
|
||||
|
||||
return conv;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.";
|
||||
}
|
||||
converted = convDDToDD(convData[0], precision);
|
||||
break;
|
||||
default:
|
||||
throw "Unknown input format selection.";
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returns {number[]} An array of the different items in the string, stored as floats
|
||||
*/
|
||||
function splitInput (input){
|
||||
const split = [];
|
||||
|
||||
input.split(" ").forEach(item => {
|
||||
input.split(/[°′″'"\s]/).forEach(item => {
|
||||
// Remove any character that isn't a digit
|
||||
item = item.replace(/[^0-9.-]/g, "");
|
||||
if (item.length > 0){
|
||||
split.push(parseFloat(item, 10));
|
||||
split.push(parseFloat(item));
|
||||
}
|
||||
});
|
||||
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} 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 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]/);
|
||||
if (split.length > 0) {
|
||||
if (split.length > 1) {
|
||||
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)) {
|
||||
} 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;
|
||||
}
|
||||
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 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";
|
||||
} else if (geohashPattern.test(filteredInput)) {
|
||||
}
|
||||
if (isOsng) {
|
||||
return "Ordnance Survey National Grid";
|
||||
}
|
||||
if (isGeohash) {
|
||||
return "Geohash";
|
||||
}
|
||||
}
|
||||
|
@ -312,7 +510,7 @@ export function findFormat (input, delim) {
|
|||
*/
|
||||
export function findDelim (input) {
|
||||
input = input.trim();
|
||||
const delims = [",", ";", ":"];
|
||||
const delims = [",", ";", ":", " "];
|
||||
const testDir = input.match(/[NnEeSsWw]/g);
|
||||
if (testDir !== null && testDir.length > 0 && testDir.length < 3) {
|
||||
// Possibly contains a direction
|
||||
|
@ -340,3 +538,19 @@ export function findDelim (input) {
|
|||
}
|
||||
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];
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@
|
|||
*/
|
||||
|
||||
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
|
||||
|
@ -22,7 +20,7 @@ class ConvertCoordinateFormat extends Operation {
|
|||
|
||||
this.name = "Convert co-ordinate format";
|
||||
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.inputType = "string";
|
||||
this.outputType = "string";
|
||||
|
@ -39,6 +37,7 @@ class ConvertCoordinateFormat extends Operation {
|
|||
"Auto",
|
||||
"Direction Preceding",
|
||||
"Direction Following",
|
||||
"Space",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
|
@ -55,14 +54,21 @@ class ConvertCoordinateFormat extends Operation {
|
|||
"type": "option",
|
||||
"value": [
|
||||
"Space",
|
||||
"Direction Preceding",
|
||||
"Direction Following",
|
||||
"\\n",
|
||||
"Comma",
|
||||
"Semi-colon",
|
||||
"Colon"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Include Compass Directions",
|
||||
"type": "option",
|
||||
"value": [
|
||||
"None",
|
||||
"Before",
|
||||
"After"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Precision",
|
||||
"type": "number",
|
||||
|
@ -77,148 +83,8 @@ class ConvertCoordinateFormat extends Operation {
|
|||
* @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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue