Я не мог перестать думать об этом ... Я смог придумать хранимую процедуру для подсчета циклов. Пример пути содержит 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;