Thay thế cho MakeValid () cho dữ liệu không gian trong SQL Server 2016


13

Tôi có một bảng LINESTRINGdữ liệu địa lý rất lớn mà tôi đang chuyển từ Oracle sang SQL Server. Có một số đánh giá được thực hiện đối với dữ liệu này trong Oracle và chúng cũng sẽ cần được thực hiện đối với dữ liệu trong SQL Server.

Vấn đề: SQL Server có các yêu cầu chặt chẽ hơn về tính hợp lệ LINESTRINGso với Oracle; "Ví dụ LineString không thể tự chồng lên nhau trong một khoảng hai hoặc nhiều điểm liên tiếp". Nó chỉ xảy ra khi một tỷ lệ phần trăm của chúng tôi LINESTRINGkhông đáp ứng tiêu chí đó, có nghĩa là các chức năng chúng tôi cần để đánh giá dữ liệu không thành công. Tôi cần điều chỉnh dữ liệu để có thể xác thực thành công trong SQL Server.

Ví dụ:

Xác thực một cách rất đơn giản LINESTRINGmà nhân đôi chính nó:

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).

Thực hiện MakeValidchức năng chống lại nó:

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)

Thật không may, MakeValidchức năng thay đổi thứ tự của các điểm và loại bỏ chiều thứ ba, khiến chúng không thể sử dụng được. Tôi đang tìm kiếm một cách tiếp cận khác để giải quyết vấn đề này mà không sắp xếp lại hoặc loại bỏ chiều thứ 3.

Có ý kiến ​​gì không?

Dữ liệu thực tế của tôi chứa hàng trăm / ngàn điểm.

Câu trả lời:


12

Hãy để tôi cảnh báo rằng lần đầu tiên tôi chơi với dữ liệu không gian trong máy chủ SQL (vì vậy có lẽ bạn đã biết phần đầu tiên này), nhưng tôi phải mất một thời gian để nhận ra rằng SQL Server không coi tọa độ (xyz) là đúng Các giá trị 3D, nó được coi là (kinh độ vĩ độ) với giá trị "độ cao" tùy chọn, Z, bị bỏ qua bởi xác thực và các chức năng khác.

Chứng cớ:

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).

Ví dụ đầu tiên của bạn có vẻ kỳ lạ đối với tôi bởi vì (0 0 1), (0 1 2) và (0 -1 3) không được cộng tác trong không gian 3D (Tôi là một nhà toán học, vì vậy tôi đã suy nghĩ theo các thuật ngữ đó). IsValidDetailed(và MakeValid) đang coi những điều này là (0 0), (0 1) và (0, -1), tạo ra một đường chồng chéo.

Để chứng minh, chỉ cần trao đổi X và Z, và nó xác nhận:

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

24400: Valid

Điều này thực sự có ý nghĩa nếu chúng ta nghĩ về những điều này như các khu vực hoặc đường dẫn được vạch trên bề mặt địa cầu của chúng ta, thay vì các điểm trong không gian 3D toán học.


Phần thứ hai của vấn đề của bạn là các giá trị điểm Z (và M) không được SQL bảo toàn thông qua các hàm :

Các tọa độ Z không được sử dụng trong bất kỳ tính toán nào được thực hiện bởi thư viện và không được thực hiện thông qua bất kỳ tính toán nào của thư viện.

Thật không may bởi thiết kế. Điều này đã được báo cáo cho Microsoft vào năm 2010 , yêu cầu đã bị đóng là "Không sửa". Bạn có thể thấy rằng cuộc thảo luận có liên quan, lý luận của họ là:

Việc gán Z và M không rõ ràng, vì MakeValid phân tách và hợp nhất các yếu tố không gian. Điểm thường được tạo, loại bỏ hoặc di chuyển trong quá trình này. Do đó MakeValid (và các công trình khác) giảm giá trị Z và M.

Ví dụ:

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()

Giá trị Z và M không rõ ràng cho điểm (0 0). Chúng tôi quyết định bỏ hoàn toàn Z và M thay vì trả về kết quả đúng một nửa.

Bạn có thể chỉ định chúng sau này nếu bạn biết chính xác làm thế nào. Ngoài ra, bạn có thể thay đổi cách bạn tạo các đối tượng của mình thành hợp lệ trên đầu vào hoặc giữ hai phiên bản của các đối tượng của bạn, một phiên bản hợp lệ và một phiên bản khác bảo tồn tất cả các tính năng của bạn. Nếu bạn giải thích kịch bản của mình tốt hơn và những gì bạn làm với các đối tượng, có lẽ chúng tôi có thể cung cấp cho bạn cách giải quyết khác.

Ngoài ra, như bạn đã thấy, MakeValidcũng có thể thực hiện những việc không mong muốn khác , như thay đổi thứ tự các điểm, trả về ĐA SỐ, hoặc thậm chí trả về một đối tượng ĐIỂM.


