Postgresql/Postgis bug in ST_ApproximateMedialAxis? - postgresql

I get what seems like a bug in Postgresql/Postgis. This is a completely reproducible example that demonstrates the problem:
#create a new srid for local area (formula for proj4text is taken from Quantum GIS)
INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, proj4text) VALUES (998997, 'EPSG', 998997, '+proj=tmerc +lat_0=0 +lon_0=44.55 +k=1 +x_0=2250000 +y_0=-5714743.504 +ellps=krass +towgs84=24,-123,-94,0.02,-0.25,-0.13,1.1 +units=m +no_defs ');
#create a new table for storing geometry data:
CREATE TABLE layer(
id SERIAL PRIMARY KEY,
geom geometry,
CONSTRAINT enforce_dims_geom_layer CHECK (st_ndims(geom) = 2),
CONSTRAINT enforce_srid_geom_layer CHECK (st_srid(geom) = 998997)
);
#add one polygon to the table:
INSERT INTO layer (geom) VALUES (ST_Force2D(ST_Transform(ST_GeomFromText('POLYGON((4832654.676302 7570323.2813639, 4810946.560269 7597840.6115465, 4836629.4017728 7629944.1634263, 4886772.0923279 7629944.1634263, 4902059.4979849 7591725.6492837, 4864452.4800686 7553507.1351411, 4832654.676302 7570323.2813639),(4845190.3489408 7589891.1606049, 4855585.7847875 7610376.2841853, 4876988.1527074 7604567.0700356, 4874847.9159154 7588362.4200392, 4858031.7696927 7575520.9992872, 4845190.3489408 7589891.1606049))', 3857),998997)));
#check that this polygon is valid!
SELECT ST_IsValid(ST_Force2D(ST_Transform(ST_GeomFromText('POLYGON((4832654.676302 7570323.2813639, 4810946.560269 7597840.6115465, 4836629.4017728 7629944.1634263, 4886772.0923279 7629944.1634263, 4902059.4979849 7591725.6492837, 4864452.4800686 7553507.1351411, 4832654.676302 7570323.2813639),(4845190.3489408 7589891.1606049, 4855585.7847875 7610376.2841853, 4876988.1527074 7604567.0700356, 4874847.9159154 7588362.4200392, 4858031.7696927 7575520.9992872, 4845190.3489408 7589891.1606049))', 3857),998997))) AS is_valid;
#^^^ it returns t. so, the geometry is 100% ok.
#check how it looks like in geojson format:
SELECT ST_AsGeoJSON(ST_Transform(geom, 3857))::json from layer
#^^^ Again it's ok and returns some nice geojson data
#Final step. Check ST_ApproximateMedialAxis function
SELECT ST_ApproximateMedialAxis(ST_Transform(geom, 3857)) from layer;
The final query returns an error message:
ERROR: Polygon is invalid : exterior ring and interior ring 0 have the same orientation : POLYGON((5189023446929109/1073741824 2032143182100511/268435456,5165714534831303/1073741824 31867653268373/4194304,1298322818976529/268435456 8192590163056015/1073741824,26
********** Error **********
So, all ingredients seem to be ok - formula for srid is taken from some standard widely used tool, data is inserted into the table without problems, the data is validated by ST_IsValid, geojson representation of data is also ok, but one library function still does not like something.

I have found this nice thread and came to the solution. I just have to use ST_ForceRHR Postgis function which
forces the orientation of the vertices in a polygon to follow the Right-Hand-Rule
So the right way to insert the data was:
INSERT INTO layer (geom) VALUES (ST_Force2D(ST_Transform(ST_ForceRHR(ST_GeomFromText('POLYGON((4832654.676302 7570323.2813639, 4810946.560269 7597840.6115465, 4836629.4017728 7629944.1634263, 4886772.0923279 7629944.1634263, 4902059.4979849 7591725.6492837, 4864452.4800686 7553507.1351411, 4832654.676302 7570323.2813639),(4845190.3489408 7589891.1606049, 4855585.7847875 7610376.2841853, 4876988.1527074 7604567.0700356, 4874847.9159154 7588362.4200392, 4858031.7696927 7575520.9992872, 4845190.3489408 7589891.1606049))', 3857)),998997)));

Related

Postgis - Check if a Point is inside Polygon

I am trying to use the ST_Intersection function to intersect a point by city polyogn but I am having no luck (Hasura + Postgresql)
create table "public"."cities"(
geom geometry(MultiPolygon, 4326)
//other colmuns
);
then i added a row with multipolygon of my city :
INSERT INTO "public"."cities"
VALUES(ST_GeomFromText('MULTIPOLYGON(((
5.334 36.213,5.335 36.22,5.356 36.225,5.365 36.233,5.371 36.232,5.378 36.237,5.387 36.24,5.39 36.243,5.395 36.244,5.398 36.248,5.406 36.249,5.413 36.253,5.421 36.25,5.431 36.249,5.435 36.246,5.433 36.24,5.434 36.239,5.433 36.231,5.44 36.232,5.445 36.23,5.447 36.227,5.453 36.227,5.457 36.229,5.462 36.227,5.477 36.231,5.482 36.238,5.49 36.238,5.491 36.24,5.495 36.241,5.503 36.241,5.506 36.237,5.506 36.234,5.509 36.231,5.51 36.225,5.509 36.216,5.502 36.211,5.502 36.207,5.498 36.202,5.498 36.199,5.502 36.194,5.502 36.183,5.499 36.18,5.499 36.177,5.497 36.175,5.492 36.174,5.487 36.169,5.481 36.17,5.481 36.163,5.478 36.159,5.475 36.158,5.476 36.154,5.472 36.148,5.472 36.145,5.469 36.142,5.452 36.141,5.44 36.133,5.438 36.13,5.433 36.13,5.43 36.138,5.42 36.137,5.418 36.139,5.41 36.136,5.406 36.136,5.402 36.139,5.392 36.137,5.384 36.138,5.369 36.13,5.364 36.132,5.364 36.135,5.346 36.138,5.345 36.143,5.35 36.151,5.347 36.156,5.347 36.164,5.343 36.171,5.343 36.175,5.338 36.18,5.336 36.187,5.333 36.191,5.334 36.194,5.333 36.201,5.336 36.205,5.334 36.213
)))', 4326) );
But when i run this query with a Point is the center of city, i doesn't return any data.
select *
from "public"."cities"
where ST_Intersects("public"."cities"."geom", ST_GeometryFromText('POINT(36.1917 5.4235)', 4326));
UPDATE :
I stored the polygon in long-lat, but i was querying with lat-long Point, my mistake

Create a middle line through a polygon path using postgis

I'm trying to create a middle line through a polygon path but having problems, now i'm totally lost how to do it. Can anyone help to achieve this goal ?
ST_ApproximateMedialAxis might be what you're looking for.
This PostGIS function can be installed with the extension postgis_sfcgal:
CREATE EXTENSION postgis_sfcgal
Data Sample:
CREATE TABLE t (geom GEOMETRY);
INSERT INTO t VALUES ('POLYGON((-4.689807593822478 54.20411976258862,-4.68751162290573 54.20415427666532,-4.686465561389922 54.20414172609529,-4.685768187046051 54.20414800138079,-4.685280025005341 54.20414486373812,-4.685070812702178 54.204126037877415,-4.685092270374298 54.2040538719985,-4.685854017734527 54.204078973188075,-4.687039554119109 54.20407583554021,-4.688123166561126 54.204082110835685,-4.689078032970428 54.2040601472973,-4.689936339855194 54.20403818374726,-4.689807593822478 54.20411976258862))');
Query:
SELECT ST_ASText(ST_ApproximateMedialAxis(geom)) FROM t;
--------------------------------------------------------
MULTILINESTRING((-4.68993633985519 54.2040381837473,-4.68979598869017 54.2040808603332),(-4.68812343743121 54.2041135944836,-4.68907889005644 54.2040954248621),(-4.68812343743121 54.2041135944836,-4.68751156547988 54.2041164214432),(-4.68646560965613 54.2041095395079,-4.6858535007301 54.2041131034922),(-4.68646560965613 54.2041095395079,-4.68703949691079 54.2041122226661),(-4.6858535007301 54.2041131034922,-4.68576814087419 54.2041120816007),(-4.68907889005644 54.2040954248621,-4.68979598869017 54.2040808603332),(-4.68576814087419 54.2041120816007,-4.68528206303828 54.2041025125126),(-4.68703949691079 54.2041122226661,-4.68751156547988 54.2041164214432),(-4.68512015518242 54.2040925683677,-4.68528206303828 54.2041025125126))
(1 Zeile)
Depending on your use case, another option would be ST_StraightSkeleton:
SELECT ST_ASText(ST_StraightSkeleton(geom)) FROM t;
-----------------------------------------------------
MULTILINESTRING((-4.68980759382248 54.2041197625886,-4.68979598869017 54.2040808603332),(-4.68993633985519 54.2040381837473,-4.68979598869017 54.2040808603332),(-4.68907803297043 54.2040601472973,-4.68907889005644 54.2040954248621),(-4.68812316656113 54.2040821108357,-4.68812343743121 54.2041135944836),(-4.68703955411911 54.2040758355402,-4.68703949691079 54.2041122226661),(-4.68585401773453 54.2040789731881,-4.6858535007301 54.2041131034922),(-4.6850922703743 54.2040538719985,-4.68512015518242 54.2040925683677),(-4.68507081270218 54.2041260378774,-4.68512015518242 54.2040925683677),(-4.68528002500534 54.2041448637381,-4.68528206303828 54.2041025125126),(-4.68576818704605 54.2041480013808,-4.68576814087419 54.2041120816007),(-4.68646556138992 54.2041417260953,-4.68646560965613 54.2041095395079),(-4.68751162290573 54.2041542766653,-4.68751156547988 54.2041164214432),(-4.68812343743121 54.2041135944836,-4.68907889005644 54.2040954248621),(-4.68812343743121 54.2041135944836,-4.68751156547988 54.2041164214432),(-4.68646560965613 54.2041095395079,-4.6858535007301 54.2041131034922),(-4.68646560965613 54.2041095395079,-4.68703949691079 54.2041122226661),(-4.6858535007301 54.2041131034922,-4.68576814087419 54.2041120816007),(-4.68907889005644 54.2040954248621,-4.68979598869017 54.2040808603332),(-4.68576814087419 54.2041120816007,-4.68528206303828 54.2041025125126),(-4.68703949691079 54.2041122226661,-4.68751156547988 54.2041164214432),(-4.68512015518242 54.2040925683677,-4.68528206303828 54.2041025125126))
(1 Zeile)

PostgreSQL complains about inexistent comparison function for element in primary key

I have a table in a PostgreSQL database in which I want to store the following columns:
STATION LOCATION SERVICE NORTH EAST
text point text real real
Each tuple(STATION, LOCATION, SERVICE) is unique, so I decided to make it a composite type and make it the primary key.
However, when I try to insert a new entry in the database I get the following error:
psycopg2.ProgrammingError: could not identify a comparison function for type point
I guess it is complaining that you cannot order two points in a 2D plane, but I cannot see how that is relevant. I have managed to use composite types that made use of points as primary keys in a test example, so I cannot see how this is different.
I want to know:
Why this is happening.
How it can be fixed, preferrably without changing the table schema.
Debugging information:
testdb=> \d ERROR_KEY
Composite type "public.error_key"
Column | Type | Modifiers
----------+-------+-----------
station | text |
location | point |
service | text |
testdb=> \d testtable
Table "public.testtable"
Column | Type | Modifiers
--------+-----------+-----------
key | error_key | not null
north | real |
east | real |
Indexes:
"testtable_pkey" PRIMARY KEY, btree (key)
For reference, this is the code I am using for the insertion:
from collections import namedtuple
import psycopg2
DB_NAME = 'testdb'
DB_USER = 'testuser'
DB_HOST = 'localhost'
DB_PASSWORD = '123456'
PVT_TABLE_NAME = 'testtable'
Coordinate = namedtuple('Coordinate', ['lat', 'lon'])
PVT_Error_Key = namedtuple('PVT_Error_Key',
['station', 'location', 'service'])
PVT_Error_Entry = namedtuple(
'PVT_Error_Entry', ['key', 'north', 'east'])
def _adapt_coordinate(coord):
"""
Adapter from Python class to Postgre geometric point
"""
lat = psycopg2.extensions.adapt(coord.lat)
lon = psycopg2.extensions.adapt(coord.lon)
return psycopg2.extensions.AsIs("'(%s, %s)'" % (lat, lon))
def _connect_to_db(db_name, db_user, db_host, db_password):
"""
Connects to a database and returns a cursor object to handle the connection
"""
connection_str = ('dbname=\'%s\' user=\'%s\' host=\'%s\' password=\'%s\''
% (db_name, db_user, db_host, db_password))
return psycopg2.connect(connection_str).cursor()
def main():
# Register the adapter for the location
psycopg2.extensions.register_adapter(Coordinate, _adapt_coordinate)
cursor = _connect_to_db(DB_NAME, DB_USER, DB_HOST, DB_PASSWORD)
# Create a dummy entry
entry = PVT_Error_Entry(
key=PVT_Error_Key(station='GKIR',
location=Coordinate(lat=12, lon=10),
service='E1'),
north=1, east=2)
# Insert the dummy entry in the database
cursor.execute(
'INSERT INTO %s '
'(KEY, NORTH, EAST) '
'VALUES((%%s, %%s, %%s), %%s, %%s)'
% PVT_TABLE_NAME,
(entry.key.station, entry.key.location, entry.key.service,
entry.north, entry.east))
# Retrieve and print all entries of the database
cursor.execute('SELECT * FROM %s', (PVT_TABLE_NAME))
rows = cursor.fetchall()
print(rows)
if __name__ == '__main__':
main()
You cannot use a column of type point in a primary key, e.g.:
create table my_table(location point primary key);
ERROR: data type point has no default operator class for access method "btree"
HINT: You must specify an operator class for the index or define a default operator class for the data type.
The error message is clear enough, you need to create a complete btree operator class for the type.
The full procedure is described in this answer: Creating custom “equality operator” for PostgreSQL type (point) for DISTINCT calls.
Update. With the workaround you mentioned in your comment
create table my_table(
x numeric,
y numeric,
primary key (x, y));
insert into my_table values
(1.1, 1.2);
you can always create a view, which can be queried just like a table:
create view my_view as
select point(x, y) as location
from my_table;
select *
from my_view;
location
-----------
(1.1,1.2)
(1 row)

I am trying to insert Polygon data in table then I get an error

The error message is the following:
"Error: geometry contains non-closed rings"
My code is shown below:
CREATE TABLE GhanaRegions (
Id serial,
Geometry geometry DEFAULT NULL,
PRIMARY KEY (Id)
);
INSERT INTO GhanaRegions(Geometry) VALUES (ST_GeomFromText('POLYGON ((-0.024861 10.856,
-0.0250165 10.8561,
-0.0252813 10.8562,
-0.0254853 10.8563,
-0.0256633 10.8565,
-0.0259642 10.8566,
-0.0262956 10.8568,
-0.0265517 10.8572,
-0.0267774 10.8576,
-0.0270798 10.8579,
-0.0273258 10.8581,
0.02766 10.8584))'));
The first and the last points must be the same point. If they are different, the ring is not closed and the polygon cannot be built.
Solution: the first point must be used twice, as first and as last point:
INSERT INTO GhanaRegions(Geometry) VALUES (ST_GeomFromText('POLYGON ((
-0.024861 10.856,
-0.0250165 10.8561,
-0.0252813 10.8562,
-0.0254853 10.8563,
-0.0256633 10.8565,
-0.0259642 10.8566,
-0.0262956 10.8568,
-0.0265517 10.8572,
-0.0267774 10.8576,
-0.0270798 10.8579,
-0.0273258 10.8581,
0.02766 10.8584,
-0.024861 10.856
))'));

UndefinedFunction: ERROR: function st_distance(geography, geometry, numeric) does not exist

I wanna to order by distance, but I got the error
UndefinedFunction: ERROR: function st_distance(geography, geometry, numeric) does not exist
by .order("ST_Distance(lonlat, ST_Geomfromtext('#{point}'), #{radius}) ")
If I removed the above line , it works fine.
What's wrong with it ?
model
scope :nearby, lambda { |radius_in_km, lon, lat|
point = GEOG_FACTORY.point(lon, lat)
radius = radius_in_km.to_f*1000
where("ST_DWithin(lonlat, ST_GeomFromText('#{point}'), #{radius} ) ")
.order("ST_Distance(lonlat, ST_Geomfromtext('#{point}'), #{radius}) ")
}
more detail error log
PG::UndefinedFunction: ERROR: function st_distance(geography, geometry, numeric) does not exist
LINE 1: ...4028 6.530438999999999)'), 100000.0 ) ) ORDER BY ST_Distanc...
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
: SELECT "weather_logs".* FROM "weather_logs" WHERE (ST_DWithin(lonlat, ST_GeomFromText('POINT (101.3034028 6.530438999999999)'), 100000.0 ) ) ORDER BY ST_Distance(lonlat, ST_Geomfromtext('POINT (101.3034028 6.530438999999999)'), 100000.0) , "weather_logs"."datetime" ASC LIMIT 20000
Completed 200 OK in 161ms (Views: 0.2ms | ActiveRecord: 5.4ms)
You are mixing geography and geometry types, that is what the error message means. If you look at the ST_DWithin docs, you will see that the signature are ST_DWithin (geometry, geometry, distance) or ST_DWithin (geography, geography, distance).
I don't know much about Ruby, so am not sure if there is a GEOM_Factory equivalent of the GEO_Factory you have used, but if you use ST_GeogFromText instead of ST_GeomFromText in your where and order by clauses, then you will be dealing with two geographies, which should solve your issue.
As your coordinates are in lat/lon, 4326, it is appropriate to use the geography datatype. If you want to know more about the practicalities of geometry vs geography, see this gis.stackexchange question.