Lassen Sie mich bedenken, dass ich zum ersten Mal mit räumlichen Daten in SQL Server spiele (so dass Sie diesen ersten Teil wahrscheinlich bereits kennen), aber ich habe eine Weile gebraucht, um herauszufinden, dass SQL Server (xyz) -Koordinaten nicht als wahr behandelt 3D-Werte werden als (Längen- und Breitengrad) mit einem optionalen Höhenwert (Z) behandelt, der von der Validierung und anderen Funktionen ignoriert wird.
Beweis:
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).
Ihr erstes Beispiel kam mir komisch vor, weil (0 0 1), (0 1 2) und (0 -1 3) im 3D-Raum nicht kollinear sind (ich bin Mathematiker, also habe ich in diesen Begriffen nachgedacht). IsValidDetailed
(undMakeValid
) behandelt diese als (0 0), (0 1) und (0, -1), was eine überlappende Linie ergibt.
Um dies zu beweisen, tauschen Sie einfach das X und das Z aus und es wird überprüft:
select geography::STGeomFromText('LINESTRING (1 0 0, 2 1 0, 3 -1 0)', 4326)
.IsValidDetailed()
24400: Valid
Dies ist tatsächlich sinnvoll, wenn wir uns diese als Regionen oder Pfade vorstellen, die auf der Oberfläche unseres Globus statt als Punkte im mathematischen 3D-Raum gezeichnet werden.
Der zweite Teil Ihres Problems besteht darin, dass Z- (und M-) Punktwerte von SQL nicht über Funktionen beibehalten werden :
Z-Koordinaten werden in den von der Bibliothek durchgeführten Berechnungen nicht verwendet und werden nicht durch Bibliotheksberechnungen geführt.
Dies ist leider beabsichtigt. Dies wurde Microsoft im Jahr 2010 gemeldet , die Anfrage wurde als "Won't Fix" geschlossen. Vielleicht finden Sie diese Diskussion relevant, ihre Argumentation ist:
Das Zuweisen von Z und M ist nicht eindeutig, da MakeValid räumliche Elemente aufteilt und zusammenführt. Während dieses Vorgangs werden häufig Punkte erstellt, entfernt oder verschoben. Daher löscht MakeValid (und andere Konstruktionen) die Z- und M-Werte.
Beispielsweise:
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()
Die Werte Z und M sind für Punkt (0 0) nicht eindeutig. Wir haben beschlossen, Z und M komplett zu streichen, anstatt ein halbwegs korrektes Ergebnis zu liefern.
Sie können sie später zuweisen, wenn Sie genau wissen, wie. Alternativ können Sie die Art und Weise ändern, in der Sie Ihre Objekte generieren, um sie bei der Eingabe gültig zu machen, oder zwei Versionen Ihrer Objekte beibehalten, eine gültige und eine andere, die alle Ihre Funktionen beibehält. Wenn Sie Ihr Szenario besser erklären und wissen, was Sie mit den Objekten tun, können wir Ihnen möglicherweise zusätzliche Problemumgehungen anbieten.
Darüber hinaus können Sie, wie Sie bereits gesehen haben, MakeValid
auch andere unerwartete Aktionen ausführen, z. B. die Reihenfolge der Punkte ändern, einen MULTILINESTRING zurückgeben oder sogar ein POINT-Objekt zurückgeben.
Ich bin auf die Idee gekommen, sie stattdessen als MULTIPOINT-Objekt zu speichern :
Das Problem ist, wenn Ihre Linienfolge tatsächlich einen durchgehenden Linienabschnitt zwischen zwei Punkten zurückverfolgt, der zuvor von der Linie verfolgt wurde. Per Definition ist die Linienfolge nicht mehr die einfachste Geometrie, die diese Punktmenge darstellen kann, wenn Sie vorhandene Punkte nachzeichnen, und MakeValid () gibt Ihnen stattdessen eine Mehrfachlinienfolge (und verliert Ihre Z / M-Werte).
Wenn Sie mit GPS-Daten oder Ähnlichem arbeiten, ist es leider sehr wahrscheinlich, dass Sie Ihren Weg an einem bestimmten Punkt der Route zurückverfolgt haben. Daher sind Linienfolgen in den folgenden Szenarien nicht immer so nützlich: (Vermutlich sollten solche Daten wie folgt gespeichert werden Ein Multipoint, da Ihre Daten den diskreten Ort eines Objekts darstellen, das zu regelmäßigen Zeitpunkten abgetastet wird.
In Ihrem Fall ist es ganz gut validiert:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
Wenn Sie diese unbedingt als LINESTRINGS pflegen müssen, müssen Sie Ihre eigene Version von schreiben MakeValid
, die einige der X - oder Y - Punkte der Quelle geringfügig um einen winzigen Wert anpasst, während Z erhalten bleibt (und andere verrückte Dinge wie z in andere Objekttypen konvertieren).
Ich arbeite immer noch an Code, aber wirf hier einen Blick auf einige der ersten Ideen:
EDIT Ok, ein paar Dinge, die ich beim Testen gefunden habe:
- Wenn das Geometrieobjekt ungültig ist, können Sie nicht viel damit anfangen. Sie können das nicht lesen
STGeometryType
, Sie können das nicht bekommen STNumPoints
oder verwenden STPointN
, um durch sie zu iterieren. Wenn Sie nicht verwenden können MakeValid
, müssen Sie im Grunde nur die Textdarstellung des geografischen Objekts bearbeiten.
- Bei Verwendung von
STAsText()
wird die Textdarstellung auch eines ungültigen Objekts zurückgegeben, es werden jedoch keine Z- oder M-Werte zurückgegeben. Stattdessen wollen wir AsTextZM()
oder ToString()
.
- Sie können keine Funktion erstellen , die aufruft
RAND()
(Funktionen müssen deterministisch sein), also habe ich sie nur durch sukzessive größere und größere Werte angestupst. Ich habe wirklich keine Ahnung, wie genau Ihre Daten sind oder wie tolerant sie gegenüber kleinen Änderungen sind. Verwenden oder ändern Sie diese Funktion daher nach eigenem Ermessen.
Ich habe keine Ahnung, ob es mögliche Eingänge gibt, die dazu führen, dass diese Schleife für immer weitergeht. Du wurdest gewarnt.
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
Anstatt die Zeichenfolge zu analysieren, habe ich mich dafür entschieden, ein neues MultiPoint
Objekt mit der gleichen Anzahl von Punkten zu erstellen , damit ich sie durchlaufen und anstoßen und dann einen neuen LineString zusammensetzen kann. Hier ist ein Code zum Testen: 3 dieser Werte (einschließlich Ihres Beispiels) beginnen ungültig, sind jedoch behoben:
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