What's wrong with the current longitude and latitude bounding box algorithm used for geolocation searches?

I am currently working on a piece of code that calculates a bounding box for a specific location to search for user profiles within a given radius. The code is mostly functional, but I am encountering a slight distortion in the final values. When I input 50 miles into the function, the resulting points are around 70 miles from the origin instead of the expected 50. I suspect that there might be a slight miscalculation in one of the constant values.

Here are the constants used:

const RAD_CON = Math.PI / 180; // Radian conversion constant
const R_EARTH = 6378; // Earth's radius in kilometers
const KM_PER_MILE = 1.609344; // Kilometers per mile conversion constant

// Potential error here
const METER_DEGREES = (1 / ((2 * Math.PI / 360) * R_EARTH)) / 1000; // Meters per degree for latitude and longitude

The problematic code section:

_getBoundingCoords(location: Location, miles: number) {
    const meters = miles * KM_PER_MILE * 1000;

    const latNorth = location.lat + (meters * METER_DEGREES);
    const latSouth = location.lat - (meters * METER_DEGREES);
    const lngWest = location.lng + (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);
    const lngEast = location.lng - (meters * METER_DEGREES) / Math.cos(location.lat * RAD_CON);

    return [latNorth, latSouth, lngWest, lngEast];
}

For context, I am using the output values to construct a query for a mongodb collection. The data in the collection is structured as follows:

{
  lat: number;
  lng: number;
}

The constructed query:

const query = {
  $and: [{
    'location.lat': {
      $lte: latNorth,
      $gte: latSouth,
    },
  }, {
    'location.lng': {
      $lte: lngWest,
      $gte: lngEast,
    },
  }],
};

Answer №1

Jon Trent's suggestion to utilize a geodetic library is ideal, but here's a quick hack that could enhance your existing code.

You can take advantage of the fact that at a specific latitude, a degree of longitude is equivalent to the distance covered by cos(latitude) times a degree of latitude. This means that as you move from the equator to the poles, a degree of longitude represents a smaller distance.

For small bounding boxes (a few miles, for instance), you can achieve decent accuracy by using the cosine of the latitude at the midpoint as a scale factor.

Answer №2

Citing...

Specifically referring to the Javascript code under section Destination point given distance and bearing from start point, here is the formula for determining the destination point based on latitude, longitude, bearing, distance, and earth's radius. The angles are in radians, and distances can be in various units (e.g., miles, km, meters).

Moreover, I have created two helper functions for converting Degrees, Minutes, and Seconds to radians, and vice versa. They may not have the most user-friendly interface, but they serve their purpose.

//
// Convert degrees, minutes, and seconds to radians.
//
function dmsToRad( deg, min, sec ) {
  return Math.sign( deg ) * ( Math.abs( deg ) + min / 60 + sec / 3600 ) * Math.PI / 180;
}

//
// Convert radians to degrees, minutes, and seconds.  Rounds to 1/3600th.
//
function radToDms( rad ) {
  let d = Math.round( Math.abs( rad ) * 180 / Math.PI * 3600 ) / 3600;
  let deg = Math.trunc( d );
  let remainder = ( d - deg ) * 60;
  let min = Math.trunc( remainder );
  remainder = ( remainder - min ) * 60;
  let sec = Math.round( remainder );
  
  deg = deg * Math.sign( rad );
  return {
    degrees: deg, minutes: min, seconds: sec,
    pretty: `${(Math.sign(rad)===-1?'-':'')+('00'+Math.abs(deg)).slice(-3)}°${('0'+min).slice(-2)}′${('0'+sec).slice(-2)}″`
  };
}

