SQL Server 2016에서 공간 데이터를위한 MakeValid () 대신 사용


13

LINESTRINGOracle에서 SQL Server로 옮기는 매우 큰 지리 데이터 테이블이 있습니다 . Oracle에서는이 데이터에 대해 여러 가지 평가가 수행되며 SQL Server의 데이터에 대해서도 평가가 수행되어야합니다.

문제 : SQL Server는 LINESTRINGOracle보다 유효한 요구 사항이 더 엄격합니다 . "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함수는 점의 순서를 변경하고 3 차원을 제거하여 사용할 수 없게합니다. 3 차원을 재정렬하거나 제거하지 않고이 문제를 해결하는 다른 접근법을 찾고 있습니다.

어떤 아이디어?

내 실제 데이터에는 수백 / 수천 점이 있습니다.

답변:


12

SQL Server에서 처음으로 공간 데이터를 가지고 놀고 있다는 것을 명심하십시오 (그래서 이미이 첫 번째 부분을 알고있을 것입니다). 그러나 SQL Server가 (xyz) 좌표를 true로 취급하지 않는다는 것을 알아내는 데 시간이 걸렸습니다. 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(과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 공간의 점이 아니라 지구 표면에서 추적 된 영역 또는 경로로 생각하면 실제로 의미가 있습니다.


문제의 두 번째 부분은 Z (및 M) 포인트 값이 함수를 통해 SQL에 의해 보존되지 않는다는 것입니다 .

Z 좌표는 라이브러리에서 수행 한 계산에 사용되지 않으며 라이브러리 계산을 통해 전달되지 않습니다.

불행히도 이것은 의도적으로 설계된 것입니다. 이것은 2010 년에 Microsoft에보고되었으며 요청은 "수정되지 않음"으로 마감되었습니다. 토론이 적절하다는 것을 알 수 있으며 그 이유는 다음과 같습니다.

MakeValid가 공간 요소를 분할하고 병합하기 때문에 Z 및 M 할당은 모호합니다. 이 과정에서 종종 포인트가 생성, 제거 또는 이동됩니다. 따라서 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을 완전히 떨어 뜨리기로 결정했습니다.

방법을 정확히 알고 있다면 나중에 할당 할 수 있습니다. 또는 입력시 객체 생성 방식을 변경하거나 객체의 두 가지 버전 (유효한 버전과 모든 기능을 유지하는 버전)을 유지할 수 있습니다. 시나리오를 더 잘 설명하고 객체로 수행하는 작업을 설명하면 추가 해결 방법을 제공 할 수 있습니다.

또한 이미 본 것처럼 포인트 순서 변경, MULTILINESTRING 또는 POINT 객체 반환과 같은 MakeValid다른 예기치 않은 작업을 수행 할 수도 있습니다 .


내가 만난 한 가지 아이디어는 대신 MULTIPOINT 객체로 저장하는 것입니다 .

문제는 선 스트링이 실제로 이전에 선으로 추적 한 두 점 사이의 연속적인 선 섹션을 되돌릴 때입니다. 정의에 따라 기존 점을 다시 추적하는 경우 선 스트링은 더 이상이 포인트 세트를 나타낼 수있는 가장 단순한 형상이 아니며 MakeValid ()는 대신 여러 줄로 된 문자열을 제공하며 Z / M 값을 잃게됩니다.

불행히도 GPS 데이터 또는 이와 유사한 작업을 수행하는 경우 경로의 어느 시점에서 경로를 다시 추적했을 가능성이 높으므로 이러한 시나리오에서 선 스트링이 항상 유용하지는 않습니다. 데이터는 규칙적인 시점에 샘플링 된 객체의 개별 위치를 나타내므로 다 지점입니다.

귀하의 경우에는 잘 검증됩니다.

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

24400: Valid

이것을 LINESTRINGS로 유지 해야하는 경우 MakeValidZ를 유지하면서 소스 X 또는 Y 포인트 중 일부를 약간의 값으로 약간 조정하는 자체 버전을 작성해야 합니다. 다른 객체 유형으로 변환하십시오).

아직 코드를 작성 중이지만 여기에서 시작 아이디어를 살펴보십시오.


편집 테스트 중에 발견 한 몇 가지 사항은 다음과 같습니다.

  • 지오메트리 객체가 유효하지 않으면 그 기능을 많이 사용할 수 없습니다. 을 읽을 수 없으며를 읽 거나 반복하여 사용할 STGeometryType수 없습니다 . 를 사용할 수없는 경우 기본적으로 지리적 객체의 텍스트 표현을 조작하는 데 문제가 있습니다.STNumPointsSTPointNMakeValid
  • 를 사용 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을 다시 어셈블 할 수있었습니다. 다음은 코드를 테스트하는 코드입니다.이 값 중 3 개 (샘플 포함)는 유효하지 않지만 수정되었습니다.

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.