PostgreSQL distance between 2 points stored in table - postgresql

I have four fields lat1,lng1,lat2,lng2 and I would like to calculate the distance in km between them (rounded to 1 deciaml place).
For example
table:
-----lat1-----|--- lng1 ----| ----lat2----|---lng2----|
53.4603045 | 9,98243 | 53,4470272 | 9,9956593 |
I have tried:
SELECT ST_Distance(POINT(lat1, lng1),POINT(lat2, lng2)) as distance FROM table
What do I have to change?

PostGIS is the answer.
I'll give it a go

If your postgres comes with plv8 extension (javascript), you can do something like this:
CREATE OR REPLACE FUNCTION distance_example(_la integer, _lb integer) RETURNS
real AS $$
var locations = plv8.execute("SELECT * FROM location WHERE id IN ($1, $2)", [_la, _lb]);
var x = locations[0].longitude - locations[1].longitude;
var y = locations[0].latitude - locations[1].latitude;
return Math.sqrt(x * x + y * y);
$$ LANGUAGE plv8 IMMUTABLE STRICT;
select distance_example();

Related

PostGIS returns record as datatype. This is unexpected

I have this query
WITH buffered AS (
SELECT
ST_Buffer(geom , 10, 'endcap=round join=round') AS geom,
id
FROM line),
hexagons AS (
SELECT
ST_HexagonGrid(10, buffered.geom) AS hex,
buffered.id
FROM buffered
) SELECT * FROM hexagons;
This gives the datatype record in the column hex. This is unexpected. I expect geometry as a datatype. Why is that?
According to the documentation, the function ST_HexagonGrid returns a setof record. These records contain however a geometry attribute called geom, so in order to access the geometry of this record you have to wrap the variable with parenthesis () and call the attribute with a dot ., e.g.
SELECT (hex).geom FROM hexagons;
or just access fetch all attributes using * (in this case, i,j and geom):
SELECT (hex).* FROM hexagons;
Demo (PostGIS 3.1):
WITH j (hex) AS (
SELECT
ST_HexagonGrid(
10,ST_Buffer('LINESTRING(-105.55 41.11,-115.48 37.16,-109.29 29.38,-98.34 27.13)',1))
)
SELECT ST_AsText((hex).geom,2) FROM j;
st_astext
----------------------------------------------------------------------------------------
POLYGON((-130 34.64,-125 25.98,-115 25.98,-110 34.64,-115 43.3,-125 43.3,-130 34.64))
POLYGON((-115 25.98,-110 17.32,-100 17.32,-95 25.98,-100 34.64,-110 34.64,-115 25.98))
POLYGON((-115 43.3,-110 34.64,-100 34.64,-95 43.3,-100 51.96,-110 51.96,-115 43.3))
POLYGON((-100 34.64,-95 25.98,-85 25.98,-80 34.64,-85 43.3,-95 43.3,-100 34.64))
As ST_HexagonGrid returns a setof record, you can access the record atributes using a LATERAL as described here, or just call the function in the FROM clause:
SELECT i,j,ST_AsText(geom,2) FROM
ST_HexagonGrid(
10,ST_Buffer('LINESTRING(-105.55 41.11,-115.48 37.16,-109.29 29.38,-98.34 27.13)',1));
i | j | st_astext
----+---+----------------------------------------------------------------------------------------
-8 | 2 | POLYGON((-130 34.64,-125 25.98,-115 25.98,-110 34.64,-115 43.3,-125 43.3,-130 34.64))
-7 | 1 | POLYGON((-115 25.98,-110 17.32,-100 17.32,-95 25.98,-100 34.64,-110 34.64,-115 25.98))
-7 | 2 | POLYGON((-115 43.3,-110 34.64,-100 34.64,-95 43.3,-100 51.96,-110 51.96,-115 43.3))
-6 | 2 | POLYGON((-100 34.64,-95 25.98,-85 25.98,-80 34.64,-85 43.3,-95 43.3,-100 34.64))
Further reading: How to divide world into cells (grid)

How to select float values by mantissa length?

