简体   繁体   English

计算外环PostGIS的角度(多边形和多面)

[英]Calculate the angle of exterior rings PostGIS (polygons & multipolygons)

在此处输入图片说明

SAMPLE DATA: 样本数据:

CREATE TABLE poly_and_multipoly (
  "id" SERIAL NOT NULL PRIMARY KEY,
  "name" char(1) NOT NULL,
  "the_geom" geometry NOT NULL
);
-- add data, A is a polygon, B is a multipolygon
INSERT INTO poly_and_multipoly (name, the_geom) VALUES (
    'A', 'POLYGON((7.7 3.8,7.7 5.8,9.0 5.8,7.7 3.8))'::geometry
    ), (
    'B',
    'MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))'::geometry
);

I have a table of multipolygons and polygons and I am trying to calculate the interior angles of the exterior rings in the table (ie no interior rings...) using ST_Azimuth . 我有一个由多面和多边形组成的表格,我正在尝试使用ST_Azimuth计算表格中外圈的内角 (即没有内圈...)。 Is there any way to modify the attached query to use ST_Azimuth on the sp and ep of the linestrings? 有什么方法可以修改附加的查询以在线串的sp和ep上使用ST_Azimuth

    SELECT id, name, ST_AsText( ST_MakeLine(sp,ep) )
FROM
   -- extract the endpoints for every 2-point line segment for each linestring
   (SELECT id, name,
      ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)) as sp,
      ST_PointN(geom, generate_series(2, ST_NPoints(geom)  )) as ep
    FROM
       -- extract the individual linestrings
      (SELECT id, name, (ST_Dump(ST_Boundary(the_geom))).geom
       FROM poly_and_multipoly
       ) AS linestrings
    ) AS segments;

1;"A";"LINESTRING(7.7 3.8,7.7 5.8)"
1;"A";"LINESTRING(7.7 5.8,9 5.8)"
1;"A";"LINESTRING(9 5.8,7.7 3.8)"
2;"B";"LINESTRING(0 0,4 0)"

The use of ST_Azimuth function at aengus example is giving an error because you can't use two generate_series as parameters for a single function. 在aengus示例中使用ST_Azimuth函数会产生错误,因为您不能将两个generate_series用作单个函数的参数。 If you use ep / sp values in another subquery then you won't have any trouble. 如果在另一个子查询中使用ep / sp值,则不会有任何麻烦。

Here is my solution: 这是我的解决方案:

-- 3.- Create segments from points and calculate azimuth for each line.
--     two calls of generate_series for a single function wont work (azimuth).
select id,
       name,
       polygon_num,
       point_order as vertex,
       --
       case when point_order = 1
         then last_value(ST_Astext(ST_Makeline(sp,ep))) over (partition by id, polygon_num)
         else lag(ST_Astext(ST_Makeline(sp,ep)),1) over (partition by id, polygon_num order by point_order)
       end ||' - '||ST_Astext(ST_Makeline(sp,ep)) as lines,
       --
       abs(abs(
       case when point_order = 1
         then last_value(degrees(ST_Azimuth(sp,ep))) over (partition by id, polygon_num)
         else lag(degrees(ST_Azimuth(sp,ep)),1) over (partition by id, polygon_num order by point_order)
       end - degrees(ST_Azimuth(sp,ep))) -180 ) as ang
from (-- 2.- extract the endpoints for every 2-point line segment for each linestring
      --     Group polygons from multipolygon
      select id,
             name,
             coalesce(path[1],0) as polygon_num,
             generate_series(1, ST_Npoints(geom)-1) as point_order,
             ST_Pointn(geom, generate_series(1, ST_Npoints(geom)-1)) as sp,
             ST_Pointn(geom, generate_series(2, ST_Npoints(geom)  )) as ep
      from ( -- 1.- Extract the individual linestrings and the Polygon number for later identification
             select id,
                    name,
                    (ST_Dump(ST_Boundary(the_geom))).geom as geom,
                    (ST_Dump(ST_Boundary(the_geom))).path as path -- To identify the polygon
              from poly_and_multipoly ) as pointlist ) as segments;

I've added some complexity as I wanted to identify each polygon to avoid mixing lines for angle calculation. 我增加了一些复杂性,因为我想标识每个多边形以避免混合用于角度计算的线。

As sqlfiddle don't have support for PostGIS I've uploaded this example with some complementary code to github here 由于sqlfiddle没有对PostGIS的支持,我上传这个例子与一些辅助代码github上这里

