How to create geography object by two pairs lat and long? - tsql

I am first time trying the spatial types of T-SQL And the problem is that I don't know how to verify is point belong to circle which is defined by two pairs of points (lat1, long1; lat2, long2)
I tried to create the geography object:
declare #p1 geography = geography::STGeomFromText('POINT(51,067222 -114,110043)',4326);
Neither of this code works:
declare #p1 geography = geography::STGeomFromText('POINT(51,067222 -114,110043)',4326);
declare #p2 geography = geography::STGeomFromText('POINT(51,100004 -113,850491)',4326);
declare #g, #g2 Geometry
set #g = 'Point(51,067222 -114,110043)';
set #g2 = 'Point(51,100004 -113,850491)';
select #g.STBuffer(#g2)
but without success.
Please don't kill me, I am trying this first time ;)

Eventually found the answer. (NOTE: STContains works ONLY in MS SQL Server 2012 or greater)
-- First point
declare #p1 geography = geography::STGeomFromText('POINT(-114.110043 51.067222)', 4326);
-- Second point
declare #p2 geography = geography::STGeomFromText('POINT(-113.850491 51.100004)', 4326);
-- Find the distance between points in meters
declare #distanceInMeters float = #p1.STDistance(#p2);
-- Create circle geography object
declare #cicleGeography geography = #p1.STBuffer(#distanceInMeters)
declare #p3 geography = geography::STGeomFromText('POINT(-112.850491 51.100004)', 4326);
-- Returns true if the third point is inside the circle
select Id, #cicleGeography.STContains(#p3)
Very easy :)

Glad you found your own answer. As an alternative, you can also use STIntersects() or STWithin(). For further reading / learning, I've also used an alternative for creating points using lat/long order..
-- First point
declare #p1 geography = geography::Point(51.067222, -114.110043, 4326);
-- Second point
declare #p2 geography = geography::Point(51.100004, -113.850491, 4326);
-- Find the distance between points in meters
declare #distanceInMeters float = #p1.STDistance(#p2);
-- Create circle geography object
declare #cicleGeography geography = #p1.STBuffer(#distanceInMeters)
declare #p3 geography = geography::Point(51.100004, -112.850491, 4326);
-- Returns true if the third point is inside the circle
select #id, #cicleGeography.STIntersects(#p3)
-- OR Alternatively
select #id, #cicleGeography.STWithin(#p3)

Related

What is the fastest way to generate custom column values in Postgres while importing data with \COPY?

I'm loading ~45 million records from a CSV file with \COPY. The data contains x, y, z values representing 3D coordinates.
CREATE TABLE mytable (
id integer,
x double precision,
y double precision,
z double precision,
coordinate geometry default NULL,
CONSTRAINT mytable_pkey PRIMARY KEY (id)
);
The geometry type is coming from postgis. I'd like to create the coordinate value on the fly with ST_MakePoint(x, y, z) while importing the data. Currently, I'm using a trigger for this purpose, but it significantly slows down the import.
CREATE OR REPLACE FUNCTION create_coordinates()
RETURNS trigger AS
$$
BEGIN
update mytable set coordinate = ST_MakePoint(NEW.x, NEW.y, NEW.z) where id = NEW.id;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
CREATE TRIGGER create_coordinates_trigger
AFTER INSERT
ON mytable
FOR EACH ROW
EXECUTE PROCEDURE create_coordinates();
Is there a better aproach for creating custom column values while importing data?
Starting in v12, you can use a generated column.
CREATE TABLE mytable2 (
id integer,
x double precision,
y double precision,
z double precision,
coordinate geometry generated always as (ST_MakePoint(x, y, z)) stored,
CONSTRAINT mytable2_pkey PRIMARY KEY (id)
);
In my hands, this seems to be about 6 times faster to bulk load than the trigger version, (even more for very larger imports)
And if you don't want to us this newish feature, just turning the AFTER trigger into a BEFORE trigger is also several times faster. Rather than doing an update after the fact, you just alter the row before ever inserting it:
NEW.coordinate = ST_MakePoint(NEW.x, NEW.y, NEW.z);

Populate fields x,y or long, lat, depending on input data, and create a point based on input