// Calculate the destination point given the latitude, longitude, bearing,
// distance, and radius of earth.  Note that angles are in radians, and
// the distances are in whatever units desired (ie, miles, km, meters, etc).
//
// Sourced directly from https://www.movable-type.co.uk/scripts/latlong.html
//
function destinationPoint( φ1, λ1, brng, d, R ) {
  const φ2 = Math.asin( Math.sin( φ1 ) * Math.cos( d / R ) +
             Math.cos( φ1 ) * Math.sin( d / R ) * Math.cos( brng ) );
  const λ2 = λ1 + Math.atan2( Math.sin( brng )*Math.sin( d / R ) * Math.cos( φ1 ),
             Math.cos( d / R ) - Math.sin( φ1 ) * Math.sin( φ2 ) );
  return { lat: φ2, lon: λ2 };
}

//
// Test using the sample data from the reference web site.
//
let dest = destinationPoint( dmsToRad( 53, 19, 14 ), dmsToRad( -1, 43, 47 ), dmsToRad( 96, 1, 18 ), 124.8, 6371 );
console.log( dest )
console.log( [ radToDms( dest.lat ), radToDms( dest.lon ) ] ); // 053°11′18″ 000°08′00″

It's important to note that the bearing is clockwise, with North at 0°. These functions not only help calculate the destination in cardinal directions (N, E, S, W) but also allow for adding intermediate points for a more circular boundary range display.

Answer №3

When it comes to calculating latitude and longitude coordinates, there is a plethora of math involved, along with in-depth explanations available at...

  • .

Thankfully, you don't need to convert this to Javascript, as there is a valuable resource that includes these "geodesy functions" at...

To assist you in utilizing these functions, here is a brief example using some of the functions to...

  • find the distance between two coordinates, and
  • determine a coordinate based on a starting coordinate, bearing, and distance (in meters). (The values in this example come from the initial reference mentioned in the section labeled "Destination point given distance and bearing from start point.")

<script type="module">

  import LatLon, { Dms } from 'https://cdn.jsdelivr.net/npm/geodesy@latest/latlon-spherical.min.js';
  
  Dms.separator = '';
  // Calculate distance between two points.
  let d = new LatLon( 52.205, 0.119 ).distanceTo( new LatLon( 48.857, 2.351 ) );
  console.log( d );
  
  // Calculate destination point given starting point, distance and bearing.
  let p1 = new LatLon( Dms.parse( "053°19′14″" ), Dms.parse( "-001°43′47″" ) );
  let brng = Dms.parse( "096°01′18″" );
  let dist = 124.8 * 1000;  // Distance in meters.
  let p2 = p1.destinationPoint( dist, brng ); // 53°11′18″N, 000°08′00″E
  console.log( p2 );
  console.log( [ Dms.toDms( p2.lat, 'dms' ), Dms.toDms( p2.lon, 'dms' ) ] );
  
</script>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Steps to enable checkbox functionality in Chakra UI

I need help implementing a checkbox functionality in my project. I am using Chakra UI and trying to achieve a scenario where clicking on the parent checkbox checks all the children checkboxes, but also allows for individual selection. The challenge I am ...

Executing two Ajax calls in ReactJS with different parameters can improve the efficiency of your

Why does the second Ajax call overwrite the first one, causing the results to be different each time I refresh it? In the first Ajax call, I have set tests: [], testsHistories: [] in the setState function. However, the second Ajax call only sets the stat ...

What is the best way to decouple the data layer from Next.js API routes?

Currently, I am working on a project using Next.js with MongoDB. My setup involves using the MongoDB client directly in TypeScript. However, I have started thinking about the possibility of switching to a different database in the future and how that would ...

Having trouble with my JSFiddle code not functioning properly in a standard web browser for a Google Authenticator

Here is the code snippet that I am having trouble with: http://jsfiddle.net/sk1hg4h3/1/. It works fine in jsfiddle, but not in a normal browser. I have included the necessary elements in the head to call all the external files. <head> <scri ...

How about using a JQuery variable?

I'm a beginner in jQuery and I have this code snippet that displays a div when a specific link on a map is hovered over: <div id="locationmap"> <a class="linkhide" id="link1" href="#">Occupier 1</a> <a class="linkhide" id ...

How come the information isn't being transferred between components?

