3

I have searched the Web a lot to find the solution for my problem but I still can't figure it out.

I have a very simple database with id, city name, latitude, longitude and city_info. When someone enters a city page a would like to show the 10 nearby cities.

How can I calculate this with MySQL and return it with PHP?

I have seen a lot of suggestions on this website, however none of these work somehow.

What I tried without success. I do not get any results.

<?php
$slatitude = 43.2141341;
$slongitude = 64.4368684;
$miles = 200;

//connect

$query = "SELECT *, 
( 3959 * acos( cos( radians('$slatitude') ) * 
cos( radians( latitude ) ) * 
cos( radians( longitude ) - 
radians('$slongitude') ) + 
sin( radians('$slatitude') ) * 
sin( radians( latitude ) ) ) ) 
AS distance FROM cities HAVING distance < '$miles' ORDER BY distance ASC LIMIT 0, 10";

$query = mysql_query($query);
$numrows = mysql_num_rows($query);
if ($numrows > 0){

while ($row = mysql_fetch_assoc($query)){
$id = $row['id'];
$cityname = $row['cityname'];
$latitude = $row['latitude'];
$longitude = $row['longitude'];

echo "$cityname<br />";

}}

?>`
Peter O.
  • 32,158
  • 14
  • 82
  • 96
A Kuijk
  • 51
  • 2
  • 3

5 Answers5

2

You can't use having because your not grouping by anything. What you need to do is repeat what you did in the select in the where.

$query = "SELECT *, 
( 3959 * acos( cos( radians('$slatitude') ) * 
cos( radians( latitude ) ) * 
cos( radians( longitude ) - 
radians('$slongitude') ) + 
sin( radians('$slatitude') ) * 
sin( radians( latitude ) ) ) ) 
AS distance FROM cities WHERE ( 3959 * acos( cos( radians('$slatitude') ) * 
cos( radians( latitude ) ) * 
cos( radians( longitude ) - 
radians('$slongitude') ) + 
sin( radians('$slatitude') ) * 
sin( radians( latitude ) ) ) ) < '$miles' ORDER BY distance ASC LIMIT 0, 10";

or you could do something like this:

$query = "
SELECT * FROM (
  select *, 
  ( 3959 * acos( cos( radians('$slatitude') ) * 
  cos( radians( latitude ) ) * 
  cos( radians( longitude ) - 
  radians('$slongitude') ) + 
  sin( radians('$slatitude') ) * 
  sin( radians( latitude ) ) )) as distance from cities
) WHERE distance < '$miles' ORDER BY distance ASC LIMIT 0, 10";
Ian Overton
  • 1,060
  • 7
  • 17
  • 1
    You need to move the `AS distance` in your second query to inside to subquery. – G-Nugget Nov 21 '12 at 21:39
  • I believe the query would work as originally written with the `HAVING` clause. Aliased expressions can be used in `HAVING` clauses without a `GROUP BY` in MySQL, but it is not standard SQL. – G-Nugget Nov 21 '12 at 21:50
  • Thanks for your quick replies. @Ian Overton, ok I replaced my code with your code. Still I do not get any results... – A Kuijk Nov 21 '12 at 22:03
  • are you able to get results with SELECT * FROM ( select *, ( 3959 * acos( cos( radians('$slatitude') ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians('$slongitude') ) + sin( radians('$slatitude') ) * sin( radians( latitude ) ) )) as distance from cities ) ? – Ian Overton Nov 21 '12 at 22:05
  • I feel like there is an error with your sql. Are you able to echo out what sql is produced and run the sql by hand via your database directly? Please try both my queries without the where, order by and limit. They should be work though. – Ian Overton Nov 21 '12 at 22:28
  • Thanks. Could you point me to a tut how to run a sql by hand? – A Kuijk Nov 22 '12 at 07:19
  • I'd recommend installing PHPMyAdmin. It's pretty user friendly – Ian Overton Nov 22 '12 at 12:39
  • @Ian Overton thanks. I runned a lot of qsls in PHPmyAdmin. I had to place apostrophes before and after latitude and longitude. Now it does show results but all distances are the same: 3631.06525887798. I am not sure how that is possible. I store the coordinates in float (type). Any idea? – A Kuijk Nov 22 '12 at 19:14
  • To be clear..I used your first query. Your second query works as well when I deleted the first "SELECT * FROM" and the WHERE statement. The outcomes stays the same tho, 3631.06525887798 – A Kuijk Nov 22 '12 at 19:20
  • Ok, I have a working solution. Somehow I found the solution in the ' and the `. Not quite sure what the difference is. Thanks anyone for all suggestions and solutions! – A Kuijk Nov 23 '12 at 00:19
  • I'm glad you found the solution. I'm sorry I was way from my computer for a few days, so I wasn't able to help you. The difference between ' and ` is ` is used in the select to talk about a certain column ie `column1`, `column2` where the ' is used for strings. – Ian Overton Nov 26 '12 at 14:25
  • Second example is missing `AS ...` after `FROM (...)` giving `every derived table must have its own alias` error. –  Jun 30 '17 at 12:20
  • **Tip:** In big databases this method is very slow. You might want to limit it by lat/long, like I did: `"... WHERE Latitude > ".($slatitude-1.5)." AND Latitude < ".($slatitude+1.5)." AND Longitude > ".($slongitude-1.5)." AND Longitude < ".($slongitude+1.5)." ..."` With this method, it become 100 times faster (0.03s) than before (3s) –  Jul 01 '17 at 10:22