I have a column with double precision values. I am trying to select only those with precision greater than a tenth (e.g., 24.13, 1.347, etc.).
I know I can convert the float to a string and query based on string length using the following:
select * from schema.table where char_length(to_char(float_column, 'FM999999999D999999999')) > 3;
This will return all the rows with more than 1 decimal place if the integer portion of the number is single digit (eg., it will return 24.1).
How can I select any float value that has a mitissa length greater than one?
Use split_part():
with my_table(float_column) as (
values
(24.13::float), (1.347), (12345), (.1)
)
select float_column, split_part(float_column::text, '.', 2)
from my_table;
float_column | split_part
--------------+------------
24.13 | 13
1.347 | 347
12345 |
0.1 | 1
(4 rows)
So your query may look like this:
select *
from my_table
where length(split_part(float_column::text, '.', 2)) > 1;
float_column
--------------
24.13
1.347
(2 rows)
If you stored the value as numeric (fixed point versus floating point), then you could simply do:
where floor(col * 100) <> floor(col * 10) * 10
Unfortunately, there are edge cases where this logic doesn't work for floating point numbers, because you can get something like 21.99999999997.
look for rounding errors:
where round(float_column,1) <> round(float_column,5)

Altering STEndPoint or STStartpoint of a line using SQL Spatial