Is my method correct if I want to pass the this.prompt() function from TitleBar to Portfolio? This is what my index.html file looks like: var TitleBar = React.createClass({ render: function() { return( <div className="jumbotron"> ...

The .forEach() method in Javascript is not suitable for DOM nodes as they are subject to change during the iteration

Having an issue with moving all DOM elements from one node to another, I initially used this code: div.childNodes.forEach((n) => me.container.appendChild(n)); However, I noticed that only half of the nodes were being copied. It seems that the problem ...

Implementing event handling with .On() in Jquery following .Off()

I need assistance with my responsive navigation bar. I am having trouble with the jQuery code to disable hover events if the width is less than or equal to 768px and enable it for desktop screens. $(window).on('load resize', function (e) { v ...

Utilize generic types as object properties in TypeScript

Is there a way to achieve something similar in TypeScript like the following: export type CoordinateSelector = <T>(d: Coordinate) => d[T]; export interface LinkVerticalLineProps { x: CoordinateSelector<'x'>; y: CoordinateSele ...

How do I convert the object value/data to lowercase in JavaScript using underscore for an HTML5 document?

I am working with an array of objects (arr) where each object consists of 3 properties (Department, Categories, ProductTypes). The values for these properties are a mix of upper and lower case. To perform a comparison between arr and a search filter (alrea ...

Regex tips: Matching multiple words in regex

I am struggling with creating a simple regex. My challenge is to write a regex that ensures a string contains all 3 specific words, instead of just any one of them: /advancebrain|com_ixxocart|p\=completed/ I need the regex to match only if all thre ...

Determine the `overall` amount and adjust `overall` to equal `quantity * price` within a Typescript

I am looking to perform a calculation similar to this: total = quantity * price and continuously update the total when either the quantity or the price changes. template-output-snapshot app.component.html <form [formGroup]="editform" (ngSubm ...

Troubleshoot: jQuery Datalink functionality not functioning properly with object methods

Within my JavaScript code, I have defined an object that includes various properties and an instance method that calculates a value based on two of those properties. To synchronize the object's property values with form elements in the UI, I am utili ...

Populate an array with the present date and proceed in reverse order

In my code, there is an array that has a specific structure: var graphData = [{ data: [[1, 1300],[2, 1600], [3, 1900], [4, 2100], [5, 2500], [6, 2200], [7, 1800]} I want to replace the numbers in the first element of each sub-array with the corresponding ...

Creating a multi-tiered cascading menu in a web form: Harnessing the power of

In my form, there is a field called 'Protein Change' that includes a multi-level dropdown. Currently, when a user selects an option from the dropdown (for example, CNV->Deletion), the selection should be shown in the field. However, this funct ...

What's the point of using defer() in Node.js Q promises when you have the option to simply use this

I had a plan in mind: somePromiseFunc(value1) .then(function(value2, callback) { // insert the next then() into this function: funcWithCallback(callback); }) .then(function(dronesYouAreLookingFor){ // Let's celebrate }) .done(); Unfortun ...

Could use some help with configuring express routes with parameters

Greetings! I am new to working with Express and I am currently in the process of creating a portfolio website. In my project, I have a Pug file named project.pug which includes HTML content that should dynamically render based on the ID of each project sto ...

Combining the date from one source and the time from another source to create a unified date in Typescript

In my TypeScript code, I'm working with four fields of type Date: StartDate StartTime EndDate EndTime My goal is to combine StartDate and StartTime into a single field called Start, as well as EndDate and EndTime into a field called End. Desp ...

Ensure that my program is halted until the xmlhttprequest has completed

It seems like I've overcomplicated this problem for myself. In my page, I have 2 XMLHTTPRequests - the first request retrieves data from an API and fills my elements with this data. However, one of the fields in my elements requires a second request t ...

Passing a click event to a reusable component in Angular 2 for enhanced functionality

I am currently working on abstracting out a table that is used by several components. While most of my dynamic table population needs have been met, I am facing a challenge with making the rows clickable in one instance of the table. Previously, I simply ...