Using PostGIS for location based data - postgresql

I have a table in postgresql 9.2 that stores the latitude and longitude of locations as integer values.
I intend to do something like when a user searches for a location, he also gets information on other locations that are within a 7 mile radius of that searched location.
How do i use postGIS for this since i am new to it. Any idea.?

You'll actually want to store the latitude and longitude as a point.
CREATE TABLE postal_codes
(
zip character varying(7) NOT NULL,
state character(2) NOT NULL,
city character varying(50) NOT NULL,
geoloc geography(Point,4326) NOT NULL
);
INSERT INTO postal_codes
VALUES (
'35004', 'AL', 'Moody',
ST_GeographyFromText('SRID=4326;POINT(-86.50249 33.606379)') -- lon lat
);
You can then get all points within a certain distance using STDWithin.
SELECT geoloc
FROM postal_codes
WHERE ST_DWithin(
geoloc, -- The current point being evaluated
'thegeolocyoustoredawaysomewhere', -- the location of the person
-- you're giving info to
7 * 1.6 * 1000 -- Miles to meters
);
Or:
SELECT geoloc
FROM postal_codes
WHERE ST_DWithin(
geoloc,
(
SELECT geoloc
FROM postal_codes
WHERE zip = '12345'
),
7 * 1.6 * 1000
);
To boost performance a bit for other kinds of queries (not any seen here), use a GiST index:
CREATE INDEX geoloc_idx
ON postal_codes
USING gist (geoloc);

Related

How to make the index work for the query with PostGIS

There is table
CREATE TABLE IF NOT EXISTS dbo.gps_online_state
(
accountid integer NOT NULL,
lat double precision,
lng double precision,
updatedon timestamp without time zone DEFAULT now()
);
I make sql that search all points in circle with radius 1500 m. But index in that query does not work
SELECT *
from gps_online_state gps
where gps.lat <> 'NaN' and gps.lng <> 'NaN'
AND
gps.updatedon > now() - '1 minute'::interval
AND
ST_Contains(
geometry(
ST_Buffer(geography(
'SRID=4326;POINT(' || 82.599620::text ||' ' || 49.957620::text ||')'),
1500)
),
ST_SetSRID( ST_MakePoint(gps.lng, gps.lat),4326)
)
Index query:
CREATE INDEX idx_gps_online_state_point_updatedon2
ON dbo.gps_online_state USING gist
((st_setsrid(st_makepoint(lng, lat), 4326)::geography), updatedon)
TABLESPACE pg_default
WHERE lat <> 'NaN'::double precision AND lng <> 'NaN'::double precision;
And I cant to force to work it. Trying geography and geomtry. Nothing help
I can't find anything as old as 2.4.3 to test with, but if I go back to 2.5.5 in PG 10.18 (using PGDG apt repo), I can get it to work as long as I leave the ::geography out of the index definition. Is leaving it out incorrect for some reason? If so, can you explain why or provide an example to demonstrate the error?
You also have a problem with the other column in the index. Your column is a timestamp but your computed value is a timestamptz, and this mismatch will prevent it from using that operator from the index.

Store circles in Postgres geometry field

