How do I add the Postgis distance to the result set - postgresql

Here’s my current query which currently returns the name and point of eligible results ordered by distance, how can I include the distance as well in the result set?
Query:
select name, ST_AsText(location) from places where ST_DWithin(
ST_GeogFromText('SRID=4326;POINT($point)'),
location,
$distance
) ORDER BY ST_Distance(
ST_GeogFromText('SRID=4326;POINT($point)'),
location
)
$distance and $point will need to be guarded against sql injection in due course obviously.

SELECT name,
ST_AsText(location),
ST_Distance(ST_GeogFromText('SRID=4326;POINT($point)'),location)
FROM places
WHERE ST_DWithin(ST_GeogFromText('SRID=4326;POINT($point)'), location, $distance)
ORDER BY ST_Distance( ST_GeogFromText('SRID=4326;POINT($point)'), location);
However, for your query to work location should also be casted to a geography (if it is not).

Related

pgRouting: route starting from closest node along edge

I am trying to find a way to route from the closest point on a linestring to my current location (lat, long). So far I am able to get the shortest path but it starts from the very beginning of the linestring (aka source). I am using prg_trsp
http://docs.pgrouting.org/2.0/en/src/trsp/doc/index.html
because it has a feature to specify the starting position along the linestring. I am able to correctly calculate the distance along the linestring and pass the values to the function but cannot figure out how to use the results from the function (pgr_costResult[]) to specify where the route should start (partially along the closest linestring).
I have a feeling I am doing something wrong with the join when I go to join the results from the routing algorithm to my edge table to get the geometry because when I join it uses the edge table's full geometry and not segments. Although, looking at the documentation, I don't see where you get a returned segment from the routing function.
Below is a screenshot of what I am trying to do (red line) and what I have (blue line) the point is the current location. The red line comes from using the pgrouting plugin in qgis with the trsp(edge) selection.
See code below:
Any help would be much appreciated!
SELECT st_linemerge(edgeTable.geom_way) FROM pgr_trsp('SELECT id, source, target, cost FROM edgeTable',
(SELECT id FROM origin),
(SELECT * FROM sourcePos),
(SELECT id FROM destination),
(SELECT * FROM destPos),
false, false) AS shortestPath
JOIN edgeTable ON shortestPath.id2 = edgeTable.id;
origin is the id of the starting route
sourcePos is how far along the linestring to offset
destination is the id of the end linestring
destPos is the fraction of the end linestring
all as specified here: http://docs.pgrouting.org/2.0/en/src/trsp/doc/index.html
Its because pgr_trsp() function doesn't give the output your are excepting. Pg_routing Plugin in QGIS does the snapping of the route generated from pgr_trsp(). So you have the output of red line snapped close to your point. So just pgr_trsp() won't give you your desired output. What you are trying to do is a bit complicated but possible. Here is how I solved this problem
WITH
--Make a start point
start_pt as (
select st_setsrid(st_makepoint(204845.95, 2410097.47), 32643) as starting),
--Make a End Point
end_pt as (
select st_setsrid(st_makepoint(204937.15, 2409430.86), 32643) as ending),
--Select Closest source node and its geom for start point
source_code AS (
select source, geom from edgeTable order by st_distance(geom, (select starting from start_pt)) limit 1),
--Select closest target node and its geom for end point
target_code AS (
select target, geom from edgeTable order by st_distance(geom, (select ending from end_pt)) limit 1),
--Route Union from pgr_trsp()
route as (
SELECT ST_LineMerge(ST_union(geom)) as geom, round( CAST(float8 (st_length(ST_union(geom))/1000) as numeric), 2) as length from (
SELECT geom FROM pgr_trsp(
'SELECT feat_id as id, source, target, cost_len as cost, geom FROM edgeTable',
(select source from source_code), (select target from target_code), false, false
) as di JOIN edgeTable
ON di.id2 = edgeTable.id) as foo)
--Finaly snap the route to precisely matach our start and end point
select ST_Line_Substring(geom,
ST_LineLocatePoint(geom, (select starting from start_pt)),
ST_LineLocatePoint(geom, (select ending from end_pt)))
from route
The only issue I have is that I have to switch Starting and Ending Point in last select statement. This can be handled via writing a function. Here is my output
Hope this Help...

Correct way to add points and calculate distances in miles using postGIS

