SQL सर्वर 2016 में स्थानिक डेटा के लिए MakeValid () के लिए वैकल्पिक


13

मेरे पास भूगोल LINESTRINGडेटा की एक बहुत बड़ी तालिका है जो मैं Oracle से SQL सर्वर पर स्थानांतरित कर रहा हूं। ओरेकल में इस डेटा के खिलाफ कई मूल्यांकन किए गए हैं, और उन्हें SQL सर्वर में डेटा के खिलाफ भी निष्पादित करने की आवश्यकता होगी।

समस्या: LINESTRINGओरेकल की तुलना में एसक्यूएल सर्वर की वैध आवश्यकताएं हैं ; "LineString उदाहरण दो या अधिक लगातार बिंदुओं के अंतराल पर स्वयं को ओवरलैप नहीं कर सकता है"। यह सिर्फ इतना होता है कि हमारे प्रतिशत का प्रतिशत LINESTRINGउस मानदंड को पूरा नहीं करता है, जिसका अर्थ है कि जिन कार्यों के लिए हमें डेटा का मूल्यांकन करने की आवश्यकता है वे विफल हैं। मुझे डेटा को समायोजित करने की आवश्यकता है ताकि इसे SQL सर्वर में सफलतापूर्वक मान्य किया जा सके।

उदाहरण के लिए:

एक बहुत ही सरल है LINESTRINGकि खुद पर वापस दोगुना मान्य :

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

MakeValidइसके विरुद्ध कार्य निष्पादित करना:

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)

दुर्भाग्य से MakeValidफ़ंक्शन अंकों के क्रम को बदल देता है और तीसरे आयाम को हटा देता है, जो हमारे लिए अनुपयोगी बनाता है। मैं एक और दृष्टिकोण की तलाश कर रहा हूं जो कि 3 आयामों को पुनः व्यवस्थित या हटाए बिना इस समस्या को हल करता है।

कोई विचार?

मेरे वास्तविक डेटा में सैकड़ों / हजारों अंक हैं।

जवाबों:


12

मुझे बताएं कि मैं पहली बार 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

शानदार उत्तर, धन्यवाद ब्रैड। मैंने इसे अपने प्रश्न में शामिल नहीं किया, लेकिन मेरे वास्तविक डेटा में सैकड़ों / हजारों अंक हैं, इसलिए "@tinynum * 2" टिकाऊ नहीं था। इसके बजाय मैंने "@tinynum" को पूरी तरह से गिरा दिया और 0 और 0.000000003 के बीच एक यादृच्छिक संख्या का उपयोग किया। मैं इसे डेटा के खिलाफ चला रहा हूं और अब तक, 22k के पूरा होने पर, सभी को LINESTRINGs के रूप में मान्य किया गया था।
CaptainSlock

3

यह वह जगह है BradC के FixBadLineStringसमारोह 0 और ०.००००००००३ के बीच एक यादृच्छिक संख्या का उपयोग करने के बदलाव है, जिससे इसके लिए पैमाने पर करने के लिए अनुमति देता है LINESTRINGsअंक की एक बड़ी संख्या के साथ, और यह भी निर्देशांक के परिवर्तन को कम करने:

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
वास्तव में अच्छा लग रहा है, मैं PWDENCRYPTसमारोह के बारे में पता नहीं था । आप इसे छोड़ सकते थे ABSऔर यह एक सकारात्मक या नकारात्मक संख्या में वापस आ गया होगा, इसलिए हम हमेशा एक्स से नहीं जोड़ रहे हैं और वाई से घटा रहे हैं
ब्रैडेक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.