Lately, I’ve been digging into Oracle Spatial. It’s a lot of fun, but wow, there’s a lot to learn.
I’m working on a series of tutorials that will walk through the process of building an island for our DinoDate dinosaurs to live on. But in the meantime, I thought we could have some fun and look at a function to generate simple shapes.
For a detailed explanation of the SDO_GEOMETRY object, you can check out the docs, or watch for my upcoming tutorial. In this post, I’ll just add brief comments to explain the parameters.
I realize the code formatting isn’t the best on this post, so I’ve created a gist with the finished function.
1 2 3 4 5 6 7 8 9 10 |
create or replace function generate_shape( p_lon in number default 0, p_lat in number default 0, p_sides in number default 1, p_radius in number default 0, p_start_radian in number default 0) return sdo_geometry is v_generated_shape sdo_geometry; begin |
Our function accepts a latitude and longitude we’ll use to position our shape, the number of sides, a radius and a starting radian.
A Point
Accepting the defaults gives us a 1 sided object with a 0 radius, sounds like a point to me. In fact, let’s make anything with a radius of 0 a point.
1 2 3 4 5 6 |
if p_radius = 0 then v_generated_shape := SDO_GEOMETRY(2001, 4326, MDSYS.SDO_POINT_TYPE(p_lon, p_lat,NULL), NULL, NULL); |
- 2001, –2 dimensions, 0 LRS, 01 is a point
- 4326, –SRID for Earth lat/lon system
- MDSYS.SDO_POINT_TYPE(p_lon, p_lat,NULL), –point located p_lon, p_lat
- NULL, –not used for a point
- NULL) –not used for a point;
Generating Shapes
When we generate the rest of our shapes, we will:
- Start with the given point at p_lon / p_lat.
- Go a distance of p_radius in meters.
- In a direction of p_start_radian.
- Get the lat / lon position of the new point.
- Decrement our current radian by (2pi / sides. ) 2pi = (asin(1)*4)
- Repeat for each corner.
If you’re like me and it’s been so long since you took a trig class that you’re a bit fuzzy on what a radian is here’s an excellent definition. The example in that post shows a 0 radian starting in the 3 o’clock position and moving counter clockwise. use a 0 radian at the 12 o’clock position and move clockwise.
Generating Corner Points
The function we’re using to generate corner points is sdo_util.point_at_bearing. It considers 0 radians to be at the 12 o’clock position and moves clockwise.
Parameters:
- sdo_geometry point object
- radian from 0 to 2pi
- radius in meters
Once we have the new point, we’ll get the x / y coordinates of the point using sdo_util.getvertices and we’ll add those values to an sdo_ordinate_array.
The sdo_ordinate_array defines the corner point for our shapes (or end points for a line) and the sdo_elem_info_array acts as a key used to define how we use those coordinates.
Once again, if you want to dig into the details look at the docs, but here I’ll add short definitions.
Let’s add some new variables to work with.
1 2 3 4 5 6 7 |
v_cur_radian number := p_start_radian; c_radian_increment CONSTANT number := (asin(1)*4)/p_sides; v_x number; v_y number; /* array of points used to construct the polygon */ v_ordinate_array_points sdo_ordinate_array := sdo_ordinate_array(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
else /* each itteration will add a point at a distance of p_radius from the center point on the current radian. asin(1)*4 = 2pi */ for i in 1..p_sides loop select t.x, t.y into v_x, v_y from table(sdo_util.getvertices( sdo_util.point_at_bearing( sdo_geometry(2001, 4326, sdo_point_type(p_lon, p_lat, null), null, null), v_cur_radian, p_radius ) ) ) t; /* add x and y value of new point */ v_ordinate_array_points.extend(2); v_ordinate_array_points(v_ordinate_array_points.count-1) := v_x; v_ordinate_array_points(v_ordinate_array_points.count) := v_y; /* It's a line, we only need the start and end points*/ exit when p_sides < 3; /* decrement the current radian by c_radian_increment so we move in a counter clockwise direction */ v_cur_radian := v_cur_radian - c_radian_increment; end loop; |
A Line
A 1 sided object with a radius > 0 could be a line or a circle. I’ve chosen to use an SRID of 4326 which is a lat/lon earth based system and is not a geodetic coordinate system, so circles are a bit more complex than I want to cover in this post. So, a line it is and we’ll include 2 sided objects as lines also just to keep it simple.
I know, I used a bunch of fancy words and didn’t explain them. I’m trying to keep this post simple, but if you’d like to dig into all of the details, take a look at the docs.
1 2 3 4 5 6 7 8 9 10 |
/* It's a line */ if p_sides < 3 then v_generated_shape := SDO_GEOMETRY(2002, 4326, NULL, SDO_ELEM_INFO_ARRAY(1, 2, 1), SDO_ORDINATE_ARRAY(p_lon, p_lat, v_ordinate_array_points(1), v_ordinate_array_points(2))); |
- 2002, –2 dimensions, 0 LRS, 02 is a line
- 4326, –SRID for Earth lat/lon system
- NULL, — only used for a point
- SDO_ELEM_INFO_ARRAY(1, –start with 1st ordinate
- 2, –a straight line
- 1), –a simple straight line
- SDO_ORDINATE_ARRAY(p_lon, p_lat, –start coordinates
- v_ordinate_array_points(1), v_ordinate_array_points(2))); –end coordinates
For a line, we use the p_lon, p_lat parameters as our first point and our generated values as our second point.
Polygons With 3 or More Sides
For our polygons, we use the p_lon, p_lat parameters as a center point and generate our corner points around the center.
When you define a simple polygon the exterior edge points need to be defined in a counter-clockwise direction. If you create more complex polygons, such as a polygon with holes, the interior polygons are defined in a clockwise direct. Check out the docks if you want to experiment with more complex objects.
When we define a closed polygon, our sdo_ordinate_array will contain a set of points for each corner. In order to close our polygon, we add an extra point at the end that is the same as the start point.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* It's a polygon */ else /* add x and y value of first point as the last point of the polygon to close it */ v_ordinate_array_points.extend(2); v_ordinate_array_points(v_ordinate_array_points.count-1) := v_ordinate_array_points(1); v_ordinate_array_points(v_ordinate_array_points.count) := v_ordinate_array_points(2); v_generated_shape := sdo_geometry(2003, 4326, null, sdo_elem_info_array(1,1003,1), v_ordinate_array_points ); end if; |
- 2003, — 2-dimensional polygon
- 4326, –SRID for Earth lat/lon system
- NULL, — only used for a point
- SDO_ELEM_INFO_ARRAY(1, –start with 1st ordinate
- 1003, — simple polygon
- 1), — simple polygon
- v_ordinate_array_points –coordinate array
Let’s make some shapes
First, we’ll make a point at the center of the Bermuda Triangle.
1 2 3 |
select generate_shape(p_lon => -69.322920, p_lat => 26.846786) from dual; |
Using the SQL Developer Map View we can see our point.
Now let’s add a 100-meter line at 0 radians.
1 2 3 4 5 |
select generate_shape(p_lon => -69.322920, p_lat => 26.846786, p_sides => 1, p_radius => 100) from dual; |
We’ll move the starting radian to -.75 * pi. We’ll also set p_sides to 2 since we treat 1 and 2 as a line.
1 2 3 4 5 6 |
select generate_shape(p_lon => -69.322920, p_lat => 26.846786, p_sides => 2, p_radius => 100, p_start_radian => (asin(1)*4) * -.75) from dual; |
From here on, we’ll leave this point and line on the view for reference and we’ll just increment the p_sides parameter.
1 |
p_sides => 3 |
1 |
p_sides => 4 |
1 |
p_sides => 5 |
1 |
p_sides => 6 |
1 |
p_sides => 7 |
1 |
p_sides => 8 |
1 |
p_sides => 9 |
1 |
p_sides => 10 |
I think that’s enough examples. I hope you’ve found this useful or at least a little fun.
Feel free to leave questions or suggestions in the comments.