UNPKG

spatials

Version:

A bunch of utilities for geocoding and spatial calculation

846 lines (667 loc) 28.3 kB
(function (root, factory) { //UMD - Universal Module Definition if (typeof define === 'function' && define.amd) { //AMD: register as an anonymous module without dependencies define(['geolib'], factory); } else if (typeof exports === 'object') { //Node: does not work with strict CommonJS, but only CommonJS-like //environments that support module.exports, like Node. module.exports = factory(require('geolib')); } else { //Browser: globals (root is window) root.spatials = factory(root.geolib); } }(this, function (geolib) { //Check if "geolib" is available if (!geolib) throw new Error("spatials requires 'geolib' library"); //*** UTILITIES //Check is provided object is an array function isArray(obj) { return '[object Array]' === {}.toString.call(obj); } //Check if object is null or undefined function isNullOrUndefined(obj) { return typeof obj === 'undefined' || obj == null; } //Check is object is number greater of equals zero function isNumberGreaterOrEqualsToZero(obj) { //If undefined or null, false if (isNullOrUndefined(obj)) { return true; } //If is not a number if (isNaN(obj)) { return false; } //True if match condition return obj >= 0; } //Check if provided element is a coordinate function isCoordinate(obj) { //If is null or undefined, exit if (isNullOrUndefined(obj)) { return false; } //If it does not have 'latitude' is invalid if (isNullOrUndefined(obj.latitude)) { return false; } //If it does not have 'longitude' is invalid if (isNullOrUndefined(obj.longitude)) { return false; } //If latitude is not a number if (isNaN(obj.latitude)) { return false; } //If longitude is not a number if (isNaN(obj.longitude)) { return false; } //If latitude is outside boundaries if (obj.latitude < -90 || obj.latitude > 90) { return false; } //If longitude is outside boundaries if (obj.longitude < -90 || obj.longitude > 90) { return false; } //Confirm return true; } //Check if provided object is a valid array of coordinates function isCoordinatesArray(obj) { //If is null or undefined, exit if (isNullOrUndefined(obj)) { return false; } //If non array, exit if (!isArray(obj)) { return false; } //Iterate all over elements for (var i = 0; i < obj.length; i++) { //if current element is not coordinate, fail if (!isCoordinate(obj[i])) { return false; } } //Confirm return true; } //Check if provided object is a valid polygon function isPolygon(obj) { //If it's not an array of coordinates, fail if (!isCoordinatesArray(obj)) { return false; } //To be a valid polygon requires at least 3 vertex return obj.length >= 3; } //*** CONVERTS //Converts provided latitude in decimal notation to sexagesimal notation function latitudeToSexagesimal(latDecimalValue) { //Arguments validation if (!latDecimalValue) throw new Error("Argument 'latDecimalValue' is invalid"); //Check is it's a number if (isNaN(latDecimalValue)) throw new Error("Provided latitude is not a valid number"); //If number is greater that 0, cardinal point is "N", otherwise "S" var cardinal = latDecimalValue > 0 ? "N" : "S"; //Use geolib to convert var converted = geolib.decimal2sexagesimal(latDecimalValue); //Return converted value and cardinal return converted + " " + cardinal; } //Converts provided latitude in decimal notation to sexagesimal notation function longitudeToSexagesimal(lonDecimalValue) { //Arguments validation if (!lonDecimalValue) throw new Error("Argument 'lonDecimalValue' is invalid"); //Check is it's a number if (isNaN(lonDecimalValue)) throw new Error("Provided longitude is not a valid number"); //If number is greater that 0, cardinal point is "E", otherwise "W" var cardinal = lonDecimalValue > 0 ? "E" : "W"; //Use geolib to convert var converted = geolib.decimal2sexagesimal(lonDecimalValue); //Return converted value and cardinal return converted + " " + cardinal; } //Converts provided latitude in sexagesimal notation to decimal notation function latitudeToDecimal(latSexagesimalValue) { //Arguments validation if (!latSexagesimalValue) throw new Error("Argument 'latSexagesimalValue' is invalid"); //Check if string was passed as argument if (typeof latSexagesimalValue !== 'string') throw new Error("Provided latitude is not a valid string"); //Use geolib to convert return geolib.sexagesimal2decimal(latSexagesimalValue); } //Converts provided longitude in sexagesimal notation to decimal notation function longitudeToDecimal(lonSexagesimalValue) { //Arguments validation if (!lonSexagesimalValue) throw new Error("Argument 'lonSexagesimalValue' is invalid"); //Check if string was passed as argument if (typeof lonSexagesimalValue !== 'string') throw new Error("Provided longitude is not a valid string"); //Use geolib to convert return geolib.sexagesimal2decimal(lonSexagesimalValue); } //Converts meters value to target unit function metersToSomething(meters, decimal, targetUnit) { //Arguments validation if (typeof meters === 'undefined' || meters === null) throw new Error("Argument 'meters' is invalid"); if (!targetUnit) throw new Error("Argument 'targetUnit' is invalid"); //Check if is a number if (isNaN(meters)) throw new Error("Provided meters value should be number"); //Check if value is greater equals o if (meters < 0) throw new Error("Provided meters value cannot be lower then 0"); //If equals 0, just return 0 if (meters === 0) return 0; //Use geolib to do convert return geolib.convertUnit(targetUnit, meters, decimal); } //Converts provided value in meters, to kilometers function metersToKilometers(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'km'); } //Converts provided value in meters, to miles function metersToMiles(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'mi'); } //Converts provided value in meters, to seamiles function metersToSeamiles(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'sm'); } //Converts provided value in meters, to feet function metersToFeet(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'ft'); } //Converts provided value in meters, to inches function metersToInches(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'in'); } //Converts provided value in meters, to yards function metersToYards(meters, decimals) { //Use basic function return metersToSomething(meters, decimals, 'yd'); } /** * Converts miles to meters * @param miles Miles * @param decimals Decimals * @returns {number} Returns meters */ function milesToMeters(miles, decimals){ //Use basic function return somethingToMeters(miles, 'mi', decimals); } /** * Converts seamiles to meters * @param seamiles Seamiles * @param decimals Decimals * @returns {*} Returns meters */ function seamilesToMeters(seamiles, decimals){ //Use basic function return somethingToMeters(seamiles, 'sm', decimals); } /** * Converts feet to meters * @param feet Feet * @param decimals Decimals * @returns {*} Returns meters */ function feetToMeters(feet, decimals){ //Use basic function return somethingToMeters(feet, 'ft', decimals); } /** * Converts inches to meters * @param inches Inches * @param decimals Decimals * @returns {*} Returns meters */ function inchesToMeters(inches, decimals){ //Use basic function return somethingToMeters(inches, 'in', decimals); } /** * Converts yards to meters * @param yards Yards * @param decimals Decimals * @returns {*} Returns meters */ function yardsToMeters(yards, decimals){ //Use basic function return somethingToMeters(yards, 'yd', decimals); } /** * Converts the source value (with provided unit) to meters * @param sourceValue Source value * @param sourceUnit Source unit * @param decimals Decimal places * @returns {number} Returns converted value */ function somethingToMeters(sourceValue, sourceUnit, decimals){ //Arguments validation if (typeof sourceValue === 'undefined' || sourceValue === null) throw new Error("Argument 'sourceValue' is invalid"); //Check if is a number if (isNaN(sourceValue)) throw new Error("Provided source value should be number"); //Set default for decimals decimals = (null == decimals ? 4 : decimals); //Get multiplier for source value from geolib var multiplier = geolib.measures[sourceUnit]; //Use multiplier to get meters var meters = sourceValue / multiplier; //Round value meters = geolib.round(meters, decimals); //Returns value return meters; } /** * Converts speed in meters/second to km/h * @param metersSecond Speed in ms/s * @param decimals Number of decimals * @returns {*} Returns km/h value */ function metersSecondToKilometersHour(metersSecond, decimals){ //Use basic function return convertSpeed(metersSecond, 'msToKmh', decimals); } /** * Converts speed in meters/second to knots * @param metersSecond Speed in ms/s * @param decimals Number of decimals * @returns {*} Returns knots value */ function metersSecondToKnots(metersSecond, decimals){ //Use basic function return convertSpeed(metersSecond, 'msToKn', decimals); } /** * Converts speed in km/h to m/s * @param metersSecond Speed in km/h * @param decimals Number of decimals * @returns {*} Returns m/s value */ function kilometersHourToMetersSecond(kilometersHour, decimals){ //Use basic function return convertSpeed(kilometersHour, 'kmhToMs', decimals); } /** * Converts speed in knts to m/s * @param knots Speed in knots * @param decimals Number of decimals * @returns {*} Returns m/s value */ function knotsToMetersSecond(knots, decimals){ //Use basic function return convertSpeed(knots, 'knToMs', decimals); } /** * Converts speed in km/h to knots * @param kilometersHour Speed in km/h * @param decimals Number of decimals * @returns {*} Returns knots value */ function kilometersHourToKnots(kilometersHour, decimals){ //Use basic function return convertSpeed(kilometersHour, 'kmhToKn', decimals); } /** * Converts speed in knots to km/h * @param kilometersHour Speed in knots * @param decimals Number of decimals * @returns {*} Returns km/h value */ function knotsToKilometersHour(kilometersHour, decimals){ //Use basic function return convertSpeed(kilometersHour, 'knToKmh', decimals); } /** * Converts speed measure to target * @param sourceValue Source value * @param multiplierName Name of multiplier * @param decimals Number of decimals * @returns {number} Returns conversion */ function convertSpeed(sourceValue, multiplierName, decimals){ //Arguments validation if (typeof sourceValue === 'undefined' || sourceValue === null) throw new Error("Argument 'sourceValue' is invalid"); //Check if is a number if (isNaN(sourceValue)) throw new Error("Provided source value should be number"); //Set default for decimals decimals = (null == decimals ? 4 : decimals); //Multipliers var speedMultipliers = { kmhToMs: 1/3.6, msToKmh: 3.6, knToMs: 1/1.943844492441, msToKn: 1.943844492441, kmhToKn: null, knToKmh: null }; //Create multipliers for kmh and kn speedMultipliers.kmhToKn = speedMultipliers.kmhToMs * speedMultipliers.msToKn; speedMultipliers.knToKmh = speedMultipliers.knToMs * speedMultipliers.msToKmh; //Get multiplier for source value from geolib var multiplier = speedMultipliers[multiplierName]; //If multipler cannot be found, error if (!multiplier) throw new Error("Target speed unit '" + targetUnit + "' is not supported"); //Use multiplier to get target unit var targetValue = sourceValue * multiplier; //Round value targetValue = geolib.round(targetValue, decimals); //Returns value return targetValue; } /* function(value, n) { var decPlace = Math.pow(10, n); return Math.round(value * decPlace)/decPlace; } */ //*** DISTANCE /** * Get distance between provided points * @param pointA Origin coordinates * @param pointB Destination coordinates * @param accuracy Accuracy (in meters) of calculation * @returns Returns distance in meters */ function getSegmentDistance(pointA, pointB, accuracy) { //Arguments validation if (!pointA) throw new Error("Argument 'pointA' is invalid"); if (!pointB) throw new Error("Argument 'pointB' is invalid"); //Check if elements are valid coordinates if (!isCoordinate(pointA)) throw new Error("Argument 'pointA' is not a valid coordinate"); if (!isCoordinate(pointB)) throw new Error("Argument 'pointB' is not a valid coordinate"); //Use geolib return geolib.getDistance(pointA, pointB, accuracy); } /** * Get distance of route defined by array of coordinates * @param routeCoordinates Array of coordinates * @param accuracy Accuracy (in meters) of calculation * @returns Number distance in meters */ function getRouteDistance(routeCoordinates, accuracy) { //Arguments validation if (!routeCoordinates) throw new Error("Argument 'pointA' is invalid"); //Check if route is a valid coordinate array if (!isCoordinatesArray(routeCoordinates)) throw new Error("Argument 'routeCoordinates' is not a valid coordinates array"); //Define total distance var distance = 0; //Iterate calculation of each segment for (var i = 0; i < routeCoordinates.length; i++) { //Proceed only if there a "next" element if ((i + 1) >= routeCoordinates.length) { break; } //Define origin and destination of segment var origin = routeCoordinates[i]; var destination = routeCoordinates[i + 1]; //Calculate segment distance and increment total distance distance += getSegmentDistance(origin, destination, accuracy); } //Returns distance return distance; } /** * Get compass direction between provided points * @param pointA Origin point * @param pointB Destination point * @param useBearing Boolean for activate bearing mode * @returns {*|Object} Returns direction */ function getCompassDirection(pointA, pointB, useBearing) { //Arguments validation if (!pointA) throw new Error("Argument 'pointA' is invalid"); if (!pointB) throw new Error("Argument 'pointB' is invalid"); //Check if elements are valid coordinates if (!isCoordinate(pointA)) throw new Error("Argument 'pointA' is not a valid coordinate"); if (!isCoordinate(pointB)) throw new Error("Argument 'pointB' is not a valid coordinate"); //Define bearing mode var bearingMode = useBearing === true ? 'circle': null; //Use geolib return geolib.getCompassDirection(pointA, pointB, bearingMode); } //*** POSITION /** * Checks if provided point is inside specified polygon * @param point Reference coordinate * @param polygonArrayBounds Polygon boundaries * @returns {*|boolean} Returns true or false */ function isInsidePolygon(point, polygonArrayBounds) { //Arguments validation if (!point) throw new Error("Argument 'point' is invalid"); if (!polygonArrayBounds) throw new Error("Argument 'polygonArrayBounds' is invalid"); //Check is point is a valid coordinate if (!isCoordinate(point)) throw new Error("Argument 'point' is not a valid coordinate"); //Check if it is a valid polygon if (!isPolygon(polygonArrayBounds)) throw new Error("Argument 'polygonArrayBounds' is not a valid polygon"); //Use geolib to calculate return geolib.isPointInside(point, polygonArrayBounds); } //*** FORECAST /** * Calculate duration (in seconds) of trip between two segments at given speed * @param pointA Point of origin * @param pointB Point of destination * @param speedMetersPerSecond Speed in meters/second * @param decimals Decimals of precision * @returns {number} Returns duration in seconds */ function getSegmentTripDuration(pointA, pointB, speedMetersPerSecond, decimals) { //Arguments validation if (!pointA) throw new Error("Argument 'pointA' is invalid"); if (!pointB) throw new Error("Argument 'pointB' is invalid"); if (!speedMetersPerSecond) throw new Error("Argument 'speedMetersPerSecond' is invalid"); //Check if elements are valid coordinates if (!isCoordinate(pointA)) throw new Error("Argument 'pointA' is not a valid coordinate"); if (!isCoordinate(pointB)) throw new Error("Argument 'pointB' is not a valid coordinate"); //If speed is not a number greater than zero if (!isNumberGreaterOrEqualsToZero(speedMetersPerSecond)) { throw new Error("Provided speed must be a number greater or equals zero"); } //If speed is zero, duration is infinite if (speedMetersPerSecond == 0) { return Infinity; } //If decimals is undefined, set default decimals = decimals || 2; //Check is decimal is a valid number if (!isNumberGreaterOrEqualsToZero(decimals)) { throw new Error("Provided decimals must be a number greater or equals zero"); } //Calculate distance between points (precise accuracy, in meters) var distanceMeters = getSegmentDistance(pointA, pointB, 0); //Simple formula: // => speed = space / time // => space = speed * time // => time = space / speed //Calculate duration of trip in seconds var value = distanceMeters / speedMetersPerSecond; return parseFloat(value.toFixed(decimals)); } /** * Calculate duration (in seconds) crossing all coordinates and given speed * @param routeCoordinates Array of coordinates * @param speedMetersPerSecond Speed in meters/second * @param decimals Number of decimals for precision * @returns {Number} Returns duration of trip in seconds */ function getRouteTripDuration(routeCoordinates, speedMetersPerSecond, decimals){ //Arguments validation if (!routeCoordinates) throw new Error("Argument 'pointA' is invalid"); if (!speedMetersPerSecond) throw new Error("Argument 'speedMetersPerSecond' is invalid"); //Check if route is a valid coordinate array if (!isCoordinatesArray(routeCoordinates)) throw new Error("Argument 'routeCoordinates' is not a valid coordinates array"); //If speed is not a number greater than zero if (!isNumberGreaterOrEqualsToZero(speedMetersPerSecond)) { throw new Error("Provided speed must be a number greater or equals zero"); } //If speed is zero, duration is infinite if (speedMetersPerSecond == 0) { return Infinity; } //If decimals is undefined, set default decimals = decimals || 2; //Check is decimal is a valid number if (!isNumberGreaterOrEqualsToZero(decimals)) { throw new Error("Provided decimals must be a number greater or equals zero"); } //Calculate distance between points (precise accuracy, in meters) var distanceMeters = getRouteDistance(routeCoordinates, 0); //Calculate duration of trip in seconds var value = distanceMeters / speedMetersPerSecond; return parseFloat(value.toFixed(decimals)); } /** * Calculate speed (in meters/second) of trip between two segments and given duration * @param pointA Point of origin * @param pointB Point of destination * @param durationSeconds Duration of trip in seconds * @param decimals Decimals of precision * @returns {number} Returns speed in meters/second */ function getSegmentTripSpeed(pointA, pointB, durationSeconds, decimals) { //Arguments validation if (!pointA) throw new Error("Argument 'pointA' is invalid"); if (!pointB) throw new Error("Argument 'pointB' is invalid"); if (!durationSeconds) throw new Error("Argument 'durationSeconds' is invalid"); //Check if elements are valid coordinates if (!isCoordinate(pointA)) throw new Error("Argument 'pointA' is not a valid coordinate"); if (!isCoordinate(pointB)) throw new Error("Argument 'pointB' is not a valid coordinate"); //If duration is not a number greater than zero if (!isNumberGreaterOrEqualsToZero(durationSeconds)) { throw new Error("Provided duration must be a number greater or equals zero"); } //If duration is zero, speed is infinite if (durationSeconds == 0) { return Infinity; } //If decimals is undefined, set default decimals = decimals || 2; //Check is decimal is a valid number if (!isNumberGreaterOrEqualsToZero(decimals)) { throw new Error("Provided decimals must be a number greater or equals zero"); } //Calculate distance between points (precise accuracy, in meters) var distanceMeters = getSegmentDistance(pointA, pointB, 0); //Simple formula: // => speed = space / time //Calculate speed of trip in meters/second var value = distanceMeters / durationSeconds; return parseFloat(value.toFixed(decimals)); } /** * Calculate speed (in meters/second) crossing all coordinates and given duration * @param routeCoordinates Array of coordinates * @param durationSeconds Duration of trip in seconds * @param decimals Number of decimals for precision * @returns {Number} Returns speed in meters/second */ function getRouteTripSpeed(routeCoordinates, durationSeconds, decimals){ //Arguments validation if (!routeCoordinates) throw new Error("Argument 'pointA' is invalid"); if (!durationSeconds) throw new Error("Argument 'durationSeconds' is invalid"); //Check if route is a valid coordinate array if (!isCoordinatesArray(routeCoordinates)) throw new Error("Argument 'routeCoordinates' is not a valid coordinates array"); //If duration is not a number greater than zero if (!isNumberGreaterOrEqualsToZero(durationSeconds)) { throw new Error("Provided duration must be a number greater or equals zero"); } //If duration is zero, speed is infinite if (durationSeconds == 0) { return Infinity; } //If decimals is undefined, set default decimals = decimals || 2; //Check is decimal is a valid number if (!isNumberGreaterOrEqualsToZero(decimals)) { throw new Error("Provided decimals must be a number greater or equals zero"); } //Calculate distance between points (precise accuracy, in meters) var distanceMeters = getRouteDistance(routeCoordinates, 0); //Calculate speed of trip in meters/second var value = distanceMeters / durationSeconds; return parseFloat(value.toFixed(decimals)); } //Exports functions return { //utilities isCoordinate: isCoordinate, isCoordinatesArray: isCoordinatesArray, isPolygon: isPolygon, //converts latitudeToSexagesimal: latitudeToSexagesimal, longitudeToSexagesimal: longitudeToSexagesimal, latitudeToDecimal: latitudeToDecimal, longitudeToDecimal: longitudeToDecimal, metersToKilometers: metersToKilometers, metersToMiles: metersToMiles, metersToSeamiles: metersToSeamiles, metersToFeet: metersToFeet, metersToInches: metersToInches, metersToYards: metersToYards, milesToMeters: milesToMeters, seamilesToMeters: seamilesToMeters, feetToMeters: feetToMeters, inchesToMeters: inchesToMeters, yardsToMeters: yardsToMeters, //speed metersSecondToKilometersHour: metersSecondToKilometersHour, metersSecondToKnots: metersSecondToKnots, kilometersHourToMetersSecond: kilometersHourToMetersSecond, knotsToMetersSecond: knotsToMetersSecond, kilometersHourToKnots: kilometersHourToKnots, knotsToKilometersHour: knotsToKilometersHour, //distance getSegmentDistance: getSegmentDistance, getRouteDistance: getRouteDistance, getCompassDirection: getCompassDirection, //position isInsidePolygon: isInsidePolygon, //forecast getSegmentTripDuration: getSegmentTripDuration, getRouteTripDuration: getRouteTripDuration, getSegmentTripSpeed: getSegmentTripSpeed, getRouteTripSpeed: getRouteTripSpeed }; }));