Ideally it would be something like this, but WKT doesn't have circle type.
ST_GeomFromText('CIRCLE(10 20, 10)',4326)
Although, circle type is listed among geometric types,
circle <(x,y),r> (center point and radius)
I wonder if it's possible to use circle type directly in sql:
update <table>
set the_geom = circle '((10, 20),10)'::geometry
where id = <id>;
But it says SQL Error [42846]: ERROR: cannot cast type circle to geometry.
Using ST_Buffer for storing circles is a kludge so I don't want to use it.
Alternative solution could be jsonb + geojson, but it doesn't support circles either.
UPD: There is my table structure. Currently I'm using longitude/latitude/radius, but I'd like to use either geo_json or the_geom. How could GeoJSON and WKT not support a circle?
CREATE SEQUENCE my_table_id_seq INCREMENT BY 1 MINVALUE 1 START 1;
CREATE TABLE my_table (
id INT NOT NULL,
longitude NUMERIC(10, 7) DEFAULT NULL,
latitude NUMERIC(10, 7) DEFAULT NULL,
radius INT DEFAULT NULL,
geo_json JSONB,
the_geom Geometry DEFAULT NULL, PRIMARY KEY(id)
);
Circle is native for postgresql as you can see on the manual documentation.
Geometry is a type related to PostGis Extension, and doesnt have CIRCLE but use polygons with lot of points instead.
Function starting with ST_ are also Postgis functions and work only with Postgis geometry or geography data type
SQL DEMO:
create table points ( p POINT not null);
create table lines ( l LINE not null);
create table circles ( c CIRCLE not null);
insert into points (p) values ( POINT(1.2, 123.1) );
insert into lines (l) values ( LINE(POINT(1.2, 123.1), POINT(-5, -123)) );
insert into circles (c) values ( CIRCLE(POINT(1.2, 123.1), 10) );
SELECT * FROM points;
SELECT * FROM lines;
SELECT * FROM circles;
The GIST index allows you to work efficiently with circles. If that's the only thing you intend to store in this table, then you can do it like this:
CREATE TABLE my_table (
id INT NOT NULL,
longitude NUMERIC(10, 7) DEFAULT NULL,
latitude NUMERIC(10, 7) DEFAULT NULL,
radius INT DEFAULT NULL,
geo_json JSONB
);
CREATE INDEX idx_my_table ON my_table USING GIST ( circle( point( latitude, longitude ), radius ));
As others have pointed out, you cannot mix this table with GEOMETRY types, which are incompatible.
In order to utilize the index above, you must express your WHERE criteria in similar terms: circle( point( latitude, longitude ), radius ) or '<( latitude, longitude ), radius >'::circle and use the operators that GIST knows about ... which are listed below. I'm aware that projecting the Euclidian shape of a circle onto a non-Euclidian spherical geometry has limitations, but for index purposes it should work OK with care.
https://www.postgresql.org/docs/current/gist-builtin-opclasses.html

Postgis nearest coordinates

I'm trying to make a REST service that returns a list of places ordered by distance from the user coordinate. I found this query using postgis:
SELECT *
FROM your_table
ORDER BY your_table.geom <-> "your location..."
LIMIT 5;
But I'm not able to apply this to my actual database. I have a table that contains these columns:
title, address, description, latitude, longitude
all these values as Strings.
I'll be very happy if someone help me. Thx!
I dont know why, but ORDER BY <-> isnt exact. Sometime the closest link is on the 3rd position. So I get 101 element and then use distance to selected the closest one.
CREATE OR REPLACE FUNCTION map.get_near_link(
x numeric,
y numeric)
RETURNS TABLE(Link_ID int, distance int) AS
$BODY$
DECLARE
strPoint text;
BEGIN
strPoint = 'POINT('|| X || ' ' || Y || ')';
With CTE AS (
SELECT Link_ID,
TRUNC(ST_Distance(ST_GeomFromText(strPoint,4326), geom )*100000)::integer as distance
FROM map.vzla_seg S
ORDER BY
geom <-> ST_GeomFromText(strPoint, 4326)
LIMIT 101
)
SELECT *
FROM CTE
ORDER BY distance
LIMIT 5
In order to use PostGIS you have to enable the extension in the database. Ideally, you just run the CREATE EXTENSION postgis; command and it works. NOTE form the install page: DO NOT INSTALL it in the database called postgres. For more information visit the site.
Adding a geometry column (spatial data can be stored in this type of columns) to your table:
SELECT AddGeometryColumn(
'your_schema',
'your_table',
'geom', -- name of the column
4326, -- SRID, for GPS coordinates you can use this, for more information https://en.wikipedia.org/wiki/Spatial_reference_system
'POINT', -- type of geometry eg. POINT, POLYGON etc.
2 -- number of dimension (2 xy - 3 xyz)
);
UPDATE yourtable t SET t.geom = ST_SetSRID(ST_MakePoint(t.x, t.y), 4326)
-- the x and y is the latitude and longitude
Now you can use spatial queries on your table like this:
SELECT
*
FROM
your_table
ORDER BY
your_table.geom <-> ST_SetSRID(ST_MakePoint(x, y), 4326)
LIMIT 5;
NOTE: as others mentioned, below PostgreSQL 9.5 <-> isn't always reliable.

