django/contrib/gis/geos/polygon.py
author Justin Bronn <jbronn@geodjango.org>
Sat Jan 31 15:18:50 2009 -0600 (3 years ago)
branchtrunk
changeset 138 466bece04a15
parent 95 233dbb50fdf2
child 352 ed67864049e9
permissions -rw-r--r--
Merged in patch from Aryeh Leib Taurog for #9877, adapting as necessary.
jbronn@1
     1
from ctypes import c_uint, byref
jbronn@1
     2
from django.contrib.gis.geos.error import GEOSIndexError
jbronn@1
     3
from django.contrib.gis.geos.geometry import GEOSGeometry
jbronn@1
     4
from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
jbronn@1
     5
from django.contrib.gis.geos.linestring import LinearRing
jbronn@95
     6
from django.contrib.gis.geos import prototypes as capi
jbronn@1
     7
jbronn@1
     8
class Polygon(GEOSGeometry):
jbronn@1
     9
jbronn@1
    10
    def __init__(self, *args, **kwargs):
jbronn@1
    11
        """
jbronn@1
    12
        Initializes on an exterior ring and a sequence of holes (both
jbronn@1
    13
        instances may be either LinearRing instances, or a tuple/list
jbronn@1
    14
        that may be constructed into a LinearRing).
jbronn@1
    15
        
jbronn@1
    16
        Examples of initialization, where shell, hole1, and hole2 are 
jbronn@1
    17
        valid LinearRing geometries:
jbronn@1
    18
        >>> poly = Polygon(shell, hole1, hole2)
jbronn@1
    19
        >>> poly = Polygon(shell, (hole1, hole2))
jbronn@1
    20
jbronn@1
    21
        Example where a tuple parameters are used:
jbronn@1
    22
        >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), 
jbronn@1
    23
                           ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
jbronn@1
    24
        """
jbronn@1
    25
        if not args:
jbronn@1
    26
            raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
jbronn@1
    27
jbronn@1
    28
        # Getting the ext_ring and init_holes parameters from the argument list
jbronn@1
    29
        ext_ring = args[0]
jbronn@1
    30
        init_holes = args[1:]
jbronn@1
    31
        n_holes = len(init_holes)
jbronn@1
    32
jbronn@1
    33
        # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
jbronn@1
    34
        if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \
jbronn@1
    35
                (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)): 
jbronn@1
    36
            init_holes = init_holes[0]
jbronn@1
    37
            n_holes = len(init_holes)
jbronn@1
    38
jbronn@1
    39
        # Ensuring the exterior ring and holes parameters are LinearRing objects
jbronn@1
    40
        # or may be instantiated into LinearRings.
jbronn@1
    41
        ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.')
jbronn@1
    42
        holes_list = [] # Create new list, cause init_holes is a tuple.
jbronn@1
    43
        for i in xrange(n_holes):
jbronn@1
    44
            holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'))
jbronn@1
    45
jbronn@1
    46
        # Why another loop?  Because if a TypeError is raised, cloned pointers will
jbronn@1
    47
        # be around that can't be cleaned up.
jbronn@1
    48
        holes = get_pointer_arr(n_holes)
jbronn@95
    49
        for i in xrange(n_holes): holes[i] = capi.geom_clone(holes_list[i].ptr)
jbronn@1
    50
                      
jbronn@1
    51
        # Getting the shell pointer address.
jbronn@95
    52
        shell = capi.geom_clone(ext_ring.ptr)
jbronn@1
    53
jbronn@1
    54
        # Calling with the GEOS createPolygon factory.
