Ticket #21273: oracle_xe_sdo_geometry_3.py

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

oracle_xe-sdo_geometry.3.py

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