I've realized this «silly» spatial query to find all the points that lie 5Km far form a center.
Source table holds +150K rows.
Here the query:
DECLARE #position geography = geography::Parse('POINT(9.123 45.123)')
DECLARE #circle geography = #position.STBuffer(5000) -- A circle of 5Km of radius
SELECT
g.Coordinate.STDistance(#position), g.Coordinate.Filter(#circle)
FROM
[DB_NAME].[SCHEMA].[TABLE] AS g WITH (nolock)
WHERE
g.Coordinate.Filter(#circle) = 1
I oddly observe that the WHERE condition doesn't work: in fact I retrieve even +600 points where the condition returns 0.
Any suggestions?
For the sake of clarity table schema was
[DB_NAME].[SCHEMA].[TABLE](Coordinate geography NOT NULL)
Official documentation states: «Returns 1 if a geography instance potentially intersects another geography instance. This method may produce a false-positive return, and the exact result may be plan-dependent. Returns an accurate 0 value (true negative return) if there is no intersection of geography instances found.»
So I mean that 0 is always ok, while 1 could be approximated (IMHO this behaviour is absolutely reasonable)
By the way #Damien observation lead me to simply work around:
DECLARE #position geography = geography::Parse('POINT(9.123 45.123)')
DECLARE #circle geography = #position.STBuffer(5000) -- A circle of 5Km of radius
SELECT * FROM
(SELECT
g.Coordinate.Filter(#circle) filter, g.Coordinate Coord
FROM [DB_NAME].[SCHEMA].[TABLE] AS g WITH (nolock)
WHERE
g.Coordinate.Filter(#circle) = 1
) t
WHERE t.filter = 1
that recalls me the «Double Check Pattern» esoterism… but in that case It's clear the motivation.
One point that could be more investigated is about the return value conversion… Many years ago I stumbled upon on a similar issue where in a server farm an implicit conversion of a boolean tre to int led to -1 (0xFFFFFFFF) instead of 1 (0x00000001)… COM ages…
Related
SELECT TOP 1 KMLLocation FROM Polygon
Result: 0xE61000000104210000004ACE893DB4BF19403EB2B96A9EFF534082919735B1C0194039807EDFBFFF534052F17F4754C81940976F7D586F0054405AD76839D0D31940AED3484BE50054409E961FB8CAD3194087BF266BD40054403F90BC7328D3194071C806D2C5005440844A5CC7B8D21940E4D70FB1C100544014CE6E2D93D1194062BF27D6A90054404FB2D5E594D01940E27668588C005440E910381268D01940E7E26F7B820054409AEAC9FCA3CF1940A54E4013610054409432A9A10DD01940CD902A8A5700544015E126A3CAD01940FE0E45813E0054407FBDC282FBD11940C30DF8FC30005440EB1B98DC28D2194038312427130054403065E08096CE19404A7CEE04FBFF534082035ABA82CD1940C2F7FE06EDFF5340462234828DCB19409373620FEDFF534031B2648EE5CD1940E338F06AB9FF5340CFDC43C2F7CE1940321EA5129EFF5340B98C9B1A68CE194055A3570394FF5340A8E15B5837CE1940639CBF0985FF53403868AF3E1ECA1940001FBC7669FF534029CB10C7BAC81940ED9C668176FF5340C6DB4AAFCDC61940AD18AE0E80FF534047CCECF318C51940F390291F82FF5340EDB776A224C41940FD851E317AFF5340E4F90CA837C31940C440D7BE80FF5340DF1AD82AC1C21940462234828DFF53409C69C2F693C1194007D2C5A695FF5340C5387F130AC119406AFB57569AFF5340D1E7A38CB8C01940574277499CFF53404ACE893DB4BF19403EB2B96A9EFF534001000000020000000001000000FFFFFFFF0000000003
SELECT TOP 1 KMLLocation .ToString() FROM Polygon
Result:
POLYGON ((79.994044 6.437211, 79.996086 6.438176, 80.006796 6.445634, 80.013995 6.456849...
Expected Result:
POLYGON ((6.437211 79.994044, 6.438176 79.996086, 6.445634 80.006796, 6.456849, 80.013995...
How do I swap Lat and Long from geography polygon?
Here's what I came up with:
DECLARE #g GEOGRAPHY = /* your hex representation from above */
#new VARCHAR(MAX);
WITH points AS (
SELECT #g.STPointN(n.n) AS p, n
FROM tempdb.dbo.Numbers AS n
WHERE n <= #g.STNumPoints()
)
SELECT #new = CONCAT('POLYGON((', (
SELECT STRING_AGG(CONCAT(p.Lat, ' ', p.Long), ',') WITHIN GROUP (ORDER BY p.n)
FROM points AS p
), '))')
SELECT geography::STGeomFromText(#new, #g.STSrid).ReorientObject();
It's pretty straightforward. I'm creating a common table expression (CTE) to decompose the existing polygon into its constituent corners using a numbers (or tally) table. All that is is a table with one column with integers from 1 to some large number. It's helpful for cases like this. From there, I'm swapping the latitude and longitude of the points and then re-assembling them into the WKT for the polygon. Lastly, I'm creating an actual geography instance from the WKT. Note - I'm calling ReorientObject() as the new polygon appears to suffer from a ring orientation problem insofar as it defined the whole globe with a small hole in it (the shape of your polygon).
We currently use the Geography type to calculate distance between a current location and the coordinates in our tsql table. Our code is based on this sqlauthority.com example.
Is there a faster way to retrieve the distance between two points? These calls will be done by a mobile phone app, so they should ideally be very fast.
After testing it with a distance I know, looping 100 times per batch and running the batch 15 times to make sure the 10 runs the client statistics stores in SSMS are cycled past initial query plan generation so it doesn't skew the results. Here are the averages of the remaining. The calculation method seems to be twice as fast as the geography option.
With a difference in distance returned of 0.0000000020044.
Calculation script used (returned miles: 41.9013152732833)
set nocount on;
declare
#lat1 float = 45.489614
,#lon1 float = -122.650021
,#lat2 float = 44.94404
,#lon2 float = -123.025739
select 3959.1825574 * acos(sin(#lat1/57.295779513082323) * sin(#lat2/57.295779513082323) + cos(#lat1/57.295779513082323) * cos(#lat2/57.295779513082323) * cos((#lon2-#lon1)/57.295779513082323)) distance_in_miles
GO 100
Geography script used (returned miles: 41.9013152752877)
set nocount on;
declare
#g geography = geography::Point(45.489614, -122.650021, 4326)
,#h geography = geography::Point(44.94404, -123.025739, 4326)
select #h.STDistance(#g) / 1609.344 distance_in_miles -- 1609.344 is meters in mile. STDistance = meters.
GO 100
Fair warning, doing it in a non-system function will still have unpredictable performance. I would recommend doing it inline for calculation.
Here's a raw calculation example.
Working example of inline syntax for miles. It is the easiest, most accurate and shortest syntax I could find.
adjusted for accuracy
if object_id('tempdb..#LatLongInfo','U') is not null
begin
drop table #LatLongInfo;
end;
create table #LatLongInfo (
lat1 float,
lon1 float,
lat2 float,
lon2 float
);
insert into #LatLongInfo
values (21, -76, 23, -72);
select
3959.1825574 * acos(sin(lat1/57.295779513082323) * sin(lat2/57.295779513082323) + cos(lat1/57.295779513082323) * cos(lat2/57.295779513082323) * cos((lon2-lon1)/57.295779513082323)) distance_in_miles
from #LatLongInfo;
Hope this helps. I used something like this to find the doctors within a given range for patients back when sql2000 was released, it's been a while. Google was a newborn, no maps, nothing but a search box and one button. You have me all nostalgic now...I remember reading this when I coded that the first time.
Consider the overly simplistic example: POINT(0 0) and LINESTRING (1 -10, 1 10)
The closest point on the line to the POINT would be 1, 0.
How would one determine this in TSQL? My simple, not entirely accurate, approach was to make a linestring (POINT POINT) and extend out the X coord of one coords until the two linestrings intersected.
So:
linestring (0 0, 0.25 0) (no intersect)
linestring (0 0, 0.5 0) (no intersect)
linestring (0 0, 0.75 0) (no intersect)
linestring (0 0, 1 0) (intersection - so 1 0 is the point closest to POINT
This quasi worked, but doesn't seem to the most bestest/more performant way of accomplishing this.
For example, one inefficiency is that I move it one direction (positive increments), and if there was no match (after x attempts), then I would start over, but with negative increments.
To optimize, I tried moving in larger steps, then when intersected (probably went past the point), I backed off 1 increment and started from there with a smaller increment. I did this a couple of times - instead of going in tiny tiny increments so as not to overshoot by too much.
One acceptable assumption based on my processing that the POINT will be next to (left/right) of the LINESTRING.
Another acceptable assumption is that the LINESTRING will be fairly "perpendicular" to the POINT.
I think you can do this mathematically rather than with a brute-force iterative algorithm.
There is a post to get closest point to a line that describes the method.
I converted this method to SQL which returns the correct value (1,0). Your 'trivial' example is actually a bit of an edge case (vertical line with infinite slope) so it seems robust.
I also tested the source code with this example: https://www.desmos.com/calculator/iz07az84f5 and using the input for the line of (-1,2) (3,0) and a point at (2,2) got the correct answer (1.4, 0.8).
SQL code (also in SQL Fiddle at http://sqlfiddle.com/#!6/d87aa/15)
DECLARE #x int, #y int, #x1 int, #y1 int, #x2 int, #y2 int
DECLARE #atb2 float, #atp_dot_atb float
DECLARE #t float
--SELECT #x=0, #y=0
--SELECT #x1=1, #y1=10, #x2=1, #y2=-10
SELECT #x=2, #y=2
SELECT #x1=-1, #y1=2, #x2=3, #y2=0
SELECT #atb2 = SQUARE(#x2-#x1) + SQUARE(#y2-#y1) -- Basically finding the squared magnitude of a_to_b
SELECT #atp_dot_atb = (#x-#x1)*(#x2-#x1) + (#y-#y1)*(#y2-#y1) -- The dot product of a_to_p and a_to_b
SELECT #t = #atp_dot_atb / #atb2 -- The normalized "distance" from a to your closest point
SELECT #x1 + (#x2-#x1)*#t, #y1 + (#y2-#y1)*#t --Add the distance to A, moving towards B
This query on my sql server returns lots of rows:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
but the equivalent in EF returns none:
DbGeography referencepoint = DbGeography.PointFromText("POINT(48.208173 16.373813)", 4326);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint ) <= 20000
select r).ToList();
Looking at the query generated via profiler I see this:
declare #p3 sys.geography
set #p3=convert(sys.geography,0xE6100000010CD4D17135B25F30408274B169A51A4840)
SELECT *
FROM [myTable]
WHERE ([Location].STDistance(#p3)) <= 20000
Does EF have an issue here, or do I?
OP has the issue here :) Both SQL and EF are working as expected. OP's statement was incorrect.
SQL Point Syntax:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
is actually equivalent to .Net:
DbGeography referencepoint = DbGeography.PointFromText("POINT(16.373813 48.208173)", 4326);
// Note the parameters are reversed from OP's statement
In SQL and EF (.Net) the Geography data type uses a standard WellKnownText notation to define points and polygons and other structures internally.
In WellKnownText format a Point is specified as POINT(X Y) on a Cartesian plane.
- Note the lack of a comma, the values are only delimited by a space
When we want to express the location on the earth as a point on a Cartesian plane, the X axis is the equator, the Y axis is then a Meridian line running between the North and South Poles.
Longitute, by definition is the east-west position on the surface of the Earth (so parallel with the equator, the X ordinate)
Latitude, by definition is the north-south position on the surface of the Earth (perpendicular to the equator, the Y ordinate)
Therefore to express a Point on the earth in WellKnownText format as if it were a point on a Cartesian plane we must use this syntax:
POINT(Longitude Latitude)
What confuses the issue is that in most verbal and written forms we refer to Latitude and Longitude in that order, so in SQL we have a helper function that takes the parameters in that order, because this was supposed to make it less confusing. And in a way it is, because the parameters are named appropriately. To further explain the point I have expanded out OP's statements with the correction
SQL
DECLARE #latitude float = 48.208173
DECLARE #longitude float = 16.373813
DECLARE #srid int = 4326
DECLARE #referencepoint Geography = Geography::Point(#latitude, #longitude, #srid);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
.Net
double latitude = 48.208173;
double longitude = 16.373813;
int srid = 4326;
DbGeography referencepoint = DbGeography.PointFromText($"POINT({longitude} {latitude})", srid);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint) <= 20000
select r).ToList();
I can't even find a good reference explaining why we generally refer to Latitude and Longitude (in that order) I suspect it's based on the fact that LatLon rolls off the tongue better or because latitude was discovered/measured first?
i am working on windows application, i have a code in vb.net like this:
PT1 = PT - LT
If PT1 > modvAL Then
q1 = PT1 / modvAL
Else
q1 = 1
End If
here my PT1 value is 2752 and ModvAL is 1440 then i am getting my q1 value is 2 (this is my vb.net code)
set #PT1=#PT-#LT
if #PT1>#modvAL
begin
set #q1=#PT1 / 1440
else
begin
set #q1=1
end
this is my stored procedure part ,here am doing same thing in sql server but my q1 is not getting proper value? what is wrong with my stored procedure
As you are using INT datatype, the results will all be rounded. Take the following examples:
DECLARE #floatValue FLOAT = 100
DECLARE #intValue INT = 100
DECLARE #divisor INT = 90
SELECT #intValue / #divisor -- this will return 1
SELECT #floatValue / #divisor -- this will return 1.11
So to fix your query, you could make the data types of your variables FLOAT (or another non-integer numeric type) instead.
warning: this is a wild guess because your question lacks basic info (example data, data type of involved fields, expected result).
i suppose you are performing the calculation sql side relying upon approximate datatype fields hence the result is not the actual result but the nearest value supported by the datatype.
should you will to share proper information it would be possible to understand if this is the issue and find a solution.
here you can find some info about approximate data type in sql server; also check on ms documentation for more detail.
You're missing 'end' of if block.
set #PT1=#PT-#LT
if #PT1>#modvAL
begin
set #q1=#PT1 / 1440
end //missing
else
begin
set #q1=1
end
Also with single statements you can use if-else without blocks.
set #PT1=#PT-#LT
if (#PT1>#modvAL)
set #q1=#PT1 / 1440
else
set #q1=1