Geocoder Rails plugin - Near search problem with ActiveRecord - sql

This is dragging at my nerves!
Scenario:
I am using the geocoder plugin: http://github.com/alexreisner/geocoder/
I have two models: Address and Item
the latitude and the longitude are in Address and an Item belongs_to :address
Now, when I try to do a near search:
Item.near(Person.first.address.coordinates, 20)
on an Item it fails with the following SQL:
Unknown column 'latitude' in 'field list':
SELECT *, 3956 * 2 * ASIN(SQRT(POWER(SIN((51.3883 - latitude) * PI() / 180 / 2), 2) + COS(51.3883 * PI()/180) * COS(latitude * PI() / 180) * POWER(SIN((6.65326 - longitude) * PI() / 180 / 2), 2) )) AS distance FROM `items` WHERE ((latitude BETWEEN 51.0984449275362 AND 51.6781550724638 AND longitude BETWEEN 6.188777824785 AND 7.117742175215) AND (`items`.`accepted` IS NULL AND `items`.`taken_by` IS NULL)) ORDER BY distance ASC
Don't look at the weird Math stuff in the query,
the problem lies within the FROM and the WHERE clause because
the latitude and the longitude attributes are part of the address table,
so a simple addition to the Query would solve the problem:
... FROM items, addresses WHERE ...
... null)) AND items.address_id = addresses.id ORDER BY ...
I just want to add a table to the FROM field and a condition :(
I tried using several named_scopes using :include and :joins, but none worked!
Does anybody know a solution to this? How could I insert this into a scoped ActiveRecord object?

So the Item has address(es) and that's where the longitude and latitude actually exist, right? That's where I think the query is going wrong for you. You could try adding a :joins=>:address ... but I like the following better:
Since all the math occurs in the database, I think you could do:
Address.near(Person.first.address.coordinates, 20)
and then get the items that the addresses belong to, maybe even an :include=>:item or something.

Related

How to use the inverse cosine (ACOS) in Access SQL

I have a website in Classic ASP with an Access database. I'm using the Google maps API. Now I want to find Companies (from the database) within a radius of 20 km from a given location.
I have found a formular (haversine), but it seems like I can't use ACOS(). I get this error:
Microsoft JET Database Engine error '80040e14'
Undefined function 'acos' in expression.
And here is my sql:
lat = 56.113517
lng = 10.145928
sql = "SELECT companyId, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) ) AS distance FROM company HAVING distance < 25 ORDER BY distance"
Set rs = Connect.Execute(sql)
I have search and search but I cant find a solution.
Can anybody help?
It is true that Access SQL does not have a built-in ACOS() function. If you were running your query in Access itself then you could create your own ACOS() function in VBA to use in an SQL query, but that option is not available to SQL queries against an Access database from other applications (like your ASP app).
So, you will need to do the precise distance calculations in ASP after you retrieve the records that might qualify. That is, you could calculate the latitude and longitude boundaries of a 40km by 40km "square" centered on the target location (to exclude records that are guaranteed to be too far away), then calculate the actual distance for each of the returned records using your ASP function (example code here).

An invalid floating point operation occurred when checking longitude and latitudes

I know there are a lot of questions like mine asked. I looked at them, but I can't seem to resolve the problem. I'm just bad in math :s.
In c# i'm using a SqlQuery to get cities in a range. This works most of the time, except for some cities.
For example, if i ask the nearby cities of the city with postalcode 2060. I get this error. When i ask it for the city with code 2000, it returns 2000 and 2060.
2060: Lat = 51.2293515000 Long = 4.4279883000
2000: Lat = 51.2198771000 Long = 4.4011356000
this is the query:
return base.Database.Database.SqlQuery<City>(
"SELECT * FROM [dbo].[City] WHERE #p0 >= (((acos(sin((#p1*pi()/180)) *
sin(([Latitude]*pi()/180))+cos((#p1*pi()/180)) * cos(([Latitude]*pi()/180)) *
cos(((#p2- [Longitude])*pi()/180))))*180/pi())*60*1.1515*1.609344)"
, radius, latitude, longitude);
Can somebody explain how this can be changed so it works for all the "cities" and the reason if this error for a noob in math?
Thank you
The problem is that the statement inside acos() gets a rounding error somtimes when you compare cities to themselves, and the statement becomes slightly over 1, which causes the error.
You need to cap the value to max 1 somehow. Here is a rather ugly solution:
return base.Database.Database.SqlQuery<City>(
"SELECT * FROM [dbo].[City] WHERE #p0 >= (((acos((select min(v) from (values (1), (sin((#p1*pi()/180)) *
sin(([Latitude]*pi()/180))+cos((#p1*pi()/180)) * cos(([Latitude]*pi()/180)) *
cos(((#p2- [Longitude])*pi()/180)))) as x(v))))*180/pi())*60*1.1515*1.609344)"
, radius, latitude, longitude);
I'm not sure, but it could be that it sometimes gets slightly smaller than -1 as well, which would case the same error. Then you'd have to cap the value to between -1 and 1.

Find nearest locations to a point with sql in Grails 2.1.1

I am using Grails 2.1.1 and have to find all nearest locations for a given point. My code is looking like this (works in my app):
def findNearLocations(double latitude, double longitude) {
def locationsFromQuery = Location.executeQuery(
"SELECT id,(6371 * 2 * ASIN(SQRT(POWER(SIN((:ulatitude - abs(latitude)) * pi()/180 / 2),2) +" +
"COS(:ulatitude * pi()/180 ) * COS(abs(latitude) * pi()/180) *" +
"POWER(SIN((:ulongitude - longitude) * pi()/180 / 2), 2))))*1000 as distance " +
"FROM Location WHERE is_public = TRUE ORDER BY distance", // HAVING distance < 1000
[ulatitude: latitude.toString().toDouble(), ulongitude: longitude.toString().toDouble()])
// do stuff with locationsFromQuery
}
The formula for the computation of the nearest locations I used for is this stack overflow post but I cannot use the HAVING clause like in the comment. If I use it, I get an hql.hibernate exception, saying, that there is an unexpected identifier HAVING. But why this is working in nearly all examples in google but not working for me?
I am using the h2 database in the memory.

Linq to NHibernate - The method Cos is not implemented

In Linq to NHibernate I'm trying to return businesses within a certain distance of a user. Here is what I have so far:
var query = from b in ActiveRecordLinq.AsQueryable<Business>()
where (3959 * Math.Acos(Math.Cos((Math.PI * coordinates.Latitude / 180)) * Math.Cos((Math.PI * b.Latitude / 180))
* Math.Cos((Math.PI * b.Longitude / 180) - (Math.PI * coordinates.Longitude / 180))
+ Math.Sin((Math.PI * coordinates.Latitude / 180)) * Math.Sin((Math.PI * b.Latitude / 180)))) <= radiusInMiles
orderby b.Name ascending
select b;
return query.ToList();
Unfortunately, it seems that the C# Math class isn't supported in Linq to NHibernate so it gives me the following error:
The method Cos is not implemented
How would I get around this?
Thanks!
Justin
You can do three things:
Implement it and submit a patch to NHibernate so that everybody can use your implementation
Create a failing unit test that isolates the problem and submit it to NHibernate Jira and wait until somebody else implements it.
Do not use Linq, but plain old sql or mixed hql for this query.
You will make some people happy when you choose option 1.
You can't use arbitrary .Net functions in server-side Linq queries.
You can use server-side functions with HQL.

Geolocation SQL query not finding exact location

I have been testing my geolocation query for some time now and I haven't found any issues with it until now.
I am trying to search for all cities within a given radius, often times I'm searching for cities surrounding a city using that city's coords, but recently I tried searching around a city and found that the city itself was not returned.
I have these cities as an excerpt in my database:
city latitude longitude
Saint-Mathieu 45.316708 -73.516253
Saint-Édouard 45.233374 -73.516254
Saint-Michel 45.233374 -73.566256
Saint-Rémi 45.266708 -73.616257
But when I run my query around the city of Saint-Rémi, with the following query...
SELECT tblcity.city, tblcity.latitude, tblcity.longitude,
truncate((degrees(acos( sin(radians(tblcity.latitude))
* sin(radians(45.266708))
+ cos(radians(tblcity.latitude))
* cos(radians(45.266708))
* cos(radians(tblcity.longitude - -73.616257) ) ) )
* 69.09*1.6),1) as distance
FROM tblcity HAVING distance < 10 ORDER BY distance desc
I get these results:
city latitude longitude distance
Saint-Mathieu 45.316708 -73.516253 9.5
Saint-Édouard 45.233374 -73.516254 8.6
Saint-Michel 45.233374 -73.566256 5.3
The town of Saint-Rémi is missing from the search.
So I tried a modified query hoping to get a better result:
SELECT tblcity.city, tblcity.latitude, tblcity.longitude,
truncate(( 6371 * acos( cos( radians( 45.266708 ) )
* cos( radians( tblcity.latitude ) )
* cos( radians( tblcity.longitude )
- radians( -73.616257 ) )
+ sin( radians( 45.266708 ) )
* sin( radians( tblcity.latitude ) ) ) ),1) AS distance
FROM tblcity HAVING distance < 10 ORDER BY distance desc
But I get the same result...
However, if I modify Saint-Rémi's coords slighly by changing the last digit of the lat or long by 1, both queries will return Saint-Rémi. Also, if I center the query on any of the other cities above, the searched city is returned in the results.
Can anyone shed some light on what may be causing my queries above to not display the searched city of Saint-Rémi? I have added a sample of the table (with extra fields removed) below.
I'm using MySQL 5.0.45, thanks in advance.
CREATE TABLE `tblcity` (
`IDCity` int(1) NOT NULL auto_increment,
`City` varchar(155) NOT NULL default '',
`Latitude` decimal(9,6) NOT NULL default '0.000000',
`Longitude` decimal(9,6) NOT NULL default '0.000000',
PRIMARY KEY (`IDCity`)
) ENGINE=MyISAM AUTO_INCREMENT=52743 DEFAULT CHARSET=latin1 AUTO_INCREMENT=52743;
INSERT INTO `tblcity` (`city`, `latitude`, `longitude`) VALUES
('Saint-Mathieu', 45.316708, -73.516253),
('Saint-Édouard', 45.233374, -73.516254),
('Saint-Michel', 45.233374, -73.566256),
('Saint-Rémi', 45.266708, -73.616257);
In your first query, I believe you've inverted the longitudes in the subtraction. The Spherical Law of Cosines is:
d = acos(sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(long2−long1))*R
If lat1 is substituted with tblcity.latitude, long1 must be substituted with tblcity.longitude. I think you've accidentally substituted long2 in your query. Does this one work better?
SELECT tblcity.city, tblcity.latitude, tblcity.longitude,
truncate((degrees(acos( sin(radians(tblcity.latitude))
* sin(radians(45.266708))
+ cos(radians(tblcity.latitude))
* cos(radians(45.266708))
* cos(radians(-73.616257 - tblcity.longitude) ) ) )
* 69.09*1.6),1) as distance
FROM tblcity HAVING distance < 10 ORDER BY distance desc
I haven't looked into your second query yet, but hopefully that helps.
You are using the "spherical law of cosines" formula, which is susceptable to rounding error at small distances (like zero!) -- see this discussion. The long expression that is fed into acos() is evaluating to slightly more than 1.0, which is out of bounds.
Here's the problem illustrated using Python to do the calculations:
>>> from math import sin, cos, acos, radians
>>> lat = radians(45.266708)
>>> long_expression = sin(lat) * sin(lat) + cos(lat) * cos(lat) * cos(0.0)
>>> repr(long_expression)
'1.0000000000000002'
>>> acos(long_expression)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: math domain error
>>>
It seems that MySQL is substituting NULL instead of raising an exception. I know little about MySQL, but you should be able to overcome that by doing something like ifnull(acos(long_expression), 0.0) or coalesce(acos(long_expression), 0.0).
Alternatively you could use the haversine formula, which shifts the rounding problem from your door-step to the opposite side of the earth.
Update: I've tested using that formula in Python to calculate the should-be-zero distance between a point and the same point, for each of the 37582 unique (lat, lon) 2-tuples in a file of US zip codes.
Of these:
31591 (84.1%) produced a zero distance
4244 (11.3%) produced a distance of 9.5 cm.
831 (2.2%) produced a distance of 13.4 cm.
916 (2.4%) produced a "cos" value of 1.0000000000000002 which would cause an exception in acos() if not detected and avoided.
It appears that explicit testing for lat1 = lat2 and lon1 = lon2 and avoiding the formula in that case (just use zero) might be a good idea -- it would give a consistent answer and avoid puzzlement.