Geometry puzzler

Alex Martelli aleaxit at yahoo.com
Tue Nov 7 05:39:36 EST 2000


"Paul Winkler" <slinkp23 at yahoo.com> wrote in message
news:3A073A51.AE3691A3 at yahoo.com...
> Frédéric van der Plancke wrote:
> >
> > The comp.graphics.algorithms FAQ answers your geometry questions too.
> > FvdP.
>
> Ooh, that's a good one. Thanks all!
> I'll check out those books too.

Yep, very neat collection of formulas, thanks Frédéric!

Translating those formulas into Python is a simple and
interesting exercise.  For example, representing points
as pairs (2-element tuples) of coordinates, and segments
as pairs of points, one could write:

def intersect( ((Ax,Ay),(Bx,By)), ((Cx,Cy),(Dx,Dy)) ):
    denom = (Bx-Ax)*(Dy-Cy)-(By-Ay)*(Dx-Cx)
    if not denom: return None    # collinear segments
    r = ((Ay-Cy)*(Dx-Cx)-(Ax-Cx)*(Dy-Cy)) / denom
    s = ((Ay-Cy)*(Bx-Ax)-(Ax-Cx)*(By-Ay)) / denom
    if 0<=r<=1 and 0<=s<=1:
        return Ax+r*(Bx-Ax), Ay+r*(By-Ay)

I like the way this exploits explicit-tuple-unpacking
(much like FP language's more extensive use of similar
pattern-matching ideas) to let me call this in terms
of either coordinate-level, point-level, or segment-
level objects, while letting all computations be a
straightforward transcription of the formulas... e.g.:

>>> origin=(0.0,0.0)
>>> diag_ne_sw=((1.0,0.0), (0.0,1.0))
>>> for i in range(-3,4):
 factor = i/10.0
 print i, geom.intersect( (origin,(1.0+factor,1.0-factor)), diag_ne_sw)


-3 (0.34999999999999998, 0.65000000000000002)
-2 (0.40000000000000002, 0.59999999999999998)
-1 (0.45000000000000001, 0.55000000000000004)
0 (0.5, 0.5)
1 (0.55000000000000004, 0.45000000000000001)
2 (0.59999999999999998, 0.40000000000000002)
3 (0.65000000000000002, 0.34999999999999998)
>>>


Of course, intersect as written is doing more work than
it needs to -- take it as a good, simple exercise in
factoring out common expressions...:

def intersect( ((Ax,Ay),(Bx,By)), ((Cx,Cy),(Dx,Dy)) ):
    deXba = Bx-Ax
    deYba = By-Ay
    deYdc = Dy-Cy
    deXdc = Dx-Cx
    denom = deXba*deYdc - deYba*deXdc
    if not denom: return None    # collinear segments
    deYac = Ay-Cy
    deXac = Ax-Cx
    r = (deYac*deXdc - deXac*deYdc) / denom
    if r<0 or r>1: return None
    s = (deYac*deXba - deXac*deYba) / denom
    if s<0 or s>1: return None
    return Ax+r*deXba, Ay+r*deYba

...more verbose, yes, but no less clear -- maybe more,
as it emphasizes the symmetry of the computations --
and it avoids some recomputing (or computing of s
that is not needed if r is outside of range...).  Next
obvious optimization step is avoiding the division by
denom when possible -- but it's trickier, to take
proper account of the signs in play, to do so without
impairing clarity (and risk introducing a bug...).
Neat classroom exercise!


The point-in-polygon formula is also interesting:
representing a polygon by the sequence of its
vertices, we get:

def pnpoly(polygon_vertices, (x,y)):
    inside = 0
    xpj, ypj = polygon_vertices[-1]
    for xpi, ypi in polygon_vertices:
        if (ypi <= y < ypj or ypj <= y < ypi) and (
            x < (xpj-xpi)*(y-ypi)/(ypj-ypi) + xpi):
                inside = not inside
        xpj, ypj = xpi, ypi
    return inside

Here, optimization is not much of an issue, but
clarity and simplicity are always worth attention.
The naming of xpj, etc, is doubtful, for example --
I've chosen it for parallelism with xp[j] &tc in
the original C source.  Maybe taking the slight hit
(in performance terms) of an embedded function...:

    xpj, ypj = polygon_vertices[-1]
    def between(y1, y, y2):
        return y1<=y<y2 or y2<=y<y1
    for xpi, ypi in polygon_vertices:
        if between(ypi, y, ypj) and (

would be worth it for enhanced clarity (coming
just from the fact that we _name_ the test we
are performing).  And again, tuple pack/unpack
and the Python for statement show to good use.

Recoding the second part of the condition in
more symmetrical terms, making it into...:

            (x-xpi)*(ypj-ypi) < (xpj-xpi)*(y-ypi)):

might be nice for both clarity and performance,
BUT it's only correct if ypj-ypi is > 0 -- the
same kind of sign-issue we met in intersect...!


In other words, this seems to be mostly about
style and idiom issues... good things to ponder!-)



Alex






More information about the Python-list mailing list