SQL Server 2016中用于空间数据的MakeValid()的替代方法


13

我有一张非常大的地理LINESTRING数据表,我正在从Oracle迁移到SQL Server。在Oracle中有许多针对此数据执行的评估,并且也需要针对SQL Server中的数据执行评估。

问题是:SQL Server对有效的要求LINESTRING比对Oracle的要求严格;“ LineString实例不能在两个或多个连续点的间隔内重叠”。 碰巧的是,我们LINESTRING的某个百分比不符合该标准,这意味着我们需要评估数据的功能会失败。我需要调整数据,以便可以在SQL Server中成功对其进行验证。

例如:

验证一个非常简单的方法LINESTRING,使其自身加倍:

select geography::STGeomFromText(
    'LINESTRING (0 0 1, 0 1 2, 0 -1 3)',4326).IsValidDetailed()
24413: Not valid because of two overlapping edges in curve (1).

MakeValid针对它执行功能:

select geography::STGeomFromText(
    'LINESTRING (0 0 1, 0 1 2, 0 -1 3)',4326).MakeValid().STAsText()
LINESTRING (0 -0.999999999999867, 0 0, 0 0.999999999999867)

不幸的是,该MakeValid函数更改了点的顺序并删除了第三维,这使它对我们不可用。我正在寻找另一种方法来解决此问题,而无需重新排序或删除第三维。

有任何想法吗?

我的实际数据包含成百上千个点。

Answers:


12

让我告诫我是第一次使用SQL Server中的空间数据(所以您可能已经知道了第一部分),但是花了我一段时间才弄清楚SQL Server并未将(xyz)坐标视为真实3D值,将其视为(纬度经度),带有可选的“高程”值Z,该值会被验证和其他功能忽略。

证据:

select geography::STGeomFromText('LINESTRING (0 0 1, 0 1 2, 0 -1 3)', 4326)
    .IsValidDetailed()

24413: Not valid because of two overlapping edges in curve (1).

您的第一个示例对我来说很奇怪,因为(0 0 1),(0 1 2)和(0 -1 3)在3D空间中不是共线的(我是数学家,所以我在考虑这些术语)。IsValidDetailed(and MakeValid)将它们视为(0 0),(0 1)和(0,-1),它们确实形成一条重叠线。

为了证明这一点,只需交换X和Z,它就会验证:

select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
    .IsValidDetailed()

24400: Valid

如果我们将这些视为地球表面上的区域或路径,而不是数学3D空间中的点,则这实际上是有道理的。


问题的第二部分是SQL 无法通过函数保留 Z(和M)点值

库进行的任何计算都不会使用Z坐标,库计算也不会进行Z坐标处理。

不幸的是,这是设计使然。这是在2010年向Microsoft报告的,该请求被关闭为“ Wo n't Fix”。您可能会发现相关的讨论很重要,其理由是:

分配Z和M是模棱两可的,因为MakeValid会分割并合并空间元素。在此过程中通常会创建,删除或移动点。因此,MakeValid(和其他构造)会降低Z和M值。

例如:

DECLARE @a geometry = geometry::Parse('POINT(0 0 2 2)');
DECLARE @b geometry = geometry::Parse('POINT(0 0 1 1)');
SELECT @a.STUnion(@b).AsTextZM()

Z和M的值对于点(0 0)是不明确的。我们决定完全删除Z和M,而不返回半正确的结果。

如果您确切知道如何,可以稍后再分配它们。或者,您可以更改生成对象的方式以使其在输入时有效,或者保留对象的两个版本,一个版本有效,另一个版本保留所有功能。如果您更好地说明您的情况以及您对这些对象的处理方式,也许我们可以为您提供其他解决方法。

此外,您已经知道,MakeValid还可以执行其他意外操作,例如更改点的顺序,返回MULTILINESTRING甚至返回POINT对象。


我遇到的一个想法是将它们存储为MULTIPOINT对象

