[Community] Geometry subclassing and factory methods
Sean Gillies
sgillies at frii.com
Wed Apr 26 09:04:26 EEST 2006
On Apr 24, 2006, at 9:35 AM, Howard Butler wrote:
> All,
>
> I have run into an issue with the Geometry.fromWKT and .fromWKB
> methods not supporting subclassing in the way I would like to use
> it. Sean and Kai said I should bring the issue up with the list
> after some discussion on IRC and in the tracker, so here it is.
>
> The problem
> ------------------
>
> I would like to create a subclass of Point that does a weird buffer
> (uses a hexagon instead of GEOS' buffer).
>
>> class Structure(Point):
>> def buffer(self, side):
>> radical_3 = math.sqrt(3.0)
>> shell = LinearRing([
>> Point(self.x - side*2.0/
>> radical_3, self.y),
>> Point(self.x - side/radical_3,
>> self.y + side),
>> Point(self.x + side/radical_3,
>> self.y + side),
>> Point(self.x + side*2.0/
>> radical_3, self.y),
>> Point(self.x + side/radical_3,
>> self.y - side),
>> Point(self.x - side/radical_3,
>> self.y - side),
>> Point(self.x - side*2.0/
>> radical_3, self.y)
>> ])
>> return Polygon(shell)
>
> Things behave as expected when instantiating using the normal
> __init__ method of Structure:
>
>> init_struct = Structure(3.0, 4.0)
>> print "Type for Structure using __init__ : ",type(init_struct)
>>> Type for Structure using __init__ : <class '__main__.Structure'>
>
> The problem comes when using the fromWKT (or fromWKB) factory
> method to instantiate a Structure:
>
>> factory_struct = Structure.fromWKT(init_struct.toWKT())
>> print "Type for factory Structure : ",type(factory_struct)
>>> Type for factory Structure : <class
>>> 'cartography.spatial.geometry.Point'>
>
> factory_struct comes back as a Point, not a Structure, so calling
> factory_struct.buffer() will be using the normal buffer function
> instead of the weird hexagon one.
>
> How we can fix this (if it really needs fixing)
> ---------------------------------------------------------------
>
> The fromWKT and fromWKB factory (classmethod) functions in Geometry
> have some code in them to set their __class__ using the
> GEOSGeometry TypeId. The GEOSGeometry is updated with the WKT, the
> type (Polygon, Point, LineString, etc) is grabbed from the
> GEOSGeometry, and then the instance's __class__ is set using a
> lookup table. All of the geometry types that inherit from Geometry
> in geometry.py are supported this way. The __class__ currently can
> only be set to those ids that are in the lookup table -- not to the
> actual class that called the fromWK[TB] method originally (in my
> case the Structure class)
>
> This can be fixed by checking if we have a super and setting the
> __class__ to that if it exists, otherwise use the normal lookup.
>> if super(cls):
>> g.__class__= super(cls).__thisclass__
>> else:
>> g.__class__ = CLASS_TYPE[g.getTypeId()]
>
>
> Arguments for doing this
> -----------------------------------
>
> Sean has provided some push back against making this change <http://
> trac.gispython.org/projects/PCL/ticket/68>, stating that it was a
> rare use case, and it complicates the factory methods. Here's my
> arguments for making this change:
>
> 1) The geometry factory methods and their normal constructors are
> currently incongruent with respect to subclassing (see the above
> example).
> 2) Geometry already does a specialized version of this in its
> factory methods, upcasting the Geometry to Point, Line, Polygon,
> etc based on the on the underlying GEOSGeometry TypeId. This takes
> it all the way, casting the __class__ to the class that originally
> called the factory.
>
> Thoughts, discussion, boos and cheers are welcomed.
>
> Howard
>
The original intent of the Geometry.fromWKT and Geometry.fromWKB
factory methods is to take WKT or WKB encoded geometries (see OGC
05-126, aka "Simple features access" for details) and, without *any*
a priori knowledge of the geometry type encoded in the WKT/WKB
string, instantiate from these strings geometry objects of the
*exact* same type that is encoded. The only geometry types that can
be encoded in WKT/WKB are:
- Point
- LineString
- Polygon
- MultiPoint
- MultiLineString
- MultiPolygon
- GeometryCollection
It was my intent that Python geometries of these types *only* would
be created by these particular factory methods. This is a common
capability of GIS packages. Where PCL differs from OGR or MapServer
is that we exploit the mutability of Python objects to return
instances of Point, LineString, Polygon, MultiPoint, etc classes from
the factories.
I never intended that the factories should be used via sub-classes of
Geometry, because this could lead to confusing behavior. For example,
g = Polygon.fromWKT("POINT (0 0)") # example 1
Although unfortunately allowed in the current state of PCL, this is
nonsensical. In hindsight, I should have written
createGeometryFromWK* functions. Perhaps it's not too late to drop
Geometry.fromWK*.
Now, to the specific issue of sub-classing Point like, and modifying
Geometry.fromWK* so that you can create instances of this new class.
To me it seems much better if your new class accepts *all* the
responsibility for setting the appropriate class. Why not
class Structure(geometry.Point):
...
def fromWKT(cls, wkt):
g = geometry.Geometry()
g._updateGeomFromWKT(wkt)
g.__class__ = cls
return g
fromWKT = classmethod(fromWKT)
? This keeps a rarely needed if/else out of Geometry.fromWK*, which I
strongly prefer. And now that your new class is taking full
responsibility, you can implement a guard against your users passing
the wrong geometry types into the factory method. This is where
example 1 reappears in a different form:
g = Structure.fromWKT("POLYGON ...")
This has to raise an exception, right? In order to have a Structure
class that does the right thing when given bogus data, you'll need to
do something like this:
class Structure(geometry.Point):
...
def fromWKT(cls, wkt):
g = geometry.Geometry()
g._updateGeomFromWKT(wkt)
if g.getTypeId() is not geometry.GEOS_POINT:
raise Exception, "Invalid WKT, only POINT is permissable"
g.__class__ = cls
return g
fromWKT = classmethod(fromWKT)
I don't think we want to push that geometry type checking into
Geometry.fromWKT because it would require a rarely necessary hash
lookup.
That's my suggestion, and mostly based on my sense that the need for
highly customized geometry types is rare. Rare enough that users of
the highly customized geometries should accept all responsibility for
decoding WKT/WKB into non-standard types. Overwriting fromWK* for
this specialized Structure class just doesn't seem to be that much of
a burden.
So why do you want hexagonally buffered points anyway? Why not use
points and hexagons?
Sean
---
Sean Gillies
http://zcologia.com
More information about the Community
mailing list