Ticket #21273: oracle_xe_sdo_geometry.2.py

File oracle_xe_sdo_geometry.2.py, 17.5 KB (added by vinhussey, 10 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