Một ý tưởng tôi đã gặp là để lưu trữ chúng dưới dạng đối tượng MULTIPOINT :

Vấn đề là khi linestring của bạn thực sự rút lại một đoạn đường liên tục giữa hai điểm trước đó được vạch ra bởi đường. Theo định nghĩa, nếu bạn truy xuất các điểm hiện có, thì linestring không còn là hình học đơn giản nhất có thể đại diện cho điểm này và MakeValid () sẽ cung cấp cho bạn đa tuyến thay thế (và mất các giá trị Z / M của bạn).

Thật không may, nếu bạn đang làm việc với dữ liệu GPS hoặc tương tự thì có khả năng là bạn có thể đã lưu lại đường dẫn của mình tại một số điểm trên tuyến đường, do đó, các chuỗi liên kết không phải lúc nào cũng hữu ích trong các tình huống này :( Có thể cho rằng, dữ liệu đó nên được lưu trữ như một đa điểm dù sao vì dữ liệu của bạn đại diện cho vị trí riêng biệt của một đối tượng được lấy mẫu tại các điểm thông thường theo thời gian.

Trong trường hợp của bạn, nó xác nhận tốt:

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

24400: Valid

Nếu bạn thực sự cần duy trì những điều này dưới dạng LINESTRING, thì bạn sẽ phải viết phiên bản của riêng mình MakeValidmột chút điều chỉnh một số điểm X hoặc Y theo một giá trị nhỏ, trong khi vẫn bảo toàn Z (và không làm những việc điên rồ khác như chuyển đổi nó thành các loại đối tượng khác).

Tôi vẫn đang làm việc với một số mã, nhưng hãy xem một số ý tưởng bắt đầu ở đây:


EDIT Ok, một vài điều tôi tìm thấy trong khi thử nghiệm:

  • Nếu đối tượng hình học không hợp lệ, bạn không thể làm gì nhiều với nó. Bạn không thể đọc STGeometryType, bạn không thể lấy STNumPointshoặc sử dụng STPointNđể lặp qua chúng. Nếu bạn không thể sử dụngMakeValid , về cơ bản, bạn bị mắc kẹt với hoạt động trên biểu diễn văn bản của đối tượng địa lý.
  • Việc sử dụng STAsText()sẽ trả về đại diện văn bản của ngay cả một đối tượng không hợp lệ, nhưng không trả về giá trị Z hoặc M. Thay vào đó, chúng tôi muốn AsTextZM()hoặc ToString().
  • Bạn không thể tạo một hàm mà các lệnh gọi RAND()(các hàm cần phải có tính xác định), vì vậy tôi chỉ làm cho nó chuyển động bằng các giá trị lớn hơn và lớn hơn liên tiếp. Tôi thực sự không biết độ chính xác của dữ liệu của bạn là bao nhiêu hoặc mức độ chịu đựng của những thay đổi nhỏ, vì vậy hãy sử dụng hoặc sửa đổi chức năng này theo ý của bạn.

Tôi không biết nếu có những đầu vào khả dĩ sẽ khiến vòng lặp này tiếp diễn mãi mãi. Bạn đã được cảnh báo.

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

Thay vì phân tích chuỗi, tôi đã chọn tạo một MultiPointđối tượng mới bằng cách sử dụng cùng một tập hợp các điểm, vì vậy tôi có thể lặp lại qua chúng và huých chúng, sau đó lắp lại LineString mới. Đây là một số mã để kiểm tra nó, 3 trong số các giá trị này (bao gồm cả mẫu của bạn) bắt đầu không hợp lệ nhưng đã được sửa:

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

Câu trả lời tuyệt vời, cảm ơn BradC. Tôi không bao gồm điều này trong câu hỏi của mình, nhưng dữ liệu thực tế của tôi chứa hàng trăm / nghìn điểm, vì vậy "@tinynum * 2" không bền vững. Thay vào đó, tôi đã bỏ hoàn toàn "@tinynum" và sử dụng một số ngẫu nhiên trong khoảng từ 0 đến 0,000000003. Tôi đã chạy dữ liệu này dựa trên dữ liệu và cho đến nay, trong số 22 nghìn đã hoàn thành, tất cả đều được xác thực là LINESTRING.
CaptainSlock

3

Đây là chức năng của BradC đượcFixBadLineString điều chỉnh để sử dụng một số ngẫu nhiên trong khoảng từ 0 đến 0,000000003, do đó cho phép nó mở rộng quy mô LINESTRINGsvới số lượng điểm lớn và cũng giảm thiểu thay đổi tọa độ:

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
Có vẻ rất tốt, tôi không biết về PWDENCRYPTchức năng này. Bạn có thể đã bỏ qua ABSvà nó sẽ trả về số dương hoặc số âm, vì vậy chúng tôi không phải lúc nào cũng thêm vào X và trừ đi từ Y.
BradC
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.