Biarkan saya perhatikan bahwa saya bermain dengan data spasial di SQL server untuk pertama kalinya (jadi Anda mungkin sudah tahu bagian pertama ini), tetapi butuh beberapa saat untuk mengetahui bahwa SQL Server tidak memperlakukan koordinat (xyz) sebagai benar Nilai 3D, itu memperlakukan mereka sebagai (lintang bujur) dengan nilai "ketinggian" opsional, Z, yang diabaikan oleh validasi dan fungsi lainnya.
Bukti:
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).
Contoh pertama Anda terasa aneh bagi saya karena (0 0 1), (0 1 2), dan (0 -1 3) tidak collinear dalam ruang 3D (saya seorang ahli matematika, jadi saya berpikir dalam istilah itu). IsValidDetailed
(dan MakeValid
) memperlakukan ini sebagai (0 0), (0 1), dan (0, -1), yang membuat garis tumpang tindih.
Untuk membuktikannya, cukup tukar X dan Z, dan validasi:
select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
.IsValidDetailed()
24400: Valid
Ini sebenarnya masuk akal jika kita menganggap ini sebagai wilayah atau jalur yang dilacak di permukaan dunia kita, alih-alih titik dalam ruang 3D matematika.
Bagian kedua dari masalah Anda adalah bahwa nilai-nilai titik Z (dan M) tidak dipertahankan oleh fungsi-fungsi SQL :
Koordinat Z tidak digunakan dalam perhitungan apa pun yang dibuat oleh perpustakaan dan tidak dilakukan melalui perhitungan perpustakaan apa pun.
Sayangnya ini karena desain. Ini dilaporkan ke Microsoft pada 2010 , permintaan ditutup sebagai "Tidak akan Perbaiki". Anda mungkin menemukan diskusi itu relevan, alasannya adalah:
Menetapkan Z dan M bersifat mendua, karena MakeValid memecah dan menggabungkan elemen spasial. Poin sering dibuat, dihapus atau dipindahkan selama proses ini. Oleh karena itu MakeValid (dan konstruksi lainnya) menjatuhkan nilai Z dan M.
Sebagai contoh:
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()
Nilai Z dan M adalah mendua untuk poin (0 0). Kami memutuskan untuk menjatuhkan Z dan M sepenuhnya daripada mengembalikan hasil yang setengah benar.
Anda dapat menetapkannya nanti jika tahu persis bagaimana caranya. Sebagai alternatif, Anda dapat mengubah cara Anda membuat objek menjadi valid pada input, atau menyimpan dua versi objek Anda, satu yang valid dan satu lagi yang mempertahankan semua fitur Anda. Jika Anda menjelaskan skenario Anda dengan lebih baik dan apa yang Anda lakukan dengan objek mungkin kami dapat memberikan Anda solusi tambahan.
Selain itu, seperti yang sudah Anda lihat, MakeValid
juga dapat melakukan hal-hal tak terduga lainnya , seperti mengubah urutan poin, mengembalikan MULTILINESTRING, atau bahkan mengembalikan objek TITIK.
Satu ide yang saya temui adalah untuk menyimpannya sebagai objek MULTIPOINT :
Masalahnya adalah ketika linestring Anda benar-benar menelusuri bagian garis kontinu antara dua titik yang sebelumnya dilacak oleh garis. Menurut definisi, jika Anda menelusuri kembali titik yang ada, maka linestring tidak lagi menjadi geometri paling sederhana yang dapat mewakili titik ini, dan MakeValid () akan memberi Anda multilinestring sebagai gantinya (dan kehilangan nilai Z / M Anda).
Sayangnya, jika Anda bekerja dengan data GPS atau sejenisnya maka sangat mungkin bahwa Anda mungkin telah menelusuri kembali jalur Anda di beberapa titik dalam rute, jadi linestrings tidak selalu berguna dalam skenario ini :( Bisa dibilang, data tersebut harus disimpan sebagai bagaimanapun, multipoint karena data Anda mewakili lokasi diskrit suatu objek yang diambil sampelnya pada titik reguler dalam waktu.
Dalam kasus Anda itu memvalidasi dengan baik:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
Jika Anda benar-benar perlu mempertahankan ini sebagai LINESTRING, maka Anda harus menulis versi Anda sendiri MakeValid
yang sedikit menyesuaikan beberapa sumber X atau poin Y dengan nilai kecil, sambil tetap mempertahankan Z (dan tidak melakukan hal-hal gila lainnya seperti mengubahnya menjadi tipe objek lain).
Saya masih mengerjakan beberapa kode, tetapi coba lihat beberapa ide awal di sini:
EDIT Ok, beberapa hal yang saya temukan saat menguji:
- Jika objek geometri tidak valid, Anda tidak bisa berbuat banyak dengannya. Anda tidak dapat membaca
STGeometryType
, Anda tidak bisa mendapatkan STNumPoints
atau menggunakannya STPointN
untuk mengulanginya. Jika Anda tidak dapat menggunakan MakeValid
, Anda pada dasarnya terjebak dengan operasi pada representasi teks dari objek geografis.
- Menggunakan
STAsText()
akan mengembalikan representasi teks bahkan objek yang tidak valid, tetapi tidak mengembalikan nilai Z atau M. Sebaliknya, kami ingin AsTextZM()
atau ToString()
.
- Anda tidak dapat membuat fungsi yang memanggil
RAND()
(fungsi harus bersifat deterministik), jadi saya hanya membuatnya mendorong dengan nilai yang lebih besar dan lebih besar secara berturut-turut. Saya benar-benar tidak tahu apa presisi data Anda, atau seberapa tolerannya perubahan kecil, jadi gunakan atau modifikasi fungsi ini atas kebijakan Anda sendiri.
Saya tidak tahu apakah ada input yang mungkin menyebabkan loop ini berlangsung selamanya. Anda telah diperingatkan.
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
Alih-alih mengurai string, saya memilih untuk membuat MultiPoint
objek baru menggunakan set poin yang sama, jadi saya bisa beralih melalui mereka dan mendorong mereka, lalu memasang kembali LineString baru. Berikut beberapa kode untuk mengujinya, 3 dari nilai-nilai ini (termasuk sampel Anda) mulai tidak valid tetapi sudah diperbaiki:
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