Ticket #21273: oracle_xe_sdo_geometry.2.py

File oracle_xe_sdo_geometry.2.py, 17.5 KB (added by vinhussey, 8 years ago)

Unpdated (incomplete) class for handling SDO_GEOMETRY

Line 
1class sdo_geometry(object):
2    '''
3    Process an Oracle SDO_GEOMETRY object as returned by cx_Oracle.
4    '''
5    # definitions
6    geom_types = {'00': 'UNKNOWN_GEOMETRY', # UNKNOWN_GEOMETRY
7                  '01': 'POINT', # POINT
8                  '02': 'LINESTRING', # LINE or CURVE
9                  '03': 'POLYGON', # POLYGON
10                  '04': 'GEOMETRYCOLLECTION', # COLLECTION
11                  '05': 'MULTIPOINT', # MULTIPOINT
12                  '06': 'MULTILINESTRING', # MULTILINE or MULTICURVE
13                  '07': 'MULTIPOLYGON'} # MULTIPOLYGON
14   
15    # SDO_ETYPES
16    # first element of triplet in SDO_ELEM_INFO
17    sdo_etype = {0: 'UNSUPPORTED_GEOMETRY_ETYPE',
18                 1: 'POINT_ETYPE',
19                 2: 'LINE_ETYPE', 
20                 4: 'COMPOUND_LINESTRING_ETYPE', 
21                 1003: 'EXTERIOR_CLOSED_SHAPE_ETYPE', 
22                 2003: 'INTERIOR_CLOSED_SHAPE_ETYPE',
23                 1005: 'COMPOUND_EXTERIOR_CLOSED_SHAPE_ETYPE', 
24                 2005: 'COMPOUND_INTERIOR_CLOSED_SHAPE_ETYPE'}
25   
26    # SDO_INTERPRETATIONS
27    # second element of triplet in SDO_ELEM_INFO
28    # applies to points - sdo_etype 1
29    sdo_interpretation_point = {0: 'ORIENTED_POINT', 
30                                1: 'SIMPLE_POINT'
31                                # n > 1: point cluster with n points
32                                }
33   
34    # applies to lines - sdo_etype 2
35    sdo_interpretation_line = {1: 'STRAIGHT_SEGMENTS', 
36                               2: 'CURVED_SEGMENTS'}
37   
38    # applies to polygons - sdo_etypes 1003 and 2003           
39    sdo_interpretation_multi = {1: 'SIMPLE_POLY', 
40                                2: 'ARCS_POLY', 
41                                3: 'RECTANGLE', 
42                                4: 'CIRCLE'} 
43   
44    # complex geometries - sdo_etypes 4, 1005, 2005 always have n > 1
45    # n is the number of contiguous subelements
46    # subsequent subelements each define one element
47   
48   
49       
50    # init function
51    def __init__(self, sdo_geometry_obj=None, debug=False, strict=False):
52        '''
53        Read the geometry from the sdo_geometry object.
54        Debug - produce some debug output.
55        Strict - if False (default), convert geometry to a supported type where possible,
56        e.g. Oriented Point to Point
57        '''
58        if debug:
59            print 'Debugging on.'
60        # read the geometry from the sdo_geometry object
61        self.geometry = sdo_geometry_obj
62        try:
63            self.g_type = str(int(self.geometry.__getattribute__('SDO_GTYPE')))
64            self.g_srid = int(self.geometry.__getattribute__('SDO_SRID'))
65            self.g_point = [self.geometry.__getattribute__('SDO_POINT').X, self.geometry.__getattribute__('SDO_POINT').Y, self.geometry.__getattribute__('SDO_POINT').Z]
66            self.g_eleminfo_arr = self.geometry.__getattribute__('SDO_ELEM_INFO')
67            self.g_ords_arr = self.geometry.__getattribute__('SDO_ORDINATES')
68        except AttributeError:
69            if debug:
70                print 'Not a geometry'
71            return None
72        self.dims = self.get_dims()
73        self.topology = self.has_topology()
74        self.gtype = self.get_gtype()
75        #self.wkb = self.get_wkb()
76        self.valid = False
77        self.wkt = self.get_wkt(debug)
78        self.coord_dim = self.st_coorddim()
79        #self.is_valid = self.st_isvalid()
80   
81    # functions
82    def get_dims(self):
83        '''
84        Return dimensions of the geometry.
85        This is extracted from the first character of the SDO_ETYPE value
86        '''
87        return int(self.g_type[0])
88   
89    def st_coorddim(self):
90        '''
91        Return dimensions of the geometry.
92        This is extracted from the first character of the SDO_ETYPE value
93        This function is a synonym of get_dims
94        '''
95        return self.get_dims()
96   
97    def has_topology(self):
98        '''
99        Return true if the geometry has topology, false if it doesn't, otherwise None
100        This is extracted from the second character of the SDO_ETYPE value
101        '''
102        if 0 <= int(self.g_type[1]) <= 1:
103            return int(self.g_type[1])
104        else:
105            return None
106           
107    def get_geometry_text(self):
108        '''
109        Return the type of geometry.
110        This is extracted from the third and fourth characters of the SDO_ETYPE value
111        '''
112        return self.geom_types[self.g_type[2:4]]
113       
114    def get_gtype(self):
115        '''
116        Return the type of geometry.
117        This is extracted from the third and fourth characters of the SDO_ETYPE value
118        '''
119        return int(self.g_type[2:4])
120               
121    def get_srid(self):
122        '''
123        Return the srid of the data.
124        This is as defined in the database and may be an Oracle specific format (not EPSG).
125        '''
126        return self.g_srid   
127   
128    def get_num_elements(self):
129        '''
130        Return the total number of elements in the SDO_ORDINATES array.
131        These may be used more than once (end and start adjacent elements).
132        '''
133        if self.g_eleminfo_arr:
134            return len(self.g_eleminfo_arr)
135        else:
136            return None
137           
138    def get_etype(self):
139        '''
140        Return the SDO_ETYPE value, if it is defined.
141        '''
142        if not self.g_eleminfo_arr:
143            return None
144        else:
145            return int(self.g_eleminfo_arr[1])
146
147    def get_interpretation(self):
148        '''
149        Return the SDO_INTERPRETATION value, if it is defined.
150        '''
151        if not self.g_eleminfo_arr:
152            return None
153        else:
154            return int(self.g_eleminfo_arr[2])
155           
156    def get_point_text(self, point):
157        '''
158        Convert a point (2d or 3d list) into WKT text.
159        '''
160        if self.dims == 2:
161            return '%.12f %.12f' % (point[0], point[1])
162        else:
163            return '%.12f %.12f %.12f' % (point[0], point[1], point[2])
164           
165    def to_points(self, l,n):
166        '''
167        Convert a list l into a list of smaller lists of dimension n.
168        '''
169        return [l[i:i+n] for i in xrange(0, len(l), n)]
170       
171    def points_to_WKT(self, points_list):
172        '''
173        Convert a list of points into WKT text format.
174        This can then be used in simple or multi WKT.
175        e.g. [x1, y1,...xn,yn] to 'x1 y1, ..., xn yn'
176        '''
177        wkt_text = ''
178        for point in points_list:
179            wkt_text += self.get_point_text(point)+','
180        wkt_text = wkt_text[:-1]
181        return wkt_text
182           
183    def make_arrays(self, debug=False):
184        '''
185        Convert the ordinates to an array of points using information from
186        SDO_ELEM_INFO
187        '''
188        num_triplets = len(self.g_eleminfo_arr)/3
189        triplets = self.to_points(self.g_eleminfo_arr, 3)
190        if debug:
191            print 'sets:',num_triplets, '\ntriplets:', triplets
192        start_positions = []
193        end_positions = []
194        elem_type = []
195        elements = []
196        elem_text = []
197        for i, triplet in enumerate(triplets):
198            print i, triplet
199            if i == 0: # first element
200                start_positions.append(0)
201            else:
202                start_positions.append(int(triplets[i][0]) - 1)
203            if i != num_triplets - 1: # intermediate element
204                end_positions.append(int(triplets[i+1][0]) - 1)
205            else:
206                end_positions.append(len(self.g_ords_arr)) # last element
207            elem_type.append(int(triplets[i][1]))
208            if debug:
209                print 'start:', start_positions, 'end:', end_positions, 'length:', len(self.g_ords_arr)
210            elements.append(self.g_ords_arr[start_positions[i]:end_positions[i]])
211            points = self.to_points(elements[i], self.dims)
212            elem_text.append(self.points_to_WKT(points))
213        if debug:
214            print 'elements:', len(elements), '\nelem_text:', len(elem_text)
215        return elem_text, elem_type
216           
217    def is_valid(self):
218        '''
219        Return True for valid geometry, False for invalid geometry, None for unsupported
220        '''
221        # Place holder for now.
222        return None
223   
224    def get_ewkt(self):
225        return 'SRID=%d:%s' % (self.get_srid(), self.get_wkt())
226
227    def get_wkt(self, debug=False):
228        '''
229        Calculate the WKT for the geometry.
230        Point geometry may require only SDO_POINT, all other geometries require the use of SDO_ELEM_INFO and SDO_ORDINATES.
231        Geometry may be simple or complex.  Simple geometries are defined in one SDO_ELEM_INFO triplet, Complex geometries require multiple SDO_ELEM_INFO triplets.
232        '''
233        geom_type = self.get_geometry_text()
234        if geom_type == 'UNKNOWN_GEOMETRY':
235            return None
236       
237        elif geom_type == 'POINT':
238            '''
239            WKT - POINT(x y)
240            '''
241            if self.g_point:
242                # case 1 - simple point
243                # point_text = '%s(%s)' % (geom_type, self.get_point_text(self.g_point))
244                point_text = self.get_point_text(self.g_point)
245                if debug:
246                    print point_text
247
248            else:
249                # case 2 - simple point - extract point from sdo_ordinates
250                if  len(self.g_eleminfo_arr) == 3:
251                    points = self.to_points(self.g_ords_arr, self.dims)
252
253                # case 3 - oriented point - doesn't seem to be supported in OGC WKT
254                # truncate to first point
255                else:
256                    points = self.to_points(self.g_ords_arr, self.dims)
257                    points = points[0]
258                   
259            if not point_text:
260                point_text = self.points_to_WKT(points)
261            self.valid = True
262            return '%s(%s)' % (geom_type, point_text) 
263               
264       
265        elif geom_type == 'LINESTRING':
266            '''
267            WKT - LINESTRING(x1 y1,x2 y2,...,xn yn)
268            simple element, with a single SDO_ELEM_INFO triplet.
269            each point is listed sequentially in the SDO_ORDINATES
270            direct conversion to WKT
271            '''
272           
273            # validity check - may need to expand
274            if self.get_etype() != 2 or len(self.g_eleminfo_arr) != 3:
275                self.valid = False
276                return None
277            # straight segments
278            if self.get_interpretation() == 1 or (self.get_interpretation() == 2 and strict == False):
279                points = self.to_points(self.g_ords_arr, self.dims)
280                ls_text = self.points_to_WKT(points)
281                self.valid = True
282                return '%s(%s)' % (geom_type, ls_text)
283            # curved segments
284            elif self.get_interpretation() == 2 and strict == True:
285                # to do
286                return None
287               
288            # compound linestrings - mix of straight and curved elements, each with a SDO_ELEM_INFO triplet, and each overlapping the last by one point
289           
290        elif geom_type == 'POLYGON':
291            '''
292            WKT - POLYGON((x1 y1,x2 y2,...,xn yn,x1 y1))
293            WKT - POLYGON((x1 y1,x2 y2,...,xn yn,x1 y1)(i1 j1, 12 j2,...,in jn,i1 j1))
294            May include more than one element if there are internal holes
295            There can be only one external ring, this must be listed counterclockwise
296            There may be zero or more internal rings, these must be listed clockwise, and each has one additional SDO_ELEM_INFO triplet.
297            Simple case
298               Simple element, with a single SDO_ELEM_INFO triplet.
299            Complex case
300               More than 1 SDO_ELEM_INFO triplet, first for external ring, other(s) for internal rings.
301            The triple can have the following values
302            [0] - 1 - starting position (base 1)
303            [1] - 1003 or 2003 (SDO_ETYPE)
304            [2] - 1, 2, 3, or 4 (SDO_INTERPRETATION)
305            Each point is listed sequentially in the SDO_ORDINATES
306            The last point in a ring is the same as first point.
307            '''
308           
309            # validity check - may need to expand
310            if (self.get_etype() != 1003 and self.get_etype() != 2003) or len(self.g_eleminfo_arr) != 3:
311                self.valid = False
312                return None
313           
314            if self.get_interpretation() == 1 or (self.get_interpretation() == 2 and strict == False): 
315            # straight segments
316                elem_text, elem_type = self.make_arrays(debug)
317                poly_text = ''
318                for elem in elem_text:
319                    poly_text = '%s(%s)' % (poly_text, elem)
320                self.valid = True
321                return '%s(%s)' % (geom_type, poly_text)
322            elif self.get_interpretation() == 2:
323                # curved segments
324                return None
325            elif self.get_interpretation() == 3:
326                # rectangle - 2 points
327                return None
328            elif self.get_interpretation() == 4:
329                # circle - 3 points
330                return None
331            else:
332                # invalid
333                return None
334
335               
336           
337        elif geom_type == 'GEOMETRYCOLLECTION':
338            '''
339            Container for other valid geometries:
340            WKT - GEOMETRYCOLLECTION(geom1,geom2,..,geomn)
341            SDO_ELEM_ARRAY triples will define different geometries. 
342            Need to watch for termination of multi geometries & complex geometries.
343            Use recursion for each element.
344            '''
345            elem_text, elem_type = self.make_arrays(debug)
346            for i, elem in enumerate(elem_text):
347                if elem_type[i] == 1:
348                    pass
349                else:
350                    pass
351                           
352            pass 
353           
354        elif geom_type == 'MULTIPOINT': # not tested yet !!!
355            '''
356            WKT - MULTIPOINT(x1 y1, ... , xn yn)
357            MULTIPOINT((x1 y1), ... , (xn yn))
358            MultiPoint - series of points
359            A single SDO_ELEM_INFO is used
360            [0] SDO_STARTING_OFFSET === 1
361            [1] SDO_ETYPE = 1
362            [2] SDO_INTERPRETATION = number of points in array
363            '''
364           
365            # validity check - may need to expand
366            if self.get_etype() != 1:
367                valid = False
368                return None
369           
370            else:
371                # num_Points = self.get_interpretation()
372                points = self.to_points(self.g_eleminfo_arr, self.dims)
373                mp_text = self.points_to_WKT(points)
374                self.valid = True
375                return '%s(%s)' % (geom_type, mp_text)
376           
377        elif geom_type == 'MULTILINESTRING':
378            '''
379            WKT - MULTILINESTRING((x1 y1, x2 y2,...,xn yn)(i1 j1, i2 j2,..., in jn))
380            MultiLineString - series of line strings
381            Each line string is defined by one SDO_ELEM_INFO triplet
382            '''
383           
384            # validity check - may need to expand
385            if self.get_etype() != 2:
386                valid = False
387                return None
388           
389            else:
390                # this is identical to polygons
391                if self.get_interpretation() == 1 or (self.get_interpretation() == 2 and strict == False):
392                    elem_text, elem_type = self.make_arrays(debug)
393                    ml_text = ''
394                    for elem in elem_text:
395                        ml_text = '%s(%s)' % (ml_text, elem)
396                    self.valid = True
397                    return '%s(%s)' % (geom_type, ml_text)
398                else:
399                    # curved segments
400                    return None
401           
402             
403           
404        elif geom_type == 'MULTIPOLYGON':
405            '''
406            WKT - MULTIPOLYGON(((x1 y1, ... , x1 y1)((x2 y2, ..., x2 y2)(x3 y3, ..., x3 y3)))
407            MultiPolygon - a number of polygons, each with one external ring and zero or more internal rings.
408            External rings have SDO_ETYPE of 1003 and are grouped with subsequent internal rings, which have SDO_ETYPE of 2003
409            so SDO_ELEM_INFO like [1,1003,1, 1,1003,1, 1,2003,1] maps to (note parenthesis (( ()  (()()) )
410            MULTIPOLYGON(((x1 y1, ... , x1 y1)((x2 y2, ..., x2 y2)(x3 y3, ..., x3 y3)))
411            SDO_ELEM_INFO[1] == 1003 => start new outer ring
412            SDO_ELEM_INFO[2] => start position
413            '''
414            # reality check
415            if self.get_interpretation() == 1 or (self.get_interpretation() == 2 and strict == False):
416                pass
417            elem_text, elem_type = self.make_arrays(debug)
418            mp_text = ''
419            for i, elem in enumerate(elem_text):
420                if elem_type[i] == 1003: # outer ring
421                    # start with '(('
422                    mp_text += '('
423                elif elem_type == 2003: # inner ring
424                    # start with '('
425                    pass
426                mp_text = '%s(%s)' % (mp_text, elem)
427                if i == len(elem_type) - 1: # last element
428                    mp_text += ')'
429                elif elem_type[i+1] == 1003: # terminate outer ring
430                    mp_text += ')'
431            self.valid = True
432            return '%s(%s)' % (geom_type, mp_text)
433        else:
434            return None
435   
Back to Top