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;