问题是当您的线串实际上在先前由该线跟踪的两点之间回线的连续部分时。根据定义,如果要回溯现有点,则线串不再是可以表示该点集的最简单的几何图形,MakeValid()会改为提供多线串(并且会丢失Z / M值)。

不幸的是,如果您正在使用GPS数据或类似数据,则很可能已经在路线的某个点上追溯了路径,因此线串在这些情况下并不总是那么有用:(可以说,此类数据应存储为由于您的数据代表在常规时间点采样的对象的离散位置,因此无论如何都是多点。

在您的情况下,它可以很好地验证:

select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
    .IsValidDetailed()

24400: Valid

如果您绝对需要将其保持为LINESTRINGS,则必须编写自己的版本,MakeValid以一些微小的值稍微调整一些源X或Y点,同时仍然保留Z(并且不会做其他疯狂的事情,例如将其转换为其他对象类型)。

我仍在编写一些代码,但请在此处了解一些入门思路:


编辑好,我在测试时发现了一些东西:

  • 如果几何对象无效,则不能做太多事情。您无法阅读STGeometryType,也无法获取STNumPoints或使用STPointN来遍历它们。如果您不能使用MakeValid,那么基本上就只能对地理对象的文本表示进行操作。
  • 使用STAsText()将返回即使无效对象的文本表示形式,但不返回Z或M值。相反,我们想要AsTextZM()ToString()
  • 您无法创建要调用的函数RAND()(函数必须是确定性的),因此我只是通过逐渐增大的值来使其微调。我真的不知道您的数据的精确度是多少,或微小变化对数据的容忍度如何,因此请自行决定使用或修改此功能。

我不知道是否有可能导致此循环永远持续下去的输入。你被警告了。

CREATE FUNCTION dbo.FixBadLineString (@input geography) RETURNS geography
AS BEGIN
DECLARE @output geography

IF @input.STIsValid() = 1   --send valid objects back as-is
  SET @output = @input;
ELSE IF LEFT(@input.IsValidDetailed(),6) = '24413:'
--"Not valid because of two overlapping edges in curve"
BEGIN
  --make a new MultiPoint object from the LineString text
  DECLARE @mp geography = geography::STGeomFromText(
      REPLACE(@input.AsTextZM(), 'LINESTRING', 'MULTIPOINT'), 4326);
  DECLARE @newText nvarchar(max); --to build output
  DECLARE @point int 
  DECLARE @tinynum float = 0;

  SET @output = @input;
  --keep going until it validates
  WHILE @output.STIsValid() = 0
  BEGIN
    SET @newText = 'LINESTRING (';
    SET @point = 1
    SET @tinynum = @tinynum + 0.00000001

    --Loop through the points, add a bit and append to the new string
    WHILE @point <= @mp.STNumPoints()
    BEGIN
      SET @newText = @newText + convert(varchar(50),
               @mp.STPointN(@point).Long + @tinynum) + ' ';
      SET @newText = @newText + convert(varchar(50),
               @mp.STPointN(@point).Lat - @tinynum) + ' ';
      SET @newText = @newText + convert(varchar(50), 
               @mp.STPointN(@point).Z) + ', ';
      SET @tinynum = @tinynum * -2
      SET @point = @point + 1
    END

    --close the parens and make the new LineString object
    SET @newText = LEFT(@newText, LEN(@newText) - 1) + ')'
    SET @output = geography::STGeomFromText(@newText, 4326);
  END; --this will loop if it is still invalid
  RETURN @output;
END;
--Any other unhandled error, just send back NULL
ELSE SET @output = NULL;

RETURN @output;
END

我没有解析字符串,而是选择MultiPoint使用相同的点集创建一个新对象,因此我可以遍历它们并轻推它们,然后重新组装一个新的LineString。这是一些测试它的代码,其中三个值(包括您的样本)开始无效,但已修复:

declare @geostuff table (baddata geography)

INSERT INTO @geostuff (baddata)
          SELECT geography::STGeomFromText('LINESTRING (0 0 1, 0 1 2, 0 -1 3)',4326)
UNION ALL SELECT geography::STGeomFromText('LINESTRING (0 2 0, 0 1 0.5, 0 -1 -14)',4326)
UNION ALL SELECT geography::STGeomFromText('LINESTRING (0 0 4, 1 1 40, -1 -1 23)',4326)
UNION ALL SELECT geography::STGeomFromText('LINESTRING (1 1 9, 0 1 -.5, 0 -1 3)',4326)
UNION ALL SELECT geography::STGeomFromText('LINESTRING (6 6 26.5, 4 4 42, 12 12 86)',4326)
UNION ALL SELECT geography::STGeomFromText('LINESTRING (0 0 2, -4 4 -2, 4 -4 0)',4326)

SELECT baddata.AsTextZM() as before, baddata.IsValidDetailed() as pretest,
 dbo.FixBadLineString(baddata).AsTextZM() as after,
 dbo.FixBadLineString(baddata).IsValidDetailed() as posttest 
FROM @geostuff

很好的答案,谢谢BradC。我没有在问题中包含此信息,但是我的实际数据包含成百上千的点,因此“ @tinynum * 2”是不可持续的。相反,我完全放弃了“ @tinynum”,并使用了0到0.000000003之间的随机数。我一直在针对数据运行此操作,到目前为止,已完成22k,全部都已验证为LINESTRING。
CaptainSlock

3

这是BradC的FixBadLineString函数,已调整为使用0到0.000000003之间的随机数,从而允许它LINESTRINGs使用大量点进行缩放,并且还最小化了对坐标的更改:

CREATE FUNCTION dbo.FixBadLineString (@input geography) RETURNS geography
AS BEGIN
DECLARE @output geography

IF @input.STIsValid() = 1   --send valid objects back as-is
  SET @output = @input;
ELSE IF LEFT(@input.IsValidDetailed(),6) = '24413:'
--"Not valid because of two overlapping edges in curve"
BEGIN
  --make a new MultiPoint object from the LineString text
  DECLARE @mp geography = geography::STGeomFromText(
      REPLACE(@input.AsTextZM(), 'LINESTRING', 'MULTIPOINT'), 4326);
  DECLARE @newText nvarchar(max); --to build output
  DECLARE @point int 

  SET @output = @input;
  --keep going until it validates
  WHILE @output.STIsValid() = 0
  BEGIN
    SET @newText = 'LINESTRING (';
    SET @point = 1

    --Loop through the points, add/subtract a random value between 0 and 3E-9 and append to the new string
    WHILE @point <= @mp.STNumPoints()
    BEGIN
      SET @newText = @newText + convert(varchar(50),
        CAST(@mp.STPointN(@point).Long AS NUMERIC(18,9)) + 
          CAST(ABS(CHECKSUM(PWDENCRYPT(N''))) / 644245094100000000 AS NUMERIC(18,9))) + ' ';
      SET @newText = @newText + convert(varchar(50),
        CAST(@mp.STPointN(@point).Lat AS NUMERIC(18,9)) - 
          CAST(ABS(CHECKSUM(PWDENCRYPT(N''))) / 644245094100000000 AS NUMERIC(18,9))) + ' ';
      SET @newText = @newText + convert(varchar(50), 
               @mp.STPointN(@point).Z) + ', ';
      SET @point = @point + 1
    END

    --close the parens and make the new LineString object
    SET @newText = LEFT(@newText, LEN(@newText) - 1) + ')'
    SET @output = geography::STGeomFromText(@newText, 4326);
  END; --this will loop if it is still invalid
  RETURN @output;
END;
--Any other unhandled error, just send back NULL
ELSE SET @output = NULL;

RETURN @output;
END

1
看起来真的很好,我不知道该PWDENCRYPT功能。您可能会省略,ABS而它会返回正数或负数,因此我们并非总是将X加到Y上
。– BradC
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.