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, MakeValid
cũ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 MakeValid
mộ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 STNumPoints
hoặ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