Missing results due to geometric proximity formula (storage locator)

OK. I struggled with this for about 3 months and since I had exhausted the whole proximity geometry formula that I came across, and I’m no closer to getting the correct results that I understood it was time to ask for help.

TARGET

I am creating a fairly basic implementation of a storage locator. The user enters his zip code and selects from a predefined list of search radii. The gmaps API generates lat / long coordinates for this address and passes them to a php script. In this script, user coordinates are requested in the mysql database table (structure below)

post_id int(11) post_type varchar(20) lat float(10,6) lng float(10,6) 

The results of this query (post ids) are entered into a Wordpress query that generates XML that contains map marker data. (the wordpress request uses post__in and posts_per_page -1 to display information for the entire identifier generated by the request

PROBLEM

In short, every implementation of the Haversin formula that I came across seems to lead to the absence of markers - in particular, any markers that are very close to the entered coordinates of users (I don’t know for sure, but I think it’s about 500 m) . This is a big problem, because the user enters his zip code, and there is a store very close to his location, he will not be displayed.

I tried about 8 different forumla permutations, which I dug from different textbooks with the same results. Below is the formula that I am currently using on a site that provides all the markers, with the exception of those that are in the immediate vicinity of the position entered by the user:

 $center_lat = $_GET["lat"]; $center_lng = $_GET["lng"]; $radius = $_GET["radius"]; // Calculate square radius search $lat1 = (float) $center_lat - ( (int) $radius / 69 ); $lat2 = (float) $center_lat + ( (int) $radius / 69 ); $lng1 = (float) $center_lng - (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 ); $lng2 = (float) $center_lng + (int) $radius / abs( cos( deg2rad( (float) $center_lat ) ) * 69 ); $sqlsquareradius = " SELECT post_id, lat, lng FROM wp_geodatastore WHERE lat BETWEEN ".$lat1." AND ".$lat2." AND lng BETWEEN ".$lng1." AND ".$lng2." "; // End $sqlsquareradius // Create sql for circle radius check $sqlcircleradius = " SELECT t.post_id, 3956 * 2 * ASIN( SQRT( POWER( SIN( ( ".(float) $center_lat." - abs(t.lat) ) * pi() / 180 / 2 ), 2 ) + COS( ".(float) $center_lat." * pi() / 180 ) * COS( abs(t.lat) * pi() / 180 ) * POWER( SIN( ( ".(float) $center_lng." - t.lng ) * pi() / 180 / 2 ), 2 ) ) ) AS distance FROM (".$sqlsquareradius.") AS t HAVING distance <= ".(int) $radius." ORDER BY distance "; // End $sqlcircleradius $result = mysql_query($sqlcircleradius); $row = mysql_fetch_array( $result ); while($row = mysql_fetch_array( $result )) { // the contents of each row $post_ids[] = $row['post_id']; } 

There was 1 formula I tried that Mike Pelly suggested here: SQL geolocation query could not find exact location

This formula seemed to show markers that were very close to the user entered, but missed others that should have been displayed within a given radius. To eliminate any confusion, this is the code I used:

 $center_lat = $_GET["lat"]; $center_lng = $_GET["lng"]; $radius = $_GET["radius"]; $sql = " SELECT post_id, lat, lng, truncate((degrees(acos( sin(radians(lat)) * sin(radians(".$center_lat.")) + cos(radians(lat)) * cos(radians(".$center_lat.")) * cos(radians(".$center_lng." - lng) ) ) ) * 69.09*1.6),1) as distance FROM wp_geodatastore HAVING distance <= ".$radius." ORDER BY distance desc "; // End $sqlcircleradius $result = mysql_query($sql); $row = mysql_fetch_array( $result ); while($row = mysql_fetch_array( $result )) { // Print out the contents of each row $post_ids[] = $row['post_id']; } 

REQUEST

Basically, I would like to know why none of these blocks of code display the correct markers. If anyone can suggest any improvements to the code or can point me to some resource that I might have missed, it would be great

EDIT

Thought my psudeo answer worked, but as it turned out, problems still arise. In the end, I switched to a completely different topic, and I use a very good jquery repository locator, which can be found here: http://www.bjornblog.com/web/jquery-store-locator-plugin

It will not work for every project, but for my needs it is perfect (and it works!)

+7
source share
5 answers

Thinking a little in the transverse direction, I came up with a “peculiar” solution to the problem of missing markers. The two initially published equations yielded the correct results, but each missed either markers close to the target or along the edges of the search radius

This is not very elegant, but I decided that running both equations and creating 2 arrays, which I then combined (by removing any duplicates), would give me all the markers that I am looking for. It works (obviously, performance, but it's not a high-traffic application), so I will work with it for now, but I still get a more practical solution if anyone has one!

0
source

EDIT This search engine appears quite often, and I wrote an article on it.

http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

Original publication

Let's start by looking at the Haversin formula once for everyone, putting it in a stored function so that we can forget about its gross details. NOTE. All this decision is in mile laws.

 DELIMITER $$ CREATE FUNCTION distance(lat1 FLOAT, long1 FLOAT, lat2 FLOAT, long2 FLOAT) RETURNS FLOAT DETERMINISTIC NO SQL BEGIN RETURN (3959 * ACOS(COS(RADIANS(lat1)) * COS(RADIANS(lat2)) * COS(RADIANS(long1) - RADIANS(long2)) + SIN(RADIANS(lat1)) * SIN(RADIANS(lat2)) )); END$$ DELIMITER ; 

Now match the query that searches in the bounding box and then refine the search using our distance function and distance orders

Based on PHP code in your question:

Suppose $radius is your radius, $center_lat , $center_lng is your breakpoint.

 $sqlsquareradius = " SELECT post_id, lat, lng FROM ( SELECT post_id, lat, lng, distance(lat, lng, " . $center_lat . "," . $center_lng . ") AS distance FROM wp_geodatastore WHERE lat >= " . $center_lat . " -(" . $radius . "/69) AND lat <= " . $center_lat . " +(" . $radius . "/69) AND lng >= " . $center_lng . " -(" . $radius . "/69) AND lng <= " . $center_lng . " +(" . $radius . "/69) )a WHERE distance <= " . $radius . " ORDER BY distance "; 

Pay attention to a few things about this.

The first is the calculation of the bounding box in SQL, not in PHP. There is no good reason for this, with the exception of saving all the calculations in one environment. (radius / 69) - the number of degrees in the radius metric tables.

Secondly, it does not bother with the size of the longitudinal bounding box based on latitude. Instead, it uses a simpler but too large bounding box. This bounding box catches a few extra entries, but the remote measurement gets rid of them. For your typical zip / store search application, the performance difference is not significant. If you searched for many more records (for example, a database of all utility poles), this may not be so trivial.

Thirdly, it uses a nested query to eliminate the distance in order to avoid the need to run the distance function more than once for each element.

Fourth, he orders by distance ASCENDING. This means that zero distance results must be displayed first in the result set. It usually makes sense to list the closest things first.

Fifth, it uses FLOAT , not DOUBLE . There is a good reason for this. The Haversin distance formula is not ideal, because it makes approximation by the earth an ideal sphere. This approximation occurs with approximately the same level of accuracy than epsilon for FLOAT numbers. So DOUBLE is a deceptive numerical excess for this problem. (Do not use this haversin formula for construction work, such as parking drainage, or you will get large puddles for a couple of inches of epsilon, I promise.) This is great for store apps.

Sixth, you will definitely want to create an index for the lat column. If your location table does not change very often, this will help to create an index for your lng column. But your lat index will give you most of your query performance increase.

Finally, I checked the stored procedure and SQL, but not PHP.

Link: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL Also, my experience with a bunch of sensors for medical institutions.

--------------- EDIT --------------------

Unless you have a user interface that allows you to define a stored procedure, this is a nuisance. Anyway, PHP allows you to use numbered parameters in a sprintf call, so you can generate an entire nested statement like this. NOTE. You might need% $ 1f, etc. You will need to experiment with this.

 $sql_stmt = sprintf (" SELECT post_id, lat, lng FROM ( SELECT post_id, lat, lng, (3959 * ACOS(COS(RADIANS(lat)) * COS(RADIANS(%$1s)) * COS(RADIANS(lng) - RADIANS(%$2s)) + SIN(RADIANS(lat)) * SIN(RADIANS(%$1s)) )) AS distance FROM wp_geodatastore WHERE lat >= %$1s -(%$3s/69) AND lat <= %$1s +(%$3s/69) AND lng >= %$2s -(%$3s/69) AND lng <= %$2s +(%$3s/69) )a WHERE distance <= %$3s ORDER BY distance ",$center_lat,$center_lng, $radius); 
+2
source

Here is a solution that I used for some time in my geometric calculations:

 /** * This portion of the routine calculates the minimum and maximum lat and * long within a given range. This portion of the code was written * by Jeff Bearer (http:return true;//www.jeffbearer.com). */ $lat = somevalue; // The latitude of our search origin $lon = someothervalue; // The longitude of our search origin $range = 50; // The range of our search, in miles, of your zip // Find Max - Min Lat / Long for Radius and zero point and query only zips in that range. $lat_range = $range / 69.172; $lon_range = abs($range / (cos($lon) * 69.172)); $min_lat = number_format($lat - $lat_range, '4', '.', ''); $max_lat = number_format($lat + $lat_range, '4', '.', ''); $min_lon = number_format($lon - $lon_range, '4', '.', ''); $max_lon = number_format($lon + $lon_range, '4', '.', ''); /* Query for matching zips: SELECT post_id, lat, lng FROM wp_geodatastore WHERE lat BETWEEN $min_lat AND $max_lat AND lng BETWEEN $min_lon AND $max_lon */ 
0
source

You can try my class at http://www.phpclasses.org/package/6202-PHP-Generate-points-of-an-Hilbert-curve.html . He uses the Harvesin formula and the Hilbert curve to calculate the quad key. Then you can search for the quad keyboard from left to right. Each key position is a point on the monster curve. The best explanation of the curve can be found on the spatial index nickname ATV blog. This is similar to using the spatial index extension from mysql, but you have more control. You can use the z curve or the sea curve or you can change the look.

0
source

This is the code of a working working system,

 6371.04 * acos(cos(pi()/2-radians(90-wgs84_lat)) * cos(pi()/2-radians(90-$lat)) * cos(radians(wgs84_long)-radians($lon)) + sin(pi()/2-radians(90-wgs84_lat)) * sin(pi()/2-radians(90-$lat))) as distance 

Uses a different distance, but for the store locator the difference is minimal.

0
source

All Articles