Postgis calculate the distance between points more than two - postgis

I have a table that has a position in it. Is there any solid way to calculate the distance between A --> B --> C --> D etc using Postgis.
Distance = AB + AC + CD etc.
Each point has {lat, lng}.
I appreciate any help you can provide.

You can use lead or lag to work with next/previous row of a window. You can then compute the distance and sum it if you want, in an outer query.
SELECT sum(dist)
FROM (
SELECT id, ST_DistanceSphere(geom, lead(geom) OVER(ORDER BY id)) as dist
FROM myTable) sub;

Related

Dimension for geozones or Lat & Long in data warehouse

I have a DimPlace dimension that has the name of the place (manually entered by the user) and the latitude and longitude of the place (automatically captured). Since the Places are entered manually the same place could be in there multiple time with different names, additionally, two distinct places could be very close to each other.
We want to be able to analyze the MPG between two "places" but we want to group them to make a larger area - i.e. using lat & long put all the various spellings of one location, as well as distinct but very close locations, in one record.
I am planning on making a new dimension for this - something like DimPlaceGeozone. I am looking for a resource to help with loading all the lat & long values mapped to ... something?? Maybe postal code, or city name? Sometimes you can find a script to load common dimensions (like DimTime) - I would love something similar for lat & long values in North America?
I've done something similar in the past... The one stumbling block I hit up front was that 2 locations, straddling a border could be physically closer together than 2 locations that are both in the same area.
I got around it by creating a "double grid" system that causes each location to fall into 4 areas. That way 2 locations that share at least 1 "area" you know they are within range of each other.
Here's an example, covering most of the United States...
IF OBJECT_ID('tempdb..#LatLngAreas', 'U') IS NOT NULL
DROP TABLE #LatLngAreas;
GO
WITH
cte_Lat AS (
SELECT
t.n,
BegLatRange = -37.9 + (t.n / 10.0),
EndLatRange = -37.7 + (t.n / 10.0)
FROM
dbo.tfn_Tally(1030, 0) t
),
cte_Lng AS (
SELECT
t.n,
BegLngRange = -159.7 + (t.n / 10.0),
EndLngRange = -159.5 + (t.n / 10.0)
FROM
dbo.tfn_Tally(3050, 0) t
)
SELECT
Area_ID = ROW_NUMBER() OVER (ORDER BY lat.n, lng.n),
lat.BegLatRange,
lat.EndLatRange,
lng.BegLngRange,
lng.EndLngRange
INTO #LatLngAreas
FROM
cte_Lat lat
CROSS JOIN cte_Lng lng;
SELECT
b3.Branch_ID,
b3.Name,
b3.Lat,
b3.Lng,
lla.Area_ID
FROM
dbo.ContactBranch b3 -- replace with DimPlace
JOIN #LatLngAreas lla
ON b3.Lat BETWEEN lla.BegLatRange AND lla.EndLatRange
AND b3.lng BETWEEN lla.BegLngRange AND lla.EndLngRange;
HTH,
Jason

Table A results based on distance to point in table B

I have 2 tables, locations and zipcodes.
Both the locations and zipcodes tables have a single point geometry.
I wrote a query that I can pass string values for lng,lat to, but I'd like to the same thing by only passing in a zipcode, which is what the distance results would be based off of.
SELECT *, ST_distance(geometry::geography, ST_PointFromText('POINT(-118.0754445 32.4836479)', 4326)::geography) as dist
FROM locations
WHERE st_dwithin(geometry, ST_PointFromText('POINT(-118.0754445 32.4836479)', 4326)::geography, 1609)
ORDER BY dist;
Put both tables in the FROM clause (assign them a alias) and add a where condition with the zipcode you need:
SELECT *, ST_distance(l.geometry::geography, zc.geometry::geography ) as dist
FROM locations AS l, zipcodes AS zc
WHERE st_dwithin(l.geometry, zc.geometry::geography), 1609) AND
zc.code = 11111
ORDER BY dist;

Picking out pairs from SQL Server

