miscalculation of distance calculation of dbgeography - sql

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;

Related

SQL Multipolygon void where it overlaps

I have a polygon WKT and turning it into a geography and am presented with two polgyons, but where they overlap there is a void.
I am trying to find markers that exist in any polygon in the string (lat long intersects). The issue is that this void that is created I am not being delivered markers that exist there when it is required.
IF OBJECT_ID('tempdb..#customPolygon') IS NOT NULL DROP TABLE #customPolygon
CREATE TABLE #customPolygon (geog GEOGRAPHY)
[![DECLARE #customPolygon geography = 'MULTIPOLYGON (((-122.31260682058497 41.7828672412252, -122.66803538233832 41.74189213171878, -123.0157152218382 41.67409631073075, -123.35191487435746 41.58021595697532, -123.67306979164175
41.461266240272025, -123.97583066087832 41.31852640120816, -124.25710598001243 41.15352132907401, -124.51409805934621 40.96800014522892, -124.74433194261869 40.763912348675035, -124.94567706168226 40.54338210199239,
-125.11636173536279 40.30868123071627, -125.25498087780294 40.06220148128715, -125.3604974831004 39.80642653665781, -125.4322385960062 39.543904229883005, -125.46988656305209 39.277219329961135, -125.4734663894492
39.008967205820475, -125.44333001234564 38.74172860785035, -125.38013825025583 38.47804574501649, -125.28484111194828 38.22039978159673, -125.1586570556347 37.971189832187214, -125.00305168962673 37.73271349727281,
-124.8197163058454 37.50714895406215, -124.61054654308778 37.296538597688055, -124.37762139148631 37.102774215181654, -124.12318267532315 36.9275836675835, -123.8496150891857 36.772519052867715, -123.55942681226277
36.638946322795874, -123.25523068653888 36.52803632929597, -122.93972591543192 36.44075727956827, -122.61568021845359 36.377868583149755, -122.28591236310895 36.339916078129036, -121.9532749859139 36.327228627297,
-121.62063760871888 36.339916078129036, -121.2908697533742 36.377868583149755, -120.96682405639588 36.44075727956827, -120.65131928528895 36.52803632929597, -120.34712315956506 36.638946322795874, -120.0569348826421
36.772519052867715, -119.78336729650465 36.9275836675835, -119.52892858034149 37.102774215181654, -119.29600342874002 37.29653859768806, -119.08683366598238 37.50714895406215, -118.90349828220107 37.73271349727281,
-118.7478929161931 37.971189832187214, -118.62170885987955 38.22039978159673, -118.52641172157198 38.47804574501649, -118.46321995948216 38.74172860785035, -118.43308358237861 39.008967205820475, -118.43666340877571
39.277219329961135, -118.4743113758216 39.543904229883005, -118.54605248872741 39.80642653665781, -118.65156909402486 40.06220148128715, -118.79018823646503 40.30868123071627, -118.96087291014557 40.54338210199239,
-119.16221802920913 40.763912348675035, -119.3924519124816 40.96800014522892, -119.6494439918154 41.15352132907401, -119.93071931094948 41.31852640120816, -120.23348018018605 41.461266240272025, -120.55463509747037 41.58021595697532, -120.89083474998958 41.67409631073075, -121.2385145894895 41.74189213171878, -121.59394315124284 41.7828672412252, -122.31260682058497 41.7828672412252)), ((-119.1795222467484 42.50521242789702, -120.70099431758788 39.07636493660857, -115.32512633395542 38.91870544294517, -119.1795222467484 42.50521242789702)))'
INSERT INTO #customPolygon
SELECT geography::STPolyFromText(#customPolygon, 4326).MakeValid().ToString()
UPDATE #customPolygon SET geog=case when geog.EnvelopeAngle() > 90 then geog.ReorientObject() else geog end
SET #customPolygonJoin = ' join #customPolygon poly on poly.geog.STIntersects(a.GeoLoc) = 1 '][1]][1]
I would like to avoid itemizing polygons within the multipolygon string as there could be an unlimited number.
I've tried ST unions and ST difference, except it seems those require two geography inputs, where the multipolgyon is just 1.
TYIA
I think this "hole" is artifact of your drawing tool. Within SQL Server, it is two overlapping polygons - so there is no hole, it is instead covered by both of the polygons.
But if you want to dissolve this into a single polygon, I would try to StUnion it with empty geography (geography::STGeomFromText('POLYGON EMPTY', 4326)) or with itself.

Distance difference in SQL - why so big discrepancy?

I use 2 ways if calculating distance between coordinates, difference between them is quite big (more than 400 m). Do you know why? And which one of all ways of calculating is the most accurate?
1st:
DECLARE #source geography = 'POINT(53.9202690124512 14.2586479187012)'
DECLARE #target geography = 'POINT(53.8970128 14.2387088)'
SELECT #source.STDistance(#target)
2nd:
SELECT geography::Point(53.9202690124512, 14.2586479187012, 4326).STDistance(geography::Point(53.8970128, 14.2387088, 4326))
geography::Point expects the latitude and longitude to be passed in that order. WKT's POINT expects longitude and latitude to be passed in that order. See the results of:
DECLARE #wkt_source geography = 'POINT(53.9202690124512 14.2586479187012)'
DECLARE #wkt_target geography = 'POINT(53.8970128 14.2387088)'
select #wkt_source.STAsText() as wkt_source, #wkt_target.STAsText() as wkt_target
declare #point_source geography = geography::Point(53.9202690124512, 14.2586479187012, 4326)
declare #point_target geography = geography::Point(53.8970128, 14.2387088, 4326)
select #point_source.STAsText() as point_source, #point_target.STAsText() as point_target
So you will need to swap one or other around to get consistent results (depending on which lat/lng is correct).

Query N closest database entries using active record query interface

In my Rails project I have a table called "items", where each item has a latitude and longitude property.
When I retrieve the items for my client, I fetch the current user's longitude and latitude. Using these two sets of coordinates, I want to use a haversine function and fetch 100 results with the smallest haversine distance.
The SQL should look something like this:
SELECT
*,
haversine_distance(user_lat, user_long, item_lat, item_long) as distance
FROM
db.items
WHERE
item_lat < {user_lat} +1
AND item_lat > {user_lat}+1
AND ..
AND ..
ORDER BY distance DESC
LIMIT 100
but this would necessitate using a custom function in the query, which I'm not sure how to do.
Also, regarding lines 7 and 8 of the query, I'm attempting to a where clause to quickly filter the set of results within some reasonable distance so I don't have to apply the haversine to the entire table.
Right now, I have the function defined in a singleton called DistanceService, but I assume I cannot use Ruby functions in the query.
When I tried to add the function haversine_distance to the controller and call it like:
#items = Item.select("items.*, haversine_distance(geo_long, geo_lat,
#{#current_user.geo_long}, #{#current_user.geo_lat}) AS distance")
.where(status: 'available', ....)
I was met with
FUNCTION ****_api_development.haversine_distance does not exist. This leads me to think the function should be somehow defined in SQL first via some migration, but I'm not very familiar with SQL and don't know how I would do that with a migration.
I'm also wondering if there's a solution that would support pagination, as it will hopefully eventually become a requirement for my project.
Thanks.
In Rails you define database functions through migrations:
class AddHaversineDistanceFunction < ActiveRecord::Migration
def up
execute <<~SQL
DELIMITER $$
DROP FUNCTION IF EXISTS haversine_distance$$
CREATE FUNCTION haversine_distance(
lat1 FLOAT, lon1 FLOAT,
lat2 FLOAT, lon2 FLOAT
) RETURNS FLOAT
NO SQL DETERMINISTIC
COMMENT 'Returns the distance in km
between two known points of latitude and longitude'
BEGIN
RETURN DEGREES(ACOS(
COS(RADIANS(lat1)) *
COS(RADIANS(lat2)) *
COS(RADIANS(lon2) - RADIANS(lon1)) +
SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))
)) * 111.045;
END$$
DELIMITER;
SQL
end
def down
execute "DROP FUNCTION IF EXISTS haversine_distance"
end
end
The actual function here is adapted from Plum Island Media.
But you might want to check out the geocoder gem which provides this functionality and uses a better formula.

