ให้ฉันข้อแม้ที่ฉันกำลังเล่นกับข้อมูลเชิงพื้นที่ในเซิร์ฟเวอร์ SQL เป็นครั้งแรก (ดังนั้นคุณอาจรู้แล้วส่วนแรกนี้) แต่ฉันใช้เวลาสักครู่เพื่อคิดออกว่าSQL Server ไม่ได้รักษาพิกัด (xyz) เป็นจริง ค่า 3D จะถือว่าเป็น (ละติจูดลองติจูด) ด้วยค่า "การยกระดับ" ซึ่งเป็นตัวเลือก Z ซึ่งจะถูกละเว้นโดยการตรวจสอบและฟังก์ชั่นอื่น ๆ
หลักฐาน:
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) ไม่ใช่ collinear ในอวกาศ 3 มิติ (ฉันเป็นนักคณิตศาสตร์ดังนั้นฉันจึงคิดในแง่เหล่านั้น) IsValidDetailed
(และMakeValid
) กำลังจัดการสิ่งเหล่านี้เป็น (0 0), (0 1) และ (0, -1) ซึ่งทำเส้นที่ทับซ้อนกัน
หากต้องการพิสูจน์เพียงสลับ X และ Z และตรวจสอบความถูกต้อง:
select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
.IsValidDetailed()
24400: Valid
สิ่งนี้สมเหตุสมผลถ้าเราคิดว่าสิ่งเหล่านี้เป็นภูมิภาคหรือเส้นทางที่ติดตามบนพื้นผิวโลกของเราแทนที่จะเป็นจุดในพื้นที่ 3 มิติทางคณิตศาสตร์
ส่วนที่สองของปัญหาของคุณคือค่าจุด Z (และ M) จะไม่ถูกสงวนไว้โดย SQL ผ่านฟังก์ชัน :
พิกัด Z ไม่ได้ใช้ในการคำนวณใด ๆ ที่ทำโดยห้องสมุดและไม่ได้ดำเนินการผ่านการคำนวณห้องสมุดใด ๆ
นี่คือการออกแบบที่น่าเสียดาย สิ่งนี้ถูกรายงานไปยัง Microsoft ในปี 2010คำขอถูกปิดเป็น "ไม่สามารถแก้ไขได้" คุณอาจพบว่าการสนทนาที่เกี่ยวข้องกับเหตุผลของพวกเขาคือ:
การกำหนด 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 แทน :
ปัญหาคือเมื่อการคืนค่าของคุณดึงส่วนต่อเนื่องของเส้นตรงระหว่างจุดสองจุดที่ติดตามโดยสายก่อนหน้านี้ ตามคำจำกัดความถ้าคุณกำลังย้อนกลับจุดที่มีอยู่แล้ว linestring จะไม่ใช่เรขาคณิตที่ง่ายที่สุดที่สามารถเป็นตัวแทนของชุดนี้ได้แล้ว MakeValid () จะให้ multilinestring แทน (และสูญเสียค่า Z / M ของคุณ)
น่าเสียดายที่หากคุณกำลังทำงานกับข้อมูล GPS หรือคล้ายกันมันอาจเป็นไปได้ว่าคุณอาจทำการสืบค้นเส้นทางของคุณอีกครั้งในบางเส้นทางดังนั้นการใช้งานอินเทอร์เนทจึงไม่เป็นประโยชน์เสมอไปในสถานการณ์เหล่านี้ :( เนื้อหาควรเก็บไว้ มัลติพอยต์ต่อไปเนื่องจากข้อมูลของคุณแสดงถึงตำแหน่งที่ไม่ต่อเนื่องของวัตถุที่ถูกสุ่มตัวอย่าง ณ เวลาปกติ
ในกรณีของคุณจะตรวจสอบได้ดี:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
หากคุณจำเป็นต้องรักษาสิ่งเหล่านี้ไว้เป็น LINESTRINGS คุณจะต้องเขียนเวอร์ชันของคุณเองMakeValid
เพื่อปรับจุดแหล่ง X หรือ Y เล็กน้อยโดยใช้ค่าเล็กน้อยในขณะที่ยังคงรักษา Z (และไม่ทำสิ่งที่บ้าอื่น ๆ เช่น แปลงเป็นวัตถุประเภทอื่น)
ฉันยังคงทำงานกับโค้ดบางส่วน แต่ให้ดูที่แนวคิดเริ่มต้นบางอย่างที่นี่:
แก้ไขตกลงบางสิ่งที่ฉันพบขณะทดสอบ:
- หากวัตถุรูปทรงไม่ถูกต้องคุณก็ไม่สามารถทำอะไรได้มากนัก คุณไม่สามารถอ่าน
STGeometryType
, คุณไม่สามารถรับSTNumPoints
หรือใช้STPointN
ในการย้ำผ่านพวกเขา หากคุณไม่สามารถใช้งานได้แสดงMakeValid
ว่าคุณติดอยู่กับการแสดงข้อความของวัตถุทางภูมิศาสตร์
- การใช้
STAsText()
จะคืนค่าการแสดงข้อความของแม้แต่วัตถุที่ไม่ถูกต้อง แต่จะไม่ส่งคืนค่า Z หรือ M แต่เราต้องการหรือ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
วัตถุใหม่โดยใช้ชุดของจุดเดียวกันดังนั้นฉันสามารถวนซ้ำผ่านพวกเขาและเขยิบพวกเขาแล้วประกอบใหม่ LineString นี่คือรหัสบางส่วนเพื่อทดสอบค่า 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