You can add the azimuth calculation into your subquery as follows. 您可以按以下方式将方位角计算添加到子查询中。 Note that ST_Azimuth computes the angle clockwise from down to up, so more work will be required to ensure that az is actually the interior angle. 请注意,ST_Azimuth从下到上顺时针计算角度,因此需要做更多工作才能确保az实际上是内角。

     SELECT id, name, ST_AsText( ST_MakeLine(sp,ep) ), az
    FROM
      -- extract the endpoints for every 2-point line segment for each      linestring
 (SELECT id, name,
  ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)) as sp,
  ST_PointN(geom, generate_series(2, ST_NPoints(geom)  )) as ep,
    ST_Azimuth(ST_PointN(geom, generate_series(1, ST_NPoints(geom)-1)),
    ST_PointN(geom, generate_series(2, ST_NPoints(geom)  ))) as az
FROM
   -- extract the individual linestrings
  (SELECT id, name, (ST_Dump(ST_Boundary(the_geom))).geom
   FROM poly_and_multipoly
   ) AS linestrings
) AS segments;

I needed the internal angles for only polygons. 我只需要多边形的内角。 The accepted solution by vamaq did not consistently give me the internal angles, but sometimes external angles, for an arbitrarily shaped polygon. vamaq接受的解决方案并不能始终为我提供任意形状的多边形的内角,有时还可以是外角。 This is because it calculates the angle between two lines without any consideration of which side of those two lines is internal, so if the internal angle is obtuse vamaq's solution gives the external angle instead. 这是因为它在计算两条线之间的角度时不考虑这两条线的哪一侧是内部角度,因此如果内部角度钝,则vamaq的解决方案将给出外部角度。

In my solution I use the cosine rule to determine the angle between two lines without reference to the Azimuth and then use ST_Contains to check whether the angle of the polygon is acute or obtuse so that I can ensure the angle is internal. 在我的解决方案中,我使用余弦规则来确定两条线之间的角度而不参考方位角,然后使用ST_Contains检查多边形的角度是锐角还是钝角,以便可以确保该角度是内部的。

CREATE OR REPLACE FUNCTION is_internal(polygon geometry, p2 geometry, p3 geometry)
RETURNS boolean as
$$
    BEGIN
        return st_contains(polygon, st_makeline(p2, p3));
    END
$$ language plpgsql;


CREATE OR REPLACE FUNCTION angle(p1 geometry, p2 geometry, p3 geometry)
RETURNS float AS
$$
    DECLARE
        p12 float;
        p23 float;
        p13 float;
    BEGIN
        select st_distance(p1, p2) into p12;
        select st_distance(p1, p3) into p13;
        select st_distance(p2, p3) into p23;
        return acos((p12^2 + p13^2 - p23^2) / (2*p12*p13));
    END
$$ language plpgsql;

CREATE OR REPLACE FUNCTION internal_angle(polygon geometry, p1 geometry, p2 geometry, p3 geometry)
RETURNS float as
$$
    DECLARE
        ang float;
        is_intern boolean;
    BEGIN
        select angle(p1, p2, p3) into ang;
        select is_internal(polygon, p2, p3) into is_intern;
        IF not is_intern THEN
            select 6.28318530718 - ang into ang;
        END IF;
        return ang;
    END
$$ language plpgsql;


CREATE OR REPLACE FUNCTION corner_triplets(geom geometry)
RETURNS table(corner_number integer, p1 geometry, p2 geometry, p3 geometry) AS
$$
    DECLARE
        max_corner_number integer;
    BEGIN
        create temp table corners on commit drop as select path[2] as corner_number, t1.geom as point from (select (st_dumppoints($1)).*) as t1 where path[1] = 1;
        select max(corners.corner_number) into max_corner_number from corners;
        insert into corners (corner_number, point) select 0, point from corners where corners.corner_number = max_corner_number - 1;
        create temp table triplets on commit drop as select t1.corner_number, t1.point as p1, t2.point as p2,  t3.point as p3 from corners as t1, corners as t2, corners as t3 where t1.corner_number = t2.corner_number + 1 and t1.corner_number = t3.corner_number - 1;
        return QUERY TABLE triplets;
    END;
$$
LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION internal_angles(geom geometry)
RETURNS table(corner geometry, angle float)
AS $$
    BEGIN
        create temp table internal_angs on commit drop as select p1, internal_angle(geom, p1, p2, p3) from (select (c).* from (select corner_triplets(geom) as c) as t1) as t2;
        return QUERY TABLE internal_angs;
    END;
$$
LANGUAGE plpgsql;

Usage: 用法:

select (c).* into temp from (select internal_angles(geom) as c from my_table) as t;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM