Ticket #21273: oracle_xe_sdo_geometry_4.py

File oracle_xe_sdo_geometry_4.py, 21.8 KB (added by vinhussey, 8 years ago)

Updated class for Oracle 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        '''Read the geometry from the sdo_geometry object.
53       
54        Keyword arguments:
55        debug - produce some debug output.
56        strict - if False (default), convert geometry to a supported type
57                where possible,
58        e.g. Oriented Point to Point
59        '''
60        if debug:
61            print 'Debugging on.'
62        # read the geometry from the sdo_geometry object
63        self.geometry = sdo_geometry_obj
64        try:
65            self.g_type = str(int(self.geometry.SDO_GTYPE))
66            self.g_srid = int(self.geometry.SDO_SRID)
67            self.g_point = [self.geometry.SDO_POINT.X, 
68                            self.geometry.SDO_POINT.Y, 
69                            self.geometry.SDO_POINT.Z]
70            self.g_eleminfo_arr = self.geometry.SDO_ELEM_INFO
71            self.g_ords_arr = self.geometry.SDO_ORDINATES
72        except AttributeError:
73            if debug:
74                print 'Not a geometry', 
75            return None
76        self.dims = self.get_dims()
77        self.topology = self.has_topology()
78        self.gtype = self.get_gtype()
79        #self.wkb = self.get_wkb()
80        self.valid = False
81        self.wkt = self.get_wkt(debug)
82        self.coord_dim = self.st_coorddim()
83        ''' for the moment, this is a reference back to the valid property,
84        updated from self.get_wkt()'''
85        self.is_valid = self.st_isvalid()
86   
87    # functions
88    def get_dims(self):
89        '''Return dimensions of the geometry.
90       
91        This is extracted from the first character of the SDO_ETYPE value
92        '''
93        return int(self.g_type[0])
94   
95    def st_coorddim(self):
96        '''Return dimensions of the geometry.
97       
98        This is extracted from the first character of the SDO_ETYPE value
99        This function is a synonym of get_dims
100        '''
101        return self.get_dims()
102   
103    def has_topology(self):
104        '''Return true if the geometry has topology, false if not, or None.
105       
106        This is extracted from the second character of the SDO_ETYPE value.
107        '''
108        if 0 <= int(self.g_type[1]) <= 1:
109            return int(self.g_type[1])
110        else:
111            return None
112           
113    def get_geometry_text(self):
114        '''
115        Return the type of geometry.
116        This is extracted from the third and fourth characters
117        of the SDO_ETYPE value
118        '''
119        return self.geom_types[self.g_type[2:4]]
120       
121    def get_gtype(self):
122        '''
123        Return the type of geometry.
124        This is extracted from the third and fourth characters
125        of the SDO_ETYPE value
126        '''
127        return int(self.g_type[2:4])
128               
129    def get_srid(self):
130        '''Return the srid of the data.
131       
132        This is as defined in the database and may be an Oracle specific format
133        (not EPSG).
134        '''
135        return self.g_srid   
136   
137    def get_num_elements(self):
138        '''Return the number of elements in the SDO_ORDINATES array or None.
139       
140        These may be used more than once (end and start adjacent elements).
141        '''
142        if self.g_eleminfo_arr:
143            return len(self.g_eleminfo_arr)
144        else:
145            return None
146           
147    def get_etype(self):
148        '''Return the SDO_ETYPE value, or None if it is not defined.'''
149        if not self.g_eleminfo_arr:
150            return None
151        else:
152            return int(self.g_eleminfo_arr[1])
153
154    def get_interpretation(self):
155        '''Return the SDO_INTERPRETATION value, or None if not defined.'''
156        if not self.g_eleminfo_arr:
157            return None
158        else:
159            return int(self.g_eleminfo_arr[2])
160           
161    def get_point_text(self, point):
162        '''Convert a point (2d or 3d list) into WKT text.
163       
164        input [x, y], [x, y, z, ...]
165        return 'x y', 'x y z ...'
166        '''
167        if self.dims == 2:
168            return '%.12f %.12f' % (point[0], point[1])
169        else:
170            return '%.12f %.12f %.12f' % (point[0], point[1], point[2])
171           
172    def to_points(self, l,n):
173        '''Convert a list l into a list of smaller lists of dimension n.'''
174        return [l[i:i+n] for i in xrange(0, len(l), n)]
175       
176    def points_to_WKT(self, points_list):
177        '''Convert a list of points into WKT text format.
178       
179        This can then be used in simple or multi WKT.
180        e.g. [x1, y1,...xn,yn] to 'x1 y1, ..., xn yn'
181        '''
182        wkt_text = ''
183        for point in points_list:
184            wkt_text += self.get_point_text(point)+','
185        wkt_text = wkt_text[:-1]
186        return wkt_text
187           
188    def make_arrays(self, g_eleminfo_arr, g_ords_arr, debug=False):
189        '''Convert the ordinates to an array of points using SDO_ELEM_INFO.'''
190        num_triplets = len(g_eleminfo_arr)/3
191        triplets = self.to_points(g_eleminfo_arr, 3)
192        if debug:
193            print 'sets:',num_triplets, '\ntriplets:', triplets
194        start_positions = []
195        end_positions = []
196        elem_type = []
197        elements = []
198        elem_text = []
199        for i, triplet in enumerate(triplets):
200            print i, triplet
201            if i == 0:
202                # first element
203                start_positions.append(0)
204            else:
205                # intermediate element
206                start_positions.append(int(triplets[i][0]) - 1)
207            if i != num_triplets - 1: 
208                # intermediate element
209                end_positions.append(int(triplets[i+1][0]) - 1)
210            else:
211                # last element
212                end_positions.append(len(g_ords_arr)) 
213            elem_type.append(int(triplets[i][1]))
214            if debug:
215                print 'start:', start_positions, \
216                      'end:', end_positions, \
217                      'length:', len(g_ords_arr)
218            elements.append(g_ords_arr[start_positions[i]:end_positions[i]])
219            points = self.to_points(elements[i], self.dims)
220            elem_text.append(self.points_to_WKT(points))
221        if debug:
222            print 'elements:', len(elements), '\nelem_text:', len(elem_text)
223        return elem_text, elem_type
224           
225    def st_isvalid(self):
226        '''Return True for valid geometry, False for invalid or None'''
227        # Place holder for now.
228        return self.valid
229   
230    def get_ewkt(self):
231        '''Return EWKT - combine SRID and WKT.'''
232        return 'SRID=%d:%s' % (self.get_srid(), self.get_wkt())
233
234    def get_wkt(self, debug=False):
235        '''Calculate the WKT for the geometry or None for invalid geometry.
236       
237        Point geometry may require only SDO_POINT, all other geometries
238        require the use of SDO_ELEM_INFO and SDO_ORDINATES.
239        Geometry may be simple or complex.  Simple geometries are defined in
240        one SDO_ELEM_INFO triplet, Complex geometries require multiple
241        SDO_ELEM_INFO triplets.
242       
243        '''
244        geom_type = self.get_geometry_text()
245        if geom_type == 'UNKNOWN_GEOMETRY':
246            return None
247       
248        elif geom_type == 'POINT':
249            '''Return WKT - POINT(x y).'''
250            if self.g_point:
251                # case 1 - simple point
252                point_text = self.get_point_text(self.g_point)
253                if debug:
254                    print point_text
255
256            else:
257                # case 2 - simple point - extract point from sdo_ordinates
258                if  len(self.g_eleminfo_arr) == 3:
259                    points = self.to_points(self.g_ords_arr, self.dims)
260
261                # case 3 - oriented point
262                #        - doesn't seem to be supported in OGC WKT
263                # truncate to first point
264                else:
265                    points = self.to_points(self.g_ords_arr, self.dims)
266                    points = points[0]
267                   
268            if not point_text:
269                point_text = self.points_to_WKT(points)
270            self.valid = True
271            return '%s(%s)' % (geom_type, point_text) 
272               
273       
274        elif geom_type == 'LINESTRING':
275            '''Return WKT - LINESTRING(x1 y1,x2 y2,...,xn yn)
276           
277            simple element, with a single SDO_ELEM_INFO triplet.
278            each point is listed sequentially in the SDO_ORDINATES
279            direct conversion to WKT
280           
281            '''
282            # validity check - may need to expand
283            if self.get_etype() != 2 or len(self.g_eleminfo_arr) != 3:
284                self.valid = False
285                return None
286            # straight segments
287            if self.get_interpretation() == 1 \
288                or ( self.get_interpretation() == 2 \
289                and strict == False):
290               
291                points = self.to_points(self.g_ords_arr, self.dims)
292                ls_text = self.points_to_WKT(points)
293                self.valid = True
294                return '%s(%s)' % (geom_type, ls_text)
295            # curved segments
296            elif self.get_interpretation() == 2 and strict == True:
297                # to do
298                return None
299               
300            # compound linestrings - mix of straight and curved elements,
301            # each with a SDO_ELEM_INFO triplet,
302            # and each overlapping the last by one point
303           
304        elif geom_type == 'POLYGON':
305            '''Return WKT - POLYGON((x1 y1,x2 y2,...,xn yn,x1 y1)
306                                    (i1 j1, 12 j2,...,in jn,i1 j1))
307           
308            May include more than one element if there are internal holes
309            There can be only one external ring, this must be listed
310            counterclockwise (not checked at present).
311            There may be zero or more internal rings, these must be listed
312            clockwise (not checked at present), and each has one additional
313            SDO_ELEM_INFO triplet.
314            Simple case
315               Simple element, with a single SDO_ELEM_INFO triplet.
316            Complex case
317               More than 1 SDO_ELEM_INFO triplet, first for external ring
318               , other(s) for internal rings.
319            The triple can have the following values
320            [0] - 1 - starting position (base 1)
321            [1] - 1003 or 2003 (SDO_ETYPE)
322            [2] - 1, 2, 3, or 4 (SDO_INTERPRETATION)
323            Each point is listed sequentially in the SDO_ORDINATES
324            The last point in a ring is the same as first point.
325           
326            '''
327            # validity check - may need to expand ToDo - review
328            if (self.get_etype() != 1003 and self.get_etype() != 2003):
329                self.valid = False
330                return None
331           
332            if self.get_interpretation() == 1 \
333                or (self.get_interpretation() == 2 \
334                and strict == False): 
335            # straight segments
336                elem_text, elem_type = self.make_arrays(self.g_eleminfo_arr
337                                                      , self.g_ords_arr, debug)
338                poly_text = ''
339                for elem in elem_text:
340                    poly_text = '%s(%s)' % (poly_text, elem)
341                self.valid = True
342                return '%s(%s)' % (geom_type, poly_text)
343            elif self.get_interpretation() == 2:
344                # curved segments
345                return None
346            elif self.get_interpretation() == 3:
347                # rectangle - 2 points x1 y1, x2 y2 -> x1 y1, x1 y2, x2 y2, x2 y1, x1 y1
348                coords = [self.g_ords_arr[0],self.g_ords_arr[1],
349                          self.g_ords_arr[0],self.g_ords_arr[3],
350                          self.g_ords_arr[3],self.g_ords_arr[3],
351                          self.g_ords_arr[3],self.g_ords_arr[1],
352                          self.g_ords_arr[0],self.g_ords_arr[1]]
353                points = self.to_points(coords, self.dims)
354                points_wkt = self.points_to_WKT(points)
355                print coords, '\n', points, '\n', points_wkt
356                poly_text = '(%s)' % points_wkt
357                self.valid = True
358                return '%s(%s)' % (geom_type, poly_text) 
359            elif self.get_interpretation() == 4:
360                # circle - 3 points
361                # this is, obviously, a triangle
362                points = self.to_points(self.g_ords_arr, self.dims)
363                points_wkt = self.points_to_WKT(points)
364                poly_text = '(%s)' % points_wkt
365                return '%s(%s)' % (geom_type, poly_text)
366            else:
367                # invalid
368                return None
369
370               
371           
372        elif geom_type == 'GEOMETRYCOLLECTION':
373            '''Return WKT - GEOMETRYCOLLECTION(geom1,geom2,..,geomn)
374               - Container for other simple geometries
375
376            SDO_ELEM_ARRAY triples will define different geometries. 
377            Need to watch for termination of polygons.
378           
379            It is not clear how oracle would handle multi geometries within a
380            geometry collection, so this is not implemented.
381           
382            '''
383            elem_text, elem_type = self.make_arrays(self.g_eleminfo_arr
384                                                      , self.g_ords_arr, debug)
385           
386            '''Create a holder for each element.'''
387            elems = []
388            elem_arr = []
389            num_elems = len(elem_type)
390            for i,e in enumerate(elem_type):
391                if e == 1 or e == 2 or e == 4:
392                    '''Point, linestring or compound linestring.'''
393                    elems.append(e)
394                    elem_arr.append(elem_text[i])
395                else:
396                    '''Polygon or multi geometry.
397                   
398                    For 1003 start a new sub element, for 2003 add to it.
399                    '''
400                    if e == 1003:
401                        sub_elem_type = []
402                        sub_elem = []
403                       
404                    if e == 1003 or e == 2003:
405                        sub_elem_type.append(e)
406                        sub_elem.append(elem_text[i])
407                   
408                    if i == num_elems:
409                        elems.append(sub_elem_type)
410                        elem_arr.append(sub_elem)
411                    elif elem_type[i+1] <> 2003:
412                        elems.append(sub_elem_type)
413                        elem_arr.append(sub_elem)
414                    else:
415                        pass
416           
417            print elems, elem_arr
418           
419            '''Put the geometry string together.'''
420            coll_text = 'GEOMETRY('
421            for i, e in enumerate(elems):
422                if e == 1:
423                    point_text = 'POINT(%s)' % (elem_arr[i])
424                    coll_text = coll_text + point_text + ','
425                elif e == 2:
426                    line_text = 'LINESTRING(%s)' % (elem_arr[i])
427                    coll_text = coll_text + line_text + ','
428                else:
429                    poly_text = ''
430                    for elem in elem_arr[i]:
431                        poly_text = '%s(%s)' % (poly_text, elem)
432                    poly_text = 'POLYGON(%s)' % (poly_text)
433                    coll_text = coll_text + poly_text + ','
434               
435            coll_text = coll_text[:-1]+')'
436            self.valid = True
437            return coll_text
438           
439        elif geom_type == 'MULTIPOINT': # not tested yet !!!
440            '''Return WKT - MULTIPOINT(x1 y1, ... , xn yn)
441           
442            OGC compliant alternative of
443               MULTIPOINT((x1 y1), ... , (xn yn)) is not currently supported.
444            MultiPoint - series of points
445            A single SDO_ELEM_INFO is used
446            [0] SDO_STARTING_OFFSET === 1
447            [1] SDO_ETYPE = 1
448            [2] SDO_INTERPRETATION = number of points in array
449           
450            '''
451           
452            # validity check - may need to expand
453            if self.get_etype() != 1:
454                self.valid = False
455                return None
456           
457            else:
458                # num_Points = self.get_interpretation()
459                points = self.to_points(self.g_ords_arr, self.dims)
460                mp_text = self.points_to_WKT(points)
461                self.valid = True
462                return '%s(%s)' % (geom_type, mp_text)
463           
464        elif geom_type == 'MULTILINESTRING':
465            '''Retrun WKT - MULTILINESTRING((x1 y1,...,xn yn)(i1 j1,...,in jn))
466           
467            MultiLineString - a series of line strings
468            Each line string is defined by one SDO_ELEM_INFO triplet
469            Mixed segments, with straight & cureved elements,
470            have different behaviour
471            - the end point of one segment is the start point of the next.
472           
473            '''
474           
475            # validity check - may need to expand
476            if self.get_etype() != 2:
477                valid = False
478                return None
479           
480            else:
481                # this is identical to polygons
482                if self.get_interpretation() == 1 \
483                       or (self.get_interpretation() == 2 \
484                       and strict == False):
485                    elem_text, elem_type = self.make_arrays(self.g_eleminfo_arr
486                                                      , self.g_ords_arr, debug)
487                    ml_text = ''
488                    for elem in elem_text:
489                        ml_text = '%s(%s)' % (ml_text, elem)
490                    self.valid = True
491                    return '%s(%s)' % (geom_type, ml_text)
492                else:
493                    # curved segments
494                    return None
495           
496             
497           
498        elif geom_type == 'MULTIPOLYGON':
499            '''Return WKT - MULTIPOLYGON(((x1 y1, ... , x1 y1)
500                                      ((x2 y2, ..., x2 y2)(x3 y3, ..., x3 y3)))
501           
502            MultiPolygon - a number of polygons, each with one external ring 
503            and zero or more internal rings.
504            External rings have SDO_ETYPE of 1003 and are grouped with
505            subsequent internal rings, which have SDO_ETYPE of 2003 so
506            SDO_ELEM_INFO like [1,1003,1, 1,1003,1, 1,2003,1] maps to
507            (note parenthesis (( ()  (()()) )
508            MULTIPOLYGON(((x1 y1, ... , x1 y1)((x2 y2, ..., x2 y2)
509                                              (x3 y3, ..., x3 y3)))
510            SDO_ELEM_INFO[1] == 1003 => start new outer ring
511            SDO_ELEM_INFO[2] => start position
512           
513            '''
514            # validity check
515            if self.get_interpretation() == 1 \
516                or (self.get_interpretation() == 2 \
517                and strict == False):
518                pass
519           
520            elem_text, elem_type = self.make_arrays(self.g_eleminfo_arr
521                                                      , self.g_ords_arr, debug)
522            mp_text = ''
523            for i, elem in enumerate(elem_text):
524                if elem_type[i] == 1003: # outer ring
525                    # start with '(('
526                    mp_text += '('
527                elif elem_type[i] == 2003: # inner ring
528                    # start with '('
529                    pass
530                mp_text = '%s(%s)' % (mp_text, elem)
531                if i == len(elem_type) - 1: # last element
532                    mp_text += ')'
533                elif elem_type[i+1] == 1003: # terminate outer ring
534                    mp_text += ')'
535            self.valid = True
536            return '%s(%s)' % (geom_type, mp_text)
537        else:
538            return None
539   
Back to Top