I have built SQL Spatial triggers so that if users of our GIS move a pit (point feature) the pipes (lines) that end or start at the pit have their endpoint/startpoint altered. I basically replace the entire geometry.
The problem is, some pipes have more than one point. In this case I only want to alter the endpoint or startpoint and leave the other points as is, otherwise the other vertices are lost. Can you set the STEndPoint/STStartPoint or is there another way to only alter these points of a line?
Below code is my attempt so far, after trying the recursive CTE which I gave up on as I couldn't reference a table variable inside this. Instead I have built a temp table which will store the x and y values. It would be good to not have to repeat the script 4 times but it works (pipes will have 2 to 4 points). I now need to alter the geometry x/y for just the pipe endpoints/startpoints after the user moves the pit. I Will look at this tomorrow.
--Create temp table to store X and Y values of pipe vertices
CREATE table #PipeGeom
(
ID int IDENTITY(1,1) NOT NULL,
N int,
X float,
Y float,
PipeGMSCKey int
)
--insert vertices. Most pipes have two points. A few have 4.
DECLARE #VertexCount INT
SET #VertexCount = 1
INSERT INTO #PipeGeom (N, X, Y, PipeGMSCKey)
SELECT N=#VertexCount, pipe.Geometry_SPA.STPointN(1).STX AS X, pipe.Geometry_SPA.STPointN(1).STY AS Y, pipe.GMSC_Key FROM [Assets_GMSC_Dev].[dbo].[vw_Pipes] pipe
INNER JOIN [Assets_GMSC_Dev].[dbo].[vw_Pits] pit ON pipe.Geometry_SPA.STDistance(pit.Geometry_SPA) <0.1
WHERE pit.GMSC_Key = '1481532'
--UNION ALL
INSERT INTO #PipeGeom (N, X, Y, PipeGMSCKey)
SELECT N=2, pipe.Geometry_SPA.STPointN(2).STX AS X, pipe.Geometry_SPA.STPointN(2).STY AS Y, pipe.GMSC_Key FROM [Assets_GMSC_Dev].[dbo].[vw_Pipes] pipe
INNER JOIN [Assets_GMSC_Dev].[dbo].[vw_Pits] pit ON pipe.Geometry_SPA.STDistance(pit.Geometry_SPA) <0.1
WHERE pit.GMSC_Key = '1481532'
--UNION ALL
INSERT INTO #PipeGeom (N, X, Y, PipeGMSCKey)
SELECT N=3, pipe.Geometry_SPA.STPointN(3).STX AS X, pipe.Geometry_SPA.STPointN(3).STY AS Y, pipe.GMSC_Key FROM [Assets_GMSC_Dev].[dbo].[vw_Pipes] pipe
INNER JOIN [Assets_GMSC_Dev].[dbo].[vw_Pits] pit ON pipe.Geometry_SPA.STDistance(pit.Geometry_SPA) <0.1
WHERE pit.GMSC_Key = '1481532'
--UNION ALL
INSERT INTO #PipeGeom (N, X, Y, PipeGMSCKey)
SELECT N=4, pipe.Geometry_SPA.STPointN(4).STX AS X, pipe.Geometry_SPA.STPointN(4).STY AS Y, pipe.GMSC_Key FROM [Assets_GMSC_Dev].[dbo].[vw_Pipes] pipe
INNER JOIN [Assets_GMSC_Dev].[dbo].[vw_Pits] pit ON pipe.Geometry_SPA.STDistance(pit.Geometry_SPA) <0.1
WHERE pit.GMSC_Key = '1481532'
--User moves pit, then get new pit location, which pipe start/endpoint will need to move to.
--New Start and Finish points
DECLARE #PitX FLOAT = (SELECT Geometry_SPA.STX FROM vw_Pits WHERE GMSC_Key = '1481532');
DECLARE #PitY FLOAT = (SELECT Geometry_SPA.STY FROM vw_Pits WHERE GMSC_Key = '1481532');
--need to grab just the endpoint/startpoint of line here and build Geometry String
drop table #PipeGeom
Thanks
This might not be an optimum solution but hope it's of use. Also, I'm on 2014 /2016 so it's untested on 2008R2.
--Initial (Existing) Line
DECLARE #g GEOMETRY = GEOMETRY::STGeomFromText('LINESTRING(5 5, 20 10, 30 20, 0 50)', 0);
--New Start and Finish points
DECLARE #X1 VARCHAR(2) = '10'
DECLARE #Y1 VARCHAR(2) = '10'
DECLARE #X2 VARCHAR(2) = '20'
DECLARE #Y2 VARCHAR(2) = '20'
DECLARE #Coords NVARCHAR(MAX)
;WITH Points(N, X, Y) AS
(
SELECT 2, #g.STPointN(2).STX, #g.STPointN(2).STY
UNION ALL
SELECT N + 1, #g.STPointN(N + 1).STX, #g.STPointN(N + 1).STY
FROM Points GP
WHERE N < #g.STNumPoints() - 1
)
SELECT #Coords = COALESCE(#Coords + ', ', '') + CAST(X AS NVARCHAR(50)) + ' ' + CAST(Y AS NVARCHAR(50)) FROM Points
SELECT GEOMETRY::STGeomFromText('LINESTRING('+#X1+' '+#Y1+', '+#Coords+', '+#X2+' '+#Y2+' '+')', 0) NewGeom
This uses a recursive CTE to parse out the co-ords except the first and last for the Geometry #G into a Nvarchar. The new first and last points are concatenated and a new Geometry returned. You could probably wrap this up in a SP or function.

wrong result with floating numbers

I have this table with 1 records. Im trying to compute something call Puntaje, to get the Puntaje Result I have to follow the following formula:
Puntaje = (Infracciones * 10) / Horas
Horas = Segundos / 60 / 60
I wrote the following script, but I have some doubt and problem.
1) Is there another way to assign the values to #variables or another way to compute the sum?
2) Why the Puntaje result is 0.00, have to be: 0.854
Im using MS SQL Server 2012
Can someone help me to resolve this? Thank you in advance.
/* content of table: #Customer_Drivers
DriverId Segundos KM QtyExcesos QtyFreAce QtyDesc Puntaje IDC
6172 717243 1782 17 0 0 0 0
*/
DECLARE #Customer_Drivers TABLE (
DriverId INT,
Segundos INT,
KM INT,
QtyExcesos INT,
QtyFreAce INT,
QtyDesc INT,
Puntaje INT,
IDC INT
);
SET NOCOUNT ON;
INSERT INTO #Customer_Drivers (DriverId, Segundos, KM, QtyExcesos, QtyFreAce, QtyDesc, Puntaje, IDC)
VALUES (6172, 717243, 1782, 17, 0, 0, 0, 0);
SET NOCOUNT OFF;
DECLARE #DriverId INT = 6172;
DECLARE #Horas INT;
DECLARE #QtyExcesos INT ;
DECLARE #QtyFreAce INT ;
DECLARE #QtyDesc INT ;
DECLARE #Infracciones INT;
DECLARE #Puntaje Decimal(18,2);
SET #Horas = (SELECT Segundos FROM #Customer_Drivers WHERE DriverId = #DriverId) / 60 / 60;
SET #QtyExcesos = (SELECT QtyExcesos FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #QtyFreAce = (SELECT QtyFreAce FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #QtyDesc = (SELECT QtyDesc FROM #Customer_Drivers WHERE DriverId = #DriverId);
SET #Infracciones = (#QtyExcesos + #QtyFreAce + #QtyDesc);
SET #Puntaje = ( #Infracciones * 10) /#Horas;
PRINT #Horas
PRINT #QtyExcesos
PRINT #QtyFreAce
PRINT #QtyDesc
PRINT #Puntaje
/* OUTPUT
199 -- #Horas
17 -- #QtyExcesos
0 -- #FreAce
0 -- #QtyDesc
0.00 -- #Puntaje must be = 0.854
*/
Even though #Puntaje is declared as Decimal(18,2), that doesn't mean your calculation will be treated as a decimal. The problem is that ( #Infracciones * 10) / #Horas is using all integers so this expression will result in the integer value 0. Then this integer 0 is converted to a decimal and stored in #Puntaje.
To fix this, you need to convert part of the expression to a decimal first so that the result will be a decimal:
SET #Puntaje = ( CAST(#Infracciones AS Decimal(18,2)) * 10) / #Horas
You are using integers in your calculation, so the result will be rounded off (or truncated) to the nearest integer. Use decimal values, or use 'cast' :
#Puntaje = (cast(#Infracciones as decimal(18,2)) * 10.0) / cast(#Horas as decimal(18,2))
Check my syntax - just typed this on without trying it
1) You can use SELECT #Horas = Segundos/3600, #QtyExcesos = QtyExcesos ... FROM [RS_Reports].[dbo].[Customer_Drivers] WHERE DriverId = #DriverId. This should work providing that there is one line of results.
2) Already answered by others, you have to divide by decimal to get a decimal, i.e. you'll have to convert #Horas to Decimal
From what I've learned. #variable should be some parameters light input parameter and output parameter.... Try to execute your Stored Procedure and see what you got in SQL Server Management Studio.
There should be a return value.