I have a table created as follows:
CREATE TABLE public."EESsite" (
"ID" integer NOT NULL,
"Name" character varying(254),
"LatitudeDe" double precision,
"Longitud_1" double precision,
"Easting" double precision,
"Northing" double precision,
geom geometry(Point,32636),
CONSTRAINT "PrimaryKey" PRIMARY KEY ("ID"))
WITH (
OIDS=FALSE
);
ALTER TABLE public."EESsite"
What I want to do is to populate either Longitud_1,LatitudeDE, or Easting ,Northing, depending on the input data. For example, sometimes I have teh coordinates in LonLat format, but sometimes I have them is UTM, but I need both in my columns. From each pair of coordinates I want to create a point using a function and a trigger. I have been able to produce the point automatically and extract the coordinates from geom to put them into the columns, but I have not been able to do it programmatically. I have tried this:
CREATE OR REPLACE FUNCTION update_latlon_column()
RETURNS TRIGGER AS
$BODY$
BEGIN
IF NEW."Longitud_1" IS NULL THEN
NEW.geom =(ST_Transform(ST_SetSRID(ST_MakePoint(NEW."Longitud_1"::float8,NEW."LatitudeDe"::float8),4326),32636));
end if;
IF NEW."Easting" IS NULL THEN
NEW.geom = ST_SetSRID(ST_MakePoint(NEW."Easting"::float8, NEW."Northing"::float8), 32636),
NEW.Longitud_1 = (ST_SetSRID(ST_Y(geom),32636),4326),
NEW.LatitudeDe = (ST_SetSRID(ST_X(geom),32636),4326);
end if;
RETURN new;
END;
$BODY$
language plpgsql;
CREATE TRIGGER update_geom
BEFORE INSERT OR UPDATE
ON public."EESsite"
FOR EACH ROW
EXECUTE PROCEDURE update_latlon_column();
I can create both the function and the trigger, but whenever I try to add a new row I run into this problem:
ERROR: record "new" has no field "longitud_1"
Any help would be very appreciated.
Thank you
I solved the problem after following wildplasser suggestion to refrain myself of using a mixed cased identifier. I double-checked my code —it had other major issues— and after some trials finally made my script to work. If you have any suggestion to improve it, would be more than appreciated. Right now, this code works like this:
If I fill a field and write the LonLat coordinates (EPSG:4326; but you
can change it of course), it will use these coordinates to populate
the geom as a UTM (EPSG:32636) point, and then will populate the field
Easting and Northing with the proper coordinates. It works the other
way around, if you fill the Easting and Northing, it will create a
point in geom and then populate latitude and longitude.
Nevertheless, and this is where you can improve it, it will no update any column if you change any of these values. I hope you find it useful, though.
CREATE OR REPLACE FUNCTION public.update_latlon_column()
RETURNS trigger AS
$BODY$BEGIN
IF NEW.longitude IS NOT NULL THEN
NEW.geom = (ST_Transform(ST_SetSRID(ST_MakePoint(NEW.longitude::float8,NEW.latitude::float8),4326),32636));
NEW.easting = (ST_X(NEW.geom));
NEW.northing = (ST_Y(NEW.geom));
ELSIF NEW.easting IS NOT NULL THEN
NEW.geom = ST_SetSRID(ST_MakePoint(NEW."easting"::float8,NEW."northing"::float8), 32636);
NEW.longitude = (ST_X(ST_Transform(ST_SetSRID((NEW.geom),32636),4326)));
NEW.latitude = (ST_Y(ST_Transform(ST_SetSRID((NEW.geom),32636),4326)));
END IF;
RETURN new;
END;
$BODY$
language plpgsql;

A Postgresql function stuck in for loop, the query never ends querying

The function is stuck in for loop, the query remains querying and never ends:
ALTER TABLE movement ADD COLUMN bar_id INTEGER;
CREATE OR REPLACE FUNCTION get_all_movement() RETURNS SETOF movement AS
$BODY$
DECLARE
barid INTEGER;
m movement%ROWTYPE;
BEGIN
FOR m IN
SELECT * FROM movement
LOOP
barid:= (SELECT bar_id FROM employee WHERE employee_id = m.employee_id);
UPDATE movement SET bar_id = barid;
RETURN NEXT m;
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql';
SELECT * FROM get_all_movement();
What can i do?
You do not need functions and loops for this, the update statement will be simplest and most efficient:
update movement m
set bar_id = e.bar_id
from employee e
where m.employee_id = e.employee_id
returning m.*;
Your function tries to update the whole table in every single step. This may last for hours depending on data amount, not to mention the fact that the result will be incorrect. You have forgotten the WHERE clause in UPDATE statement:
...
UPDATE movement SET bar_id = barid
WHERE employee_id = m.employee_id;
...

Unable to execute the function in plpgsql/postgres

