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;
Related
I am trying to run these lines:
create type _stats_agg_result_type AS (
count bigint,
min double precision,
max double precision,
mean double precision,
variance double precision,
skewness double precision,
kurtosis double precision
);
create or replace function _stats_agg_finalizer(_stats_agg_accum_type)
returns _stats_agg_result_type AS '
BEGIN
RETURN row(
$1.n,
$1.min,
$1.max,
$1.m1,
$1.m2 / nullif(($1.n - 1.0), 0),
case when $1.m2 = 0 then null else sqrt($1.n) * $1.m3 / nullif(($1.m2 ^ 1.5), 0) end,
case when $1.m2 = 0 then null else $1.n * $1.m4 / nullif(($1.m2 * $1.m2) - 3.0, 0) end
);
END;
'
language plpgsql;
Unfortunately I get following error (w.r.t. the _stats_agg_finalizer function):
RETURN must specify a record or row variable in function returning row
Version I'm running:
PostgreSQL 9.2.24 on x86_64-redhat-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-28), 64-bit
I am new to PostgreSQL and I have not been able to fix this error.
Appreciate any help, thanks!
This was enabled in 9.3. I assume it corresponds to this release note:
Allow PL/pgSQL to use RETURN with a composite-type expression (Asif
Rehman)
Previously, in a function returning a composite type, RETURN could
only reference a variable of that type.
So you should be able to rewrite it this way:
create or replace function _stats_agg_finalizer(_stats_agg_result_type)
returns _stats_agg_result_type AS '
declare f _stats_agg_result_type ;
BEGIN
f:=row(
$1.n,
$1.min,
$1.max,
$1.m1,
$1.m2 / nullif(($1.n - 1.0), 0),
case when $1.m2 = 0 then null else sqrt($1.n) * $1.m3 / nullif(($1.m2 ^ 1.5), 0) end,
case when $1.m2 = 0 then null else $1.n * $1.m4 / nullif(($1.m2 * $1.m2) - 3.0, 0) end
);
return f;
END;
'
language plpgsql;
Note that I changed the input type, since you didn't show use the definition of the original type.
I am trying to figure out if I can use a LineString with multiple coordinates, and calculate distances between each of the items in linestring, and another table with coordinates:
I have a few locations that I need to visit for e.g.
A1 40.7120879 -73.9113197
A2 40.7828647 -73.9653551
A3 40.740777 -73.841136
I want to find out distance between these linestring locations and the other locations I will stop at
B1 40.7029334,-73.6208314
B2 40.7037142,-73.6086649
B3 40.7088981,-73.6137852
B4 40.7133267,-73.6056991
B5 40.7185586,-73.6007205
e.g.
A1 B1 - xxa meters
A1 B2 - xxb meters
A1 B3 - xxc meters
A1 B4 - xxd meters
A1 B5 - xxe meters
A2 B1 - xya meters
A2 B2 - xyb meters
A3 B3 - xyc meters
A4 B4 - xyd meters
A5 B5 - xye meters
etc
CREATE TABLE SpatialTable
( id int IDENTITY (1,1),
GeogCol1 geography
);
GO
INSERT INTO SpatialTable (GeogCol1)
VALUES (geography::STGeomFromText('LINESTRING(40.7120879 -73.9113197, 40.7828647 -73.9653551, 40.740777, -73.841136)', 4326))
CREATE TABLE Route
( id int IDENTITY (1,1),
Latitude float,
Longitude float
);
GO
Insert into Route(Latitude,Longitude)
values
(40.7029334,-73.6208314),
(40.7037142,-73.6086649),
(40.7088981,-73.6137852),
(40.7133267,-73.6056991),
(40.7185586,-73.6007205)
Hcaelxxam has just beat me to a solution - and in native SQL. Never-the-less, I've worked it up for you so here it is.
Given a helper class of
public class MyPoint
{
public MyPoint(string title, SqlGeography location)
{
this.Title = title;
this.Location = location;
}
public string Title { get; set; }
public SqlGeography Location { get; set; }
}
Use the following code:
// The line, or route consisting of "waypoints"
SqlGeography lineString = SqlGeography.STLineFromText(new System.Data.SqlTypes.SqlChars("LINESTRING(40.7120879 -73.9113197, 40.7828647 -73.9653551, 40.740777 -73.841136)"), 4326);
// A container for all lookups
List<MyPoint> stopLocations = new List<MyPoint>();
// A container for all waypoints in the route
List<MyPoint> wayPoints = new List<MyPoint>();
// Add the stop locations
stopLocations.Add(new MyPoint("B1", SqlGeography.Point(-73.6208314, 40.7029334, 4326)));
stopLocations.Add(new MyPoint("B2", SqlGeography.Point(-73.6086649, 40.7037142, 4326)));
stopLocations.Add(new MyPoint("B3", SqlGeography.Point(-73.6137852, 40.7088981, 4326)));
stopLocations.Add(new MyPoint("B4", SqlGeography.Point(-73.6056991, 40.7133267, 4326)));
stopLocations.Add(new MyPoint("B5", SqlGeography.Point(-73.6007205, 40.7185586, 4326)));
// Add the waypoints
for (int i = 1; i < (lineString.STNumPoints() + 1); i++)
{
wayPoints.Add(new MyPoint("A" + i, SqlGeography.Point(lineString.STPointN(i).Lat.Value, lineString.STPointN(i).Long.Value, lineString.STSrid.Value)));
}
// Join the data (essentially a cross-join) and calculate distances
var joinAndCalculate = (
from wp in wayPoints
from sl in stopLocations
select new
{
WayPointTitle = wp.Title,
StopLocationTitle = sl.Title,
DistanceInMetres = wp.Location.STDistance(sl.Location)
}
).OrderBy(x => x.WayPointTitle).ThenBy(x => x.StopLocationTitle)
.ToList()
;
// Print to the console, for testing
foreach (var data in joinAndCalculate)
{
Console.WriteLine(data.WayPointTitle + "\t" + data.StopLocationTitle + "\t" + data.DistanceInMetres);
}
If not you, it may help someone else.
There is no inbuilt function for this (that I am aware of at least), but something like this should give you the results you are looking for.
--Index of point on linestring
DECLARE #i INT = 1
--Output
DECLARE #ResultTable TABLE (LinePointNumber INT, PointID INT, Distance FLOAT)
--Identifier for the individual points
DECLARE #id INT
DECLARE #g GEOGRAPHY
--Linestring
DECLARE #geogcol GEOGRAPHY = (SELECT TOP 1 GeogCol1 FROM SpatialTable)
--loop through each point on the linestring
WHILE #i <= #geogcol.STNumPoints()
BEGIN
--turn points into geography and loop through the collection
--if you just cast a float to a varchar it will truncate, so cast to a decimal first to prevent that
DECLARE pointcursor CURSOR FOR SELECT id, GEOGRAPHY::STPointFromText('POINT(' + CAST(CAST(Longitude AS DECIMAL(15,10)) AS VARCHAR(15)) + ' ' + CAST(CAST(Latitude AS DECIMAL(15,10)) AS VARCHAR(15)) + ')', 4326)
OPEN pointcursor
FETCH NEXT FROM pointcursor INTO #id, #g
WHILE ##FETCH_STATUS = 0
BEGIN
--STPointN returns that point, the the distance is STDistance
INSERT INTO #ResultTable (LinePointNumber, PointID, Distance) VALUES (#i, #id, #g.STDistance(#geogcol.STPointN(#i)))
FETCH NEXT FROM pointcursor INTO #id, #g
END
CLOSE pointcursor
DEALLOCATE pointcursor
SET #i = #i + 1
END
SELECT * FROM #ResultTable
I have some entities B and C inheriting from a parent entity A. Hence, I have a joined, multiple inheritance structure in my database.
Furthermore, I have defined some #NamedQueries on these entitites which work well.
I intend to have a #NamedStoredProcedureQuery which is able to find some POIs in a perimeter. I have already implemented a stored procedure which performs a SELECT on the parent table, getting longitude,latitude and radius as parameter and a CALL returns the correct records. The columns to perform the perimeter search are all in the parent table/entity.
Now I want to call this stored procedure from Java using JPA related to the inherited entity. This means that a perimeter search for entities of class B shall return all POIs of class B within the perimeter.
Is it sufficient to define the #NamedStoredProcedureQuery in the parent entity class?
How can I call such a #NamedStoredProcedureQuery from within a #NamedQuery in a subclass?
I created a stored procedure with the necessary IN parameters as well as a further IN parameter for the table to be used.
DELIMITER $$
CREATE PROCEDURE `perimeterSearch`(IN lon double, IN lat double, IN radius double,
IN poiTable varchar(45))
BEGIN
SET #perimeterSearch = CONCAT(
'SELECT
-- poiId,
-- latitude,
-- longitude,
*
FROM
', 'mySchema.', poiTable, ' pt ',
'
LEFT JOIN mySchema.pois p ON p.poiId = pt.poiId
HAVING
-- distance <= radius
(
6371 * acos(
cos(
radians( p.latitude )
) * cos(
radians( ', lat, ' )
) * cos(
radians( p.longitude ) - radians( ', lon, ' )
) + sin(
radians( ', lat, ' )
) * sin(
radians( p.latitude )
)
)
) <= ', radius, '
');
-- ORDER BY
-- distance ASC;
PREPARE stmt FROM #perimeterSearch;
EXECUTE stmt;
END
In my DAO implementation I execute
public List<MyPoiPoiEntity> findMyPoisInPerimeter(final double longitude, final double latitude, final double radius){
em = factory.createEntityManager();
final StoredProcedureQuery ppQuery =
em.createStoredProcedureQuery("perimeterSearch", MyPoiEntity.class)
.registerStoredProcedureParameter("longitude", double.class, ParameterMode.IN)
.setParameter("longitude", longitude)
.registerStoredProcedureParameter("latitude", double.class, ParameterMode.IN)
.setParameter("latitude", latitude)
.registerStoredProcedureParameter("radius", double.class, ParameterMode.IN)
.setParameter("radius", radius)
.registerStoredProcedureParameter("poiTable", String.class, ParameterMode.IN)
.setParameter("poiTable", MyPoiEntity.class.getAnnotation(Table.class).name());
final List<MyPoiEntity> cpEntities = ppQuery.getResultList();
em.close();
return cpEntities;
}
The trick to use the correct table ist done by
.setParameter("poiTable", MyPoiEntity.class.getAnnotation(Table.class).name());
This way I am sure that the correct entity table is used in opposite to use a string literal.
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.
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();