PostgreSQL latitude longitude query

i have latitude and longitude columns in location table in PostgreSQL database,
and I am trying to execute distance query with a PostgreSQL function.
I read this chapter of the manual:
https://www.postgresql.org/docs/current/static/earthdistance.html
but I think I'm missing something there.
How should I do that? Are there more examples available
Here's another example using the point operator:
Initial setup (only need to run once):
create extension cube;
create extension earthdistance;
And then the query:
select (point(-0.1277,51.5073) <#> point(-74.006,40.7144)) as distance;
distance
------------------
3461.10547602474
(1 row)
Note that points are created with LONGITUDE FIRST. Per the documentation:
Points are taken as (longitude, latitude) and not vice versa because longitude is closer to the intuitive idea of x-axis and latitude to y-axis.
Which is terrible design... but that's the way it is.
Your output will be in miles.
Gives the distance in statute miles between two points on the Earth's surface.
This module is optional and is not installed in the default PostgreSQL instalatlion. You must install it from the contrib directory.
You can use the following function to calculate the approximate distance between coordinates (in miles):
CREATE OR REPLACE FUNCTION distance(lat1 FLOAT, lon1 FLOAT, lat2 FLOAT, lon2 FLOAT) RETURNS FLOAT AS $$
DECLARE
x float = 69.1 * (lat2 - lat1);
y float = 69.1 * (lon2 - lon1) * cos(lat1 / 57.3);
BEGIN
RETURN sqrt(x * x + y * y);
END
$$ LANGUAGE plpgsql;
A more accurate version of #strkol's answer, using the Haversine formula
CREATE OR REPLACE FUNCTION distance(
lat1 double precision,
lon1 double precision,
lat2 double precision,
lon2 double precision)
RETURNS double precision AS
$BODY$
DECLARE
R integer = 6371e3; -- Meters
rad double precision = 0.01745329252;
φ1 double precision = lat1 * rad;
φ2 double precision = lat2 * rad;
Δφ double precision = (lat2-lat1) * rad;
Δλ double precision = (lon2-lon1) * rad;
a double precision = sin(Δφ/2) * sin(Δφ/2) + cos(φ1) * cos(φ2) * sin(Δλ/2) * sin(Δλ/2);
c double precision = 2 * atan2(sqrt(a), sqrt(1-a));
BEGIN
RETURN R * c;
END
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Input is in degrees (e.g. 52.34273489, 6.23847) and output is in meters.
Assuming you've installed the earthdistance module correctly, this will give you the distance in miles between two cities. This method uses the simpler point-based earth distances. Note that the arguments to point() are first longitude, then latitude.
create table lat_lon (
city varchar(50) primary key,
lat float8 not null,
lon float8 not null
);
insert into lat_lon values
('London, GB', 51.67234320, 0.14787970),
('New York, NY', 40.91524130, -73.7002720);
select
(
(select point(lon,lat) from lat_lon where city = 'London, GB') <#>
(select point(lon,lat) from lat_lon where city = 'New York, NY')
) as distance_miles
distance_miles
--
3447.58672105301
An alternate Haversine formula, returning miles. (source)
CREATE OR REPLACE FUNCTION public.geodistance(
latitude1 double precision,
longitude1 double precision,
latitude2 double precision,
longitude2 double precision)
RETURNS double precision AS
$BODY$
SELECT asin(
sqrt(
sin(radians($3-$1)/2)^2 +
sin(radians($4-$2)/2)^2 *
cos(radians($1)) *
cos(radians($3))
)
) * 7926.3352 AS distance;
$BODY$
LANGUAGE sql IMMUTABLE
COST 100;