I want to calculate distance from address points to all streets within a distance of 50 meters using plpgsql. I have tried the following function:
Create or Replace Function get_dist(ad geometry, st geometry)
Returns double precision AS
$$
Begin
Insert into street(Distance)
Select ST_Distance(ad.geom, st.geom) from ad
Left Join st ON ST_DWithin(ad.geom, st.geom, 50.0);
Return ST_Distance(ad.geom, st.geom);
End
$$
Language plpgsql volatile;
Creating the function gives no error but when I try to call it using this command:
Select get_dist(ad.geom, st.geom) from ad
Left Join st ON st.gid = ad.gid;
I get this error:
ERROR: missing FROM-clause entry for table "ad"
LINE 1: SELECT ST_Distance(ad.geom, st.geom)
Can someone please highlight what is wrong with the function (creating the function and calling it)?
For processing a single row or processing rows one-by one, you can use the SQL RETURNING clause of INSERT combined with the plpgsql INTO clause. Example:
Get default serial value after INSERT inside PL/pgSQL
But you are obviously trying to process a whole set at once.
calculate distance from address points to all streets within a distance of 50 meters ...
Use a set-based approach. Much faster and cleaner. If you want to return rows from the INSERT additionally use a set-returning function. Example:
Return a query from a function?
You would not need a function at all. Just this query:
INSERT INTO street(ad_geom, st_geom, distance, traffic_ct) -- any columns in street
SELECT ad.geom, st.geom, ST_Distance(ad.geom, st.geom), ad.traffic_ct
FROM ad
LEFT JOIN st ON ST_DWithin(ad.geom, st.geom, 50.0)
RETURNING * -- all columns in street
I guess you don't actually need anything returned any more, since this query does all you wanted, but I kept RETURNING as proof of concept. You can just skip it.
Use [INNER] JOINinstead of LEFT [OUTER] JOIN if you don't want to include adresses with no matching street.
The manual about RETURNING in INSERT:
The optional RETURNING clause causes INSERT to compute and return
value(s) based on each row actually inserted [...] any expression
using the table's columns is allowed. The syntax of the RETURNING
list is identical to that of the output list of SELECT. Only rows
that were successfully inserted or updated will be returned.
If you need to wrap this into a plpgsql function (could also be a simple SQL function) - and still return all rows:
CREATE OR REPLACE FUNCTION insert_neighbours()
RETURNS SETOF street AS
$func$
BEGIN
RETURN QUERY
INSERT INTO street(ad_geom, st_geom, distance, traffic_ct) -- any columns in street
SELECT ad.geom, st.geom, ST_Distance(ad.geom, st.geom), ad.traffic_ct
FROM ad
LEFT JOIN st ON ST_DWithin(ad.geom, st.geom, 50.0)
RETURNING *; -- all columns in street
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM insert_neighbours(); -- use SELECT * FROM ... !
For simplicity I return the whole row, but you can return select columns as well. See above example.
Creating the function gives no error
That's because PL/pgSQL currently only runs superficial syntax checks on CREATE FUNCTION. You have to actually execute the function to test it - and make sure that all branches of code in plpgsql functions get tested.
What you appear to want is to calculate a distance between two geometries and then insert that distance in a table if it is than 50.0. The function would be like this:
CREATE FUNCTION get_dist(ad geometry, st geometry) RETURNS double precision AS $$
DECLARE
dist double precision;
BEGIN
dist := ST_Distance(ad, st);
IF dist < 50.0 THEN
INSERT INTO street(Distance) VALUES (dist);
END IF;
RETURN dist;
END;
$$ LANGUAGE plpgsql;
However, I doubt that you really want that. For starters, the inserted row in table street will be assigned distance = dist when the function is called and the condition met, but no other properties (except any default values). What you really want is not clear from your question, but I hope you can work from the code above to make a working function.

Using geometry variable in PostgreSQL/PostGIS statements

I'm trying to write an UPSERT statement to insert or update a row in a PostgresSQL database containing a geometry column. My input is a KML fragment and the following statement works for me as long as the KML is valid.
UPDATE area SET shape = ST_GeomFromKML('{the KML}') WHERE area_code = '{the area}';
INSERT INTO area(area_code, shape) SELECT '{the area}', ST_GeomFromKML('{the KML}') WHERE NOT EXISTS (SELECT 1 FROM area WHERE area_code = '{0}');
In case it's relevant I am calling this from a C# ASP.NET MVC application using a SqlCommand object, but that shouldn't matter as long as the SQL statement is correct.
The changes I want are to use ST_IsValid and ST_MakeValid to ensure that the column is correct. Unfortunately my recent database experience is mostly SQL Server with a little MySQL and PostgresSQL statements don't appear to handle variables in the same way.
What I would like is for something like:
DECLARE #shape geometry;
SELECT #shape = ST_GeomFromKML('{the KML}');
IF NOT (ST_IsValid(#shape)) SELECT #shape = ST_MakeValid(#shape);
UPDATE area SET shape = #shape WHERE area_code = '{the area}';
INSERT INTO area(area_code, shape) SELECT '{the area}', #shape WHERE NOT EXISTS (SELECT 1 FROM area WHERE area_code = '{0}');
so that I am checking validity and correcting it once in the code. However, even after reading the documentation, I don't understand how to use variables to do this.
In PostgreSQL you need to write a stored procedure in the PL/pgSQL language (assuming that your kml fragment and "the area" are strings):
CREATE FUNCTION myFunc(kml text, zip text) RETURNS void AS $$
DECLARE shp geometry;
BEGIN
shp := ST_GeomFromKML(kml);
IF NOT (ST_IsValid(shp)) THEN
shp := ST_MakeValid(shp);
END IF;
UPDATE area SET shape = shp WHERE area_code = zip;
INSERT INTO area (area_code, shape)
SELECT zip, shp
WHERE NOT EXISTS (SELECT 1 FROM area WHERE area_code = zip);
END;
$$ LANGUAGE plpgsql;
Documentation on PL/pgSQL can be found here.