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.
Related
I am using EF with a MSSQL DB and I am struggling with a spatial query:
I am using the user location to search for restaurants ordered by distance here is the code:
DbGeography userLocation = DbGeography.FromText(userLocationWKT, 4326);
return (from branch in DbContext.Branches
let distance = userLocation.Distance(branch.Location)
orderby distance ascending
where branch.Name.ToLower().Contains(searchString.ToLower()) && branch.Location != null
select new BranchAndDistance { Branch = branch, Distance = distance }).ToList();
the user location WKT is POINT (32.78786115814 35.0162102747709). In my test case I am sending a query that returns one restaurant with Lat=32.1300848 & Long=34.7919443 But the distance I am getting is 370237 meters which is way off.
The next image shows the QuickWatch window for the restaurant location details:
Notice that in the ProviderValue text it shows POINT (34.7919443 32.1300848) as if it replaces the Lat and Long but the actual values seem OK
So I conducted some more tests, since I am holding the lat and long values in separate variables I could test the following:
double? distance = Branch.Location.Distance(userLocation);
DbGeography branchLocation = DbGeography.FromText(string.Format("POINT ({0} {1})",
Branch.Latitude,.Branch.Longitude), 4326);
double? distance2 = branchLocation.Distance(userLocation);
The distance variable had the same incorrect distance value of 370237.85926851362
but the distance2 variabel had the correct distance value of 65061.945208184392
How can this be?
I have also checked in the SQL Azure database managment and according to that the Location property (which yields an incorrect distance result) is spot on
Have I missed something? What am I doing wrong???
Your WKT for your user is not what you think it is. Specifically, that is a point that has (latitude, longitude) = (35.0162102747709, 32.78786115814). Here's a little T-SQL to show that:
DECLARE #g geography;
SELECT #g = geography::STPointFromText('POINT (32.78786115814 35.0162102747709)', 4326);
SELECT #g.Lat, #g.Long;
I am having a confusion on what is happening. So I just need some ideas or clarifications.
I have 3 PHP variables and two PostgreSQL queries:
$date_start = $d1 = "2013-01-01";
$date_end = $d2 = "2013-01-05";
$car = "x";
$query_1 = pg_query($con, "SELECT AVG(longitude) as lon, AVG(latitude) as lat, MAX(longitude) as maxlon, MIN(longitude) as minlon, MAX(latitude) as maxlat, MIN(latitude) as minlat FROM table WHERE car='".$car."' AND testdate>='".$date_start."' AND testdate<='".$date_end."'";
$query_2 = pg_query($con, "SELECT MIN(testtime) as t1, MAX(testtime) as t2 FROM table WHERE car='$car' AND testdate>='$d1' AND testdate=<'$d2' GROUP BY testdate");
The trouble here is that $query_1 gets through fine and gives me a proper result resource, while the other one is always false with an error message:
ERROR: operator does not exist: date =< unknown
LINE 1: ... car='BM1' AND testdate>='2013-04-04' AND testdate=<'2013-04...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.
So... any ideas what is happening there? I am sure the queries are almost the same, but for the reason I do not understand PostgreSQL is complaining about one but not the other!
I do not need help on how to fix this, I just need to know the reason. Why the behaviour is different..
Cheers!!
Yours
Andy
I am not a postgress expert but AFAIK there is no =< operator but <=
$query_2 = pg_query($con, "SELECT MIN(testtime) as t1, MAX(testtime) as t2 FROM table WHERE car='$car' AND testdate>='$d1' AND testdate=<'$d2' GROUP BY testdate");
I'm receiving the following error "An invalid floating point operation occurred." when I run this query:
SELECT PolID, LocID, Address, City, StateCode, OrigGeoLat, OrigGeoLong, NewGeoLat, NewGeoLong,
acos(sin(radians(OrigGeoLat)) * sin(radians(NewGeoLat)) +
cos(radians(OrigGeoLat)) * cos(radians(NewGeoLat)) *
cos(radians(OrigGeoLong - NewGeoLong))) * 6372.8 as Distance
FROM zzGeoDataTMP
All of the *geoLat and *geoLong data is defined as numeric(18,10). When I run the query I start getting data back and then I get errors on specific rows of data. For example the following row throws the above mentioned exception only once the ACOS function is called:
OrigGeoLat|OrigGeoLong|NewGeoLat|NewGeoLong
---------------------------------------------
32.9364620|-80.0411000|32.9364620|-80.0411000
Thanks so much in advance for any insight you may be able to help with!
You're calculation is accumulating some small errors, which mean that the calculated value (passed to ACOS) is slightly higher than 1. Try wrapping it with a ROUND call:
acos(ROUND(sin(radians(OrigGeoLat)) * sin(radians(NewGeoLat)) +
cos(radians(OrigGeoLat)) * cos(radians(NewGeoLat)) *
cos(radians(OrigGeoLong - NewGeoLong)),15)
)
Where we're only keeping 15 decimal places of accuracy.
I'm trying to get a query to work which returns a list of locId's from the database when fed a long and a lat.
Here's the sql:
eg: lat = "-37.8333300" : lon = "145.000000" : radius = (5 * 0.621371192) ^ 2
SELECT locId,longitude,latitude FROM tbliplocations WHERE (69.1*([longitude]- "&lon&") * cos("&lat&"/57.3))^2 + (69.1*([latitude]- "&lat&"))^2 < "&radius
Here's the error I receive:
The data types float and int are incompatible in the '^' operator.
I'm unsure of a workaround, can anyone point me in the right direction?
Answer:
Using SQL Server 2008 R2
SELECT city FROM tbliplocationsnew WHERE POWER((69.1*([longitude]- "&lon&") * cos("&lat&"/57.3)),2) + POWER((69.1*([latitude]- "&lat&")),2) < "&radius
Not sure what database you use, but I think that "^2" in SQL does not mean "squared" like in maths. You should use a math "power" function, like POWER(number,2) in SQL Server (since you use VB maybe you use SQL Server ?)
You need to have two of the same data type it's saying. SQL thinks "5" is an int. So, you should be able to trick it into treating it as a float, by putting "5.0" instead.
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.