SpatialDB Advisor
I have often advocated the use of a spatial sort when loading static spatial data into Oracle (See 1. Spatially sort read-only data. in my article on performance tips). The idea here is to try and place spatial data that is close together in space (called spatial autocorrelation), close together on disk. Then, when the hard-disk’s head goes down on the disk to retrieve one or more records, the head will not have to travel far to retrieve the geographically close data. Imagine you zooming in to a particular area in your GIS. The search will be a minimum bounding rectangle based query that is specifically requesting data that is close together geographically. If disk activity can be minimised by spatial autocorrelation then performance will improve.
But really, I had never gotten around to producing a robust solution for use solely within the Oracle database.
Until now.
Object Relational Databases like Oracle are ideally suited to this task through the SELECT statement’s ORDER BY clause such that we can create a new table and populate it as follows:
CREATE TABLE spatially_sorted
AS
SELECT id, geom
FROM spatially_unsorted
ORDER BY <some sort of spatial sort on the geom column>;
(Of course we are assuming that when Oracle creates the table it will use contiguous blocks on disk. This will only be true is the tablespace is new or has been degragmented.)
Now, the problem is that a sort order is not defined on an Oracle Sdo_Geometry object such that one could sort as follows:
CREATE TABLE spatially_sorted
AS
SELECT id, geom
FROM spatially_unsorted
ORDER BY geom -- geom is an SDO_Geometry object
Oracle’s lack of a sort of any kind based on the SDO_Geometry object is why I have not provided a practical example before now. (Though it is worth noting that PostGIS can sort by geometry with Regina Obe observing that the sort is a btree sort: not rtree as in PostGIS’s CLUSTER ON geometry).
The standard way of sorting based on the Oracle RTree is to simply do an SDO_Filter search using the MBR of the whole of the data as follows:
CREATE TABLE spatially_sorted
AS
SELECT id, geom
FROM spatially_unsorted su,
(SELECT sdo_aggr_mbr(su2.geom) as mbr
FROM spatially_unsorted su2) su3
WHERE SDO_Filter(su.geom,su3.mbr) = 'TRUE'
One could also sort simply by X and Y via use of any point in a geometry. Thus, for Oracle point data, this would also work:
CREATE TABLE spatially_sorted
AS
SELECT id, geom
FROM spatially_unsorted su,
ORDER BY su.geom.sdo_point.x, su.geom.sdo_point.y;
Suitability of Sort
Of course both the PostGIS “ORDER BY geometry” sort and the Oracle RTree (SDO_Filter…) and XY based sorts will sort the data, but they will do so in strips based on Y and X.
What other theory can be used to define a spatial sort function?
Space curves
There is a body of theory that describes a space-filling curve such as in Z-Order) or Peano (my favourite). These curves, I would assert, when implemented, provide a better method for taking advantage of the spatially autocorrelation that is implicit to spatial data.
PostGIS 1.4 has implemented the ST_GeoHash function which I assume is a form of a space curve – see GeoHash Website – but this only for geodetic – longitude/latitude data.
Oracle is supposed to have implemented the Z-Order access method. From what I recall this would have been part of the original MultiDimension implementation in the 1990s. Now, the only place I have can find an implementation is MD.HHENCODE. I have tried to use this over the years but have been unsatisfied that it was working as I wanted. (The MD.HHENCODE function is undocumented.)
So, to implement a spatial sort that best represents spatial autocorrelation we must implement space curve key generation function on SDO_Geometry.
Implementation
I have Peano and Morton space-filling curves implemented in C but an implementation in Oracle (as EXPROC) based on these, while technically not difficult, does required changes to the LISTENER which is not popular with many DBAs.
I have also converted both these algorithms into Java and have successfully deployed them into the Oracle Java Virtual Machine with PL/SQL wrappers. While not difficult it does require DBA involvement to implement.
So, today, after having received an email from a colleague (whom I admire a lot) asking the following question, I decided to try and create a solution based entirely on PL/SQL:
We have a production instance in Oracle for our spatial data and another instance for web viewing to which we regularly copy our production data.
Normally we run a script which first removes indices, empties the tables, fills them with an INSERT … SELECT … and recreates the indices.
Was wondering if it is possible (and a good idea) to do the INSERT in such a manner that the data is spatially clustered when copied, maybe by using some sort of ORDER BY in the INSERT … SELECT.
Morton Implementation
The original C code for generating a Morton space key is as follows:
unsigned xy_to_morton (col,row)
/* this procedure calculates the Morton number of a cell
at the given row and col[umn]
Now there are a few difficulties to over-come to get a successful implementation. The first is the use of unsigned integers. To be frank, I am going to ignore this and worry only it if I have to use all 32 bits in generating a key value. The second is the need for a bitwise left << operator. This can be done via the following PL/SQL:
Function Left_Shift( p_val Integer, p_shift Integer)
Return Integer
As
Begin
Return trunc(p_val * power(2,p_shift));
End;
OK, now let’s do the translation to PL/SQL.
create or replace Function Morton (p_col Natural, p_row Natural)
Return INTEGER
As
v_row Natural := ABS(p_row);
v_col Natural := ABS(p_col);
v_key Natural := 0;
v_level BINARY_INTEGER := 0;
v_left_bit BINARY_INTEGER;
v_right_bit BINARY_INTEGER;
v_quadrant BINARY_INTEGER;
A quick test:
SQL> select codesys.morton(100,50) from dual;
Morton Restrictions
There is a restriction to how a Morton space curve can be applied: the grid must be regular. This means that while the X and Y dimensions of the grid may be different (ie giving a rectangle not a square) the number of grids either side must be the same (ie a 100×100 grid not a 150×100 grid). Another requirement that affects the generation of a correct space curve is that the (row,column) index values must always be the same. So, in a 100×100 grid the lower left grid should be referenced as (0,0) or (1234,1234) and not (0,1) or (1234, 3456).
Apply to Real Data
First off let’s generate something that is ideal to our task: random point data. Note that the area, 5000m x 5000m, can be divided in to 100 × 100 × 500m grid cells.
DROP TABLE MORTONED_P;
DROP TABLE MORTON_P;
CREATE TABLE MORTON_P (
id integer,
morton_key integer,
geom mdsys.sdo_geometry
);
We are not going to create a spatial index as it is not needed.
What does this data look like? If we select against the table with no sort we can assume we are getting data as it is physically on disk. Note also, that we want to ensure that our column/row addresses are normalised to the start of the grid.
SQL> set pagesize 1000 linesize 131
SQL> Prompt Inspect rows to show random ordering...
Inspect rows to show random ordering...
SQL> list
1 SELECT rowid,
2 id,
3 a.geom.sdo_point.y as y,
4 a.geom.sdo_point.x as x,
5 FLOOR(a.geom.sdo_point.y/500) - MIN(FLOOR(a.geom.sdo_point.y/500)) OVER (ORDER BY FLOOR(a.geom.sdo_point.y/500)) as morton_gx,
6 FLOOR(a.geom.sdo_point.x/500) - MIN(FLOOR(a.geom.sdo_point.x/500)) OVER (ORDER BY FLOOR(a.geom.sdo_point.x/500)) as morton_gy
7 FROM MORTON_P a
8* WHERE rownum < 20
SQL> /
Note how the data is pretty unsorted.
The following image was generated by linking to the MORTON_P table using Manifold GIS and rendering the data by the ID column such that sequential ID column values have roughly the same colour. Note that the colours are displaying randomly which befits the way the data was created and written.

Now, let’s compute the morton key and add it to the base table.
SQL> Prompt Now Apply Morton Key ....
Now Apply Morton Key ....
SQL> UPDATE MORTON_P A
2 SET a.morton_key = codesys.Morton(
3 (SELECT FLOOR(b.geom.sdo_point.y/500) - MIN(FLOOR(b.geom.sdo_point.y/500)) OVER (ORDER BY FLOOR(b.geom.sdo_point.y/500)) FROM Morton_P b WHERE b.id = a.ID ),
4 (SELECT FLOOR(c.geom.sdo_point.x/500) - MIN(FLOOR (c.geom.sdo_point.x/500)) OVER (ORDER BY FLOOR(c.geom.sdo_point.x/500)) FROM Morton_P c WHERE c.id = a.ID ));
Now let’s create our sorted table using the morton_key value on the base table.
SQL> drop table mortoned_p;
Now select against the table with no sort (assume we are getting data as physically on disk).
SQL> Prompt Inspect rows to show ordering with morton attribute assigned...
Inspect rows to show ordering with morton attribute assigned...
SQL> SELECT rowid,
2 id,
3 a.geom.sdo_point.x as x,
4 a.geom.sdo_point.y as x,
5 a.morton_key
6 FROM MORTONED_P a
7 WHERE rownum < 20;
Yes, the data is nicely sorted as we want.
And visually?
Visualising the Morton Key
The best way to understand what a space curve is doing is to visualise it via the actual grid and also the actual space curve that passes through the key points in the grid.
First, let’s create the grid and visualise it.
SQL> drop table morton_grid;
Now, let’s look at it. The following image was generated by linking to the MORTONED_GRID table using Manifold GIS and rendering and labelling the data by the MORTON_KEY value.

Notice how the data is colouring shows the relatedness of spatial data next to each grid.
But to get a real sense as to how the space curve sort is going to work, let’s create a linestring that passes through the centroids of each grid order by the Morton space curve value.
SQL> Prompt Create linestring representing the morton space curve
Create linestring representing the morton space curve
SQL> drop table Morton_L;
Here is the space curve superimposed on the labelled Morton grid:

We can also look at the actual point data rendered by the MORTON_KEY and ID columns. Note that the colours are displaying the ordering of the Morton space-curve.


Finally, let’s superimpose the actual point (geometry) data on the 500m x 500m grid with everything rendered by the Morton Key.

I think we can safely assume we have achieved our goal.
I hope this is useful to someone.


















<<Swapping Ordinates in an SDO_GEOMETRY object >>Convert GML to SDO_Geometry in Oracle 10gR2
— Regina Sep 19, 06:05 am #
— Simon Sep 20, 07:59 pm #
— Regina Sep 22, 12:18 am #
— Simon Sep 23, 10:25 am #