SQL sunucusunda ilk kez uzamsal verilerle oynadığımı söyleyeyim (muhtemelen bu ilk kısmı zaten biliyorsunuz), ancak SQL Server'ın (xyz) koordinatlarını doğru olarak işlemediğini anlamak biraz zaman aldı 3B değerleri, doğrulama ve diğer işlevler tarafından göz ardı edilen isteğe bağlı bir "yükseklik" değeri olan Z'ye (enlem boylamı) muamele eder.
Kanıt:
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).
İlk örneğiniz bana garip geldi çünkü (0 0 1), (0 1 2) ve (0 -1 3) 3D uzayda eş zamanlı değiller (ben bir matematikçiyim, bu yüzden bu terimleri düşünüyordum). IsValidDetailed
(ve MakeValid
) bunları üst üste binen bir çizgi yapan (0 0), (0 1) ve (0, -1) olarak muamele etmektedir.
Bunu kanıtlamak için X ve Z'yi değiştirin ve geçerli kılar:
select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
.IsValidDetailed()
24400: Valid
Bunları, matematiksel 3B uzaydaki noktalar yerine, dünyamızın yüzeyinde izlenen bölgeler veya yollar olarak düşünürsek, bu gerçekten mantıklı.
Sorununuzun ikinci kısmı, Z (ve M) nokta değerlerinin işlevler aracılığıyla SQL tarafından korunmamasıdır :
Z-koordinatları, kütüphane tarafından yapılan hesaplamalarda kullanılmaz ve kütüphane hesaplamaları ile taşınmaz.
Bu maalesef tasarımdan kaynaklanıyor. Bu, 2010 yılında Microsoft'a bildirildi , istek "Düzeltilmeyecek" olarak kapatıldı. Bu tartışmayı alakalı bulabilirsiniz, gerekçeleri:
Z ve M atamak belirsizdir, çünkü MakeValid mekansal unsurları böler ve birleştirir. Puanlar genellikle bu işlem sırasında oluşturulur, kaldırılır veya taşınır. Bu nedenle MakeValid (ve diğer yapılar) Z ve M değerlerini düşürür.
Örneğin:
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 ve M değerleri nokta (0 0) için belirsizdir. Yarı doğru sonucu döndürmek yerine Z ve M'yi tamamen bırakmaya karar verdik.
Nasıl yapılacağını tam olarak biliyorsanız bunları daha sonra atayabilirsiniz. Alternatif olarak, nesnelerinizi girişte geçerli olacak şekilde oluşturma şeklinizi değiştirebilir veya nesnelerinizin biri geçerli diğeri de tüm özelliklerinizi koruyan iki sürümünü saklayabilirsiniz. Senaryoyu daha iyi açıklar ve nesnelerle ne yaparsanız, belki size ek çözümler verebiliriz.
Ayrıca, daha önce gördüğünüz gibi, noktaların sırasını değiştirmek, bir MULTILINESTRING döndürmek ve hatta bir POINT nesnesi döndürmek gibi MakeValid
beklenmedik şeyler de yapabilirsiniz .
Karşılaştığım bir fikir onları MULTIPOINT nesnesi olarak saklamaktı :
Sorun, linestring'inizin daha önce çizgi tarafından izlenen iki nokta arasındaki sürekli bir çizgi bölümünü geri çekmesidir. Tanım olarak, mevcut noktaları geri çekiyorsanız, linestring artık bu puan kümesini temsil edebilecek en basit geometri değildir ve MakeValid () size bunun yerine çok satırlı bir görünüm verecektir (ve Z / M değerlerinizi kaybedecektir).
Ne yazık ki, GPS verileri veya benzeri ile çalışıyorsanız, rotanızın bir noktasında yolunuzu geri çekmiş olmanız muhtemeldir, bu nedenle linestring'ler bu senaryolarda her zaman yararlı değildir :( Muhtemelen, bu tür veriler verileriniz düzenli noktalarda örneklenen bir nesnenin ayrı konumunu temsil ettiğinden, yine de çok noktalı.
Sizin durumunuzda gayet iyi geçer:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
Bunları kesinlikle LINESTRINGS olarak tutmanız gerekiyorsa, Z'yi MakeValid
korurken (ve diğer çılgın şeyleri yapmazken) kaynak X veya Y noktalarının bazılarını küçük bir değere hafifçe ayarlayan kendi versiyonunuzu yazmanız gerekecektir. diğer nesne türlerine dönüştür).
Hala bazı kodlar üzerinde çalışıyorum, ancak burada bazı başlangıç fikirlerine bir göz atın:
EDIT Tamam, test ederken bulduğum birkaç şey:
- Geometri nesnesi geçersizse, onunla fazla bir şey yapamazsınız. Sen okuyamaz
STGeometryType
, sen alamayan STNumPoints
veya kullanımını STPointN
onlara yineleme yapmak için. Eğer kullanamıyorsanız MakeValid
, coğrafi nesnenin metin gösterim uzerinde çalışan temelde sıkışmış konum.
- Kullanılması
STAsText()
, geçersiz bir nesnenin bile metin temsilini döndürür, ancak Z veya M değerlerini döndürmez. Bunun yerine, bizim istediğimiz AsTextZM()
ya ToString()
.
- Bir oluşturamazsınız işlevi çağrıları o
RAND()
ben sadece art arda daha büyük ve daha büyük değerlere göre dürtmek yapılan böylece, (fonksiyonları deterministik olması gerekir). Verilerinizin doğruluğu veya küçük değişikliklere ne kadar toleranslı olduğu hakkında hiçbir fikrim yok, bu nedenle bu işlevi kendi takdirinize bağlı olarak kullanın veya değiştirin.
Bu döngü sonsuza kadar devam edecek olası girişler varsa hiçbir fikrim yok. Uyarıldın.
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
Dizeyi ayrıştırmak yerine MultiPoint
, aynı nokta kümesini kullanarak yeni bir nesne oluşturmayı seçtim; Test etmek için bazı kodlar var, bu değerlerden 3 tanesi (örneğiniz dahil) geçersiz başlıyor, ancak düzeltildi:
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