motiejus/stud

study spacejunk
git clone https://git.jakstys.lt/motiejus/stud.git
Log | Tree | Refs | LICENSE

IV/wm.sql (19047B) - Raw


      1 \set ON_ERROR_STOP on
      2 SET plpgsql.extra_errors TO 'all';
      3 
      4 -- wm_detect_bends detects bends using the inflection angles. No corrections.
      5 drop function if exists wm_detect_bends;
      6 create function wm_detect_bends(
      7   line geometry,
      8   dbgname text default null,
      9   dbggen integer default null,
     10   OUT bends geometry[]
     11 ) as $$
     12 declare
     13   pi constant real default radians(180);
     14   p geometry;
     15   p1 geometry;
     16   p2 geometry;
     17   p3 geometry;
     18   bend geometry;
     19   prev_sign int4;
     20   cur_sign int4;
     21   l_type text;
     22   dbgpolygon geometry;
     23 begin
     24   l_type = st_geometrytype(line);
     25   if l_type != 'ST_LineString' then
     26     raise 'This function works with ST_LineString, got %', l_type;
     27   end if;
     28 
     29   -- The last vertex is iterated over twice, because the algorithm uses 3
     30   -- vertices to calculate the angle between them.
     31   --
     32   -- Given 3 vertices p1, p2, p3:
     33   --
     34   --          p1___ ...
     35   --           /
     36   -- ... _____/
     37   --     p3   p2
     38   --
     39   -- When looping over the line, p1 will be head (lead) vertex, p2 will be the
     40   -- measured angle, and p3 will be trailing. The line that will be added to
     41   -- the bend will always be [p3,p2].
     42   -- So once the p1 becomes the last vertex, the loop terminates, and the
     43   -- [p2,p1] line will not have a chance to be added. So the loop adds the last
     44   -- vertex twice, so it has a chance to become p2, and be added to the bend.
     45   for p in
     46       (select geom from st_dumppoints(line) order by path[1] asc)
     47       union all
     48       (select geom from st_dumppoints(line) order by path[1] desc limit 1)
     49      loop
     50     p3 = p2;
     51     p2 = p1;
     52     p1 = p;
     53     continue when p3 is null;
     54 
     55     cur_sign = sign(pi - st_angle(p1, p2, p2, p3));
     56 
     57     if bend is null then
     58       bend = st_makeline(p3, p2);
     59     else
     60       bend = st_linemerge(st_union(bend, st_makeline(p3, p2)));
     61     end if;
     62 
     63     if prev_sign + cur_sign = 0 then
     64       if bend is not null then
     65         bends = bends || bend;
     66       end if;
     67       bend = st_makeline(p3, p2);
     68     end if;
     69     prev_sign = cur_sign;
     70   end loop;
     71 
     72   -- the last line may be lost if there is no "final" inflection angle. Add it.
     73   if (select count(1) >= 2 from st_dumppoints(bend)) then
     74     bends = bends || bend;
     75   end if;
     76 
     77   if dbgname is not null then
     78     for i in 1..array_length(bends, 1) loop
     79       insert into wm_debug(stage, name, gen, nbend, way) values(
     80         'bbends',
     81         dbgname,
     82         dbggen,
     83         i,
     84         bends[i]
     85       );
     86 
     87       dbgpolygon = null;
     88       if st_npoints(bends[i]) >= 3 then
     89           dbgpolygon = st_makepolygon(
     90             st_addpoint(bends[i],
     91               st_startpoint(bends[i]))
     92           );
     93       end if;
     94       insert into wm_debug(stage, name, gen, nbend, way) values(
     95         'bbends-polygon',
     96         dbgname,
     97         dbggen,
     98         i,
     99         dbgpolygon
    100       );
    101     end loop;
    102   end if;
    103 end
    104 $$ language plpgsql;
    105 
    106 -- wm_fix_gentle_inflections moves bend endpoints following "Gentle Inflection
    107 -- at End of a Bend" section.
    108 --
    109 -- The text does not specify how many vertices can be "adjusted"; it can
    110 -- equally be one or many. This function is adjusting many, as long as the
    111 -- cumulative inflection angle small (see variable below).
    112 --
    113 -- The implementation could be significantly optimized to avoid `st_reverse`
    114 -- and array reversals, trading for complexity in wm_fix_gentle_inflections1.
    115 drop function if exists wm_fix_gentle_inflections;
    116 create function wm_fix_gentle_inflections(
    117   INOUT bends geometry[],
    118   dbgname text default null,
    119   dbggen integer default null
    120 ) as $$
    121 declare
    122   len int4;
    123   bends1 geometry[];
    124   dbgpolygon geometry;
    125 begin
    126   len = array_length(bends, 1);
    127 
    128   bends = wm_fix_gentle_inflections1(bends);
    129   for i in 1..len loop
    130     bends1[i] = st_reverse(bends[len-i+1]);
    131   end loop;
    132   bends1 = wm_fix_gentle_inflections1(bends1);
    133 
    134   for i in 1..len loop
    135     bends[i] = st_reverse(bends1[len-i+1]);
    136   end loop;
    137 
    138   if dbgname is not null then
    139     for i in 1..array_length(bends, 1) loop
    140       insert into wm_debug(stage, name, gen, nbend, way) values(
    141         'cinflections',
    142         dbgname,
    143         dbggen,
    144         i,
    145         bends[i]
    146       );
    147 
    148       dbgpolygon = null;
    149       if st_npoints(bends[i]) >= 3 then
    150         dbgpolygon = st_makepolygon(
    151           st_addpoint(bends[i],
    152             st_startpoint(bends[i]))
    153         );
    154       end if;
    155 
    156       insert into wm_debug(stage, name, gen, nbend, way) values(
    157         'cinflections-polygon',
    158         dbgname,
    159         dbggen,
    160         i,
    161         dbgpolygon
    162       );
    163     end loop;
    164   end if;
    165 end
    166 $$ language plpgsql;
    167 
    168 -- wm_fix_gentle_inflections1 fixes gentle inflections of an array of lines in
    169 -- one direction. An implementation detail of wm_fix_gentle_inflections.
    170 drop function if exists wm_fix_gentle_inflections1;
    171 create function wm_fix_gentle_inflections1(INOUT bends geometry[]) as $$
    172 declare
    173   pi constant real default radians(180);
    174   -- the threshold when the angle is still "small", so gentle inflections can
    175   -- be joined
    176   small_angle constant real default radians(45);
    177   ptail geometry; -- tail point of tail bend
    178   phead geometry[]; -- 3 tail points of head bend
    179   i int4; -- bends[i] is the current head
    180 begin
    181   for i in 2..array_length(bends, 1) loop
    182     -- Predicate: two bends will always share an edge. Assuming (A,B,C,D,E,F)
    183     -- is a bend:
    184     --           C________D
    185     --           /        \
    186     -- \________/          \_______/
    187     -- A       B           E       F
    188     --
    189     -- Then edges (A,B) and (E,F) are shared with the neighboring bends.
    190     --
    191     --
    192     -- Assume this curve (figure `inflection-1`), going clockwise from A:
    193     --
    194     --    \______B
    195     --    A      `-------. C
    196     --                   |
    197     --    G___ F         |
    198     --    /   `-----.____+ D
    199     --              E
    200     --
    201     -- After processing the curve following the definition of a bend, the bend
    202     -- [A-E] would be detected. Assuming inflection point E and F are "small",
    203     -- the bend needs to be extended by two edges to [A,G].
    204     select geom from st_dumppoints(bends[i-1])
    205       order by path[1] asc limit 1 into ptail;
    206 
    207     while true loop
    208       -- copy last 3 points of bends[i-1] (tail) to ptail
    209       select array(
    210         select geom from st_dumppoints(bends[i]) order by path[1] asc limit 3
    211       ) into phead;
    212 
    213       -- if the bend got too short, stop processing it
    214       exit when array_length(phead, 1) < 3;
    215 
    216       -- inflection angle between ptail[1:3] is "large", stop processing
    217       exit when abs(st_angle(phead[1], phead[2], phead[3]) - pi) > small_angle;
    218 
    219       -- distance from head's 1st vertex should be larger than from 2nd vertex
    220       exit when st_distance(ptail, phead[2]) < st_distance(ptail, phead[3]);
    221 
    222       -- Detected a gentle inflection.
    223       -- Move head of the tail to the tail of head
    224       bends[i] = st_removepoint(bends[i], 0);
    225       bends[i-1] = st_addpoint(bends[i-1], phead[3]);
    226     end loop;
    227 
    228   end loop;
    229 end
    230 $$ language plpgsql;
    231 
    232 -- wm_if_selfcross returns whether baseline of bendi crosses bendj.
    233 -- If it doesn't, returns a null geometry.
    234 -- Otherwise, it will return the baseline split into a few parts where it
    235 -- crosses bendj.
    236 drop function if exists wm_if_selfcross;
    237 create function wm_if_selfcross(
    238   bendi geometry,
    239   bendj geometry
    240 ) returns geometry as $$
    241 declare
    242   a geometry;
    243   b geometry;
    244   multi geometry;
    245 begin
    246   a = st_pointn(bendi, 1);
    247   b = st_pointn(bendi, -1);
    248   multi = st_split(bendj, st_makeline(a, b));
    249 
    250   if st_numgeometries(multi) = 1 then
    251     return null;
    252   end if;
    253 
    254   if st_numgeometries(multi) = 2 and
    255     (st_contains(bendj, a) or st_contains(bendj, b)) then
    256     return null;
    257   end if;
    258 
    259   return multi;
    260 end
    261 $$ language plpgsql;
    262 
    263 
    264 -- wm_self_crossing eliminates self-crossing from the bends, following the
    265 -- article's section "Self-line Crossing When Cutting a Bend".
    266 drop function if exists wm_self_crossing;
    267 create function wm_self_crossing(
    268   INOUT bends geometry[],
    269   dbgname text default null,
    270   dbggen integer default null,
    271   OUT mutated boolean
    272 ) as $$
    273 declare
    274   pi constant real default radians(180);
    275   i int4;
    276   j int4;
    277   multi geometry;
    278 begin
    279   mutated = false;
    280   <<bendloop>>
    281   for i in 1..array_length(bends, 1) loop
    282     continue when abs(wm_inflection_angle(bends[i])) <= pi;
    283     -- sum of inflection angles for this bend is >180, so it may be
    284     -- self-crossing. Now try to find another bend in this line that
    285     -- crosses an imaginary line of end-vertices
    286 
    287     -- Go through each bend in the given line, and see if has a potential to
    288     -- cross bends[i]. The line-cut process is different when i<j and i>j;
    289     -- therefore there are two loops, one for each case.
    290     for j in 1..i-1 loop
    291       multi = wm_if_selfcross(bends[i], bends[j]);
    292       continue when multi is null;
    293       mutated = true;
    294 
    295       -- remove first vertex of the following bend, because the last
    296       -- segment is always duplicated with the i'th bend.
    297       bends[i+1] = st_removepoint(bends[i+1], 0);
    298       bends[j] = st_geometryn(multi, 1);
    299       bends[j] = st_setpoint(
    300         bends[j],
    301         st_npoints(bends[j])-1,
    302         st_pointn(bends[i], st_npoints(bends[i]))
    303       );
    304       bends = bends[1:j] || bends[i+1:];
    305       continue bendloop;
    306     end loop;
    307 
    308     for j in reverse array_length(bends, 1)..i+1 loop
    309       multi = wm_if_selfcross(bends[i], bends[j]);
    310       continue when multi is null;
    311       mutated = true;
    312 
    313       -- remove last vertex of the previous bend, because the last
    314       -- segment is duplicated with the i'th bend.
    315       bends[i-1] = st_removepoint(bends[i-1], st_npoints(bends[i-1])-1);
    316       bends[i] = st_makeline(
    317         st_pointn(bends[i], 1),
    318         st_removepoint(st_geometryn(multi, st_numgeometries(multi)), 0)
    319       );
    320       bends = bends[1:i] || bends[j+1:];
    321       continue bendloop;
    322     end loop;
    323   end loop;
    324 
    325   if dbgname is not null then
    326     insert into wm_debug(stage, name, gen, nbend, way) values(
    327       'dcrossings',
    328       dbgname,
    329       dbggen,
    330       generate_subscripts(bends, 1),
    331       unnest(bends)
    332     );
    333   end if;
    334 end
    335 $$ language plpgsql;
    336 
    337 drop function if exists wm_inflection_angle;
    338 create function wm_inflection_angle (IN bend geometry, OUT angle real) as $$
    339 declare
    340   pi constant real default radians(180);
    341   p0 geometry;
    342   p1 geometry;
    343   p2 geometry;
    344   p3 geometry;
    345 begin
    346   angle = 0;
    347   for p0 in select geom from st_dumppoints(bend) order by path[1] asc loop
    348     p3 = p2;
    349     p2 = p1;
    350     p1 = p0;
    351     continue when p3 is null;
    352     angle = angle + abs(pi - st_angle(p1, p2, p3));
    353   end loop;
    354 end
    355 $$ language plpgsql;
    356 
    357 drop function if exists wm_bend_attrs;
    358 drop function if exists wm_isolated_bends;
    359 drop function if exists wm_elimination;
    360 drop type if exists wm_t_bend_attrs;
    361 create type wm_t_bend_attrs as (
    362   bend geometry,
    363   area real,
    364   cmp real,
    365   adjsize real,
    366   baselinelength real,
    367   curvature real,
    368   isolated boolean
    369 );
    370 create function wm_bend_attrs(
    371   bends geometry[],
    372   dbgname text default null,
    373   dbggen integer default null
    374 ) returns setof wm_t_bend_attrs as $$
    375 declare
    376   fourpi constant real default 4*radians(180);
    377   i int4;
    378   polygon geometry;
    379   bend geometry;
    380   res wm_t_bend_attrs;
    381 begin
    382   for i in 1..array_length(bends, 1) loop
    383     bend = bends[i];
    384     res = null;
    385     res.bend = bend;
    386     res.area = 0;
    387     res.cmp = 0;
    388     res.adjsize = 0;
    389     res.baselinelength = st_distance(st_startpoint(bend), st_endpoint(bend));
    390     res.curvature = wm_inflection_angle(bend) / st_length(bend);
    391     res.isolated = false;
    392     if st_numpoints(bend) >= 3 then
    393       polygon = st_makepolygon(st_addpoint(bend, st_startpoint(bend)));
    394       -- Compactness Index (cmp) is defined as "the ratio of the area of the
    395       -- polygon over the circle whose circumference length is the same as the
    396       -- length of the circumference of the polygon". I assume they meant the
    397       -- area of the circle. So here goes:
    398       -- 1. get polygon area P.
    399       -- 2. get polygon perimeter = u. Pretend it's our circle's circumference.
    400       -- 3. get A (area) of the circle from u: A = u^2/(4pi)
    401       -- 4. divide P by A: cmp = P/A = P/(u^2/(4pi)) = 4pi*P/u^2
    402       res.area = st_area(polygon);
    403       res.cmp = fourpi*res.area/(st_perimeter(polygon)^2);
    404       if res.cmp > 0 then
    405         res.adjsize = (res.area*(0.75/res.cmp));
    406       end if;
    407     end if;
    408 
    409     if dbgname is not null then
    410       insert into wm_debug (stage, name, gen, nbend, way, props) values(
    411         'ebendattrs',
    412         dbgname,
    413         dbggen,
    414         i,
    415         bend,
    416         jsonb_build_object(
    417           'area', res.area,
    418           'cmp', res.cmp,
    419           'adjsize', res.adjsize,
    420           'baselinelength', res.baselinelength,
    421           'curvature', res.curvature
    422         )
    423       );
    424     end if;
    425     return next res;
    426   end loop;
    427 end;
    428 $$ language plpgsql;
    429 
    430 create function wm_elimination(
    431   INOUT bendattrs wm_t_bend_attrs[],
    432   dhalfcircle float,
    433   dbgname text default null,
    434   dbggen integer default null,
    435   OUT mutated boolean
    436 ) as $$
    437 declare
    438   area_threshold float;
    439   leftsize float;
    440   rightsize float;
    441   i int4;
    442   j int4;
    443   tmpbendattrs wm_t_bend_attrs;
    444   dbgbends geometry[];
    445 begin
    446   area_threshold = radians(180) * ((dhalfcircle/2)^2)/2;
    447   mutated = false;
    448 
    449   i = 1;
    450   while i < array_length(bendattrs, 1)-1 loop
    451     i = i + 1;
    452     continue when bendattrs[i].adjsize = 0;
    453     continue when bendattrs[i].adjsize > area_threshold;
    454 
    455     if i = 2 then
    456       leftsize = bendattrs[i].adjsize + 1;
    457     else
    458       leftsize = bendattrs[i-1].adjsize;
    459     end if;
    460 
    461     if i = array_length(bendattrs, 1)-1 then
    462       rightsize = bendattrs[i].adjsize + 1;
    463     else
    464       rightsize = bendattrs[i+1].adjsize;
    465     end if;
    466 
    467     continue when bendattrs[i].adjsize >= leftsize;
    468     continue when bendattrs[i].adjsize >= rightsize;
    469 
    470     -- Local minimum. Elminate bend!
    471     mutated = true;
    472     tmpbendattrs.bend = st_makeline(
    473       st_pointn(bendattrs[i].bend, 1),
    474       st_pointn(bendattrs[i].bend, -1)
    475     );
    476     bendattrs[i] = tmpbendattrs;
    477     -- remove last vertex of the previous bend and
    478     -- first vertex of the next bend, because bends always
    479     -- share a line segment together
    480     tmpbendattrs.bend = st_removepoint(
    481       bendattrs[i-1].bend,
    482       st_npoints(bendattrs[i-1].bend)-1
    483     );
    484     bendattrs[i-1] = tmpbendattrs;
    485     tmpbendattrs.bend = st_removepoint(bendattrs[i+1].bend, 0);
    486     bendattrs[i+1] = tmpbendattrs;
    487     -- the next bend's adjsize is now messed up; it should not be taken
    488     -- into consideration for other local minimas. Skip over 2.
    489     i = i + 2;
    490   end loop;
    491 
    492   if dbgname is not null then
    493     for j in 1..array_length(bendattrs, 1) loop
    494       dbgbends[j] = bendattrs[j].bend;
    495     end loop;
    496 
    497     insert into wm_debug(stage, name, gen, nbend, way) values(
    498       'felimination',
    499       dbgname,
    500       dbggen,
    501       generate_subscripts(dbgbends, 1),
    502       unnest(dbgbends)
    503     );
    504   end if;
    505 end
    506 $$ language plpgsql;
    507 
    508 create function wm_isolated_bends(
    509   INOUT bendattrs wm_t_bend_attrs[],
    510   dbgname text default null,
    511   dbggen integer default null
    512 ) as $$
    513 declare
    514   -- if neighbor's curvatures are within this fraction of the current bend
    515   isolation_threshold constant real default 0.5;
    516   this real;
    517   skip_next bool;
    518   res wm_t_bend_attrs;
    519   i int4;
    520 begin
    521   for i in 2..array_length(bendattrs, 1)-1 loop
    522     res = bendattrs[i];
    523     if skip_next then
    524       skip_next = false;
    525     else
    526       this = bendattrs[i].curvature * isolation_threshold;
    527       if bendattrs[i-1].curvature < this and
    528          bendattrs[i+1].curvature < this then
    529         res.isolated = true;
    530         bendattrs[i] = res;
    531         skip_next = true;
    532       end if;
    533     end if;
    534 
    535     if dbgname is not null then
    536       insert into wm_debug (stage, name, gen, nbend, way, props) values(
    537         'fisolated_bends',
    538         dbgname,
    539         dbggen,
    540         i,
    541         res.bend,
    542         jsonb_build_object(
    543           'area', res.area,
    544           'cmp', res.cmp,
    545           'adjsize', res.adjsize,
    546           'baselinelength', res.baselinelength,
    547           'curvature', res.curvature,
    548           'isolated', res.isolated
    549         )
    550       );
    551     end if;
    552 
    553   end loop;
    554 end
    555 $$ language plpgsql;
    556 
    557 drop function if exists ST_SimplifyWM_Estimate;
    558 create function ST_SimplifyWM_Estimate(
    559   geom geometry,
    560   OUT npoints bigint,
    561   OUT secs bigint
    562 ) as $$
    563 declare
    564   lines geometry[];
    565   l_type text;
    566 begin
    567   l_type = st_geometrytype(geom);
    568   if l_type = 'ST_LineString' then
    569     lines = array[geom];
    570   elseif l_type = 'ST_MultiLineString' then
    571     lines = array((select a.geom from st_dump(geom) a order by path[1] asc));
    572   else
    573     raise 'Unknown geometry type %', l_type;
    574   end if;
    575 
    576   npoints = 0;
    577   for i in 1..array_length(lines, 1) loop
    578     npoints = npoints + st_numpoints(lines[i]);
    579   end loop;
    580   secs = npoints / 150;
    581 end
    582 $$ language plpgsql;
    583 
    584 -- ST_SimplifyWM simplifies a given geometry using Wang & Müller's
    585 -- "Line Generalization Based on Analysis of Shape Characteristics" algorithm,
    586 -- 1998.
    587 -- Input parameters:
    588 -- - geom: ST_LineString or ST_MultiLineString: the geometry to be simplified
    589 -- - dhalfcircle: the diameter of a half-circle, whose area is an approximate
    590 --   threshold for small bend elimination. If bend's area is larger than that,
    591 --   the bend will be left alone.
    592 drop function if exists ST_SimplifyWM;
    593 create function ST_SimplifyWM(
    594   geom geometry,
    595   dhalfcircle float,
    596   dbgname text default null
    597 ) returns geometry as $$
    598 declare
    599   gen integer;
    600   i integer;
    601   j integer;
    602   line geometry;
    603   lines geometry[];
    604   bends geometry[];
    605   bendattrs wm_t_bend_attrs[];
    606   mutated boolean;
    607   l_type text;
    608 begin
    609   l_type = st_geometrytype(geom);
    610   if l_type = 'ST_LineString' then
    611     lines = array[geom];
    612   elseif l_type = 'ST_MultiLineString' then
    613     lines = array((select a.geom from st_dump(geom) a order by path[1] asc));
    614   else
    615     raise 'Unknown geometry type %', l_type;
    616   end if;
    617 
    618   for i in 1..array_length(lines, 1) loop
    619     mutated = true;
    620     gen = 1;
    621     while mutated loop
    622       if dbgname is not null then
    623         insert into wm_debug (stage, name, gen, nbend, way) values(
    624           'afigures', dbgname, gen, i, lines[i]);
    625       end if;
    626 
    627       bends = wm_detect_bends(lines[i], dbgname, gen);
    628       bends = wm_fix_gentle_inflections(bends, dbgname, gen);
    629 
    630       select * from wm_self_crossing(bends, dbgname, gen) into bends, mutated;
    631 
    632       if mutated then
    633         lines[i] = st_linemerge(st_union(bends));
    634         gen = gen + 1;
    635         continue;
    636       end if;
    637 
    638       bendattrs = array((select wm_bend_attrs(bends, dbgname, gen)));
    639 
    640       select * from wm_elimination(
    641         bendattrs, dhalfcircle, dbgname, gen) into bendattrs, mutated;
    642       if mutated then
    643         for j in 1..array_length(bendattrs, 1) loop
    644           bends[j] = bendattrs[j].bend;
    645         end loop;
    646         lines[i] = st_linemerge(st_union(bends));
    647         gen = gen + 1;
    648         continue;
    649       end if;
    650 
    651       -- code to detect isolated bends is there, but bend exaggeration
    652       -- is not implemented.
    653       --perform wm_isolated_bends(bendattrs, dbgname, gen);
    654     end loop;
    655 
    656   end loop;
    657 
    658   if l_type = 'ST_LineString' then
    659     return st_linemerge(st_union(lines));
    660   elseif l_type = 'ST_MultiLineString' then
    661     return st_union(lines);
    662   end if;
    663 end
    664 $$ language plpgsql;