2

To improve the speed of your query you could first limit the set of results that you'll do calculations for using something like the sub-select below (note that I also used a different method for calculating the distance - it works for me, ymmv). In my tests, using the where clause with a tolerance of 1 was over 100 times faster than the query without it.

...
$tol = 1; // limits the search to lat/long within 1 from the given values
$query_args = array($lat,$long,$lat-$tol,$lat+$tol,$long-$tol,$long+$tol);
$query = "
  SELECT *,latitude, longitude, 
    SQRT( POW( 69.1 * ( latitude - %s) , 2 ) 
        + POW( 69.1 * ( %s - longitude ) 
          * COS( latitude / 57.3 ) , 2 ) ) 
    AS distance FROM zipcodes
      WHERE latitude > %d AND latitude < %d 
      AND longitude > %d AND longitude < %d 
  ORDER BY distance ASC limit 10
";
...
1

Here is a query I use to get the closest zip codes from a given latitude/longitude set. The table is very simple:

id, zip, lat, lng

This query assumes your center point is using "$slatitude: and :$slongitude". This will return all matches that are within the distance variable "$miles" (i.e. 3, 2.4, 1000). In my script the results are put in to an array and asorted for closest X number of results.

$getzip = mysql_query("SELECT zip,((ACOS(SIN(".$slatitude." * PI() / 180) * SIN(lat * PI() / 180) + COS(".$slatitude." * PI() / 180) * COS(lat * PI() / 180) * COS((".$slongitude." - lng) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS distance FROM zips HAVING distance<='$miles' ORDER BY 'distance' ASC", $link2) or die (mysql_error($link2));

Hope it helps!

MelArlo
  • 157
  • 9
0

the far most easy solution would be:

get the coordinates for the city and

select ...
order by abs(`Latt`-reqLatt) * abs(`Long`-reqLong)

it isn't perfect but you'd be able to sort out what you want from the top 50.

Teson
  • 6,644
  • 8
  • 46
  • 69
  • That would probably work in general, but it doesn't use a projection, so some errors would show up with cities near the edge of the radius and sorting of cities with similar distances. The "radius" would also be "square". – G-Nugget Nov 21 '12 at 21:43
  • This also doesn't offer a way to limit by 200 miles. – Ian Overton Nov 21 '12 at 21:50
0

I have used PDO for this as mysql_ functions are being deprecated

<?php
 //Connect to database
$dbh = new PDO("mysql:host=$host;dbname=$database", $username, $password);//Change to suit 
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    // Prepare statement
    $stmt = $dbh->prepare("SELECT  *, ( 3959 * acos( cos( radians(?) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(?) ) + sin( radians(?) ) * sin( radians( lat ) ) ) ) AS distance FROM cities HAVING distance < ? ORDER BY distance LIMIT 0 , 10");
    // Assign parameters
    $stmt->bindParam(1,$slatitude);
    $stmt->bindParam(2,$slongitude);
    $stmt->bindParam(3,$slatitude);
    $stmt->bindParam(4,$miles);
    //Execute query
    $stmt->setFetchMode(PDO::FETCH_ASSOC);
    $stmt->execute();
    if ($stmt->rowCount()>0) { 
    // Iterate through the rows
    while($row = $stmt->fetch()) {
        $id = $row['id'];
        $cityname = $row['cityname'];
        $latitude = $row['latitude'];
        $longitude = $row['longitude'];
        echo "$cityname<br />";]);
        }
    }
   else{
        echo "No Records";
       }   
}

catch(PDOException $e) {
    echo "I'm sorry I'm afraid you can't do that.". $e->getMessage() ;// Remove or modify after testing 
    file_put_contents('PDOErrors.txt',date('[Y-m-d H:i:s]').", mapSelect.php, ". $e->getMessage()."\r\n", FILE_APPEND);  
 }
//Close the connection
$dbh = null; 
?>

This DEMO uses this query

david strachan
  • 7,174
  • 2
  • 23
  • 33