wm

Wang–Müller line generalization algorithm in PostGIS
Log | Files | Refs | README | LICENSE

wm.sql (26545B) - 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   p geometry;
     14   p1 geometry;
     15   p2 geometry;
     16   p3 geometry;
     17   bend geometry;
     18   prev_sign int4;
     19   cur_sign int4;
     20   l_type text;
     21   dbgpolygon geometry;
     22 begin
     23   l_type = st_geometrytype(line);
     24   if l_type != 'ST_LineString' then
     25     raise 'This function works with ST_LineString, got %', l_type;
     26   end if;
     27 
     28   -- The last vertex is iterated over twice, because the algorithm uses 3
     29   -- vertices to calculate the angle between them.
     30   --
     31   -- Given 3 vertices p1, p2, p3:
     32   --
     33   --          p1___ ...
     34   --           /
     35   -- ... _____/
     36   --     p3   p2
     37   --
     38   -- When looping over the line, p1 will be head (lead) vertex, p2 will be the
     39   -- measured angle, and p3 will be trailing. The line that will be added to
     40   -- the bend will always be [p3,p2].
     41   -- So once the p1 becomes the last vertex, the loop terminates, and the
     42   -- [p2,p1] line will not have a chance to be added. So the loop adds the last
     43   -- vertex twice, so it has a chance to become p2, and be added to the bend.
     44   for p in
     45       (select geom from st_dumppoints(line) order by path[1] asc)
     46       union all
     47       (select geom from st_dumppoints(line) order by path[1] desc limit 1)
     48      loop
     49     p3 = p2;
     50     p2 = p1;
     51     p1 = p;
     52     continue when p3 is null;
     53 
     54     cur_sign = sign(pi() - st_angle(p1, p2, p2, p3));
     55 
     56     if bend is null then
     57       bend = st_makeline(p3, p2);
     58     else
     59       bend = st_linemerge(st_union(bend, st_makeline(p3, p2)));
     60     end if;
     61 
     62     if prev_sign + cur_sign = 0 then
     63       if bend is not null then
     64         bends = bends || bend;
     65       end if;
     66       bend = st_makeline(p3, p2);
     67     end if;
     68     prev_sign = cur_sign;
     69   end loop;
     70 
     71   -- the last line may be lost if there is no "final" inflection angle. Add it.
     72   if (select count(1) >= 2 from st_dumppoints(bend)) then
     73     bends = bends || bend;
     74   end if;
     75 
     76   if dbgname is not null then
     77     for i in 1..array_length(bends, 1) loop
     78       insert into wm_debug(stage, name, gen, nbend, way) values(
     79         'bbends', dbgname, dbggen, i, bends[i]);
     80 
     81       dbgpolygon = null;
     82       if st_npoints(bends[i]) >= 3 then
     83           dbgpolygon = st_makepolygon(
     84             st_addpoint(bends[i], st_startpoint(bends[i]))
     85           );
     86       end if;
     87       insert into wm_debug(stage, name, gen, nbend, way) values(
     88         'bbends-polygon', dbgname, dbggen, i, dbgpolygon);
     89     end loop;
     90   end if;
     91 end $$ language plpgsql;
     92 
     93 -- wm_fix_gentle_inflections moves bend endpoints following "Gentle Inflection
     94 -- at End of a Bend" section.
     95 --
     96 -- The text does not specify how many vertices can be "adjusted"; it can
     97 -- equally be one or many. This function is adjusting many, as long as the
     98 -- cumulative inflection angle is small (see variable below).
     99 --
    100 -- The implementation could be significantly optimized to avoid `st_reverse`
    101 -- and array reversals, trading for complexity in wm_fix_gentle_inflections1.
    102 drop function if exists wm_fix_gentle_inflections;
    103 create function wm_fix_gentle_inflections(
    104   INOUT bends geometry[],
    105   dbgname text default null,
    106   dbggen integer default null
    107 ) as $$
    108 declare
    109   len int4;
    110   bends1 geometry[];
    111   dbgpolygon geometry;
    112 begin
    113   len = array_length(bends, 1);
    114 
    115   bends = wm_fix_gentle_inflections1(bends);
    116   for i in 1..len loop
    117     bends1[i] = st_reverse(bends[len-i+1]);
    118   end loop;
    119   bends1 = wm_fix_gentle_inflections1(bends1);
    120 
    121   for i in 1..len loop
    122     bends[i] = st_reverse(bends1[len-i+1]);
    123   end loop;
    124 
    125   if dbgname is not null then
    126     for i in 1..array_length(bends, 1) loop
    127       insert into wm_debug(stage, name, gen, nbend, way) values(
    128         'cinflections', dbgname, dbggen, i, bends[i]);
    129 
    130       dbgpolygon = null;
    131       if st_npoints(bends[i]) >= 3 then
    132         dbgpolygon = st_makepolygon(
    133           st_addpoint(bends[i],
    134             st_startpoint(bends[i]))
    135         );
    136       end if;
    137 
    138       insert into wm_debug(stage, name, gen, nbend, way) values(
    139         'cinflections-polygon', dbgname, dbggen, i, dbgpolygon);
    140     end loop;
    141   end if;
    142 end $$ language plpgsql;
    143 
    144 -- wm_fix_gentle_inflections1 fixes gentle inflections of an array of lines in
    145 -- one direction. An implementation detail of wm_fix_gentle_inflections.
    146 drop function if exists wm_fix_gentle_inflections1;
    147 create function wm_fix_gentle_inflections1(INOUT bends geometry[]) as $$
    148 declare
    149   -- the threshold when the angle is still "small", so gentle inflections can
    150   -- be joined
    151   small_angle constant real default radians(45);
    152   ptail geometry; -- tail point of tail bend
    153   phead geometry[]; -- 3 tail points of head bend
    154   i int4; -- bends[i] is the current head
    155 begin
    156   for i in 2..array_length(bends, 1) loop
    157     -- Predicate: two bends will always share an edge. Assuming (A,B,C,D,E,F)
    158     -- is a bend:
    159     --           C________D
    160     --           /        \
    161     -- \________/          \_______/
    162     -- A       B           E       F
    163     --
    164     -- Then edges (A,B) and (E,F) are shared with the neighboring bends.
    165     --
    166     --
    167     -- Assume this curve (figure `inflection-1`), going clockwise from A:
    168     --
    169     --    \______B
    170     --    A      `-------. C
    171     --                   |
    172     --    G___ F         |
    173     --    /   `-----.____+ D
    174     --              E
    175     --
    176     -- After processing the curve following the definition of a bend, the bend
    177     -- [A-E] would be detected. Assuming inflection point E and F are "small",
    178     -- the bend needs to be extended by two edges to [A,G].
    179     select geom from st_dumppoints(bends[i-1])
    180       order by path[1] asc limit 1 into ptail;
    181 
    182     while true loop
    183       -- copy last 3 points of bends[i-1] (tail) to ptail
    184       select array(
    185         select geom from st_dumppoints(bends[i]) order by path[1] asc limit 3
    186       ) into phead;
    187 
    188       -- if the bend got too short, stop processing it
    189       exit when array_length(phead, 1) < 3;
    190 
    191       -- inflection angle between ptail[1:3] is "large", stop processing
    192       exit when abs(st_angle(phead[1], phead[2], phead[3]) - pi()) > small_angle;
    193 
    194       -- distance from head's 1st vertex should be larger than from 2nd vertex
    195       exit when st_distance(ptail, phead[2]) < st_distance(ptail, phead[3]);
    196 
    197       -- Between two bends, bend with smaller baseline wins when two
    198       -- neighboring bends can have gentle inflections. This is a heuristic
    199       -- that can be safely removed, but in practice has shown to avoid
    200       -- creating some very bendy lines.
    201       exit when st_distance(st_pointn(bends[i], 1), st_pointn(bends[i], -1)) <
    202         st_distance(st_pointn(bends[i-1], 1), st_pointn(bends[i-1], -1));
    203 
    204       -- Detected a gentle inflection.
    205       -- Move head of the tail to the tail of head
    206       bends[i] = st_removepoint(bends[i], 0);
    207       bends[i-1] = st_addpoint(bends[i-1], phead[3]);
    208     end loop;
    209 
    210   end loop;
    211 end $$ language plpgsql;
    212 
    213 -- wm_if_selfcross returns whether baseline of bendi crosses bendj.
    214 -- If it doesn't, returns a null geometry.
    215 -- Otherwise, it will return the baseline split into a few parts where it
    216 -- crosses bendj.
    217 drop function if exists wm_if_selfcross;
    218 create function wm_if_selfcross(
    219   bendi geometry,
    220   bendj geometry
    221 ) returns geometry as $$
    222 declare
    223   a geometry;
    224   b geometry;
    225   multi geometry;
    226 begin
    227   a = st_pointn(bendi, 1);
    228   b = st_pointn(bendi, -1);
    229   multi = st_split(bendj, st_makeline(a, b));
    230 
    231   if st_numgeometries(multi) = 1 then
    232     return null;
    233   end if;
    234 
    235   if st_numgeometries(multi) = 2 and
    236     (st_contains(bendj, a) or st_contains(bendj, b)) then
    237     return null;
    238   end if;
    239 
    240   return multi;
    241 end $$ language plpgsql;
    242 
    243 -- wm_self_crossing eliminates self-crossing from the bends, following the
    244 -- article's section "Self-line Crossing When Cutting a Bend".
    245 drop function if exists wm_self_crossing;
    246 create function wm_self_crossing(
    247   INOUT bends geometry[],
    248   dbgname text default null,
    249   dbggen integer default null,
    250   OUT mutated boolean
    251 ) as $$
    252 declare
    253   i int4;
    254   j int4;
    255   multi geometry;
    256 begin
    257   mutated = false;
    258   <<bendloop>>
    259   for i in 1..array_length(bends, 1) loop
    260     continue when abs(wm_inflection_angle(bends[i])) <= pi();
    261     -- sum of inflection angles for this bend is >180, so it may be
    262     -- self-crossing. Now try to find another bend in this line that
    263     -- crosses an imaginary line of end-vertices
    264 
    265     -- Go through each bend in the given line, and see if has a potential to
    266     -- cross bends[i]. The line-cut process is different when i<j and i>j;
    267     -- therefore there are two loops, one for each case.
    268     for j in 1..i-1 loop
    269       multi = wm_if_selfcross(bends[i], bends[j]);
    270       continue when multi is null;
    271       mutated = true;
    272 
    273       -- remove first vertex of the following bend, because the last
    274       -- segment is always duplicated with the i'th bend.
    275       bends[i+1] = st_removepoint(bends[i+1], 0);
    276       bends[j] = st_geometryn(multi, 1);
    277       bends[j] = st_setpoint(
    278         bends[j],
    279         st_npoints(bends[j])-1,
    280         st_pointn(bends[i], st_npoints(bends[i]))
    281       );
    282       bends = bends[1:j] || bends[i+1:];
    283       continue bendloop;
    284     end loop;
    285 
    286     for j in reverse array_length(bends, 1)..i+1 loop
    287       multi = wm_if_selfcross(bends[i], bends[j]);
    288       continue when multi is null;
    289       mutated = true;
    290 
    291       -- remove last vertex of the previous bend, because the last
    292       -- segment is duplicated with the i'th bend.
    293       bends[i-1] = st_removepoint(bends[i-1], st_npoints(bends[i-1])-1);
    294       bends[i] = st_makeline(
    295         st_pointn(bends[i], 1),
    296         st_removepoint(st_geometryn(multi, st_numgeometries(multi)), 0)
    297       );
    298       bends = bends[1:i] || bends[j+1:];
    299       continue bendloop;
    300     end loop;
    301   end loop;
    302 
    303   if dbgname is not null then
    304     insert into wm_debug(stage, name, gen, nbend, way) values(
    305       'dcrossings', dbgname, dbggen, generate_subscripts(bends, 1),
    306       unnest(bends)
    307     );
    308   end if;
    309 end $$ language plpgsql;
    310 
    311 drop function if exists wm_inflection_angle;
    312 create function wm_inflection_angle (IN bend geometry, OUT angle real) as $$
    313 declare
    314   p0 geometry;
    315   p1 geometry;
    316   p2 geometry;
    317   p3 geometry;
    318 begin
    319   angle = 0;
    320   for p0 in select geom from st_dumppoints(bend) order by path[1] asc loop
    321     p3 = p2;
    322     p2 = p1;
    323     p1 = p0;
    324     continue when p3 is null;
    325     angle = angle + abs(pi() - st_angle(p1, p2, p3));
    326   end loop;
    327 end $$ language plpgsql;
    328 
    329 drop function if exists wm_bend_attrs;
    330 drop function if exists wm_elimination;
    331 drop function if exists wm_exaggeration;
    332 drop type if exists wm_t_attrs;
    333 create type wm_t_attrs as (
    334   adjsize real,
    335   baselinelength real,
    336   curvature real,
    337   isolated boolean
    338 );
    339 create function wm_bend_attrs(
    340   bends geometry[],
    341   dbgname text default null,
    342   dbggen integer default null
    343 ) returns wm_t_attrs[] as $$
    344 declare
    345   isolation_threshold constant real default 0.5;
    346   attrs wm_t_attrs[];
    347   attr wm_t_attrs;
    348   bend geometry;
    349   i int4;
    350   needs_curvature real;
    351   skip_next boolean;
    352   dbglastid integer;
    353 begin
    354   for i in 1..array_length(bends, 1) loop
    355     bend = bends[i];
    356     attr.adjsize = 0;
    357     attr.baselinelength = st_distance(st_startpoint(bend), st_endpoint(bend));
    358     attr.curvature = wm_inflection_angle(bend) / st_length(bend);
    359     attr.isolated = false;
    360     if st_numpoints(bend) >= 3 then
    361       attr.adjsize = wm_adjsize(bend);
    362     end if;
    363     attrs[i] = attr;
    364   end loop;
    365 
    366   for i in 1..array_length(attrs, 1) loop
    367     if dbgname is not null then
    368       insert into wm_debug (stage, name, gen, nbend, way, props) values(
    369         'ebendattrs', dbgname, dbggen, i, bends[i],
    370         jsonb_build_object(
    371           'adjsize', attrs[i].adjsize,
    372           'baselinelength', attrs[i].baselinelength,
    373           'curvature', attrs[i].curvature,
    374           'isolated', false
    375         )
    376       ) returning id into dbglastid;
    377     end if;
    378 
    379     -- first and last bends can never be isolated by definition
    380     if skip_next or i = 1 or i = array_length(attrs, 1) then
    381       -- invariant: two bends that touch cannot be isolated.
    382       if st_npoints(bends[i]) > 3 then
    383         skip_next = false;
    384       end if;
    385       continue;
    386     end if;
    387 
    388     needs_curvature = attrs[i].curvature * isolation_threshold;
    389     if attrs[i-1].curvature < needs_curvature and
    390        attrs[i+1].curvature < needs_curvature then
    391       attr = attrs[i];
    392       attr.isolated = true;
    393       attrs[i] = attr;
    394       skip_next = true;
    395 
    396       if dbgname is not null then
    397         update wm_debug
    398         set props=props || jsonb_build_object('isolated', true)
    399         where id=dbglastid;
    400       end if;
    401     end if;
    402   end loop;
    403 
    404   return attrs;
    405 end $$ language plpgsql;
    406 
    407 -- sm_st_split a line by a point in a more robust way than st_split.
    408 -- See https://trac.osgeo.org/postgis/ticket/2192
    409 drop function if exists wm_st_split;
    410 create function wm_st_split(
    411   input geometry,
    412   blade geometry
    413 ) returns geometry as $$
    414 declare
    415   type1 text;
    416   type2 text;
    417 begin
    418   type1 = st_geometrytype(input);
    419   type2 = st_geometrytype(blade);
    420   if not (type1 = 'ST_LineString' and
    421           type2 = 'ST_Point') then
    422     raise 'Arguments must be LineString and Point, got: % and %', type1, type2;
    423   end if;
    424   return st_split(st_snap(input, blade, 0.00000001), blade);
    425 end $$ language plpgsql;
    426 
    427 -- wm_exaggerate_bend2 is the second version of bend exaggeration. Uses
    428 -- non-linear interpolation by point azimuth. Slower, but produces nicer
    429 -- exaggerated geometries.
    430 drop function if exists wm_exaggerate_bend2;
    431 create function wm_exaggerate_bend2(
    432   INOUT bend geometry,
    433   size float,
    434   desired_size float
    435 ) as $$
    436 declare
    437   scale2 constant float default 1.2; -- exaggeration enthusiasm
    438   midpoint geometry; -- midpoint of the baseline
    439   points geometry[];
    440   startazimuth float;
    441   azimuth float;
    442   diffazimuth float;
    443   point geometry;
    444   sss float;
    445   protect int = 10;
    446 begin
    447   if size = 0 then
    448     raise 'invalid input: zero-area bend';
    449   end if;
    450   midpoint = st_lineinterpolatepoint(st_makeline(
    451       st_pointn(bend, 1),
    452       st_pointn(bend, -1)
    453     ), .5);
    454   startazimuth = st_azimuth(midpoint, st_pointn(bend, 1));
    455 
    456   while (size < desired_size) and (protect > 0) loop
    457     protect = protect - 1;
    458     for i in 2..st_npoints(bend)-1 loop
    459       point = st_pointn(bend, i);
    460       azimuth = st_azimuth(midpoint, point);
    461       diffazimuth = degrees(azimuth - startazimuth);
    462       if diffazimuth > 180 then
    463         diffazimuth = diffazimuth - 360;
    464       elseif diffazimuth < -180 then
    465         diffazimuth = diffazimuth + 360;
    466       end if;
    467       diffazimuth = abs(diffazimuth);
    468       if diffazimuth > 90 then
    469         diffazimuth = 180 - diffazimuth;
    470       end if;
    471       sss = ((scale2-1) * (diffazimuth / 90)^0.5);
    472       point = st_transform(
    473         st_project(
    474           st_transform(point, 4326)::geography,
    475           st_distance(midpoint, point) * sss, azimuth)::geometry,
    476         st_srid(midpoint)
    477       );
    478       bend = st_setpoint(bend, i-1, point);
    479     end loop;
    480     size = wm_adjsize(bend);
    481   end loop;
    482 end $$ language plpgsql;
    483 
    484 -- wm_exaggerate_bend exaggerates a given bend. Uses naive linear
    485 -- interpolation. Faster than wm_exaggerate_bend2, but result visually looks
    486 -- worse.
    487 drop function if exists wm_exaggerate_bend;
    488 create function wm_exaggerate_bend(
    489   INOUT bend geometry,
    490   size float,
    491   desired_size float
    492 ) as $$
    493 declare
    494   scale constant float default 1.2; -- exaggeration enthusiasm
    495   midpoint geometry; -- midpoint of the baseline
    496   splitbend geometry; -- bend split across its half
    497   bendm geometry; -- bend with coefficients to prolong the lines
    498   points geometry[];
    499 begin
    500   if size = 0 then
    501     raise 'invalid input: zero-area bend';
    502   end if;
    503   midpoint = st_lineinterpolatepoint(st_makeline(
    504       st_pointn(bend, 1),
    505       st_pointn(bend, -1)
    506     ), .5);
    507 
    508   while size < desired_size loop
    509     splitbend = wm_st_split(bend, st_lineinterpolatepoint(bend, .5));
    510     -- Convert bend to LINESTRINGM, where M is the fraction by how
    511     -- much the point will be prolonged:
    512     -- 1. draw a line between midpoint and the point on the bend.
    513     -- 2. multiply the line length by M. Midpoint stays intact.
    514     -- 3. the new set of lines form a new bend.
    515     -- Uses linear interpolation; can be updated to gaussian or similar;
    516     -- then interpolate manually instead of relying on st_addmeasure.
    517     bendm = st_collect(
    518       st_addmeasure(st_geometryn(splitbend, 1), 1, scale),
    519       st_addmeasure(st_geometryn(splitbend, 2), scale, 1)
    520     );
    521 
    522     points = array((
    523         select st_scale(
    524           st_makepoint(st_x(geom), st_y(geom)),
    525           st_makepoint(st_m(geom), st_m(geom)),
    526           midpoint
    527         )
    528         from st_dumppoints(bendm)
    529         order by path[1], path[2]
    530       ));
    531 
    532     bend = st_setsrid(st_makeline(points), st_srid(bend));
    533     size = wm_adjsize(bend);
    534   end loop;
    535 end $$ language plpgsql;
    536 
    537 
    538 -- wm_adjsize calculates adjusted size for a polygon. Can return 0.
    539 drop function if exists wm_adjsize;
    540 create function wm_adjsize(bend geometry, OUT adjsize float) as $$
    541 declare
    542   polygon geometry;
    543   area float;
    544   cmp float;
    545 begin
    546   adjsize = 0;
    547   polygon = st_makepolygon(st_addpoint(bend, st_startpoint(bend)));
    548   -- Compactness Index (cmp) is defined as "the ratio of the area of the
    549   -- polygon over the circle whose circumference length is the same as the
    550   -- length of the circumference of the polygon". I assume they meant the
    551   -- area of the circle. So here goes:
    552   -- 1. get polygon area P.
    553   -- 2. get polygon perimeter = u. Pretend it's our circle's circumference.
    554   -- 3. get A (area) of the circle from u: A = u^2/(4pi)
    555   -- 4. divide P by A: cmp = P/A = P/(u^2/(4pi)) = 4pi*P/u^2
    556   area = st_area(polygon);
    557   cmp = 4*pi()*area/(st_perimeter(polygon)^2);
    558   if cmp > 0 then
    559     adjsize = (area*(0.75/cmp));
    560   end if;
    561 end $$ language plpgsql;
    562 
    563 -- wm_exaggeration is the Exaggeration Operator described in the WM paper.
    564 create function wm_exaggeration(
    565   INOUT bends geometry[],
    566   attrs wm_t_attrs[],
    567   dhalfcircle float,
    568   intersect_patience integer,
    569   dbgname text default null,
    570   dbggen integer default null,
    571   OUT mutated boolean
    572 ) as $$
    573 declare
    574   desired_size constant float default pi()*(dhalfcircle^2)/8;
    575   bend geometry;
    576   tmpint geometry;
    577   i integer;
    578   n integer;
    579   last_id integer;
    580 begin
    581   mutated = false;
    582   <<bendloop>>
    583   for i in 1..array_length(attrs, 1) loop
    584     if attrs[i].isolated and attrs[i].adjsize < desired_size then
    585       bend = wm_exaggerate_bend2(bends[i], attrs[i].adjsize, desired_size);
    586       -- Does bend intersect with the previous or next
    587       -- intersect_patience bends? If they do, abort exaggeration for this one.
    588 
    589       -- Do close-by bends intersect with this one? Special
    590       -- handling first, because 2 vertices need to be removed before checking.
    591       n = st_npoints(bends[i-1]);
    592       if n > 3 then
    593         continue when st_intersects(bend,
    594           st_removepoint(st_removepoint(bends[i-1], n-1), n-2));
    595       end if;
    596       if n > 2 then
    597         tmpint = st_intersection(bend, st_removepoint(bends[i-1], n-1));
    598         continue when st_npoints(tmpint) > 1;
    599       end if;
    600 
    601       n = st_npoints(bends[i+1]);
    602       if n > 3 then
    603         continue when st_intersects(bend,
    604           st_removepoint(st_removepoint(bends[i+1], 0), 0));
    605       end if;
    606       if n > 2 then
    607         tmpint = st_intersection(bend, st_removepoint(bends[i+1], 0));
    608         continue when st_npoints(tmpint) > 1;
    609       end if;
    610 
    611       for n in -intersect_patience+1..intersect_patience-1 loop
    612         continue when n in (-1, 0, 1);
    613         continue when i+n < 1;
    614         continue when i+n > array_length(attrs, 1);
    615 
    616         -- More special handling: if the neigbhoring bend has 3 vertices, the
    617         -- neighbor's neighbor may just touch the tmpbendattr.bend; in this
    618         -- case, the nearest vertex should be removed before comparing.
    619         tmpint = bends[i+n];
    620         if st_npoints(tmpint) > 2 then
    621           if n = -2 and st_npoints(bends[i+n+1]) = 3 then
    622             tmpint = st_removepoint(tmpint, st_npoints(tmpint)-1);
    623           elsif n = 2 and st_npoints(bends[i+n-1]) = 3 then
    624             tmpint = st_removepoint(tmpint, 0);
    625           end if;
    626         end if;
    627 
    628         continue bendloop when st_intersects(bend, tmpint);
    629       end loop;
    630 
    631       -- No intersections within intersect_patience, mutate bend!
    632       mutated = true;
    633       bends[i] = bend;
    634 
    635       -- remove last vertex of the previous bend and first vertex of the next
    636       -- bend, because bends always share a line segment together this is
    637       -- duplicated in a few places, because PostGIS does not allow (?)
    638       -- mutating an array when passed to a function.
    639       bends[i-1] = st_addpoint(
    640         st_removepoint(bends[i-1], st_npoints(bends[i-1])-1),
    641         st_pointn(bends[i], 1),
    642         -1
    643       );
    644 
    645       bends[i+1] = st_addpoint(
    646         st_removepoint(bends[i+1], 0),
    647         st_pointn(bends[i], st_npoints(bends[i])-1),
    648         0
    649       );
    650       if dbgname is not null then
    651         insert into wm_debug (stage, name, gen, nbend, way) values(
    652           'gexaggeration', dbgname, dbggen, i, bends[i]);
    653       end if;
    654     end if;
    655   end loop;
    656 end $$ language plpgsql;
    657 
    658 create function wm_elimination(
    659   INOUT bends geometry[],
    660   attrs wm_t_attrs[],
    661   dhalfcircle float,
    662   dbgname text default null,
    663   dbggen integer default null,
    664   OUT mutated boolean
    665 ) as $$
    666 declare
    667   desired_size constant float default pi()*(dhalfcircle^2)/8;
    668   leftsize float;
    669   rightsize float;
    670   i int4;
    671 begin
    672   mutated = false;
    673 
    674   i = 1;
    675   while i < array_length(attrs, 1)-1 loop
    676     i = i + 1;
    677     continue when attrs[i].adjsize = 0;
    678     continue when attrs[i].adjsize > desired_size;
    679 
    680     if i = 2 then
    681       leftsize = attrs[i].adjsize + 1;
    682     else
    683       leftsize = attrs[i-1].adjsize;
    684     end if;
    685 
    686     if i = array_length(attrs, 1)-1 then
    687       rightsize = attrs[i].adjsize + 1;
    688     else
    689       rightsize = attrs[i+1].adjsize;
    690     end if;
    691 
    692     continue when attrs[i].adjsize >= leftsize;
    693     continue when attrs[i].adjsize >= rightsize;
    694 
    695     -- Local minimum. Elminate bend!
    696     mutated = true;
    697     bends[i] = st_makeline(st_pointn(bends[i], 1), st_pointn(bends[i], -1));
    698 
    699     -- remove last vertex of the previous bend and
    700     -- first vertex of the next bend, because bends always
    701     -- share a line segment together
    702     bends[i-1] = st_addpoint(
    703       st_removepoint(bends[i-1], st_npoints(bends[i-1])-1),
    704       st_pointn(bends[i], 1),
    705       -1
    706     );
    707 
    708     bends[i+1] = st_addpoint(
    709       st_removepoint(bends[i+1], 0),
    710       st_pointn(bends[i], st_npoints(bends[i])-1),
    711       0
    712     );
    713     -- the next bend's adjsize is now messed up; it should not be taken
    714     -- into consideration for other local minimas. Skip over 2.
    715     i = i + 2;
    716   end loop;
    717 
    718   if dbgname is not null then
    719     insert into wm_debug(stage, name, gen, nbend, way) values(
    720       'helimination',
    721       dbgname,
    722       dbggen,
    723       generate_subscripts(bends, 1),
    724       unnest(bends)
    725     );
    726   end if;
    727 end $$ language plpgsql;
    728 
    729 
    730 drop function if exists ST_SimplifyWM_Estimate;
    731 create function ST_SimplifyWM_Estimate(
    732   geom geometry,
    733   OUT npoints bigint,
    734   OUT secs bigint
    735 ) as $$
    736 declare
    737   lines geometry[];
    738   l_type text;
    739 begin
    740   l_type = st_geometrytype(geom);
    741   if l_type = 'ST_LineString' then
    742     lines = array[geom];
    743   elseif l_type = 'ST_MultiLineString' then
    744     lines = array((select a.geom from st_dump(geom) a order by path[1] asc));
    745   else
    746     raise 'Unknown geometry type %', l_type;
    747   end if;
    748 
    749   npoints = 0;
    750   for i in 1..array_length(lines, 1) loop
    751     npoints = npoints + st_numpoints(lines[i]);
    752   end loop;
    753   secs = npoints / 33;
    754 end $$ language plpgsql;
    755 
    756 -- ST_SimplifyWM simplifies a given geometry using Wang & Müller's
    757 -- "Line Generalization Based on Analysis of Shape Characteristics" algorithm,
    758 -- 1998.
    759 -- Input parameters:
    760 -- - geom: ST_LineString or ST_MultiLineString: the geometry to be simplified
    761 -- - dhalfcircle: the diameter of a half-circle, whose area is an approximate
    762 --   threshold for small bend elimination. If bend's area is larger than that,
    763 --   the bend will be left alone.
    764 drop function if exists ST_SimplifyWM;
    765 create function ST_SimplifyWM(
    766   geom geometry,
    767   dhalfcircle float,
    768   intersect_patience integer default 50,
    769   dbgname text default null
    770 ) returns geometry as $$
    771 declare
    772   gen integer;
    773   i integer;
    774   j integer;
    775   line geometry;
    776   lines geometry[];
    777   bends geometry[];
    778   attrs wm_t_attrs[];
    779   mutated boolean;
    780   l_type text;
    781 begin
    782   if intersect_patience is null then
    783     intersect_patience = 50;
    784   end if;
    785   l_type = st_geometrytype(geom);
    786   if l_type = 'ST_LineString' then
    787     lines = array[geom];
    788   elseif l_type = 'ST_MultiLineString' then
    789     lines = array((select a.geom from st_dump(geom) a order by path[1] asc));
    790   else
    791     raise 'Unknown geometry type %', l_type;
    792   end if;
    793 
    794   <<lineloop>>
    795   for i in 1..array_length(lines, 1) loop
    796     mutated = true;
    797     gen = 1;
    798 
    799     while mutated loop
    800 
    801       if dbgname is not null then
    802         insert into wm_debug (stage, name, gen, nbend, way) values(
    803           'afigures', dbgname, gen, i, lines[i]);
    804       end if;
    805 
    806       bends = wm_detect_bends(lines[i], dbgname, gen);
    807       bends = wm_fix_gentle_inflections(bends, dbgname, gen);
    808 
    809       select * from wm_self_crossing(bends, dbgname, gen) into bends, mutated;
    810       if not mutated then
    811         attrs = wm_bend_attrs(bends, dbgname, gen);
    812 
    813         select * from wm_exaggeration(bends, attrs,
    814           dhalfcircle, intersect_patience, dbgname, gen) into bends, mutated;
    815       end if;
    816 
    817       -- TODO: wm_combination
    818 
    819       if not mutated then
    820         select * from wm_elimination(bends, attrs,
    821           dhalfcircle, dbgname, gen) into bends, mutated;
    822       end if;
    823 
    824       if mutated then
    825         lines[i] = st_linemerge(st_union(bends));
    826 
    827         if st_geometrytype(lines[i]) != 'ST_LineString' then
    828           -- For manual debugging:
    829           --insert into wm_manual(name, way)
    830           --select 'non-linestring-' || a.path[1], a.geom
    831           --from st_dump(lines[i]) a
    832           --order by a.path[1];
    833           raise '[%] Got % (in %) instead of ST_LineString. '
    834           'Does the exaggerated bend intersect with the line? '
    835           'If so, try increasing intersect_patience.',
    836           gen, st_geometrytype(lines[i]), dbgname;
    837           --exit lineloop;
    838         end if;
    839         gen = gen + 1;
    840         continue;
    841       end if;
    842     end loop;
    843   end loop;
    844 
    845   if l_type = 'ST_LineString' then
    846     return st_linemerge(st_union(lines));
    847   elseif l_type = 'ST_MultiLineString' then
    848     return st_union(lines);
    849   end if;
    850 end $$ language plpgsql;