Шукаєте алгоритм для виявлення кругообігу та початку та кінця кола?


24

У мене є багато польотних даних від пілотів планера у формі виправлень gps через фіксований інтервал. Я хотів би проаналізувати траєкторію польоту і виявити початок і кінець «кружляння», який пілот планера зробить, коли знайде терміки.

В ідеалі алгоритм дав би мені початок і кінцеву точку на лінії, визначаючи одне "коло". Ці точки можуть дорівнювати одному з виправлень gps і не потрібно їх інтерполювати.

Я просто міг пройтися по трасі польоту, перевірити швидкість повороту і мати деякі критерії, щоб вирішити, крутить чи ні планер.

Оскільки я використовую PostgreSQL з розширенням PostGIS, мені було цікаво, чи є кращий підхід до цієї проблеми. У мене вже є процедура обчислення кута двох відрізків рядків:

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: list.osgeo.org/pipermail/postgis-users/2015-February/…
Barrett

Дякую обом. Я погляну на перетворення Хауфа. Дискусія на osgeo.org передбачає, що я вже знаю, з чого починається і закінчується коло, якщо я правильно його зрозумів?
pgross


@DevdattaTengshe Так, але все одно дякую. Це був би підхід, де мені довелося б обчислити сплайни і кривизни зовні, правда? Під зовнішнім я маю на увазі не як процедуру чи запит безпосередньо в базі даних. Оскільки рейси не змінюються, як тільки вони знаходяться в базі даних, це буде можливим варіантом.
pgross

Чи можете ви розмістити деякі зразкові дані у форматі .sql?
дбастон

Відповіді:


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 Він аналізує, коли лінія робить цикл (ST_BuildArea поверне істину лише тоді, коли лінія створює закриту область), а не шукає перехрестя.
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

Я дав вам нагороду, оскільки ваша відповідь, як правило, однакова.
kttii
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.