Why does ST_Transform fail when transforming to 4326? - postgresql

I have a postgis table with some data which looks like this:
distance
id
nr
X
Y
description
strecke
point
gsal_strecke
rikz
gsal_km_anf_p
gsal_km_end_p
gsal_geo_compound
rk
kilometer
basepoint
2.088918198132633
9105
1
7.59833269418573
50.3171094720011
valide with result
3507
POINT (3400245.168425543 5576618.697108934)
3507
2.0
123905.310
123945.537
LINESTRING (3400253.52199817 5576605.02429315, 3400251.48999817 5576609.59229315, 3400247.37399817 5576618.87129315, 3400243.28599817 5576628.16129316, 3400239.24399817 5576637.47229316, 3400237.27599817 5576642.06929316)
1
123.92110139140328
POINT (3400247.0804134225 5576619.538465925)
4.601389947759623
9106
611171
8.83478109
54.7923646
crash
1201
POINT (3489442.4653895213 6073687.653162317)
1201
0.0
162291.691
162329.922
LINESTRING (3489446.77287361 6073662.83069441, 3489447.226 6073701.05844041)
1
162.31646103819043
POINT (3489447.066456252 6073687.598624319)
This table holds the end result of some calculations. In short what happens is, that a collection of points is inserted into the database and if they are within a certain perimter of a given rail, a basepoint to that rail is calculated. This calculation takes place in SRID 5683
The data is fetched by a Service, which returns them as geojson. According to the specification, geoJSON works best when used with WGS84 coordinates.
So when fetching the data, I have to transform the coordinates.
The query I use looks like this:
select *, ST_X(ST_Transform(point, 4326)) as x, ST_Y(ST_Transform(point, 4326)) as y from bp40.bp40_punktlage;
The first of these two example rows yields the following result:
distance
id
nr
X
Y
Objektbezeichnung
strecke
punkt
gsal_strecke
rikz
gsal_km_anf_p
gsal_km_end_p
gsal_geo_compound
rk
kilometer
fusspunkt
x
y
2.088918198132633
9105
1
7.59833269418573
50.3171094720011
valide mit ergebnis
3507
POINT (3400245.168425543 5576618.697108934)
3507
2.0
123905.310
123945.537
LINESTRING (3400253.52199817 5576605.02429315, 3400251.48999817 5576609.59229315, 3400247.37399817 5576618.87129315, 3400243.28599817 5576628.16129316, 3400239.24399817 5576637.47229316, 3400237.27599817 5576642.06929316)
1
123.92110139140328
POINT (3400247.0804134225 5576619.538465925)
7.598332691520598
50.317109473199004
Now for some reason I cannot explain, the second row just crashes yielding the follow error message:
SQL Error [XX000]: ERROR: transform: Invalid argument (22)
According to the postgres documentation
This is a internal error, which does not really help me understand what is wrong here.
I have checked the geometry for validity (st_isvalid) and both rows contain valid geometry.
Also the initial X,Y coordinates are valid and pinpoint the location I want them to be in.
EDIT 1
Out of curiosity i tried the following queries:
select st_transform(point,5682) from bp40_punktlage
--works just fine with both rows
select st_transform(st_transform(punkt, 5682),4326) from bp40_punktlage
-- crashes with the same error
PostgreSQL 13.4, compiled by Visual C++ build 1914, 64-bit
Postgis Version : 3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
Edit 2
As requestet here is the CREATE TABLE statement:
CREATE TABLE bp40.bp40_punktlage (
distance float8 NULL,
id int4 NULL,
nr varchar NULL,
"X" float8 NULL,
"Y" float8 NULL,
"description" varchar NULL,
strecke varchar NULL,
point geometry NULL,
gsal_strecke varchar(4) NULL,
rikz float8 NULL,
gsal_km_anf_p numeric(19, 3) NULL,
gsal_km_end_p numeric(19, 3) NULL,
gsal_geo_compound geometry NULL,
rk int8 NULL,
kilometer float8 NULL,
basepoint geometry NULL
);
Inserts are a bit more complicated, since the data goes through several tables and processing steps until it arrives at the table shown in this post. I will try to add them over the course of the day.
Edit 3
The geometries are valid.
st_srid() return 5863 for both points, as expected. I tried transforming them into 5862 just for the sake of trying. But it fails when transforming to 4326, no matter from which SRID i start

Related

Insert data in a column geometry on redshift

