मुझे बताएं कि मैं पहली बार SQL सर्वर में स्थानिक डेटा के साथ खेल रहा हूं (इसलिए आप शायद पहले से ही इस पहले भाग को जानते हैं), लेकिन मुझे यह पता लगाने में थोड़ा समय लगा कि SQL सर्वर (xyz) को सही नहीं मान रहा है 3 डी मान, यह उन्हें एक वैकल्पिक "उन्नयन" मान, जेड के साथ (अक्षांश देशांतर) के रूप में मान रहा है, जिसे मान्यता और अन्य कार्यों द्वारा अनदेखा किया जाता है।
साक्ष्य:
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) 3 डी स्पेस में नहीं मिलते हैं (मैं एक गणितज्ञ हूं, इसलिए मैं उन शब्दों में सोच रहा था)। IsValidDetailed
(और MakeValid
) इन्हें (0 0), (0 1), और (0, -1) के रूप में मान रहा है, जो ओवरलैपिंग लाइन बनाता है।
इसे साबित करने के लिए, बस एक्स और जेड स्वैप करें, और यह मान्य करता है:
select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
.IsValidDetailed()
24400: Valid
यह वास्तव में समझ में आता है अगर हम गणितीय 3 डी अंतरिक्ष में बिंदुओं के बजाय, हमारे ग्लोब की सतह पर इन क्षेत्रों या रास्तों के बारे में सोचते हैं।
आपके समस्या का दूसरा भाग यह है कि Z (और M) बिंदु मान फ़ंक्शन के माध्यम से SQL द्वारा संरक्षित नहीं हैं :
पुस्तकालय द्वारा किए गए किसी भी गणना में जेड-निर्देशांक का उपयोग नहीं किया जाता है और किसी भी पुस्तकालय गणना के माध्यम से नहीं किया जाता है।
यह दुर्भाग्य से डिजाइन के अनुसार है। यह 2010 में Microsoft को सूचित किया गया था , अनुरोध "W't Fix" के रूप में बंद किया गया था। आपको वह चर्चा प्रासंगिक लग सकती है, उनका तर्क है:
Z और M को असाइन करना अस्पष्ट है, क्योंकि MakeValid विभाजन और स्थानिक तत्वों को मिलाता है। इस प्रक्रिया के दौरान अंक अक्सर निर्मित, हटाए या स्थानांतरित किए जाते हैं। इसलिए 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 को आधा-सही परिणाम देने के बजाय पूरी तरह से छोड़ने का फैसला किया।
आप उन्हें बाद में असाइन कर सकते हैं यदि आप वास्तव में जानते हैं कि कैसे। वैकल्पिक रूप से आप इनपुट पर मान्य होने के लिए अपनी वस्तुओं को बनाने के तरीके को बदल सकते हैं, या अपनी वस्तुओं के दो संस्करण रख सकते हैं, एक जो वैध है और दूसरा वह जो आपकी सभी विशेषताओं को सुरक्षित रखता है। यदि आप अपने परिदृश्य को बेहतर ढंग से समझाते हैं और आप वस्तुओं के साथ क्या करते हैं, तो शायद हम आपको अतिरिक्त वर्कआर्डर दे सकें।
इसके अलावा, जैसा कि आप पहले ही देख चुके हैं, MakeValid
आप अन्य अप्रत्याशित चीजें भी कर सकते हैं , जैसे अंकों के क्रम को बदलना, एक MULTILINESTRING लौटना, या यहां तक कि एक POINT ऑब्जेक्ट वापस करना।
एक विचार जो मुझे आया था, वह था कि उन्हें एक MULTIPOINT वस्तु के स्थान पर संग्रहीत करना था :
समस्या यह है कि जब आपके लाइनस्ट्रिंग वास्तव में दो बिंदुओं के बीच लाइन के एक निरंतर खंड को पीछे हटाते हैं जो पहले लाइन द्वारा पता लगाया गया था। परिभाषा के अनुसार, यदि आप मौजूदा बिंदुओं को पुन: प्राप्त कर रहे हैं, तो लिनस्ट्रिंग अब सबसे सरल ज्यामिति नहीं है जो इस बिंदु का प्रतिनिधित्व कर सकता है, और MakeValid () आपको इसके बजाय एक मल्टीलाइनरिंग देगा (और अपना Z / M मान खो देगा)।
दुर्भाग्य से, यदि आप GPS डेटा या इसी तरह के साथ काम कर रहे हैं, तो यह काफी संभावना है कि आपने मार्ग में किसी बिंदु पर अपना रास्ता निकाल लिया होगा, इसलिए linestrings हमेशा इन परिदृश्यों में उपयोगी नहीं होते हैं :( संभवतः, ऐसे डेटा को संग्रहीत किया जाना चाहिए किसी भी समय एक मल्टीपॉइंट, क्योंकि आपका डेटा समय में नियमित बिंदुओं पर नमूना किए गए ऑब्जेक्ट के असतत स्थान का प्रतिनिधित्व करता है।
आपके मामले में यह ठीक है:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
यदि आपको LINESTRINGS के रूप में इन्हें बनाए रखने की आवश्यकता है, तो आपको अपने स्वयं के संस्करण को लिखना होगा, MakeValid
थोड़ा स्रोत द्वारा कुछ X या Y बिंदुओं को थोड़ा समायोजित करता है, जबकि Z को संरक्षित करते हुए (और अन्य पागल चीजें नहीं करता है) इसे अन्य वस्तु प्रकारों में परिवर्तित करें)।
मैं अभी भी कुछ कोड पर काम कर रहा हूं, लेकिन यहां कुछ शुरुआती विचारों पर एक नजर डालते हैं:
EDIT Ok, परीक्षण के दौरान मुझे मिली कुछ चीजें:
- यदि ज्यामिति ऑब्जेक्ट अमान्य है, तो आप इसके साथ बहुत कुछ नहीं कर सकते। आप इसे नहीं पढ़ सकते हैं
STGeometryType
, आप इनके माध्यम से पुनरावृत्ति करने STNumPoints
या उपयोग STPointN
करने के लिए प्राप्त नहीं कर सकते हैं। यदि आप उपयोग नहीं कर सकते हैं MakeValid
, तो आप मूल रूप से भौगोलिक वस्तु के पाठ प्रतिनिधित्व पर काम कर रहे हैं।
- उपयोग करने
STAsText()
से अमान्य ऑब्जेक्ट का पाठ प्रतिनिधित्व वापस आ जाएगा, लेकिन जेड या एम मान वापस नहीं करता है। इसके बजाय, हम चाहते हैं 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
ही बिंदु के सेट का उपयोग करके एक नई वस्तु बनाने के लिए चुना , इसलिए मैं उनके माध्यम से पुनरावृति कर सकता हूं और उन्हें कुल्ला कर सकता हूं, फिर एक नया लाइनस्ट्रीम इकट्ठा कर सकता हूं। इसका परीक्षण करने के लिए यहां कुछ कोड दिए गए हैं, इनमें से 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