寻求算法来检测圆以及圆的起点和终点?


24

我从固定滑翔机飞行员那里获得了许多飞行数据,这些数据以固定间隔的gps修复的形式出现。我想分析飞行路径,并检测滑翔机飞行员在发现热量时将进行的“绕圈”的开始和结束。

理想情况下,一种算法将为我提供直线上的起点和终点,从而定义一个“圆”。这些点可以等于gps修复之一,不需要插值。

我只是可以沿着飞行路线行走,检查转弯速率,并有一些标准来确定滑翔机是否在盘旋。

当我使用带有PostGIS扩展名的PostgreSQL时,我很好奇是否有更好的方法可以解决此问题。我已经有一个过程来计算两个线段的角度:

CREATE OR REPLACE FUNCTION angle_between(
  _p1 GEOMETRY(PointZ,4326),
  _p2 GEOMETRY(PointZ,4326),
  _p3 GEOMETRY(PointZ,4326)
) RETURNS DECIMAL AS $$
DECLARE
  az1 FLOAT;
  az3 FLOAT;
BEGIN
  az1 = st_azimuth(_p2,_p1);
  az3 = st_azimuth(_p2,_p3);
IF az3 > az1 THEN
  RETURN (
      degrees(az3 - az1)::decimal - 180
  );
ELSE
  RETURN (
      degrees(az3 - az1)::decimal + 180
  );
END IF;
END;
$$ LANGUAGE plpgsql;

当角度的总和大于360度或小于-360度时,应该可以遍历所有线段并进行检查。然后,如果需要,我可以使用st_centroid检测圆的中心。

有没有更好的方法?


根据要求,我上传了一个示例航班

样本飞行路线


1
环顾四周,引发了霍夫环变换。这里有一个类似的(尽管带有多边形)postgis用户讨论:lists.osgeo.org/pipermail/postgis-users/2015-February/…–
Barrett

谢谢你们俩。我来看看霍夫变换。osgeo.org上的讨论假设我已经知道圆的起点和终点,如果我正确理解的话?
pgross


@DevdattaTengshe是的,但是还是谢谢。那是一种我必须从外部计算样条曲线和曲率的方法,对吗?外部的,我的意思不是作为过程或直接在数据库上查询。由于航班不会更改,因此一旦将其保存在数据库中,这将是一个选择。
pgross

您可以将一些示例数据发布为.sql文件吗?
dbaston

Answers:


14

我一直不停地思考这个问题...我能够提出一个存储过程来进行循环计数。示例路径包含109个循环!

以下是显示的飞行点,其中的循环质心为红色: 在此处输入图片说明

基本上,它按捕获点的顺序遍历各个点,并在遍历这些点时构建一条线。当我们正在构建的线创建一个循环(使用ST_BuildArea)时,我们计算一个循环并从该点开始再次构建线。

此函数返回每个循环的记录集,其中包含循环号,其几何形状,其起点/终点和质心(我也对其进行了一些整理,并使用了更好的变量名):

DROP FUNCTION test.find_loop_count(flightid int);

create function test.find_Loop_count(
    IN flightid      int,
    OUT loopnumber   int,
    OUT loopgeometry geometry,
    OUT loopstartend geometry,
    OUT loopcentroid geometry
    ) 
  RETURNS SETOF record AS
$BODY$

-- s schema 'test' must exist
-- a table 'points' of flight points must exist
--  we are going to iterate through the point path, building a line as we go
--   If the line creates a loop then we count a loop and start over building a new line
--     add the intersection point to the returning recordset
--     add the centroid of the loop to the resulting recordset
-- pass in the flight ID of the flight that you wish to count its loops for example:
--   SELECT * FROM find_loop_count(37);

DECLARE
    rPoint              RECORD;
    gSegment            geometry = NULL;
    gLastPoint          geometry = NULL;
    gLoopPolygon        geometry = NULL;
    gIntersectionPoint  geometry = NULL;
    gLoopCentroid       geometry = NULL;
    iLoops              integer := 0;