I created this table on a database in redshift; and try to insert data. Do you know how to insert the point coordinate into the column geometry ?
CREATE TABLE airports_data (
airport_code character(3),
airport_name character varying,
city character varying,
coordinates geometry,
timezone timestamp with time zone
);
INSERT INTO airports_data(airport_code,airport_name,city,coordinates,timezone)
VALUES ('YKS','Yakutsk Airport','129.77099609375, 62.093299865722656', 'AsiaYakutsk');
I had an error when trying to make this insert.
Query ELAPSED TIME: 13 m 05 s ERROR: Compass I/O exception: Invalid
hexadecimal character(s) found
In Redshift, make your longitude and latitude values into a geometry object. Use:
ST_Point(longitude, latitude) -- for an XY point
ST_GeomFromText('LINESTRING(4 5,6 7)') -- for other geometries
You're missing city in your INSERT VALUES and 'AsiaYakutsk' is not a valid datetime value - see https://docs.aws.amazon.com/redshift/latest/dg/r_Datetime_types.html#r_Datetime_types-timestamptz
Ignoring your timezone column and adding city into values, use this:
INSERT INTO airports_data(airport_code,airport_name,city,coordinates)
VALUES ('YKS','Yakutsk Airport','Yakutsk',ST_Point(129.77099609375, 62.093299865722656));

How to store point (x, y) in a database?

I need to store location (x,y point) in my database where, point can be null and X and Y are always less then 999. At the moment I'm using EFCore Code First and Postgresql database, but I'd like to be flexible so that I can switch to MSSql without too much work. I'm not planning to move away from EF Core.
Right now, I have two columns: LocationX and LocationY, both are int? type. I'm not sure if this is good solution, because technically DB allows (X=2, Y=null), and it's should be. It's either both are null, or both are not.
My option two is to store it in a one string column: "123x321", with max length of 7.
Is there a better way?
Thanks,
Check constraint could be used to enforce both column are NULL or NOT NULL at the same time:
CREATE TABLE t(id INT,
x INT,
y INT,
CHECK((x IS NULL AND y IS NULL) OR (x IS NOT NULL AND y IS NOT NULL))
);
db<>fiddle demo
In addition to the check constraint suggested by #LukaszSzozda you can restrict the x,y values with an additional check constraint on each. So assuming they must also be in range 0,999 then
CREATE TABLE t(id INT,
x INT constraint x_range check ( x>=0 and x<=999),
y INT constraint y_range check ( y>=0 and y<=999),
CHECK((x IS NULL AND y IS NULL) OR (x IS NOT NULL AND y IS NOT NULL))
);
As far a your idea of storing a single string - very bad. Not only will you have the issue of separating values every time you need them it allows for distinctly invalid data. Values '1234567' and even 'abcdefg' are completely valid as far as the database is concerned.
So your table definition must account for and eliminate them. With this your table definition becomes:
create table txy
( xy_string varchar(7)
, constraint xy_format check( xy_string ~* '^\d{1,3}x\d{1,3}')
)
insert into txy(xy_string)
( values ('1x2'), ('354X512'), ('38x92') );
Which is actually a reduction as it is back to a single constraint, but your queries now require something like:
select xy_string
, regexp_replace(xy_string, '^(\d+)(X|x)(\d+)','\1') x
, regexp_replace(xy_string, '^(\d+)(X|x)(\d+)','\3') y
from txy;
See demo here.
In short never store groups of numbers values as a single delimited string. The additional work is just not worth it.

Indexing issue in postgres

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);

input geometry has unkown(0) geometry (Although st_transform is used)

I have 2 tables:
A table with 3 fields:
id (text)
geom (geometry)
select ST_SRID(geom)
from A
where geom is not null
result: 32636
B table with 2 fields:
name (text)
geom (geometry)
select ST_SRID(geom)
from B
where geom is not null
result: 0
A.geom contains polygons
B.geom contains points
I want to get all the distances between A.id, A.geom and B.geom.
I tried with:
select id, st_distance(a.geom, ST_Transform(b.geom, 32636)) as dist
from A as a, B as b
where a.geom is not null
group by id, a.geom, b.geom
order by dist desc
But I'm getting error:
"input geom has unkown(0) SRID"
How can it be if I'm using ST_Transform ?
How can I fix it ?
The error message is talking about the SRID of the argument to ST_Transform, which is 0. The message means that the function has no idea in which coordinate system the point is, so it cannot transform it to another coordinate system.
The documentation says:
ST_Transform is often confused with ST_SetSRID. ST_Transform actually changes the coordinates of a geometry from one spatial reference system to another, while ST_SetSRID() simply changes the SRID identifier of the geometry.
That seems to be the case here.
You should probably use ST_SetSRID to interpret b.geom in SRID 32636.

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