I'm brand new to PostGIS and want to create a column in my database for a new place that stores its latitude and longitude (a 2D point). Later I will want to be able to find the distances between these two points.
It seems I can accomplish what I want by running the following queries:
CREATE EXTENSION postgis;
CREATE TABLE places (
id SERIAL PRIMARY KEY,
name VARCHAR(500) NOT NULL,
location geometry NOT NULL
);
INSERT INTO places (name, location)
VALUES ('The Place of Luke', ST_SetSRID(ST_MakePoint(-71.1043443253471, 42.3150676015829),4326)),
('The Place of Bob', ST_SetSRID(ST_MakePoint(-75.1043443253471, 43.3150676015829),4326));
SELECT ST_Distance(
(SELECT location FROM places WHERE name = 'The Place of Luke'),
(SELECT location FROM places WHERE name = 'The Place of Bob')
);
I believe this is correctly returning the distance in latitude and longitude of 4.12310562561766, but I have several questions about best practice.
Is geometry the correct type for what I am trying to accomplish? I tried changing geometry to point:
CREATE TABLE places (
id SERIAL PRIMARY KEY,
name VARCHAR(500) NOT NULL,
location point NOT NULL
);
INSERT INTO places (name, location)
VALUES ('The Place of Luke', ST_MakePoint(-71.1043443253471, 42.3150676015829)),
('The Place of Bob', ST_MakePoint(-75.1043443253471, 43.3150676015829));
SELECT ST_Distance(
(SELECT location FROM places WHERE name = 'The Place of Luke'),
(SELECT location FROM places WHERE name = 'The Place of Bob')
);
but I kept getting ERROR: column "location" is of type point but expression is of type geometry. I understand that ST_MakePoint is probably trying to create something of type geometry and that is causing the error, but how do I create something of type point?
The documentation seems pretty insistent that I should be using AddGeometryColumn() instead of adding the column directly to my places table, but I don't understand why and if this will be a problem as my application grows. If all I need to do is find the distance between two places (on a 2D plane) for travel distance by land, will this be a problem for me?
How can I convert this result into miles (and possibly kilometers). Should I be running a different query all together? My understanding of longitude and latitude makes me think that this number being returned will not be a simple linear conversion to miles.
Yes, never use point if you're using PostGIS. Point is a native type. PostGIS is an extension to do just what you want. If everything is in 4326, you should look at the geography type and use it.
Never use AddGeometryColumn() unless you're using PostGIS 1.x. PostGIS 2.x supports type modifiers, and you can use ALTER TABLE instead.
To use get miles, simply return km and * 0.621371192
This is slightly weird, but functional.
SELECT ST_Distance(
(SELECT location FROM places WHERE name = 'The Place of Luke'),
(SELECT location FROM places WHERE name = 'The Place of Bob')
);
Typically you see this in a JOIN working over batches, but it works in correlated subqueries as you're intending too.
SELECT
l1.location,
l2.location,
ST_Distance( l1.location, l2.location )
FROM location AS l1
JOIN location AS l2
ON l2.location = 'The Place of Luke'
WHERE places = 'The Place of Bob';
I would recommend using
Use geography(Point,4326) as the data type for location
Query as ST_Distance(location1::geography, location2::geography)
By casting to geography data type, the result is in meters, instead of degrees, which would be meaningless. Note that 4326 is the same system as WGS84 (what most GPS data is referencing)
See also this Q/A for more details:
https://gis.stackexchange.com/questions/76967/what-is-the-unit-used-in-st-distance
edit:
re: AddGeometryColumn it is not required to use that function, it just a convenience- as far as I know.
edit:
An additional note, not something you asked about, but there is also spatial index type in postgis:
CREATE INDEX loc_idx ON places USING gist (location);

number of points within a radius of another set of points

I have two tables. One is a list of stores (with lat/long). The other is a list of customer addresses (with lat/long). What I want is a query that will return the number of customers within a certain radius for each store in my table. This gives me the total number of customers within 10,000 meters of ANY store, but I'm not sure how to loop it to return one row for each store with a count.
Note that I'm doing this queries using cartoDB, where the_geom is basically long/lat.
SELECT COUNT(*) as customer_count FROM customer_table
WHERE EXISTS(
SELECT 1 FROM store_table
WHERE ST_Distance_Sphere(store_table.the_geom, customer_table.the_geom) < 10000
)
This results in a single row :
customer_count
4009
Suggestions on how to make this work against my problem? I'm open to doing this other ways that might be more efficient (faster).
For reference, the column with store names, which would be in one column is store_identifier.store_table
I'll assume that you use the_geom to represent the coordinate (lat/lon) of store and customer. I will also assume that the_geom is of geography type. Your query will be something like this
select s.id, count(*) as customer_count
from customers c
inner join stores s
on st_dwithin(c.the_geom, s.the_geom, 10000)
group by s.id
This should give you neat table with a store id and count of customers within 10,000 meters from the store.
If the_geom is of type geometry, you query will be very similar but you should use st_distance_sphere() instead in order to express distance in kilometers (not degrees).

Filtering dependent data table, returns results from main table

Can I search in dependent data tables but returns results from main table?
This problem occurs where we have N x N relation in database like in example below: every user can have multiple locations but even if user has many location it is still one physical person.
I want to query sphinx with condition in table locations and return set should be from table users.
Query results will be filter by geo coordinates GEODIST() but its only
information because its not the main subject of this question. Goal is
for example: find persons who have location in 20 kilometers radius from some explicit point.
SQL structure
TABLE users
id PRIMARY KEY
name TEXT
etc...
TABLE locations
id PRIMARY KEY
name TEXT
coord_x FLOAT
coord_y FLOAT
etc...
TABLE user_location
user_id INTEGER FK
location_id INTEGER FK
Of course I can simply JOIN this 3 tables in Sphinx sql_query and filter this set but then I got duplicated persons when person have more than one location.
Any tips how to achieve this goal with Sphinx Search?
Of course I can simply JOIN this 3 tables in Sphinx sql_query and filter this set but then I got duplicated persons when person have more than one location.
Just add a GROUP BY to the sphinx query, then will only ever get own row per user.
You will need to make the users.id a sphinx attribute (so can group on it) and use a primary key from user_location as the sphinx document-id (so its unique)
(gets more complicated if have users that don't have locations, and still want to be able to search then - without the location filter. But it can still be done. Perhaps use a second source on the index, to find the unlocationed users)
SELECT DISTINCT u.*
FROM users u
JOIN user_location ul ON ul.user_id = u.id
JOIN locations l ON l.id = ul.location_id
WHERE ((l.coord_x - <<your X>>) * (l.coord_x - <<your X>>)) +
((l.coord_y - <<your Y>>) * (l.coord_y - <<your Y>>)) < 400;
You might want to wrap this in a SQL language function that takes the location coordinates as parameters and possibly the distance too. Note that this code assumes that coord_x and coord_y are in kilometers. If in some other unit, change the value 400 accordingly.
Note also that the query does not compute the distance to the given point by taking the square root of the squared differences in the two cardinal directions: you are not interested in the distance itself but only in locations being closer than a specified distance from a specified point. So you square that distance and then forget about the square root which is computationally expensive. If your locations table has many records, you will notice the difference.
SELECT *
FROM users u
WHERE EXISTS (
SELECT * FROM user_location ul
JOIN locations l ON l.id = ul.location_id
WHERE ul.user_id = u.id
AND l.coord_x ...
AND l.coord_y ...
);

Order by custom named rows

I’d like to sort my postgres results by some fancy ranking function, but for sake of simplicity, let’s say that I’d like to add two custom rows and sort by them.
SELECT my_table.*,
extract(epoch from (age(current_date, '2012-09-12 10:43:40'::date)))/3600 AS age_in_hours
Fancy_function_counting_distance() AS distance
FROM my_table
ORDER BY distance + age_in_hours;
However, it doesn’t work, since I’m getting error: ERROR: column "distance" does not exist.
Is it possible to order my results by that custom named rows?
I’m running postgres 9.1.x
As per the SQL standard, aliases in the SELECT list are not visible in ORDER BY.
You can use column-position specification (eg ORDER BY 1,2), but that doesn't accept an expression; you cannot ORDER BY 1+2, for example. So you need to use a subquery to generate the result set then sort it in an outer query:
SELECT *
FROM (
SELECT my_table.*,
extract(epoch from (age(current_date, '2012-09-12 10:43:40'::date)))/3600 AS age_in_hours
Fancy_function_counting_distance() AS distance
FROM my_table
) x
ORDER BY distance + age_in_hours;