BEGIN
    -- for each line segment in Point Path
    FOR rPoint IN 
        WITH
            pts as (
                SELECT location as geom,datetime,row_number() OVER () as rnum 
                FROM test.points 
                WHERE flight_id=flightid
                ORDER BY 2) 
            SELECT ST_AsText(ST_MakeLine(ARRAY[a.geom, b.geom])) AS geom, a.rnum, b.rnum 
            FROM pts as a, pts as b 
            WHERE a.rnum = b.rnum-1 AND b.rnum > 1
        LOOP

        -- if this is the start of a new line then start the segment otherwise add the point to the segment
        if gSegment is null then
            gSegment=rPoint.geom;
        elseif rPoint.geom::geometry=gLastPoint::geometry then
        -- do not add this point to the segment because it is at the same location as the last point
        else
        -- add this point to the line
        gSegment=ST_Makeline(gSegment,rPoint.geom);
        end if;
        -- ST_BuildArea will return true if the line segment is noded and closed
        --  we must also flatten the line to 2D
        --  lets also make sure that there are more than three points in our line to define a loop
        gLoopPolygon=ST_BuildArea(ST_Node(ST_Force2D(gSegment)));
        if gLoopPolygon is not NULL and ST_Numpoints(gSegment) > 3 then
        -- we found a loop
        iLoops:=iLoops+1;

        -- get the intersection point (start/end)
        gIntersectionPoint=ST_Intersection(gSegment::geometry,rPoint.geom::geometry);

        -- get the centroid of the loop
        gLoopCentroid=ST_Centroid(gLoopPolygon);

        -- start building a new line
        gSegment=null;

        LOOPNUMBER   := iLoops;
        LOOPGEOMETRY := gLoopPolygon;
        LOOPSTARTEND := gIntersectionPoint;
        LOOPCENTROID := gLoopCentroid;

        RETURN NEXT;
        end if;
        -- keep track of last segment
        gLastPoint=rPoint.geom;
    END LOOP;
    RAISE NOTICE 'Total loop count is %.', iLoops;
END;
$BODY$
  LANGUAGE plpgsql STABLE
  COST 100
  ROWS 1000;

这是一个仅返回循环计数的简单函数:

DROP FUNCTION test.find_loop_count(flightid int);

create function test.find_Loop_count(flightid int) RETURNS integer AS $$
-- s schema 'test' must exist
-- a table 'points' of flight points must exist
--  we are going to iterate through the line path, building the line as we go
--   If the line creates a loop then we count a loop and start over building a new line
-- pass in the flight ID of the flight that you wish to count its loops for example:
--   SELECT find_loop_count(37);

DECLARE
    segment RECORD;
    s geometry = NULL;
    lastS geometry = NULL;
    b geometry = NULL;
    loops integer := 1;
BEGIN
    -- for each line segment is Point Path
    FOR segment IN 
        WITH
            pts as (
                SELECT location as geom,datetime,row_number() OVER () as rnum 
                FROM test.points 
                WHERE flight_id=flightid
                ORDER BY 2) 
            SELECT ST_AsText(ST_MakeLine(ARRAY[a.geom, b.geom])) AS geom, a.rnum, b.rnum 
            FROM pts as a, pts as b 
            WHERE a.rnum = b.rnum-1 AND b.rnum > 1
        LOOP

        -- if this is the start of a new line then make s be the segment otherwise add the segment to s
        if s is null then
            s=segment.geom;
        elseif segment.geom::geometry=lastS::geometry then
        else
            s=ST_Makeline(s,segment.geom);
        end if;
        -- ST_BuildArea will return true if the line segment is noded and closed
        --  we must also flatten the line to 2D
        b=ST_BuildArea(st_node(ST_Force2D(s)));
        if b is not NULL and st_numpoints(s) > 3 then
            RAISE NOTICE 's: %', s;
            RAISE NOTICE 'vvvvv %',st_numpoints(s);
            RAISE NOTICE 'I found a loop! Loop count is now %', loops;
            RAISE NOTICE '^^^^^';
            s=null;
            loops:=loops +1;
        end if;
        lastS=segment.geom;
    END LOOP;
    RAISE NOTICE 'Total loop count is %.', loops-1;
    RETURN loops-1;
END;
$$ LANGUAGE plpgsql;


这看起来很有希望。非常感谢。我将需要增强它,因为我对圈数不感兴趣,但对起点/终点不感兴趣。但是我想那应该很容易返回。
pgross

听起来很聪明。如何处理一个循环与另一循环相交的情况?还是在找到循环后就跳过初始点?
PeterHorsbøllMøller'16

@PeterHorsbøllMøller它分析线何时循环(ST_BuildArea仅在线创建封闭区域时返回true),而不是寻找交集。
kttii

@pgross糟糕!我有些困惑,忘了起点/终点,但是是的,既然可以区分出循环,这是一个很容易的确定。
kttii

@pgross在我看来,通过定位每个循环的ST_Centroid而不是定位每个循环的开始/结束,您可能会获得更合理的散热位置。你怎么看?当然,该功能可以提供所有三个统计信息。
kttii

3

我注意到gpx文件具有可以被利用的时间戳。也许以下方法可能有效。

Make a linesegement with Vi,Vi+1
Make it Polyline
Proceed to Vi+2,Vi+3 check intersection with Polyline
  if it intersects 
      find the point of intersection-Designate this as start/end point of the loop
      Make this intersection point as Vi and Vi+1 would next gpx point per time sequence  
  if the linesegement does not intersect with polyyline then  increment 'i' 

我发现很难使用ST_Intersects,因为圆圈重叠导致我不得不使用ST_BuildArea。
kttii'9

我给了您赏金,因为您的答案通常在同一轨道上。
kttii
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.