In SQL Server I want to be able to take a single latitude and longitude (51.500709, -0.124646) and move these coordinates 50 meters north, but I'm not sure how to do this. I am aware of the geography spatial data type and have used the STDistance function to get the distance between points when writing other queries. But I'm struggling to see any information on moving coordinates by a fixed distance.
I was curious about the n/111111 approach in the link provided by Mitch.
I am pleased to report it "Ain't half bad". In this example, we were 0.06 meters (2.3 inches) off.
Example
Declare #Lat float = 51.500709
Declare #Lng float = -0.124646
Declare #LatM float = -50
Declare #LngM float = 0
;with cte as (
Select OldLat = #Lat
,OldLng = #Lng
,NewLat = #Lat + #LatM/111111
,NewLng = #Lng + #LngM/(111111 * cos(radians(#Lat)) )
)
Select *
,DistanceCheck = geography::Point(OldLat, OldLng, 4326).STDistance(geography::Point(NewLat, NewLng, 4326))
From cte
Results
Assume a table structure like this:
Tablename: TempDistance
ID : Int
Lat: Float
Lng: Float
Val: Money
You can calculate the cost radius for every combination of points via a CROSS JOIN
with cte as (
Select A.ID
,BID =B.ID
,BVal=B.Val
,Distance = geography::Point(A.Lat, A.Lng, 4326).STDistance(geography::Point(B.Lat, B.Lng, 4326))
From TempDistance A
cross join TempDistance B
)
Select Top 10
A.ID
,Cnt = sum(1)
,Val = sum(BVal)
From cte A
Where Distance<=200
Group By A.ID
Order By sum(BVal) Desc
Results
JUST TO BE CLEAR. This approach can get expensive if you have thousands of records. In my sample table there are 500 records. The CROSS JOIN will generate 250,000 records. There are some methods that can be applied to REDUCE the number of distance calculations via the n/111111 estimate, but that would require further study.
Related
We currently have a working script to return a list of all our products within a radius of a postcode/zipcode. It searches for all products that have locations in area codes within the #miles parameter of #lookingfor postcode, also returning the distance.
The PostodeLocation table contains four columns: the town/city, the postcode (pcd) and the Northing (grn) and Easting (gre) grid references. The primary key is on the postcode.
The problem is: I do not know the centre centre of each town or the postcodes for the outer radius of each town. I cannot adapt the script to give me all products in locations within a radius of a town, or even a town centre.
Has anybody else encountered this requirement and found a solution? I'm looking for a technique more than the actual code requirement.
Current script:
declare #lookingfor nvarchar(10) = 'sw1a 1aa'
declare #miles int = 5
DECLARE #UserCoordX bigint
DECLARE #UserCoordY bigint
SELECT #UserCoordX = GRE
, #UserCoordY = GRN
FROM MyCompany.dbo.PostcodeLocations pcls
WHERE pcls.PCD = #lookingfor
select pcls.PCD
, p.ProdName
, sqrt((((pcls.GRE - #UserCoordX) * (pcls.GRE - #UserCoordX)) + ((pcls.GRN - #UserCoordY) * (pcls.GRN - #UserCoordY)))/(160.9334*160.9334)) as Distance
from MyCompany.dbo.pcls_disk pcls
inner join MyCompany.dbo.Loc l on l.Postcode = pcls.pcd
inner join MyCompany.dbo.X_ProdLoc xpl on xpl.LocId = l.LocId
inner join MyCompany.dbo.Prod p on p.ProdId = xpl.ProdId
where p.StatusId = 4 and p.HideFromSearch = 0
and ((pcls.GRE - #UserCoordX) * (pcls.GRE - #UserCoordX)) + ((pcls.GRN - #UserCoordY) * (pcls.GRN - #UserCoordY))
<= cast(#Miles * 160.9334 * #Miles * 160.9334 as bigint)
order by [Distance]
So i have a spatial database with ship coordinates named "ships".
It has the following tables:
SHIPS
============
id (serial)
vessel(uuid)
time(timestamp without timezone)
coord(geometry(Point, 4326)
I need to write a query that returns vessels that their coordinates are X meters far, with a Y time interval. So far i have this:
SELECT DISTINCT ON (a.vessel, b.vessel) a.vessel, b.vessel
FROM ships a
INNER JOIN ships b
ON a.vessel = b.vessel
WHERE
ST_DWithin((ST_Transform(a.coord, 3035)),(ST_Transform(b.coord, 3035)), 5000)
AND (EXTRACT (EPOCH FROM(a.time - b.time))) <= 1500
AND (a.vessel != b.vessel);
but it always returns null if i leave the a.vessel != b.vessel constraint, and if i delete it the results are the same vessel on both columns.
You are using the wrong condition in the join:
SELECT DISTINCT ON (a.vessel, b.vessel) a.vessel, b.vessel
FROM ships a
INNER JOIN ships b
ON ST_DWithin((ST_Transform(a.coord, 3035)),(ST_Transform(b.coord, 3035)), 5000)
WHERE
(EXTRACT (EPOCH FROM(a.time - b.time))) <= 1500
AND (a.vessel != b.vessel);
Am trying to merge and split polygons from postgis. Merge works but I dont have any idea how to divide polygon into two parts.
I have tried using this query but it is not dividing
insert into edited values('2','338',
(SELECT ST_ASText(geom_part2) FROM(
WITH RECURSIVE ref(geom, env) AS (
SELECT geom,
ST_Envelope(geom) As env,
ST_Area(geom)/2 As targ_area,
1000 As nit
FROM plots
WHERE plot_no = 338 limit 1
),
T(n,overlap) AS (
VALUES (CAST(0 As Float),CAST(0 As Float))
UNION ALL
SELECT n + nit, ST_Area(ST_Intersection(geom, ST_Translate(env, n+nit, 0)))
FROM T CROSS JOIN ref
WHERE ST_Area(ST_Intersection(geom, ST_Translate(env, n+nit, 0)))>
ref.targ_area
),
bi(n) AS(
SELECT n
FROM T
ORDER BY n DESC LIMIT 1
)
SELECT bi.n,
ST_Difference(geom, ST_Translate(ref.env, n,0)) As geom_part1,
ST_Intersection(geom, ST_Translate(ref.env, n,0)) As geom_part2
FROM bi CROSS JOIN ref) AS TT));
I have a data table with destinations and LAT/LON data (~100K records)
DESTINATIONS {
id,
lat,
lon,
...
}
Now I need to insert distances into a new table...
DISTANCES {
id_a,
id_b,
distance
}
What's the best way to do that?
I don't need all data (cartesian product), only the 100 closest.
No duplicates (a_id+b_id == b_id+a_id), e.g. [NYC:Chicago] == [Chicago:NYC] (same distance)
Not by itself (a_id != b_id), because it 0 miles from [NYC:NYC] ;)
This is the calculation (in kilometers/meters):
ROUND(111045
* DEGREES(ACOS(COS(RADIANS(A.lat))
* COS(RADIANS(B.lat))
* COS(RADIANS(A.lon) - RADIANS(B.lon))
+ SIN(RADIANS(A.lat))
* SIN(RADIANS(B.lat)))),0)
AS 'distance'
Okay, the JOIN is no problem, but how can I implement the three "filters"?
Maybe with a WHILE loop and SUBSELECT LIMIT/TOP 100 ORDER BY distance ASC?
Or is it also possible to INSERT by JOIN?
Does somebody have a idea?
Psuedocode:
INSERT INTO [newTable] (ColumnList...)
SELECT TOP 100 a.id, b.id, DistanceFormula(a.id, b.id)
FROM Destination a
CROSS JOIN Destination b
WHERE a.id<b.id
ORDER BY DistanceFormula(a.id, b.id) ASC
EDIT to get 100 b for every a:
INSERT INTO [newTable] (ColumnList...)
SELECT a.id, b.id, DistanceFormula(a.id, b.id)
FROM Destination a
INNER JOIN Destination b
ON b.id=(
SELECT TOP 100 c.id
FROM Destination c
WHERE a.id<c.id
ORDER BY DistanceFormula(a.id, c.id) ASC
)
I've simplified it (distcalc)...
INSERT INTO [DISTANCES] (id_a, id_b, distance)
SELECT
A.id,
B.id,
25 /*ROUND(111045 * DEGREES(ACOS(COS(RADIANS(A.geo_lat)) * COS(RADIANS(B.geo_lat)) * COS(RADIANS(A.geo_lon) - RADIANS(B.geo_lon)) + SIN(RADIANS(A.geo_lat)) * SIN(RADIANS(B.geo_lat)))),0)*/
FROM [DESTINATIONS] AS A
INNER JOIN [DESTINATIONS] AS B
ON b.id IN(
SELECT TOP 100
C.id
FROM [DESTINATIONS] AS C
WHERE
A.id < C.id
ORDER BY A.id /*ROUND(111045 * DEGREES(ACOS(COS(RADIANS(A.geo_lat)) * COS(RADIANS(C.geo_lat)) * COS(RADIANS(A.geo_lon) - RADIANS(C.geo_lon)) + SIN(RADIANS(A.geo_lat)) * SIN(RADIANS(C.geo_lat)))),0)*/ ASC
)
You mean like this?
Okay. That works. :)
But it is definitely too slow!
I'll program a routine that returns only the 100 nearest results on request.
And another (sub) routine will insert/update these (program-sided) results with timestamp into the distances table, so that it's possible to accessed to any existing results by the next call.
But thank you very very much! :)
this is my first time posting a question and I hope not to waste any of your time. Thanks in advance for any help.
I am attempting to calculate the distance between every zip-code in the United States and a list of 495 buildings (384 unique). I already have the latitude/longitude and an approximation for the distance. My problem comes in the form of not being sure how to update the rows in the table individually. For instance, the set function I use will change all the rows in the table to the most recently calculated value. I am using Sql Server 2008 R2, and have the following tables with the following columns: tbl_Zip_Code_Coordinates w/ Zip, Lat, Long, State, Closest_Building, Zip_ID; Tbl_Building_Coordinates w/ Zip, Lat, Long, Store_ID
Here is the code I am currently attempting
Declare #ZipID int = 1
DECLARE #ZipLat Decimal (12,6)
DECLARE #ZipLong Decimal (12,6)
while (#ZipID < 43194)
Begin
Set #ZipLat = (Select Lat from Zip_Code_Coordinates where Zip_ID = #ZipID)
Set #ZipLong= (Select Long from Zip_Code_Coordinates where Zip_ID = #ZipID)
Update Zip_Code_Coordinates
set Closest_Building = (select min(Sqrt( SQUARE((#ZipLat - Buildings_Coordinates.lat)*68.96799738887665)
+SQUARE((#ZipLong - Buildings_Coordinates.Long)*54.69366983621222)))
from Buildings_Coordinates, Zip_Code_Coordinates
where Zip_ID = #ZipID
)
set #ZipID = #ZipID + 1
end
I suppose you also need the ID for the closest building. If so, you could use the following code as is or with some modifications:
;WITH mycte
AS (SELECT Z.zipid,
B.bldgid,
(( Sqrt(Square((Z.lat - B.lat)*68.96799738887665)
+ Square((Z.long - B.long)*54.69366983621222)) )) Dist,
Row_number()
OVER (
partition BY zipid
ORDER BY ((Sqrt( Square((Z.lat - B.lat)*68.96799738887665) +
Square((
Z.long
- B.long)*54.69366983621222)))))
DistOrder
FROM Buildings_Coordinates B
CROSS JOIN Zip_Code_Coordinates Z)
UPDATE z
SET closest_buildingid = bldgid,
closest_bldgdistance = dist
FROM Zip_Code_Coordinates Z
INNER JOIN mycte C
ON Z.zipid = C.zipid
WHERE distorder = 1
I eventually found a code that worked:
Select Sub2.Zip_ID, Sub2.Distance into Temp_Table from
(
select
Sub1.Zip_ID as Zip_ID,
MIN(Sub1.Distance) as Distance from
(
SELECT
Z.Zip_ID,
((Sqrt(Square((Z.lat - S.lat) *68.96799738887665)
+ Square((Z.long - S.long)*54.69366983621222)))) Distance
FROM
Tbl_Stores_Coordinates S
CROSS JOIN Zip_Code_Coordinates Z
)Sub1
group by Sub1.Zip_ID) Sub2
Update Zip_Code_Coordinates
Set Closest_Store = temp_Table.Distance
from temp_Table
where Zip_Code_Coordinates.Zip_ID = Temp_Table.Zip_ID;
drop table temp_Table;
select * from Zip_Code_Coordinates
select * from Temp_Table
The only problem I had with this was that I couldn't pass the which Building has the shortest distance to each Zip Code through the Subquery. What I ended up doing was creating a temporary table with another query and performed a Join where the Distances and Zip_ID's were the same.