Let's try this by loading the shapes into a table:
drop table t1 purge;
create table t1 (id char, geom sdo_geometry);
insert into t1 values('A', sdo_geometry('POLYGON ((-100 40, -100 44, -96 44, -96 40, -100 40))',4326));
insert into t1 values('B', sdo_geometry('LINESTRING (-99 37, -99 40)',4326));
insert into t1 values('C', sdo_geometry('POINT (-100 41)',4326));
insert into t1 values('D', sdo_geometry('POLYGON ((-96 41.5, -95 42, -95 41, -96 41.5))',4326));
commit;
The data looks like this:
SQL> select * from t1;
ID GEOM(SDO_GTYPE, SDO_SRID, SDO_POINT(X, Y, Z), SDO_ELEM_INFO, SDO_ORDINATES)
---- -----------------------------------------------------------------------------------------------------------------------------------
A SDO_GEOMETRY(2003, 4326, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 1), SDO_ORDINATE_ARRAY(-100, 40, -100, 44, -96, 44, -96, 40, -100, 40))
B SDO_GEOMETRY(2002, 4326, NULL, SDO_ELEM_INFO_ARRAY(1, 2, 1), SDO_ORDINATE_ARRAY(-99, 37, -99, 40))
C SDO_GEOMETRY(2001, 4326, SDO_POINT_TYPE(-100, 41, NULL), NULL, NULL)
D SDO_GEOMETRY(2003, 4326, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 1), SDO_ORDINATE_ARRAY(-96, 41.5, -95, 42, -95, 41, -96, 41.5))
4 rows selected.
Then let's do the query to run the spatial comparisons:
select a.id, b.id, sdo_relate(a.geom,b.geom,'mask=TOUCH') as touch, sdo_relate(a.geom,b.geom,'mask=DISJOINT') as disjoint
from t1 a, t1 b
where a.id in ('B','C','D')
and b.id in ('A')
order by a.id, b.id
and check the results:
ID ID TOUCH DISJOINT
---- ---- ---------- ----------
B A FALSE TRUE
C A TRUE FALSE
D A TRUE FALSE
3 rows selected.
Those are the same you have. Line B does not touch rectangle A. Why is that ? Let's see how those shapes look on a map:
It does look like line B does touch rectangle A. But does it really ? Here is where Oracle differs from the other spatial databases (one of them being PostGIS I imagine). And why does it ?
Because in Oracle's geometry system, line segments between geodetic points represent the shortest distance between those points on the WGS84 ellipsoid, i.e they are geodesics - they follow great circles.
That, and Oracle does all geodetic calculations directly on the ellipsoidal representation of the earth.
So the "top" and "bottom" lines of the mauve rectangle do not follow the 40th and 44th parallels as the graphical representation would make us believe. That is because the map is a global mercator projection, and the mapping tool just draws straight line on that projection.
But in reality they follow great circles, i.e. on the projection, they are slightly curved northward.
To observe this, let's create a copy of rectangle A, but densified so that we have one vertex every 10km:
insert into t1
select 'E',sdo_util.DENSIFY_GEOMETRY(geom,10000)
from t1
where id = 'A';
commit;
and add to the map:
It's hard to see, but the "horizontal" lines are now slightly curved. Let's zoom in to get a better view:
This shows that there is a significant distance between the line and the southern border of your rectangle: 1.4km.
And that explains why "Oracle thinks B does not touch A": simply because B does not touch A. It only does if your spatial database first projects geodetic shapes on a plane, such as a mercator projection. But when the calculations are done directly on the ellipsoidal representation of the globe, as Oracle does, then the reality appears: the line does not touch the rectangle.
EDIT
Two more important points to note:
First, the SDO_RELATE()
function is really not designed to be used in a SELECT list: its primary intent is to be used as a predicate in a where clause. For example to find all shapes that touch a selected shape you would write something like:
SELECT ...
FROM land_parcels lp, pipes p
WHERE sdo_relate (lp.geom.p.geom, 'mask=TOUCH') = 'TRUE'
AND p.pipe_id = ...
or better, use the simplified syntax:
WHERE sdo_touch (lp.geom.p.geom) = 'TRUE'
In the select list, you can use the sdo_geom.relate()
function, like this:
select a.id, b.id, sdo_geom.relate(a.geom,'TOUCH',b.geom) as touch, sdo_geom.relate(a.geom,'DISJOINT',b.geom) as disjoint
from t1 a, t1 b
where a.id in ('B','C','D')
and b.id in ('A')
order by a.id, b.id;
The result is the same as before, but the function allows you to specify the tolerance at which the computation is made. When no tolerance is specified, Oracle uses a default of 0.5m. For the TOUCH
relationship, it means that Oracle will say that your line touches the polygon as long as the end point is less than 50cm from the polygon border, or up to 50cm inside the polygon.
For example, this uses a tolerance of 1500m:
select a.id, b.id, sdo_geom.relate(a.geom,'TOUCH',b.geom,1500) as touch
from t1 a, t1 b
where a.id in ('B','C','D')
and b.id in ('A')
order by a.id, b.id;
The result:
ID ID TOUCH
---- ---- ----------
B A TOUCH
C A TOUCH
D A TOUCH
3 rows selected.
Now line B
touches polygon A
. That is because the end point of B
is some 1400m from the southern border of A
, and therefore is within tolerance.
The tolerance used by the SDO_RELATE()
operator and its variants (SDO_INSIDE()
, SDO_TOUCH()
etc) is specified in the metadata for that table (USER_SDO_GEOM_METADATA
). For functions, you can pass it as a parameter.
Typical tolerance values are much smaller: 1m, 50cm, 5cm ... depending on the quality and accuracy of the data. The SDO_TOUCH
predicate is typically used to find things like land parcels adjacent to another parcel. The tolerance then reflects that the actual parcel boundaries have been measured at different times with different instruments, and the border between two adjacent parcels is not always the exact same line: it compensates for inaccuracies.
For example, two parcels may be adjacent (they TOUCH
) at tolerance 0.05 (5cm), but not at tolerance 0.5 (50cm). This would then reflect that there actually exists a narrow path 50cm wide between the parcels. Or that the two parcels actually overlap slightly (up to 50cm). In other words it is very important to understand the accuracy and quality of the data you work with, and to use the proper tolerance especially for spatial relationships like TOUCH
, COVERS
, INSIDE
...
POLYGON (( -100 40, -100 44, -96 44, -96 40, -99 40, -100 40 ))
?