I am working on exercise 16 from SQL-EX.com
Find the pairs of PC models having identical speeds and RAM.
As a result, each resulting pair is shown only once, i.e. (i, j) but not (j, i).
Result set: model with higher number, model with lower number, speed, and RAM.
I used the following query
SELECT B.code, B.model AS BM, A.code, A.model, A.speed, A.ram
FROM PC A
JOIN PC B
ON A.speed = B.speed AND A.ram = B.ram
WHERE A.model <> B.model
ORDER BY B.model ASC
How do I retrieve only the pairs where BM is higher than model?
Instead of using <>, use <:
SELECT
a.model,
b.model,
a.speed,
a.ram
FROM PC a
INNER JOIN PC b
ON b.speed = a.speed
AND b.ram = a.ram
AND b.model < a.model
Change this line:
WHERE A.model <> B.model
To this:
WHERE A.model > B.model
You also need to select the correct columns, but getting that WHERE expression right was the hard part.

Selecting Nodes Separating Line Segments

I want to select nodes separating line segments in a layer. I want to select nodes only where they are intersected by two lines, NOT when they meet with more than two line (e.g. a T intersection or four way intersection, etc.).
Here's the best picture I can give (I dont have the reputation to post pictures). The --- line on the left is the first segment and the --x--x--x line on the right the second. The O is the node in the middle I want to select.
--------------------------------------0--x---x--x---x---x---x--x--x--x--x--x--x--x
I do NOT want to select nodes where more than two lines touch the node.
So far I have tried this query
CREATE TABLE contacts_st_touching_faults as
SELECT ST_Intersection(a.the_geom, b.the_geom), Count(Distinct a.gid) = 2
FROM final_layer as a, final_layer as b
WHERE ST_Touches(a.the_geom, b.the_geom)
AND a.gid != b.gid
GROUP BY ST_Intersection(a.the_geom, b.the_geom)
When I run this query it gives me intersections with more than two lines intersecting (T intersections and 4 way intersections).
I have also tried subing ST_intersects in and that didn't seem to work as well as ST_touches, but if you know how to make them work or any other method, it would be much appreciated!
Thanks for the help!
This should work:
WITH contacts AS(
SELECT a.gid AS gid1,b.gid AS gid2, ST_Intersection(a.the_geom, b.the_geom) AS intersection
FROM final_layer as a, final_layer as b
WHERE ST_Touches(a.the_geom, b.the_geom)
AND a.gid<b.gid
)
SELECT *
FROM contacts c1
LEFT JOIN contacts c2
ON ((c1.gid1=c2.gid1 AND c1.gid2<>c2.gid2) OR (c1.gid1=c2.gid2 AND c1.gid1<>c1.gid2))
AND c1.intersection=c2.intersection
WHERE c2.gid1 IS NULL;
It will perform better if ST_Intersection is moved to the final query but I wanted to make it simple.
This will list nodes with tow lines intersecting.
SELECT array_agg(gid) AS gids, count(gid) AS count, geom
FROM
-- lists all vertices (points) from lines
(SELECT gid, (ST_DumpPoints(geom)).geom AS geom
FROM lines_layer) AS p
GROUP BY p.geom
HAVING count(gid) = 2
ORDER BY count(gid);
For all nodes, replace '= 2' with '> 1'

Is a point within a geographical radius - SQL Server 2008