Postgresql + PostGIS: find record by geometry column

I have a table:
CREATE TABLE main.address_list
(
id serial NOT NULL,
address_string text NOT NULL,
address_position geometry(Point,4326)
CONSTRAINT pk_address_list PRIMARY KEY (id)
)
I want to find some record in this table using the address_position column:
select *
, public.ST_Y(al.address_position) latitude
, public.ST_X(al.address_position) longitude
from address_list al
where al.address_position = public.ST_SetSRID(public.ST_MakePoint(56.187339 --longitude$n
, 57.964295 --latitude$n
), 4326)
But I got the following error:
ERROR: operator is not unique: public.geometry = public.geometry
LINE 5: where al.address_position = public.ST_SetSRID(public.ST_Make...
^
HINT: Could not choose a best candidate operator. You might need to add explicit type casts.
How can I get explicit type cast?
You are trying to compare two geometries with the equals-to = operator. That is not supported; instead you should use some spatial relationship function. Probably the best thing is to use a minimal distance:
select *
, public.ST_Y(al.address_position) latitude
, public.ST_X(al.address_position) longitude
from address_list al
where public.ST_DWithin(al.address_position::geography
, public.ST_SetSRID(public.ST_MakePoint(56.187339 --longitude$n
, 57.964295 --latitude$n
), 4326)::geography
, 1.); -- maximum separation of 1m
Alternatively, you can compare the latitude and longitude values for equality:
select *
, public.ST_Y(al.address_position) latitude
, public.ST_X(al.address_position) longitude
from address_list al
where public.ST_X(al.address_position) = 56.187339 --longitude$n
and public.ST_Y(al.address_position) = 57.964295; --latitude$n
But you should keep in mind that comparing floating point values for equality is usually not a safe option due to the representation in binary form.
I've converted geometry column to WKT (Well Known Text) and compare that values.
select *
, public.ST_Y(al.address_position) latitude
, public.ST_X(al.address_position) longitude
, public.ST_AsText(al.address_position)
from address_list al
where public.ST_AsText(al.address_position) = public.ST_AsText(public.ST_SetSRID(public.ST_MakePoint(56.187339 --longitude$n
, 57.964295 --latitude$n
), 4326)
)
And added function index on column address_position
CREATE INDEX i_address_position_as_text
ON address_list
USING btree
(public.ST_AsText(address_position));

Nearest places from a certain point

I have the following table
create table places(lat_lng point, place_name varchar(50));
insert into places values (POINT(-126.4, 45.32), 'Food Bar');
What should be the query to get all places close to particular lat/long?
gis is installed.
If you actually wanted to use PostGIS:
create table places(
lat_lng geography(Point,4326),
place_name varchar(50)
);
-- Two ways to make a geography point
insert into places values (ST_MakePoint(-126.4, 45.32), 'Food Bar1');
insert into places values ('POINT(-126.4 45.32)', 'Food Bar2');
-- Spatial index
create index places_lat_lng_idx on places using gist(lat_lng);
Now to find all of the places within 1 km (or 1000 m):
select *, ST_Distance(lat_lng, ST_MakePoint(-126.4, 45.32)::geography)
from places
where ST_DWithin(lat_lng, ST_MakePoint(-126.4, 45.32)::geography, 1000)
order by ST_Distance(lat_lng, ST_MakePoint(-126.4, 45.32)::geography);
select *
from places
where lat_lng <-> POINT(-125.4, 46.32) < 1
order by lat_lng <-> POINT(-125.4, 46.32)
Create an Indexing on a location field :
CREATE INDEX ON table_name USING GIST(location);
GiST index is capable of optimizing “nearest-neighbor” search :
SELECT * FROM table_name ORDER BY location <-> point '(-74.013, 40.711)' LIMIT 10;
Note: The point first element is longitude and the second element is latitude.