jbronn@95
    55
        super(Polygon, self).__init__(capi.create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
jbronn@1
    56
jbronn@1
    57
    def __getitem__(self, index):
jbronn@1
    58
        """
jbronn@1
    59
        Returns the ring at the specified index.  The first index, 0, will 
jbronn@1
    60
        always return the exterior ring.  Indices > 0 will return the 
jbronn@1
    61
        interior ring at the given index (e.g., poly[1] and poly[2] would
jbronn@1
    62
        return the first and second interior ring, respectively).
jbronn@1
    63
        """
jbronn@1
    64
        if index == 0:
jbronn@1
    65
            return self.exterior_ring
jbronn@1
    66
        else:
jbronn@1
    67
            # Getting the interior ring, have to subtract 1 from the index.
jbronn@1
    68
            return self.get_interior_ring(index-1) 
jbronn@1
    69
jbronn@1
    70
    def __setitem__(self, index, ring):
jbronn@1
    71
        "Sets the ring at the specified index with the given ring."
jbronn@1
    72
        # Checking the index and ring parameters.
jbronn@1
    73
        self._checkindex(index)
jbronn@1
    74
        if not isinstance(ring, LinearRing):
jbronn@1
    75
            raise TypeError('must set Polygon index with a LinearRing object')
jbronn@1
    76
jbronn@1
    77
        # Getting the shell
jbronn@1
    78
        if index == 0:
jbronn@95
    79
            shell = capi.geom_clone(ring.ptr)
jbronn@1
    80
        else:
jbronn@95
    81
            shell = capi.geom_clone(capi.get_extring(self.ptr))
jbronn@1
    82
jbronn@1
    83
        # Getting the interior rings (holes)
jbronn@1
    84
        nholes = len(self)-1
jbronn@1
    85
        if nholes > 0:
jbronn@1
    86
            holes = get_pointer_arr(nholes)
jbronn@1
    87
            for i in xrange(nholes):
jbronn@1
    88
                if i == (index-1):
jbronn@95
    89
                    holes[i] = capi.geom_clone(ring.ptr)
jbronn@1
    90
                else:
jbronn@95
    91
                    holes[i] = capi.geom_clone(capi.get_intring(self.ptr, i))
jbronn@1
    92
            holes_param = byref(holes)
jbronn@1
    93
        else:
jbronn@1
    94
            holes_param = None
jbronn@1
    95
         
jbronn@1
    96
        # Getting the current pointer, replacing with the newly constructed
jbronn@1
    97
        # geometry, and destroying the old geometry.
jbronn@1
    98
        prev_ptr = self.ptr
jbronn@1
    99
        srid = self.srid
jbronn@138
   100
        self.ptr = capi.create_polygon(shell, holes_param, c_uint(nholes))
jbronn@1
   101
        if srid: self.srid = srid
jbronn@95
   102
        capi.destroy_geom(prev_ptr)
jbronn@1
   103
jbronn@1
   104
    def __iter__(self):
jbronn@1
   105
        "Iterates over each ring in the polygon."
jbronn@1
   106
        for i in xrange(len(self)):
jbronn@1
   107
            yield self[i]
jbronn@1
   108
jbronn@1
   109
    def __len__(self):
jbronn@1
   110
        "Returns the number of rings in this Polygon."
jbronn@1
   111
        return self.num_interior_rings + 1
jbronn@1
   112
jbronn@1
   113
    def _construct_ring(self, param, msg=''):
jbronn@1
   114
        "Helper routine for trying to construct a ring from the given parameter."
jbronn@1
   115
        if isinstance(param, LinearRing): return param
jbronn@1
   116
        try:
jbronn@1
   117
            ring = LinearRing(param)
jbronn@1
   118
            return ring
jbronn@1
   119
        except TypeError:
jbronn@1
   120
            raise TypeError(msg)
jbronn@1
   121
jbronn@13
   122
    @classmethod
jbronn@13
   123
    def from_bbox(cls, bbox):
jbronn@13
   124
        "Constructs a Polygon from a bounding box (4-tuple)."
jbronn@13
   125
        x0, y0, x1, y1 = bbox
jbronn@13
   126
        return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %  (
jbronn@13
   127
                x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
jbronn@13
   128
jbronn@1
   129
    def get_interior_ring(self, ring_i):
jbronn@1
   130
        """
jbronn@1
   131
        Gets the interior ring at the specified index, 0 is for the first 
jbronn@1
   132
        interior ring, not the exterior ring.
jbronn@1
   133
        """
jbronn@1
   134
        self._checkindex(ring_i+1)
jbronn@95
   135
        return GEOSGeometry(capi.geom_clone(capi.get_intring(self.ptr, ring_i)), srid=self.srid)
jbronn@1
   136
                                                        
jbronn@1
   137
    #### Polygon Properties ####
jbronn@1
   138
    @property
jbronn@1
   139
    def num_interior_rings(self):
jbronn@1
   140
        "Returns the number of interior rings."
jbronn@1
   141
        # Getting the number of rings
jbronn@95
   142
        return capi.get_nrings(self.ptr)
jbronn@1
   143
jbronn@1
   144
    def get_ext_ring(self):
jbronn@1
   145
        "Gets the exterior ring of the Polygon."
jbronn@95
   146
        return GEOSGeometry(capi.geom_clone(capi.get_extring(self.ptr)), srid=self.srid)
jbronn@1
   147
jbronn@1
   148
    def set_ext_ring(self, ring):
jbronn@1
   149
        "Sets the exterior ring of the Polygon."
jbronn@1
   150
        self[0] = ring
jbronn@1
   151
jbronn@1
   152
    # properties for the exterior ring/shell
jbronn@1
   153
    exterior_ring = property(get_ext_ring, set_ext_ring)
jbronn@1
   154
    shell = exterior_ring
jbronn@1
   155
    
jbronn@1
   156
    @property
jbronn@1
   157
    def tuple(self):
jbronn@1
   158
        "Gets the tuple for each ring in this Polygon."
jbronn@1
   159
        return tuple([self[i].tuple for i in xrange(len(self))])
jbronn@1
   160
    coords = tuple
jbronn@1
   161
jbronn@1
   162
    @property
jbronn@1
   163
    def kml(self):
jbronn@1
   164
        "Returns the KML representation of this Polygon."
jbronn@1
   165
        inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml 
jbronn@1
   166
                             for i in xrange(self.num_interior_rings)])
jbronn@1
   167
        return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)