Ticket #21273: oracle_xe_sdo_geometry_4.py

File oracle_xe_sdo_geometry_4.py, 21.8 KB (added by vinhussey, 11 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