Beefy Boxes and Bandwidth Generously Provided by pair Networks
Just another Perl shrine
 
PerlMonks  

Re: Better maps with Math::Geometry::Voronoi, (Working* code)

by BrowserUk (Patriarch)
on Jul 03, 2008 at 07:50 UTC ( [id://695306]=note: print w/replies, xml ) Need Help??


in reply to Better maps with Math::Geometry::Voronoi, and a Challenge for Math Monks

*almost

There are are still occasional "extra" points being included, but I believe these are being output (wrongly) from M::G::V, as with the duplicated points I noted here. And once that problem has been solved, the spurious edges that show up occasionally will go away.

How it works

If you look at the example (requires FireFox) that samtregar posted, and consider just the edges that form the overall outline of the shaded polygons. In the set of all the edges of all the small polygons, all the "internal edges", that is edges that are not part of the bounding polygon, will appear in two of the smaller polygons. But those edges making up the bounding polygon will only appear in one.

By processing all the edges from all the small polygons through a filter selecting just those edges that only appear once in the total set of edges, you will be left with just those edges that make up the bounding polygon. So, the problem then becomes one of simply (!) re-constructing the boundary polygon from that disordered set of edges.

The exclamation mark is because although the final cut of the reconstruction (reordering) process is actually quite simple, arriving at the algorithm was anything but.

The problem on a simple scale, is that you end up with an array of arrays of arrays like this:

[ [[177, 518], [205, 353]], [[259, 614], [177, 518]], [[205, 353], [383, 489]], [[259, 614], [380, 498]], [[367, 274], [415, 456]], [[464, 246], [367, 274]], [[380, 498], [486, 622]], [[383, 489], [415, 456]], [[452, 160], [464, 246]], [[490, 135], [452, 160]], [[486, 622], [579, 606]], [[490, 135], [586, 244]], [[579, 606], [632, 643]], [[586, 244], [717, 227]], [[632, 643], [719, 549]], [[717, 227], [746, 269]], [[719, 549], [775, 555]], [[746, 269], [856, 312]], [[775, 555], [856, 312]], ]

And you need to re-order them so that the (right-most) end of one edge mates up with the start (left-most) end of the next. And do this all the way down the list until the end of the last edge matches the start of the first. You can then take the set of starts (or ends) and the first (or last) point from the other end, and you end up with an ordered set of points to construct the closed polygon.

The problem is that the end of the first point could match up with any of the subsequent edges. Start or end. Having tried half a dozen combinations of: sorting and then swapping; or swapping then sorting; or sorting, swapping some and then resorting (rinse, repeat); I finally arrived at the algorithm by working through the above dataset manually and noting what I did. I then reproduced that in code. The result is the following subroutine:

sub edges2poly { my $edges = shift; ## Ref to the AoAoA above. ## For each edge in the list (which is reordered as this loop prog +resses) for my $i ( 0 .. $#$edges - 1 ) { ## Stringyfy the end-point my $target = "@{ $edges->[ $i ][ 1 ] }"; ## And scan the remain edges for my $j ( $i + 1 .. $#$edges ) { ## Comparing the target, ## first against the start if( $target eq "@{ $edges->[ $j ][ 0 ] }" ) { ## found the next edge correctly oriented. last if $j == $i + 1; ## nowt to do if its already the + next edge } ## and then the end of each elsif( $target eq "@{ $edges->[ $j ][ 1 ] }" ) { ## Found it, but its ends need swapping @{ $edges->[ $j ] } = reverse @{ $edges->[ $j ] }; last if $j == $i + 1; ## nowt more to do if its the ne +xt edge } else { next; ## try the next edge } ## If we got here, we found it (and possible swapped then +ends, ## But it is in the wrong place in the list, so swap the e +dges. @{ $edges }[ $i+1, $j ] = @{ $edges }[ $j, $i+1 ]; last; ## And skip to the next target } } ## return an AoA of just the required points for the bounding poly +gon. return( $edges->[ 0 ][ 0 ], map{ $_->[ 1 ] } @$edges ); }

The POC code uses GD to plot the points (in red), the Voronoi diagram (in green), and the boundary polygon (in blue). (Update: Added the pre-filtered polygons in grey to show that incomplete polygons are being removed prior to the 1-edging process).

There are three operational modes: -PAT=[square|diamond|random]. The square and diamond options show up the duplicate vertices problem noted elsewhere, (but I filter these out) and show that the algorthm works.

The random (default) pattern takes another option (-N=nnn) which is the number of randomly generated points passed to M::G::V to start the thing off. This demonstrates that the algorithm is very fast, but also shows up the fact that my filtering process is not succeeding in filtering all the spurious elements.

If you run with the default (-PAT=random -N=20), then most of the time everything is fine. If you increase that to -N=100, it will sometimes be fine, but will often show the effects of the spurious points. Run with -N=1000 and it will (mostly) draw the full bounding polygon, but usually with a few "extra" edges.

Update Added a couple of extra test modes: -PAT=hex|hex2 which more clearly demonstrate the "spurious points" problem.

(If you run this on linux, you'll need to tweak the line:system 'voronoi.png'; to cause the program to display the resulting .png)

The code: ('scuse the mass of debug comments)


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.

Replies are listed 'Best First'.
Re^2: Better maps with Math::Geometry::Voronoi, (Working* code)
by samtregar (Abbot) on Jul 03, 2008 at 17:09 UTC
    Awesome, thanks! I'll give this a try and let you know how it goes.

    One question - does it deal with shapes that have holes in them? Unfortunately I think tye is right, that will happen. Perhaps it can explain some of the spurious edges? I'm skeptical of blaming them on the underlying Voronoi code - that's some very old, well tested code and it doesn't show any anomalies when you graph the results without joining. Happy to be proven wrong, of course, but I'd need proof based on the original data to believe it.

    -sam

      does it deal with shapes that have holes in them?

      I think it should, though it may need some modification to isolate the two (or more) polygons. I'll need to construct a known testcase and see what happens.

      As for the source of the errors. The presence of 'holes' could explain the anomolies I'm seeing. But either way, the problem is exacerbated by the presence of duplicate points that are being returned in individual polys. I've tried printing the values from the simple 'squares' testcase I posted earlier, to the fullest precision Perl can give me, and this isn't a case of points that differ by infinitesimal amounts being mapped to the same pixel. The values as returned bu M::G::V are all exact integer values.

      I'm currently filtering each of the individual polys for adjacent duplicate vertices, but that won't handle the case of there being 'extra points' in the solution. If you run the code above with -PAT=hex2 and look at the grey (unfiltered) polys on the left-hand edge, you'll see that there are various edges showing up that do not obviously result from the normal dividors from the points.

      In particular, if you look at the poly in the lower left corner, the extreme left-hand edge appears to be parallel to the left edge of the coordinate space (white box). There is no way you should be able to generate a 'parallel normal' from points wholy contained within the white coordinate space--but there it is.

      I'm not accusing the underlying libraries of having errors, I just don't know any way to explain the presence of that edge (and others) based on what I know of the algorithms involved. Now I'm wondering what hoops would be involved in compiling the underlying library on Win32? (Did you post a pointer to teh source code anywhere?)

      I had to make a couple of minor edits to memory.c (in myalloc() to get it to compile with MSC. Apparently cl doesn't know how to calculate sizeof( void* )? I just switched the two occurances to char* until I got a chance to investigate that, work out the correct solution and produce a patch for you. But I do not see that it could have any affect on the math.

      I'll play and try and come up with a simple dataset that produces a 'hole' to see what happens. If you have any ideas on how to produce such a dataset, please let me know. I'm having trouble conceiving of how that can arise right now?


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
      One question - does it deal with shapes that have holes in them?

      You know, the more I think about this, the more I am convinced (on the basis of my own brand of logic rather than any real mathematic understanding), that it isn't possible to have a 'hole' in a Voronoi diagram?

      If someone knows better, please demonstrate?


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

        I don't think you can get even a polygon that is not convex, because by construction, you start out with a convex polygon (the whole space) and all areas you're clipping away from that are using lines/half-spaces perpendicular to the line connecting the two points. If you assume a metric space with a symmetric metric respecting the triangle inequality, I have the feeling that you encounter a contradiction fairly quickly, but I haven't written down any formal proof either :)

        BrowserUk:

        You can't get a polygon with a hole in it in a Voronoi diagram. He's starting with a voronoi diagram and then combining adjacent polygons of the same "color". You may then get a polygon with a "hole" in it. Example: A nine by nine array of points, where the center one is red, the eight adjacent points are all blue, and the rest of the points are red. If you take one of the blue points and merge all adjacent blue polygons until you run out, you'll get a small red polygon in the center (originally in the voronoi diagram), then a polygon surrounding that one that's blue, etc. I believe that's what was meant.

        ...roboticus

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://695306]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (5)
As of 2024-04-23 15:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found