Postgres - How to automatically call ST_SetSRID(ST_MakePoint(lng, lat), 4326) on inserts?

▼魔方 西西 提交于 2019-12-10 15:38:13

问题


I'm using postGIS, and I'm not very familiar with SQL.

I can successfully insert into my markers table as long as I do something like this (pseudocode!):

'INSERT INTO markers(created_by, title, description, lat, lng, geography)\
       values($1, $2, $3, $4::decimal, $5::decimal, ST_SetSRID(ST_MakePoint($5::decimal, $4::decimal), $6))',
      [
        data.created_by,
        data.title,
        data.description,
        data.lat,
        data.lng,
        4326
      ]'

As you can see, I am doing ST_SetSRID(ST_MakePoint($5::decimal, $4::decimal), $6) from my application code.

I want to avoid that, and instead put this on the database side. Since I can do things like auto time stamps on the db side, I'm wondering if I can also do ST_SetSRID(ST_MakePoint(lng, lat), 4326) on the table itself.

This way, the application only needs to provide lat and lng, and the database will do the ST_SetSRID(ST_MakePoint()) operation.

Thanks

Edit: I'm not sure whose answer to mark as +1 since both offered tremendously good help and advice. I hope future readers will read both and hopefully learn something!


回答1:


You can do this by using a trigger. Your insert call would not deal with the geometry, only with lat-long and other non-spatial fields, and the trigger function will create the geometry. Let's not forget to do the same when updating the row. Note that I have hard-coded the SRID as it is not possible to pass extra dynamic parameters to the trigger function. If need be, add a field to your table to hold this value and you can refer to it as new.srid

CREATE OR REPLACE FUNCTION markers_geog_tg_fn() RETURNS trigger AS
$BODY$BEGIN
  IF TG_OP = 'INSERT' AND (NEW.lat ISNULL or NEW.lng ISNULL  ) THEN
    RETURN NEW; -- no  geometry
  ELSIF TG_OP = 'UPDATE' THEN
    --Lat Long updated to null, erase geometry
    IF NEW.lat ISNULL or NEW.lng ISNULL THEN
        NEW.geography = NULL;
    END IF;

    IF NEW.lat IS NOT DISTINCT FROM OLD.lat and NEW.lng IS NOT DISTINCT FROM OLD.lng THEN
      RETURN NEW; -- same old geometry
    END IF;
  END IF;
  -- Attempt to transform a geometry
  BEGIN
    NEW.geography := ST_SetSRID(ST_MakePoint(NEW.lng::decimal, NEW.lat::decimal), 4326))
  EXCEPTION WHEN SQLSTATE 'XX000' THEN
    RAISE WARNING 'geography  not updated: %', SQLERRM;
  END;
  RETURN NEW;
END;$BODY$ LANGUAGE plpgsql;

CREATE TRIGGER markers_geog_tg BEFORE INSERT OR UPDATE
   ON markers FOR EACH ROW
   EXECUTE PROCEDURE markers_geog_tg_fn();



回答2:


I want to avoid that, and instead put this on the database side. Since I can do things like auto time stamps on the db side, I'm wondering if I can also do ST_SetSRID(ST_MakePoint(lng, lat), 4326) on the table itself.

It's really not a good idea, I know it sounds like a good idea though and seems appealing. That said, you're doing more work than you need to do.

CREATE TABLE markers (geom geography(POINT,4326) );
INSERT INTO markers(geom) VALUES (ST_MakePoint(0,0));

As you can see here, geography has a default SRID of 4326. You don't have to set it to 4326, though being explicit doesn't hurt. Moreover, casting the input to ST_MakePoint() to decimal is a bad idea. ST_MakePoint only accepts double precision. Result from \df ST_MakePoint

                                                     List of functions
 Schema |     Name     | Result data type |                          Argument data types                           |  Type  
--------+--------------+------------------+------------------------------------------------------------------------+--------
 public | st_makepoint | geometry         | double precision, double precision                                     | normal
 public | st_makepoint | geometry         | double precision, double precision, double precision                   | normal
 public | st_makepoint | geometry         | double precision, double precision, double precision, double precision | normal

So you can see here you extra cast is actually just adding more work. What you really want is just the simple ST_MakePoint(lng,lat) into a geography(POINT,4326) column.

The rest of the task requires a trigger. It would also require having an extra column lat and long. While I can tell you how to do it, I would highly suggest not doing it.

ALTER TABLE markers
  ADD COLUMN lat double precision,
  ADD COLUMN long double precision;

CREATE OR REPLACE FUNCTION do_stupid_stuff()
RETURNS trigger
AS $$
  BEGIN
    NEW.geom = ST_MakePoint(NEW.long, NEW.lat);
    RETURN NEW;
  END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER really_lazy
  AFTER INSERT ON markers
  FOR EACH ROW
  EXECUTE PROCEDURE do_stupid_stuff();


来源:https://stackoverflow.com/questions/47492035/postgres-how-to-automatically-call-st-setsridst-makepointlng-lat-4326-on

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!