PostGIS – How to Convert LineStringZ to LineStringM Using Alter Column

linestringpostgispostgresqlsql

I am attempting to import linestring data with or without m values, then test whether the data has m values, then add m values to the geometry if they are missing. So far, I have working tests for the m values, and I can create the new geometry with m values included when using a select statement. However, I am trying to use this test in an alter table/alter column statement, which doesn't allow subqueries. I needed the subquery that I am using in order to convert a linestringz to a linestringm. I thought there would be a postgis function to swap coordinates between z and m, like st_force3dm(geom, st_z(geom)), which obviously doesn't work because st_z requires a point. Is there a way to swap a linestringz to linestringm using the z values as the new m values, and within an alter table statement?

This is the code I have so far. It tests whether m values exists, whether they are all 0, whether z values exist and whether they are all 0. If it finds m values that are not all 0, it returns the original geometry. If it finds no z or m values, or z and m values are all 0, it uses the geometry length to create m values. If it finds z values that are not all 0, it creates a linestringm using the z values as the m values. This last part doesn't work in an alter table statement because I have used a subquery to assign the z values to m values. I'm open to suggestions about better ways to achieve this task.

Doesn't work due to subquery in alter table:

alter table mt_altis_statewide_routes alter column geom type geometry(multilinestringm,4326) using 
    case when ((st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
        and ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_endpoint(geom)) is null) 
        then st_addmeasure(geom,0,length_miles(geom)) 
    when ((st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
        and (st_z(st_startpoint(geom)) != 0 or st_z(st_endpoint(geom)) != 0) and st_z(st_startpoint(geom)) is not null 
        then (select st_setsrid(st_makeline(st_makepointm(st_x(geom),st_y(geom),st_z(geom)) order by st_z(geom)),4326) from (select (st_dumppoints(geom)).geom) as f) 
    when ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_startpoint(geom)) is null) 
        and (st_m(st_startpoint(geom)) != 0 or st_m(st_endpoint(geom)) != 0) and st_m(st_startpoint(geom)) is not null 
        then geom end;

Works as select statement:

select 
    case when ((st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
        and ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_endpoint(geom)) is null) 
        then st_addmeasure(geom,0,length_miles(geom)) 
    when ((st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
        and (st_z(st_startpoint(geom)) != 0 or st_z(st_endpoint(geom)) != 0) and st_z(st_startpoint(geom)) is not null 
        then (select st_setsrid(st_makeline(st_makepointm(st_x(geom),st_y(geom),st_z(geom)) order by st_z(geom)),4326) from (select (st_dumppoints(geom)).geom) as f) 
    when ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_startpoint(geom)) is null) 
        and (st_m(st_startpoint(geom)) != 0 or st_m(st_endpoint(geom)) != 0) and st_m(st_startpoint(geom)) is not null 
        then geom end geom 
from mt_state_maintained_routes msmr ;

Best Answer

As said in the comments it would be necessary put your conversion code in a function like this:

CREATE OR REPLACE FUNCTION make_multilinestringm(geometry) RETURNS geometry(multilinestringm,4326) AS $$
DECLARE 
    geom geometry;  
BEGIN
    geom = $1;
   case  
       when (( st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
            and ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_endpoint(geom)) is null) 
            then return st_addmeasure(geom,0,length_miles(geom));        
       when ((st_m(st_startpoint(geom)) = 0 and st_m(st_endpoint(geom)) = 0) or st_m(st_startpoint(geom)) is null) 
            and (st_z(st_startpoint(geom)) != 0 or st_z(st_endpoint(geom)) != 0) and st_z(st_startpoint(geom)) is not null 
            then return (select st_setsrid(st_makeline(st_makepointm(st_x(geom),st_y(geom),st_z(geom)) order by st_z(geom)),4326) from (select (st_dumppoints(geom)).geom) as f); 
       when ((st_z(st_startpoint(geom)) = 0 and st_z(st_endpoint(geom)) = 0) or st_z(st_startpoint(geom)) is null) 
            and (st_m(st_startpoint(geom)) != 0 or st_m(st_endpoint(geom)) != 0) and st_m(st_startpoint(geom)) is not null 
            then return geom; 
    end case;    
END;
$$
STRICT
LANGUAGE plpgsql IMMUTABLE;

and then execute alter sentence using it

alter table mt_altis_statewide_routes alter column geom type geometry(multilinestringm,4326) using make_multilinestringm(geom);