Given the following data, would it be possible, and if so which would be the most efficient method of determining whether the location 'Shurdington' in the first table is contained within the given radius's of any of the locations in the second table.
The GeoData column is of the 'geography' type, so using SQL Servers spatial features are an option as well as using latitude and longitude.
Location GeoData Latitude Longitude
===========================================================
Shurdington XXXXXXXXXX 51.8677979 -2.113189
ID Location GeoData Latitude Longitude Radius
==============================================================================
1000 Gloucester XXXXXXXXXX 51.8907127 -2.274598 10
1001 Leafield XXXXXXXXXX 51.8360519 -1.537438 10
1002 Wotherton XXXXXXXXXX 52.5975151 -3.061798 5
1004 Nether Langwith XXXXXXXXXX 53.2275276 -1.212108 20
1005 Bromley XXXXXXXXXX 51.4152069 0.0292294 10
Any assistance is greatly apprecieded.
Create Data
CREATE TABLE #Data (
Id int,
Location nvarchar(50),
Latitude decimal(10,5),
Longitude decimal(10,5),
Radius int
)
INSERT #Data (Id,Location,Latitude,Longitude,Radius) VALUES
(1000,'Gloucester', 51.8907127 ,-2.274598 , 20), -- Increased to 20
(1001,'Leafield', 51.8360519 , -1.537438 , 10),
(1002,'Wotherton', 52.5975151, -3.061798 , 5),
(1004,'Nether Langwith', 53.2275276 , -1.212108 , 20),
(1005,'Bromley', 51.4152069 , 0.0292294 , 10)
Test
Declare your point of interest as a POINT
DECLARE #p GEOGRAPHY = GEOGRAPHY::STGeomFromText('POINT(-2.113189 51.8677979)', 4326);
To find out if it is in the radius of another point:
-- First create a Point.
DECLARE #point GEOGRAPHY = GEOGRAPHY::STGeomFromText('POINT(-2.27460 51.89071)', 4326);
-- Buffer the point (meters) and check if the 1st point intersects
SELECT #point.STBuffer(50000).STIntersects(#p)
Combining it all into a single query:
select *,
GEOGRAPHY::STGeomFromText('POINT('+
convert(nvarchar(20), Longitude)+' '+
convert( nvarchar(20), Latitude)+')', 4326)
.STBuffer(Radius * 1000).STIntersects(#p) as [Intersects]
from #Data
Gives:
Id Location Latitude Longitude Radius Intersects
1000 Gloucester 51.89071 -2.27460 20 1
1001 Leafield 51.83605 -1.53744 10 0
1002 Wotherton 52.59752 -3.06180 5 0
1004 Nether Langwith 53.22753 -1.21211 20 0
1005 Bromley 51.41521 0.02923 10 0
Re: Efficiency. With some correct indexing it appears SQL's spatial indexes can be very quick
If you want to do the maths yourself, you could use Equirectangular approximation based upon Pythagoras. The formula is:
var x = (lon2-lon1) * Math.cos((lat1+lat2)/2);
var y = (lat2-lat1);
var d = Math.sqrt(x*x + y*y) * R;
In terms of SQL, this should give those locations in your 2nd table that contain your entry in the 1st within their radius:
SELECT *
FROM Table2 t2
WHERE EXISTS (
SELECT 1 FROM Table1 t1
WHERE
ABS (
SQRT (
(SQUARE((RADIANS(t2.longitude) - RADIANS(t1.longitude)) * COS((RADIANS(t2.Latitude) + RADIANS(t1.Latitude))/2))) +
(SQUARE(RADIANS(t1.Latitude) - RADIANS(t2.Latitude)))
) * 6371 --Earth radius in km, use 3959 for miles
)
<= t2.Radius
)
Note that this is not the most accurate method available but is likely good enough. If you are looking at distances that stretch across the globe you may wish to Google 'haversine' formula.
It may be worth comparing this with Paddy's solution to see how well they agree and which performs best.
You calculate the distance between the two points and compare this distance to the given radius.
For calculating short distances, you can use the formula at Wikipedia - Geographical distance - Spherical Earth projected to a plane, which claims to be "very fast and produces fairly accurate result for small distances".
According to the formula, you need the difference in latitudes and longitudes and the mean latitude
with geo as (select g1.id, g1.latitude as lat1, g1.longitude as long1, g1.radius,
g2.latitude as lat2, g2.longitude as long2
from geography g1
join geography g2 on g2.location = 'shurdington'
and g1.location <> 'shurdington')
base as (select id,
(radians(lat1) - radians(lat2)) as dlat,
(radians(long1) - radians(long2)) as dlong,
(radians(lat1) + radians(lat2)) / 2 as mlat, radius
from geo)
dist as (select id,
6371.009 * sqrt(square(dlat) + square(cos(mlat) * dlong)) as distance,
radius
from base)
select id, distance
from dist
where distance <= radius
I used the with selects as intermediate steps to keep the calculations "readable".

Resources