SDO_INSIDE返回零记录

时间:2018-04-05 17:34:54

标签: oracle oracle-spatial

所以我有一个桌村:

CREATE TABLE village (
  building_id integer PRIMARY KEY,
  name VARCHAR2(30),
  visitors integer,
  building SDO_GEOMETRY
);

表格访问者:

create table visitors(
  id integer,
  position SDO_GEOMETRY 
);

以下是插页:

INSERT INTO village VALUES(2,'KircheV2', 4,
  SDO_GEOMETRY(
      2003,
      NULL,
      NULL,
      SDO_ELEM_INFO_ARRAY(1,1003,1),
      SDO_ORDINATE_ARRAY(100,100, 100,120, 120,100, 120,120)
  )
);


INSERT INTO visitors VALUES (1,
  SDO_GEOMETRY(
      2001,
      NULL,
      SDO_POINT_TYPE(110, 110, NULL),
      NULL,
      NULL
  )
);

出于某种原因,当我试图让所有访问者都在“KircheV2”中时,SQL语句总是返回零记录:

SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and (SDO_INSIDE(village.building,visitors.POSITION) = 'TRUE');

背后的原因是什么?坐标110; 110实际上应该在建筑物的中间,因此它应该在建筑物内部。

1 个答案:

答案 0 :(得分:0)

您的数据不正确。您可以这样验证:

SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;

SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13348 [Element <1>] [Ring <1>]

1 row selected.

该错误意味着:

ORA-13348: polygon boundary is not closed

在Oracle(实际上是所有存储系统)中,根据OGC规则,多边形必须关闭,即第一个顶点必须重复为最后一个顶点。所以:

INSERT INTO village VALUES(2,'KircheV2', 4,
  SDO_GEOMETRY(
      2003,
      NULL,
      NULL,
      SDO_ELEM_INFO_ARRAY(1,1003,1),
      SDO_ORDINATE_ARRAY(100,100, 100,120, 120,100, 120,120, 100,100)
  )
);

但是选择仍然无法返回任何结果。那是为什么?

SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;

SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13349 [Element <1>] [Ring <1>][Edge <2>][Edge <4>]

1 row selected.

此错误表示:

ORA-13349: polygon boundary crosses itself

这是有道理的:顶点明显形成蝴蝶形状:

100,100,100,120,120,100,120,120,100,100

假设您想要形成一个简单的矩形,那么正确的形状是:

100,100,100,120,120,120,120,100,100,100

INSERT INTO village VALUES(2,'KircheV2', 4,
  SDO_GEOMETRY(
      2003,
      NULL,
      NULL,
      SDO_ELEM_INFO_ARRAY(1,1003,1),
      SDO_ORDINATE_ARRAY(100,100, 100,120, 120,120, 120,100, 100,100)
  )
);

仍然没有结果。为什么?

SQL> select sdo_geom.validate_geometry_with_context (building,0.005) from village;

SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXT(BUILDING,0.005)
-------------------------------------------------------------------------------
13367 [Element <1>] [Ring <1>]

1 row selected.

这意味着:

ORA-13367: wrong orientation for interior/exterior rings

多边形中的环必须正确定向。外圈必须逆时针,内圈(孔)必须顺时针。所以你需要这样写:

100,100,120,100,120,120,100,120,100,100

INSERT INTO village VALUES(2,'KircheV2', 4,
  SDO_GEOMETRY(
      2003,
      NULL,
      NULL,
      SDO_ELEM_INFO_ARRAY(1,1003,1),
      SDO_ORDINATE_ARRAY(100,100, 120,100, 120,120, 100,120, 100,100)
  )
);

仍然没有结果!但这是因为您的查询不正确。 SDO_INSIDE(a,b)找到完全在B内部的所有A的出现。在您的情况下,就像要求访问者内部的建筑物一样。显然你想要反过来,所以要么说: SDO_INSIDE(visitor, building) SDO_CONTAINS (building,visitor) ,就像这样:

SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and SDO_INSIDE(visitors.POSITION,village.building) = 'TRUE';
SELECT * FROM visitors,village WHERE village.name like 'KircheV2' and SDO_CONTAINS(village.building,visitors.POSITION) = 'TRUE';

其他一些评论:

  1. 我想,你的例子纯粹是人为的?在现实生活中,您的多边形将来自某些GIS系统 - 例如您加载到数据库中的ESRI shapefile,或者从某些GIS工具捕获的形状。无论哪种方式,两者都将产生正确的形状,因为所有工具都适用于形状闭合和方向的OGC规则。如果不是,验证功能会告诉您错误,sdo_util.rectify_geometry功能将纠正基本错误。

  2. 您还需要了解空间运算符(INSIDE与CONTAINS等)及其效果。文档解释了它们的含义。请注意,SDO_xxx运算符集与OGC定义的ST_xxx函数略有不同。

  3. 您没有指定任何坐标系(SDO_SRID为NULL)。虽然这适用于您的人工示例,但在现实生活中,您应始终使用适当的坐标系。特别是如果你的形状是大地测量的(长/纬),你必须使用适当的SRID这样说:4326。这保证了所有的计算都是在地球椭圆形的上下文中完成的。如果您想要执行基于距离的查询或测量长度,距离或区域,这一点尤为重要。对投影数据使用适当的SRID同样重要。它允许您执行查询而不管使用的坐标系:例如,查找GPS点所在的地块(在本地投影中)。

  4. 性能和索引。确保您有空间索引。虽然Oracle 12.2允许您在不定义索引的情况下执行查询,但以前的版本始终需要一个(如果不存在则会失败)。如果该表甚至适度大,那么在没有空间索引的表上执行查询可能会非常慢。例如,考虑您的村庄和访客的例子。假设您想要从1000万访客的表中找到一个特定建筑物中的所有访客。如果访问者表上没有任何索引,则数据库需要将每个访问者与所选建筑物进行比较。这将是昂贵的CPU(I / O不是一个真正的问题)。

  5. 最后,正确编写查询非常重要。在F(a,b)之类的运算符中,b用于搜索a,因此b应该是较小的集合。例如,查找此建筑物中的所有访客必须写为SDO_INSIDE(visitors, buildings)。反过来(在这个客户的建筑物中?)写为SDO_CONTAINS(buildings, visitors)