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로 유지 해야하는 경우 MakeValid
Z를 유지하면서 소스 X 또는 Y 포인트 중 일부를 약간의 값으로 약간 조정하는 자체 버전을 작성해야 합니다. 다른 객체 유형으로 변환하십시오).
아직 코드를 작성 중이지만 여기에서 시작 아이디어를 살펴보십시오.
편집 테스트 중에 발견 한 몇 가지 사항은 다음과 같습니다.
- 지오메트리 객체가 유효하지 않으면 그 기능을 많이 사용할 수 없습니다. 을 읽을 수 없으며를 읽 거나 반복하여 사용할
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을 다시 어셈블 할 수있었습니다. 다음은 코드를 테스트하는 코드입니다.이 값 중 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