Querying SQL Geometry data using Longitude and Latitude

I have downloaded a shapefile from Ordinance Survey website, and imported it into SQL using ShpToSql.exe, here the setup
This imports fine.
Now i want to query what region a particular longitude and latitude is within.
Using the following
Postcode: WD25 7LR (Harry Potter Studios :D)
Latitude: 51.6910751568794
Longitude: -0.418128358906299
I thought i could write something like
DECLARE #g geography
set #g = geography::Point(51.6910751568794, -0.418128358906299, 4326)
select [name] from region where #g.STWithin(geom) is not null
But that returns an error message of Operand type clash: geometry is incompatible with geography
So i tried to change the data type to geometry, so code looks like this
declare #g geometry
set #g = geometry::Point(51.6910751568794, -0.418128358906299, 4326)
select [name] from region where #g.STWithin(geom) = 1
But no results are returned.
Can someone help me with this please. Just want to know if a longitude and latitude is within a particular POLYGON.
UPDATE:
I have tried to import the .shp file using Geography data type, but this gives an error message when i try to import it
And even if i still try to import it, i them get a message like this for every shape in the shp file
SQL Server will not allow comparisons between geometry and geography data types.
If you are not concerned with being exact, you could perform your original query with the point as a geometry type. This will work reasonably well in most small cases where you aren't comparing odd shapes and across hemispheres.
DECLARE #g geometry
set #g = geometry::Point(51.6910751568794, -0.418128358906299, 4326)
select [name] from region where #g.STWithin(geom) is not null
Otherwise you will need to import the data as a geography type and based on your note there might be some malformed data in the shapefile.

How Can i return Longitude and latitude from a point RGEO Postgis

I have a table in my database with location city, state, zip, and a point
the point in my database looks like this 0101000020E6100000000000E0087B52C0000000402F6B4440
When i run this command i get this:
1.9.3-p547 :014 > coords = "ST_GeographyFromText('#{Location.first.coords}')"
2014-09-08 11:46:55 DEBUG -- Location Load (77.4ms) SELECT "locations".* FROM "locations" LIMIT 1
=> "ST_GeographyFromText('POINT (-85.28800201416016 48.596500396728516)')"
what i want to do if figure out how to pull and set each latitude and longitude from that
Latitude = "48.596500396728516"
Longitude = "-85.28800201416016"
I need these so i can use it with Google maps...
Use ST_Y(geom) for latitude and ST_X(geom) for longitude.
Figured it out...
unless someone else has an easier solution:
coords = "ST_GeographyFromText('#{Location.first.coords}')"
lat, lng = coords.gsub(/[^-.0-9\s]/, "").strip.split(" ")
if there is a faster way to do this... let me know because i have to do this 100,000 + times