It is impossible to speed up the database due to indexing.
I create a table:
CREATE TABLE IF NOT EXISTS coordinate( Id serial primary key,
Lat DECIMAL(9,6),
Lon DECIMAL(9,6));
After that I add indexing:
CREATE INDEX indeLat ON coordinate(Lat);
CREATE INDEX indeLon ON coordinate(Lon);
Then the table is filled in:
INSERT INTO coordinate (Lat, Lon) VALUES(48.685444, 44.474254);
Fill in 100k random coordinates.
Now I need to return all coordinates that are included in a radius of N km from a given coordinate.
SELECT id, Lat, Lon
FROM coordinate
WHERE acos(sin(radians(48.704578))*sin(radians(Lat)) + cos(radians(48.704578))*cos(radians(Lat))*cos(radians(Lon)-radians(44.507112))) * 6371 < 50;
The test execution time is approximately 0.2 seconds, and if you do not do CREATE INDEX, the time does not change. I suspect that there is an error in the request, maybe you need to rebuild it somehow?
I'm sorry for my english
An index can only be used if the indexed expression is exactly what you have on the non-constant side of the operator. That is obviously not the case here.
For operations like this, you need to use the PostGIS extension. Then you can define a table like:
CREATE TABLE coordinate (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
p geography NOT NULL
);
and query like this:
SELECT id, p
FROM coordinate
WHERE ST_DWithin(p, 'POINT(48.704578 44.507112)'::geography, 50);
This index would speed up the query:
CREATE INDEX ON coordinate USING gist (p);
Related
I have two point tables, tab_1 and tab_2. I want to query all points from the first table that are probably the same points from the table 2. So i give the points from table 2 a buffer. Then I want to get the points from table 1 and query from table 2 within a 30 m buffer. My problem is, I get the points from table 1 and table 2 twice. But point 1 from table 1 exists only once and point 1 from table 2 also only once.
My query is:
with
"points1" as
(
select id, geom from tab_1
)
,
"points2" as
(
select id, geom from tab_2
)
select "points1".*, "points2".* from "points1", "points2"
where
st_within(st_transform("points1".geom, 31468), st_buffer(st_transform("points2".geom, 31468), 30)) = true;
id_tab1
geom
id_tab2
geom
st_distance
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
The query should be give only one result:
id_tab1
geom
id_tab2
geom
st_distance
767074270
POINT (11.6968379 48.132722)
16455
POINT (11.69707 48.13265)
19.041083533921977
Is my query wrong?
STEP 1. Query
SELECT *
FROM tab_1
JOIN tab_2
ON ST_DWithin
( ST_Transform(tab_1.geom, 31468)
, ST_Transform(tab_2.geom, 31468)
, 30
)
STEP 2. Spatial index
Most likely, the query cannot use the spatial index (even if it exists) and the function ST_DWithin() properly (ST_Transform() does not allow using an existing spatial index).
Solution - create new spatial indexes for EPSG:31468
CREATE
INDEX tab_1_geom_31468_idx
ON tab_1
USING GIST (ST_Transform(geom, 31468))
;
CREATE
INDEX tab_2_geom_31468_idx
ON tab_2
USING GIST (ST_Transform(geom, 31468))
;
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
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.
I create the data set of 1'000'000 uniformly random points in [0,1]x[0,1] with R and then export it as a CSV so I can load it into Postgres/PostGIS.
R:
N <- 1000000
df <- data.frame(id=1:N, lon=runif(N), lat=runif(N))
write.table(df,"/media/Volume/temp/random_points.csv",row.names=FALSE,sep=";")
# manually remove header from CSV
head(df)
# id lat lon
#1 1 0.9094180 0.1208861
#2 2 0.8009161 0.8499304
#3 3 0.9800282 0.3757218
#4 4 0.5795991 0.4551454
#5 5 0.8988043 0.7801994
#6 6 0.9456310 0.2343178
PostGIS:
create table random_points (id serial, lat float, lon float);
ALTER TABLE random_points ADD PRIMARY KEY (id);
# now I import CSV into table
ALTER TABLE random_points ADD COLUMN geom geometry(POINT,4326);
UPDATE random_points SET geom = ST_SetSRID(ST_MakePoint(lon,lat),4326);
CREATE INDEX idx_lon_lat ON random_points USING GIST(geom);
And now I would like to query for all points lying within the bounding box - powered by an index:
min lat = 0.342, max lat = 0.352, min lon = 0.793, max lon = 0.812
SELECT *
FROM random_points
WHERE random_points.geom && ST_MakeEnvelope(0.342, 0.352, 0.793, 0.812, 4326);
But not only do I get a result set of size 207'376 which is much more than what I get with R (177) - the expected value for the area would be 190.
Already the first record does not belong to the intended bounding box; even if I confused longitude and latitude somewhere:
4;0.579599140677601;0.45514538907446;"...138CE23F"
9;0.618269162718207;0.392739744856954;"...9DCC8E33F"
10;0.742938967887312;0.58326911740005;"...127C6E73F"
17;0.665668761124834;0.475526283029467;"...92284DE53F"
27;0.668456399813294;0.747356393141672;"...ACFE63E53F"
Any idea what I am doing wrong?
My intention is to benchmark querying points with a bounding box in PostGIS to evaluate when it makes sense to use PostGIS for that purpose from R as opposed to using data frame/table tools.
The arguments to ST_MakeEnvelope are in the order xmim, ymin, xmax, ymax. You're passing ymin, ymax, xmin, xmax.
http://postgis.refractions.net/docs/ST_MakeEnvelope.html
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.