(あなたはおそらくすでにこの最初の部分を知っているので)私が最初に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)が3D空間で同一線上にないために奇妙に見えました(私は数学者なので、それらの用語で考えていました)。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
これは、これらを数学的な3D空間の点ではなく、地球の表面上でトレースされた領域またはパスと考える場合、実際に意味があります。
問題の2番目の部分は、Z(およびM)ポイント値が関数によってSQLによって保存されないことです:
Z座標は、ライブラリによって行われた計算では使用されず、ライブラリ計算を介して実行されません。
残念ながら、これは仕様です。これは2010年にMicrosoftに報告され、リクエストは「修正しない」としてクローズされました。あなたは議論が関連していると思うかもしれません、彼らの理由は次のとおりです:
MakeValidは空間要素を分割および結合するため、ZとMの割り当てはあいまいです。多くの場合、このプロセス中にポイントが作成、削除、または移動されます。したがって、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を完全に削除することにしました。
方法を正確に知っていれば、後で割り当てることができます。または、入力時に有効になるようにオブジェクトを生成する方法を変更したり、有効なオブジェクトとすべての機能を保持するオブジェクトの2つのバージョンを保持したりできます。シナリオをよりよく説明し、オブジェクトで何をするかを説明すれば、追加の回避策を提供できるかもしれません。
さらに、既に見たように、ポイントの順序を変更したり、MULTILINESTRINGを返したり、POINTオブジェクトを返したりするなど、MakeValid
他の予期しないこともできます。
私が出会った1つのアイデアは、代わりにMULTIPOINTオブジェクトとして保存することでした:
問題は、ラインストリングが、以前にラインでトレースされた2つのポイント間のラインの連続セクションを実際にリトレースするときです。定義により、既存のポイントをトレースしている場合、ラインストリングはこのポイントセットを表すことができる最も単純なジオメトリではなくなり、MakeValid()は代わりにマルチラインストリングを提供します(そしてZ / M値を失います)。
残念ながら、GPSデータなどで作業している場合は、ルートのあるポイントでパスをトレースした可能性が高いため、これらのシナリオでは線ストリングは必ずしも有用ではありません:(おそらく、そのようなデータはとにかくマルチポイント。データは、一定の時点でサンプリングされたオブジェクトの個別の位置を表すためです。
あなたの場合、それはうまく検証されます:
select geometry::STGeomFromText('MULTIPOINT (0 0 1, 0 1 2, 0 -1 3)',4326)
.IsValidDetailed()
24400: Valid
これらをLINESTRINGSとして絶対に維持する必要がある場合MakeValid
は、Zを維持しながら、ソースXまたはYポイントの一部をわずかな値でわずかに調整する独自のバージョンを記述する必要があります他のオブジェクトタイプに変換します)。
私はまだいくつかのコードに取り組んでいますが、ここでいくつかの最初のアイデアを見てみましょう。
編集 OK、テスト中に見つけたいくつかのこと:
- ジオメトリオブジェクトが無効である場合、それを使用して多くのことを行うことはできません。を読む
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