OracleからSQL Serverに移動する、地理LINESTRING
データの非常に大きなテーブルがあります。 Oracleのこのデータに対して実行される評価は多数あり、SQL Serverのデータに対しても実行する必要があります。
問題:SQL Serverでは、有効なLINESTRING
の要件がOracleよりも厳しくなっています。 「LineStringインスタンスは、2つ以上の連続したポイントの間隔でそれ自体をオーバーラップすることはできません」 あるパーセンテージのLINESTRING
sがそれを満たしていない基準。これは、データを評価するために必要な関数が失敗することを意味します。 SQL Serverで正常に検証できるようにデータを調整する必要があります。
例えば:
それ自体を倍にする非常に単純な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番目の次元を削除するため、使用できなくなります。 3番目の次元を並べ替えたり削除したりせずにこの問題を解決する別のアプローチを探しています。
何か案は?
私の実際のデータには、数百/数千のポイントが含まれています。
SQLサーバーで初めて空間データを操作しているので注意してください(おそらく、すでにこの最初の部分を知っているでしょう)が、SQLサーバーであることがわかるまでに少し時間がかかりました(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年にマイクロソフトに報告されました 、リクエストは「修正されません」としてクローズされました。あなたはその議論が関連していると思うかもしれません、彼らの推論は:
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つのバージョンのオブジェクトを保持したりできます。1つは有効で、もう1つはすべての機能を保持します。シナリオの説明とオブジェクトの操作について説明すると、追加の回避策を提供できる可能性があります。
さらに、すでに見てきたように、ポイントの順序を変更したり、MULTILINESTRINGを返したり、POINTオブジェクトを返したりするなど、 MakeValid
は他の予期しないことも実行できます 。
問題は、ラインストリングが、以前にラインによってトレースされた2つのポイント間のラインの連続セクションを実際にリトレースする場合です。定義により、既存のポイントをトレースしている場合、折れ線はこのポイントセットを表すことができる最も単純なジオメトリではなくなり、MakeValid()は代わりに複数折れ線を提供します(そして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は維持します(そしてt他のオブジェクトタイプに変換するなど、他のクレイジーなことを行います)。
私はまだいくつかのコードに取り組んでいますが、ここで最初のアイデアのいくつかを見てください:
[〜#〜] edit [〜#〜]OK、テスト中に見つけたいくつかのこと:
STGeometryType
を読み取ったり、STNumPoints
を取得したり、STPointN
を使用してそれらを反復処理したりすることはできません。 MakeValid
を使用できない場合は、基本的に地理オブジェクトのテキスト表現の操作に行き詰まっています。STAsText()
を使用すると、無効なオブジェクトのテキスト表現も返されますが、ZまたはMの値は返されません。代わりに、AsTextZM()
またはToString()
が必要です。Rand()
を呼び出すfunctionを作成することはできません(関数は決定論的である必要があります)。私はあなたのデータの精度が何であるか、またはそれが小さな変更に対してどれほど許容できるかを本当に知りません。そのため、この関数を使用するか、またはあなた自身の裁量で変更してください。このループが永遠に続く原因となる可能性のある入力があるかどうかはわかりません。あなたは警告されました。
_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
_
これは BradCのFixBadLineString
関数 0から0.000000003の乱数を使用するように微調整されたため、多数のポイントで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