Ticket #21273: oracle_xe_sdo_geometry_3.2.py

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

oracle_xe_sdo_geometry.4.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[3],
351 self.g_ords_arr[3],self.g_ords_arr[3],
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