MSSQLで電子マップタイルに描画する

habrコミュニティの読者に、CLRライブラリMicrosoft.SqlServer.Typesを使用して、電子地図のタイルを作成する方法を伝えたいと思います。 この記事では、さらにレンダリングするためのマップタイルのリストの生成について説明します。 MS SQL 2008データベースに格納されているオブジェクトのジオメトリによってタイルを生成するアルゴリズムについて説明し、全体のレンダリングプロセスについては、記事の最後の例で段階的に説明します。







内容






問題

ソースデータ

解決策

タイルリポジトリ

タイルの準備手順

使用する機能

ポリラインの例

交差点チェック

タイル画像を保存するためのテーブル

タイルにアイコンを配置する

タイルの関連付け

タイル上のジオメトリの描画

おわりに





問題






ブラウザーが大量の地理データをベクターグラフィックスで表示する場合(SVGまたはCANVASを使用)、データがクライアントマシンにダウンロードされるのを待つだけでなく、描画プロセスに時間がかかりすぎることもあります。

ブラウザーの地図に多数のアイコンを表示する場合、クラスタリングを適用できますが、複雑な幾何学的オブジェクトの場合は別のアプローチを使用する必要があります。




ソースデータ:






ジオメトリオブジェクトのセットはMicrosoft SQL 2008データベーステーブルに格納され、ノードの座標は緯度と経度(EPSG:4326)です。 地理データフィールドのタイプはGEOMETRYです。 オブジェクトは、 ポイントジオメトリのアイコンとしてマップ上に表示される必要があります。 ポリラインジオメトリの特定の太さのポリラインの形式。 ポリゴンジオメトリは、アウトライン付きの1つ以上のシェーディングポリゴンとして表示する必要があります。 タイルはWebメルカトル図法と一致する必要があります



解決策:






ベクターグラフィックスの代わりに、マップ上のオブジェクトをラスターレイヤーとして、つまりマップ自体と同じ画像(タイル)で表示します。 これを行うには、オブジェクトの画像を使用して各スケールのマップタイルのセットを準備する必要があります。 タイルを作成するには、 Google Webメルカトル図法を使用します 。つまり、 メルカトル図法を説明する式が使用される Googleコードを使用して、緯度と経度のマップピクセルへの変換が実行されます。

予測の詳細については、 こちらをご覧ください

Sql Server 2008のバージョン以降、 GEOMETRYおよびGEOGRAPHYデータ型は 、空間データを操作するためにサポートされています。

Yandex、Google、またはOpenStreetMapマッピングの既存のマッピングサービス。PNG画像の形式で提供され、通常は256x256ピクセルの固定サイズです。 現在、SVGやCANVASなどのテクノロジーを使用してタイルが形成されるサービスがあります。 PNG形式(画像ネットワークグラフィック)のラスタータイルを検討します。 PNG形式は透明度(アルファチャンネルで示される)をサポートします。これにより、複数のレイヤーを表示するときに、絶対にオーバーラップすることなくタイルを重ねることができます。



タイルリポジトリ






各スケールに対して、特定のタイルセットが保存されます。 0レベル-1タイルのスケールの場合:

画像



第1レベルスケールの場合、4つの2 * 2タイル:



スケールnの場合、2n * 2nタイルが保存されます。 スケール数が増加するタイルの数は指数関数的に増加します。



タイルは、Webサーバーのファイルシステムに保存され、http要求によってクライアントマシンに送信されます。次に例を示します。

someurl / layer {Z} / {X} / {Y} .png

ここで、Z、X、Yはそれぞれスケーリング、Xタイル位置、Yタイル位置です。 たとえば、次のURLで、サンクトペテルブルクのトリニティブリッジの画像を含むタイルを使用できます。

b.tile.openstreetmap.org/15/19144/9524.png

このURLで:

15-スケール番号。

19144– Xタイルの位置。

9524-Yタイルの位置。

当然、異なるシステムではリクエストURLの形式は異なります。 タイル番号とスケールの代わりに、 QUADキーを使用してタイルをリクエストできます。 タイルファイルは、サーバーによってファイルシステムから直接、またはhttpハンドラーを使用してクライアントに送信できます。 X、Y、Zのオプションを検討します。



タイルの準備手順






  • 各オブジェクトのジオメトリに応じたタイルのリストの形成。
  • 各オブジェクトのタイルの生成。
  • 一意のセットを取得するためのタイルの関連付け。
  • ファイルシステムに保存しています。




使用する機能






タスクを実装するには、タイルのX、Y位置およびスケール番号によってタイルジオメトリを形成する機能が必要になります。 この場合のタイルジオメトリは、緯度と経度で表された角度の座標でタイルを覆う長方形です。 このようなジオメトリの形成は、SQL関数またはSQL CLR関数で実装できます。 SQL CLR関数と通常のSQL関数の実行時間の違いは目立ちません。 SQL CLR関数コードは、添付ソースコードのCoords2PixelConversionクラスに実装されています。

次のジオメトリは、このタイルの輪郭です。つまり、その境界に沿っています。 ここのピークの座標は経度と緯度です。

タイルボーダージオメトリ
'POLYGON ((30.322265625 59.955010262062061, 30.322265625 59.949509172252277, 30.333251953125 59.949509172252277, 30.333251953125 59.955010262062061, 30.322265625 59.955010262062061))'
      
      







tile.GetTileBounds()のスカラーSQLコード
 tile.GetTileBounds(@level int, @x int, @y int) CREATE FUNCTION [tile].[GetTileBounds] (@level int, @x int, @y int) RETURNS geometry AS BEGIN DECLARE @res GEOMETRY = NULL IF @level IS NOT NULL AND @x IS NOT NULL AND @y IS NOT NULL BEGIN DECLARE @n1 FLOAT = PI() - 2.0 * PI() * @y / POWER(2.0, @level); DECLARE @n2 FLOAT = PI() - 2.0 * PI() * (@y + 1) / POWER(2.0, @level); DECLARE @top FLOAT = (180.0 / PI() * ATAN(0.5 * (EXP(@n1) - EXP(-@n1)))); DECLARE @bottom FLOAT = (180.0 / PI() * ATAN(0.5 * (EXP(@n2) - EXP(-@n2)))); DECLARE @tileWidth FLOAT = 360 / CONVERT(float, POWER(2, @level)) DECLARE @left FLOAT = @tileWidth * @x - 180, @right FLOAT = @tileWidth * (@x + 1) - 180 SET @res = geometry::STPolyFromText('POLYGON ((' + LTRIM(STR(@left, 25, 16)) + ' ' + LTRIM(STR(@top, 25, 16)) + ', ' + LTRIM(STR(@left, 25, 16)) + ' ' + LTRIM(STR(@bottom, 25, 16)) + ', ' + LTRIM(STR(@right, 25, 16)) + ' ' + LTRIM(STR(@bottom, 25, 16)) + ', ' + LTRIM(STR(@right, 25, 16)) + ' ' + LTRIM(STR(@top, 25, 16)) + ', ' + LTRIM(STR(@left, 25, 16)) + ' ' + LTRIM(STR(@top, 25, 16)) + '))', 0) END RETURN @res END
      
      







この機能の使用方法については、この記事の後半で説明します。



タイルのリストを作成する方法を検討してください。 さまざまなジオメトリに対して、タイルのリストを形成するためのさまざまなアプローチを選択できます。


方法1:


この状況を使用します。1つのスケールでオブジェクトがタイルと交差しない場合、大きい数値4のスケールでは、チェック対象のタイルと重なるタイルもオブジェクトと交差しません。 つまり、次のスケールに移動するとき、前のスケールのタイルがオブジェクトのジオメトリと交差する場合にのみ、タイルをチェックします。 これにより、方法2で実行される不要なチェックがなくなります。



方法2:


実際、この方法は最悪のシナリオです。 各オブジェクトの各縮尺について、 GEOMETRY :: STEnvelope()関数を使用してタイルのサブセットを決定し、このサブセットのタイルとオブジェクトの交差を確認します。 この方法は、より多くのタイルがチェックされるため、特に面積または長さが大きいオブジェクトの場合は効果が低くなります。



方法3:


各オブジェクトについて、タイルメッシュのジオメトリを形成し、メッシュとオブジェクトのジオメトリの交差部で、ポイントのセットを取得します。 取得した各ポイントについて、2つのタイルを定義し、タイルの最終リストに追加します。たとえば、大陸を通過する複雑な地理的ラインの場合、タイルの境界に沿って通過するグリッドとの交点を見つけ、これらのポイントからレンダリングするタイルを既に決定できます。 グリッドは、線を含む長方形領域の境界内に作成され、垂直線と水平線のセットです。 これは、オブジェクトの長方形領域の境界内にあるすべてのタイルをチェックするよりもはるかに効果的です。

最初の方法について詳しく説明します。 領域を持つオブジェクトのジオメトリの場合、オブジェクトとの交差をチェックするためのタイルのセットは、オブジェクトと重なる長方形の領域(bbox)の極端なタイルに制限できます。

オブジェクトのジオメトリ( POINTジオメトリタイプを除く)に従って、CLRの四角形はMSSQL関数GEOMETRY :: STEnvelope()によって形成されます。 POINTジオメトリを持つオブジェクトの場合、長方形の領域がbboxの境界として使用され、地図上のオブジェクトのアイコンと重なります。 重複するアイコンのジオメトリを返すGetImageBound関数は、 GoogleProjectionクラスに実装されています。 また、緯度と経度をピクセル位置番号に変換するメソッドも実装しています。 長方形領域のコーナーポイントの座標は、緯度と経度で表されます。 次に、結果の長方形をカバーするタイルのサブセットを取得します。 これを行うには、地理座標を適切なスケールのタイル番号に変換する機能が必要です。 経度と緯度でタイルのX位置とY位置を取得するには、後で説明するSQL CLR関数と、以下に示すSQL関数の両方を使用できます。



 tile.GetXTilePos((@Longitude FLOAT, @Zoom INT) tile.GetYTilePos((@Latitude FLOAT, @Zoom INT)
      
      







コーナータイルの位置を決定した後、検出されたコーナータイル間の長方形領域にあるすべてのタイルは、 tile.fn_FetchGeometryTilesZoomDepth()関数でオブジェクトのジオメトリとの交差についてチェックされます。



経度とスケール番号のXタイル位置を取得するSQL関数
 CREATE FUNCTION tile.GetXTilePos(@Longitude FLOAT, @Zoom INT) RETURNS INT AS BEGIN DECLARE @D FLOAT,@E FLOAT,@F FLOAT,@G FLOAT, @tileY INT, @tileX INT SET @D = 128 * POWER(2, @Zoom) SET @E = ROUND(@D + @Longitude * 256 / 360 * POWER(2, @Zoom), 0) SET @tileX = Floor(@E / 256); RETURN @tileX END
      
      








緯度と縮尺番号のタイルのY位置を取得する機能
 CREATE FUNCTION tile.GetYTilePos(@Latitude FLOAT, @Zoom INT) RETURNS INT AS BEGIN DECLARE @A FLOAT, @B FLOAT, @C FLOAT, @D FLOAT, @E FLOAT, @F FLOAT, @G FLOAT, @tileY INT SET @D = 128 * POWER(2, @Zoom) SET @A = Sin(PI() / 180 * @Latitude) SET @B = -0.9999 SET @C = 0.9999 IF @A < @B SET @A = @B IF @A > @C SET @A = @C SET @F = @A SET @G = Round(@D + 0.5 * Log((1.0 + @F) / (1.0 - @F)) * (-256) * POWER(2, @Zoom) / (2 * PI()),0) SET @tileY = Floor(@G / 256) RETURN @tileY END
      
      







tile.fn_FetchGeometryTilesZoomDepth()関数は、ジオメトリをカバーする最小の長方形領域の左上と右下のタイルを計算します。 次に、ネストされたループ内の図とタイルの交差点を決定するために、この領域の各タイルに対してtile.fn_GetTilesByTileNumZoomDepth()関数を使用します。左から右、上から下、左上から右下に移動します。 この関数は、オブジェクトのジオメトリとの交差が定義されているタイルのリストを返します。



タイルセット機能
 CREATE FUNCTION tile.fn_FetchGeometryTilesZoomDepth ( @GeoData GEOMETRY, @Zoom INT, @Depth INT) RETURNS @retTiles TABLE ( Zoom INT, TileX INT, TileY INT) AS BEGIN DECLARE @Left FLOAT, @Right FLOAT, @Top FLOAT, @Bottom FLOAT DECLARE @CurrentXTile INT, @CurrentYTile INT, @Quanttiles INT DECLARE @Envelope GEOMETRY, @RightTop GEOMETRY, @LeftBottom GEOMETRY DECLARE @CurTileGeom GEOMETRY, @res GEOMETRY DECLARE @tiletop FLOAT,@tilebottom FLOAT,@tileleft FLOAT, @tileright FLOAT DECLARE @LeftTilePos INT,@RightTilePos INT,@TopTilePos INT DECLARE @BottomTilePos INT SET @envelope = @GeoData.STEnvelope() SET @RightTop = @envelope.STPointN(3) SET @LeftBottom = @envelope.STPointN(1) SET @Right = @RightTop.STX SET @Left = @LeftBottom.STX SET @Top = @RightTop.STY SET @Bottom = @LeftBottom.STY SET @LeftTilePos = tile.GetXTilePos( @Left,@Zoom) SET @RightTilePos = tile.GetXTilePos( @Right,@Zoom) SET @TopTilePos = tile.GetYTilePos( @Top,@Zoom) SET @BottomTilePos = tile.GetYTilePos( @Bottom,@Zoom) SET @CurrentXTile = @LeftTilePos WHILE @CurrentXTile <= @RightTilePos BEGIN SET @currentYTile = @TopTilePos WHILE @CurrentYTile <= @BottomTilePos BEGIN INSERT INTO @retTiles (Zoom, TileX, TileY) SELECT * FROM tile.fn_GetTilesByTileNumZoomDepth ( @GeoData, @Zoom, @CurrentXTile, @CurrentYTile, @Depth ) SET @CurrentYTile = @CurrentYTile + 1 END SET @CurrentXTile =@CurrentXTile + 1 END RETURN END
      
      







タイルジオメトリとオブジェクトのジオメトリの交差を確認するには、GEOMETRY :: STIntersects()関数を使用します。 オブジェクトのジオメトリとタイルのジオメトリが交差する場合、以前に作成したtile.TileOverlapテーブルにエントリを追加し、次のスケールの4つのタイルに対して同じ関数を再帰的に呼び出します。パラメータ@Depthを1減らして、現在のスケールをカバーします。 交差チェックは、関数tile.fn_FetchGeometryTilesZoomDepth()で実装されます。



指定されたタイルのジオメトリによるタイルのリストの取得
 CREATE FUNCTION tile.fn_GetTilesByTileNumZoomDepth ( @GeoData GEOMETRY, @Zoom INT, @tileX INT, @tileY INT, @Depth INT) RETURNS @retTiles TABLE ( Zoom INT, X INT, Y INT) AS BEGIN DECLARE @currentTile TABLE ( Zoom INT, X INT, Y INT) IF GEOGRAPHY::STGeomFromWKB([tile].[GetTileBounds](@Zoom, @tileX, @tileY).STAsBinary(),4326).STIntersects(GEOGRAPHY::STGeomFromWKB(@GeoData.MakeValid().STUnion(@GeoData.STStartPoint()).STAsBinary(),4326)) = 1 BEGIN INSERT INTO @currentTile SELECT @Zoom , @tileX , @tileY INSERT INTO @retTiles SELECT d.zoom, dX, dY FROM @currentTile c CROSS APPLY (SELECT * FROM [tile].[fn_GetTilesForObjectByTileNumZoomDepth]( @GeoData , c.Zoom + 1, cX * 2, cY * 2, @Depth - 1) WHERE @Depth > 0) AS d INSERT INTO @retTiles SELECT d.zoom, dX, dY FROM @currentTile c CROSS APPLY (SELECT * FROM [tile].[fn_GetTilesForObjectByTileNumZoomDepth]( @GeoData , c.Zoom + 1, cX * 2 + 1, cY * 2, @Depth - 1) WHERE @Depth > 0) AS d INSERT INTO @retTiles SELECT d.zoom, dX, dY FROM @currentTile c CROSS APPLY (SELECT * FROM [tile].[fn_GetTilesForObjectByTileNumZoomDepth]( @GeoData , c.Zoom + 1, cX * 2, cY * 2 + 1, @Depth - 1) WHERE @Depth > 0) AS d INSERT INTO @retTiles SELECT d.zoom, dX, dY FROM @currentTile c CROSS APPLY (SELECT * FROM [tile].[fn_GetTilesForObjectByTileNumZoomDepth]( @GeoData , c.Zoom + 1, cX * 2 + 1, cY * 2 + 1, @Depth - 1) WHERE @Depth > 0) AS d INSERT INTO @retTiles SELECT * FROM @currentTile END RETURN END
      
      








1つのオブジェクトのタイルを作成する必要がある場合、タイルのセットは一意であるため、タイル番号をすぐにtile.Tileテーブルに書き込むことができます。 複数のオブジェクトのジオメトリが交差するタイルを形成するには、異なるオブジェクト用に作成され、互いに重なり合うタイルを結合する必要があります。

tile.fn_GetTilesByTileNumZoomDepth()関数は、指定された深さを考慮して、オブジェクトジオメトリとスケールタイルの交差をチェックします。 @Depthパラメーターはスキャンの深さを示します。たとえば、 @ Zoom = 2および@Depth = 1の場合、スケール2タイルがチェックされ、交差がある場合、4スケール3タイルがチェックされます。 。 GEOMETRYデータタイプGEOGRAPHYに変換した後、交差チェックを実行する必要があります。GEOGRAPHYデータタイプの場合、4326投影のジオメトリポイントのすべての座標を考慮してチェックが実行されるため、これは重要です。



ポリラインの例






サンクトペテルブルクの中心部とモスクワの中心部を結ぶ壊れたタイルを入手したいとしましょう。 長さは約800 kmです。 壊れた居住地を通過します:ノヴゴロド-ヴィシュニーヴォロホック-トベリ。

サンクトペテルブルクからモスクワまでの破線の形状
 'LINESTRING( 30.381113 59.971474, 31.26002 58.539215, 34.564158 57.591722, 35.915476 56.876838,37.622242 55.773125)'
      
      







スケール3から17のこのジオメトリの場合、タイルの合計11076を取得します。スケールのジオメトリと交差するタイルの数の分布を表1に示します



表1-スケールレベルによるタイル数の分布
スケール タイル数
3 1
4 2
5 3
6 4
7 7
8 12
9 23
10 45
11 88
12 174
13 347
14 692
15 1383
16 2766
17 5529




3番目と4番目のスケールで得られたタイルを図1に示します。





図1-タイル:3/4/2および4/9/4



各スケールに対して、タイルのサブセットが生成され、各タイルが例外なくチェックされます。 4〜5のスケールでは、オブジェクトのジオメトリでGEOMETRY :: STEnvelope()関数によって取得された長方形の領域に収まるタイルの数は少なくなります。 4番目のスケールには合計2 ^ 4 * 2 ^ 4 = 256のタイルがあります。しかし、16番目と17番目のスケールでは、さらに多くのタイルをチェックする必要があります。 最初の方法で「不要な」チェックを除外すると、作業がスピードアップします。 ポイントジオメトリ( POINT )を持つオブジェクトの場合、両方の方法の効率は同じになります。





交差点チェック






GEOMETRY :: STIntersects()関数は、GEOMETRYデータ型のSTIntersects()関数が平面内の座標で機能し、緯度と経度はデカルト座標ではないため、オブジェクトジオメトリとタイルジオメトリの交差を決定できません。 したがって、交点を確実に判別するには、 GEOMETRYタイプをGEOGRAPHYタイプに変換します。 GEOMETRYデータ型とは異なり、 GEOGRAPHYデータ型ではポリゴンリングの向きが必要であることに注意してください。 外輪の座標は反時計回りに移動する必要があります。 内側のリング(ボイド)の場合、座標は時計回りにリストする必要があります。 地理の形成におけるエラーを回避するために、 GEOMETRY :: MakeValid()およびGEOMETRY :: STUnion()関数を使用して、GEOMETRY型をGEOGRAPHY型に変換するときに正しい座標シーケンスを取得します。 地理を作成する場合、パラメーターSRID = 4326を指定します。これは、座標に対するすべての操作が球面システムで実行されることを意味します



この段階で、タイルのリスト、つまり3つの列を持つテーブル、Z、X、Y、 mapnik レンダリングプログラムを使用して、さらに作業を行うことができます。これにより、オブジェクトの画像でタイルを形成できます。 Microsoft SQL Serverデータベースへのmapnikアクセスを整理するには、ある程度の努力が必要です。 mapnikでタイルを生成する準備には、次の手順が含まれます。

•レンダリング用のオブジェクトのスタイルを宣言します。

•オブジェクトのジオメトリのデータソースの説明。

•すべてのタイルが連続して生成されないように、レンダリング用のタイルのリストを含むテーブルを指定します。

MS SQL Server 2008データベース内にタイルを生成しますこれを行うには、データベースに格納されているジオメトリを操作するためのいくつかのCLR関数を実装する必要があります。 必要となる主な機能は以下のとおりです。

  • tile.IconTile()、
  • tile.ShapeTile()、
  • tile.TileAgg()、
  • tile.SaveToFolderByZoomXY()




タイル画像を保存するためのテーブル






図2は、レンダリング用のタイルのリストが保存されているテーブル構造を示しています。 これらのテーブルの[データ]フィールドには、タイル上に落下するオブジェクトの画像を含むPNG画像が保存されます。 テーブルに多数の画像を保存して処理すると、パフォーマンスに影響する可能性があります。 このタスクの場合、より適切なオプションは、テーブル内に形成された各オブジェクトのタイルのリストを使用してデータベース外にタイルイメージを作成し、ファイルシステムに保存することです。





図2-タイルのリストを保存するためのテーブル



タイルにアイコンを配置する






タイル上にアイコンを配置するためのアルゴリズムを分析してみましょう(ジオメトリの種類POINT)。

あるオブジェクトの緯度と経度があり、現在のスケールのタイルのリストがあり、その上にアイコンが重ねられています。 タイルのリストの形成は以前に説明されています。 タイル上のアイコンの位置の計算は、次のアクションで構成されます。

1.最初に、緯度と経度を絶対ピクセル座標に変換します。

2.次に、使用可能なリストの各タイルについて、現在のスケールで、左上隅の絶対ピクセル座標を計算します。 タイルの左上のピクセル(pixXtile、pixYtile)の座標は、タイル位置のx番号とy番号にそのサイズ(この場合は256ピクセル)を掛けて計算されます。



3.オブジェクトの絶対ピクセル座標とタイルの左上隅の絶対ピクセル座標の差は、GetPixelXOnTile()およびGetPixelXOnTile()関数で決定されます。 この違いは、タイル上のアイコンの中心の相対ピクセル座標です。

4.タイル上にアイコンを描画するには、タイル上の描画領域の境界をピクセル単位で取得する必要があり、そこに挿入が行われます。 タイル上のオブジェクトの相対ピクセル座標は、前の手順で取得されました。 これで、アイコンのサイズによって、挿入する長方形の領域の境界が決まります。

5.タイルにアイコンを描画します。



タイルにアイコンを配置するCLR SQL関数
 [Microsoft.SqlServer.Server.SqlFunction] public static SqlBinary IconTile(SqlBinary image, SqlInt32 zoom, SqlDouble Lon, SqlDouble Lat, SqlInt32 xTile, SqlInt32 yTile, SqlDouble scale) { SqlBinary result = null; using (Icon2TileRendering paster = new Icon2TileRendering()) { using (MemoryStream ms = new MemoryStream()) { ms.Write(image.Value, 0, image.Length); SetBeginPosition(ms); paster.PasteFromStreamScaledImageToTile((int)zoom, (double)Lon, (double)Lat, (int)xTile, (int)yTile, (double)scale, ms); result = paster.GetBytes(); } } return result; }
      
      







描画領域の境界線を取得します
  #region [Pixel Position Calculation] Rectangle GetTargetBound(int zoom, double Lon, double Lat, int xTile, int yTile, int width, int height) { int xPix = _conv.FromLongitudeToXPixel(Lon, zoom); int yPix = _conv.FromLatitudeToYPixel(Lat, zoom); int xPos = xPix - xTile * TILE_SIZE; int yPos = yPix - yTile * TILE_SIZE; int halfWidth = width / 2; int halfHeight = height / 2; return new Rectangle(xPos - halfWidth, yPos - halfHeight, width, height } int GetPixelXOnTile(int zoom, double Lon, int xTile) { return _conv.FromLongitudeToXPixel(Lon, zoom) - xTile * TILE_SIZE; } int GetPixelYOnTile(int zoom, double Lat, int yTile) { return _conv.FromLatitudeToYPixel(Lat, zoom) - yTile * TILE_SIZE; } #endregion [Pixel Position Calculation]
      
      







アイコンをタイルにコピー
 /// <summary> ///     /// </summary> /// <param name="zoom"></param> /// <param name="Lon"></param> /// <param name="Lat"></param> /// <param name="xTile"></param> /// <param name="yTile"></param> /// <param name="iconImage"></param> public void PasteImageToTileByLatLon(int zoom, double Lon, double Lat, int xTile, int yTile, Bitmap iconImage) { int width = iconImage.Width; int height = iconImage.Height; CopyRegionIntoImage(iconImage, new Rectangle(0, 0, width, height), GetTargetBound(zoom, Lon, Lat, xTile, yTile, width, height)); }
      
      









タイルの関連付け






複数のオブジェクトのアイコンを同じタイルに重ねることができます。 すべてのオブジェクトでタイルを取得するには、まず各オブジェクトのタイルを作成してから、それらを1つにマージします。 このようなソリューションは、データベーステーブルの行をグループ化することで実装できます;このために、CLR関数集計tile.TileAgg()が作成され 、タイルを1つに結合しました。 このソリューションにはマイナスが1つあります。タイルで切り詰められた各オブジェクトに対して、タイルイメージを格納するBINARYフィールドを持つ個別のレコードを格納し、このオブジェクトのみが選択されるため、大量のメモリが必要になるためです。 より適切な解決策は、タイルの1つのインスタンスを使用し、タイル上にあるオブジェクトのすべてのアイコンを常に表示することです。 したがって、メモリの消費が少なくなります。 この場合、グループ化するものは何もありません。 グループ化を使用したい。

テーブルにタイルの位置とアイコンに描かれたタイル画像を入力する
 CREATE PROC [tile].[FillObjectTilesIntersection]( @StartZoom INT, @EndZoom INT) AS BEGIN DECLARE @CurrentZoom INT SET @CurrentZoom = @StartZoom WHILE @CurrentZoom <= @EndZoom BEGIN INSERT INTO tile.Tile (X,Y,Data,Zoom) SELECT t.TileX,t.TileY, [tile].[TileAgg] (tile.IconTile(i.Data, @CurrentZoom,o.Longitude,o.Latitude,t.tileX,t.tileY, 1.0) ),@CurrentZoom AS Zoom FROM tile.Shape o INNER JOIN tile.[Image] i ON i.ImageID = o.ImageID CROSS APPLY tile.fn_FetchObjectTiles(tile.GetIconBoundForZoom(o.Longitude, o.Latitude, 64, 64, @CurrentZoom, 0),@CurrentZoom) t WHERE o.TypeID = @TypeID GROUP BY t.TileX,t.TileY SET @CurrentZoom = @CurrentZoom + 1 END END
      
      







オブジェクトのソースとして、オブジェクトの座標と、 Binaryタイプのフィールドのtile.Imageテーブルに保存されているアイコンの画像の識別子を含むtile.Objectテーブルを使用します。

アイコンを経度30.381113および緯度59.971474に配置してタイル3/4/2および4/9/4を形成するためのスクリプト
 DECLARE @Image VARBINARY(MAX) SELECT TOP (1) @image = ( SELECT * FROM OPENROWSET(BULK N'd:\media\icons\pin_orange.png', SINGLE_BLOB) As tile) SELECT [tile].[SaveToFolderByZoomXY]([tile].[IconTile](@image, 3,30.381113, 59.971474, 4, 2, 1.0), N'D:\Tiles\',3,4,2) SELECT [tile].[SaveToFolderByZoomXY]([tile].[IconTile](@image, 4,30.381113, 59.971474, 9, 4, 1.0), N'D:\Tiles\',4,9,4)
      
      







図3-受信したタイルとアイコン








タイル上のジオメトリの描画






ポリライン(POLYLINE、MULTIPOLYLINE)の場合、タイルジオメトリをポリラインジオメトリと組み合わせて、タイル領域外のポリラインの一部が除外されるようにします。 輪郭と影付き領域を決定するアルゴリズムは、領域を持つジオメトリ、つまりPOLYGONまたはMULTIPOLYGONPOLYGONまたはMULTYPOLYGONを含むGEOMETRYCOLLECTIONに適用できます。 このアルゴリズムはShapeToTileRenderingクラスに実装されており、次の手順が含まれています。

1.ジオメトリの座標(緯度、経度)は、緯度、経度をピクセルPSG3857(Googleプロジェクション)に変換する式を使用してスケールを考慮してピクセル座標に変換され、結果のジオメトリの各座標からターゲットタイルの左上のピクセルの座標を引きます。 いわゆるジオメトリ( A )を取得します。 これらのアクションは、関数ConvertToZoomedPixelZeroedByTileGeometry(poly、Z、X、Y)で実装されます

2.タイルのジオメトリ( B )は、スケールを考慮してピクセル座標で形成されます



3.タイルのピクセルジオメトリ( B )とオブジェクトのジオメトリ( A )の交差( STIntersection )によって取得された形成済みジオメトリ( C

4.ジオメトリの輪郭( C )とタイルのジオメトリの輪郭( B )の交差の結果として得られたジオメトリ( D )が形成されます。タイルの境界に沿って、タイル内のポリゴンの想定シェーディングエリアに接するラインを取得します。 減算の結果として得られたジオメトリ( E )は、 .STDifference(other_geometry)関数を使用して形成されます



5.ジオメトリ( E )は描画用の輪郭で、関数(

6.ジオメトリポリゴンが塗りつぶされます( C )-塗りつぶされた領域が取得されます

7.ポリゴンの( E )輪郭のジオメトリは、タイルの境界との交差を除去した後に描画されます

8.現在のオブジェクトのすべてのタイルについて手順1〜7を繰り返し、タイルに保存します。TileOverlapテーブル

3 15- X 19144 Y 9524. T-SQL . , :

  SELECT [tile].[GetTileBounds](15,19144,9524).ToString()
      
      





:

  'POLYGON ((30.322265625 59.955010262062061, 30.322265625 59.949509172252277, 30.333251953125 59.949509172252277, 30.333251953125 59.955010262062061, 30.322265625 59.955010262062061))'
      
      







, . . . , , 6367 . , ( ), , . , . , 360 90 , . , c , . :

 SELECT [tile].[fn_GetCircleSegment](30.3277587890625, 59.952259717159905,0,360,440,90)
      
      







 CREATE FUNCTION [tile].[fn_ GetCircleSegment] (@X float, @Y float, @azimuth float, @angle float, @distance float, @step FLOAT) RETURNS geometry WITH EXEC AS CALLER AS BEGIN IF @X IS NULL OR @Y IS NULL OR @azimuth IS NULL OR ISNULL(@angle, 0) = 0 OR ISNULL(@distance, 0) <= 0 RETURN NULL DECLARE @sectorStepAngle FLOAT SET @sectorStepAngle = @step IF ABS(@angle) > 360 SET @angle = 360 DECLARE @pointsStr VARCHAR(MAX) DECLARE @firstPointsStr VARCHAR(MAX) DECLARE @earthRadius FLOAT DECLARE @lat FLOAT DECLARE @lon FLOAT DECLARE @d FLOAT IF ABS(@angle) < 360 SET @pointsStr = LTRIM(STR(@X, 25, 16)) + ' ' + LTRIM(STR(@Y, 25, 16)) ELSE SET @pointsStr = '' SET @earthRadius = 6367 SET @lat = RADIANS(@Y) SET @lon = RADIANS(@X) SET @d = (@distance / 1000) / @earthRadius DECLARE @angleStart FLOAT DECLARE @angleEnd FLOAT SET @angleStart = @azimuth - @angle / 2; SET @angleEnd = @azimuth + @angle / 2; DECLARE @pointsCount INT SET @pointsCount = FLOOR(@angle / @sectorStepAngle) DECLARE @brng FLOAT DECLARE @latRadians FLOAT DECLARE @lngRadians FLOAT DECLARE @ptX FLOAT DECLARE @ptY FLOAT DECLARE @i INT SET @i = 0 DECLARE @addPrefix TINYINT IF ABS(@angle) < 360 SET @addPrefix = 1 ELSE SET @addPrefix = 0 WHILE @i <= @pointsCount BEGIN SET @brng = RADIANS(@angleStart + @i * @sectorStepAngle); SET @latRadians = ASIN(SIN(@lat) * COS(@d) + COS(@lat) * SIN(@d) * COS(@brng)); SET @lngRadians = @lon + ATN2(SIN(@brng) * SIN(@d) * COS(@lat), COS(@d) - SIN(@lat) * SIN(@latRadians)); SET @ptX = 180.0 * @lngRadians / PI(); SET @ptY = 180.0 * @latRadians / PI(); IF @addPrefix = 1 BEGIN SET @pointsStr += ', ' + LTRIM(STR(@ptX, 25, 16)) + ' ' + LTRIM(STR(@ptY, 25, 16)) END ELSE BEGIN SET @pointsStr += LTRIM(STR(@ptX, 25, 16)) + ' ' + LTRIM(STR(@ptY, 25, 16)) SET @addPrefix = 1 END IF @i = 0 SET @firstPointsStr = LTRIM(STR(@ptX, 25, 16)) + ' ' + LTRIM(STR(@ptY, 25, 16)) IF @i = @pointsCount AND (@angleStart + @pointsCount * @sectorStepAngle) < @angleEnd BEGIN SET @brng = RADIANS(@angleEnd); SET @latRadians = ASIN(SIN(@lat) * COS(@d) + COS(@lat) * SIN(@d) * COS(@brng)); SET @lngRadians = @lon + ATN2(SIN(@brng) * SIN(@d) * COS(@lat), COS(@d) - SIN(@lat) * SIN(@latRadians)); SET @ptX = 180.0 * @lngRadians / PI(); SET @ptY = 180.0 * @latRadians / PI(); SET @pointsStr = @pointsStr + ', ' + LTRIM(STR(@ptX, 25, 16)) + ' ' + LTRIM(STR(@ptY, 25, 16)) END SET @i = @i + 1 END IF ABS(@angle) < 360 SET @pointsStr += ', ' + LTRIM(STR(@X, 25, 16)) + ' ' + LTRIM(STR(@Y, 25, 16)) ELSE SET @pointsStr += ', ' + @firstPointsStr RETURN geometry::STPolyFromText('POLYGON ((' + @pointsStr + '))', 0) END GO
      
      









, CLR , . .

CLR
 /// <summary> ///    /// </summary> /// <param name="longitude"></param> /// <param name="latitude"></param> /// <param name="azimuth"></param> /// <param name="angle"></param> /// <param name="radius"></param> /// <returns></returns> [Microsoft.SqlServer.Server.SqlFunction] public static SqlGeometry DrawGeoSpatialSectorVarAngle(SqlDouble longitude, SqlDouble latitude, SqlDouble azimuth, SqlDouble angle, SqlDouble radius, SqlDouble stepAngle) { if (longitude == SqlDouble.Null || latitude == SqlDouble.Null || azimuth == SqlDouble.Null || angle == SqlDouble.Null || radius == SqlDouble.Null || radius == 0 || angle == 0) return SqlGeometry.Parse("GEOMETRYCOLLECTION EMPTY"); SqlGeometryBuilder builder = new SqlGeometryBuilder(); builder.SetSrid(0); builder.BeginGeometry(OpenGisGeometryType.Polygon); double firstPointLon; double firstPointLat; double sectorStepAngle = (double) stepAngle; const double earthRadius = 6367.0; double lat = (double) latitude; double lon = (double) longitude; double azim = (double) azimuth; double ang = (double) angle; double piRad = (Math.PI/180.0); double tLat = piRad*lat; double tLon = piRad*lon; double distkm = ((double) radius/1000)/earthRadius; double angleStart = azim - ang/2; double angleEnd = azim + ang/2; var _angle = Math.Abs(ang); if (_angle > 360.0) { angle = 360.0; } int pointCount = (int) Math.Floor(ang/sectorStepAngle); double brng; double latRadians; double lngRadians; double ptX; double ptY; int i = 0; if (angle < 360.0) { builder.BeginFigure(lon, lat); firstPointLon = lon; firstPointLat = lat; } else { brng = piRad*(angleStart); latRadians = Math.Asin(Math.Sin(tLat)*Math.Cos(distkm) + Math.Cos(tLat)*Math.Sin(distkm)*Math.Cos(brng)); lngRadians = tLon + Math.Atan2(Math.Sin(brng)*Math.Sin(distkm)*Math.Cos(tLat), Math.Cos(distkm) - Math.Sin(tLat)*Math.Sin(latRadians)); ptX = 180.0*lngRadians/Math.PI; ptY = 180.0*latRadians/Math.PI; builder.BeginFigure(ptX, ptY); firstPointLon = ptX; firstPointLat = ptY; } while (i <= pointCount) { brng = piRad*(angleStart + i*sectorStepAngle); latRadians = Math.Asin(Math.Sin(tLat)*Math.Cos(distkm) + Math.Cos(tLat)*Math.Sin(distkm)*Math.Cos(brng)); lngRadians = tLon + Math.Atan2(Math.Sin(brng)*Math.Sin(distkm)*Math.Cos(tLat), Math.Cos(distkm) - Math.Sin(tLat)*Math.Sin(latRadians)); ptX = 180.0*lngRadians/Math.PI; ptY = 180.0*latRadians/Math.PI; builder.AddLine(ptX, ptY); i = i + 1; } if (((angleStart + pointCount * sectorStepAngle) < angleEnd)) { brng = piRad * (angleEnd); latRadians = Math.Asin(Math.Sin(tLat) * Math.Cos(distkm) + Math.Cos(tLat) * Math.Sin(distkm) * Math.Cos(brng)); lngRadians = tLon + Math.Atan2(Math.Sin(brng) * Math.Sin(distkm) * Math.Cos(tLat), Math.Cos(distkm) - Math.Sin(tLat) * Math.Sin(latRadians)); ptX = 180.0 * lngRadians / Math.PI; ptY = 180.0 * latRadians / Math.PI; builder.AddLine(ptX, ptY); } builder.AddLine(firstPointLon, firstPointLat); builder.EndFigure(); builder.EndGeometry(); return builder.ConstructedGeometry; }
      
      









()
 DECLARE @bbox GEOMETRY DECLARE @octagon GEOMETRY SELECT @bbox = [tile].[GetTileBounds](15,19144,9524), @octagon = [tile].[fn_GetCircleSegment](30.3277587890625, 59.952259717159905,0,360,440,90)
      
      







30.3277587890625, 59.952259717159905 – ;



, :

 SELECT @bbox.STIntersection(@octagon)
      
      





:

 'POLYGON ((30.3253442162734 59.949509172234684, 30.3301733618516 59.949509172234684, 30.333251953125 59.9510505967796, 30.333251953125 59.953468509045528, 30.330173073498937 59.955010262085125, 30.325344504626063 59.955010262085125, 30.322265625 59.953468509045528, 30.322265625 59.9510505967796, 30.3253442162734 59.949509172234684))'
      
      









, :

X Y
 SELECT [tile].[GetPixelXPosFromLongitude](30.3253442162734,15), [tile].[GetPixelYPosFromLatitude](59.949509172234684,15) , [tile].[GetPixelXPosFromLongitude](30.3301733618516,15), [tile].[GetPixelYPosFromLatitude]( 59.949509172234684,15) , [tile].[GetPixelXPosFromLongitude](30.333251953125,15), [tile].[GetPixelYPosFromLatitude]( 59.9510505967796,15) , [tile].[GetPixelXPosFromLongitude](30.333251953125,15), [tile].[GetPixelYPosFromLatitude]( 59.953468509045528,15) , [tile].[GetPixelXPosFromLongitude](30.330173073498937,15), [tile].[GetPixelYPosFromLatitude]( 59.955010262085125,15) , [tile].[GetPixelXPosFromLongitude](30.325344504626063,15), [tile].[GetPixelYPosFromLatitude]( 59.955010262085125,15) ,[tile].[GetPixelXPosFromLongitude](30.322265625,15), [tile].[GetPixelYPosFromLatitude]( 59.953468509045528,15) , [tile].[GetPixelXPosFromLongitude](30.322265625,15), [tile].[GetPixelYPosFromLatitude]( 59.9510505967796,15) , [tile].[GetPixelXPosFromLongitude](30.3253442162734,15), [tile].[GetPixelYPosFromLatitude]( 59.949509172234684,15)
      
      







X 15- Y 15-
30.3253442162734 59.949509172234684 4900936 2438400
30.3301733618516 59.949509172234684 4901048 2438400
30.333251953125 59.9510505967796 4901120 2438328
30.333251953125 59.953468509045528 4901120 2438216
30.330173073498937 59.955010262085125 4901048 2438144
30.325344504626063 59.955010262085125 4900936 2438144
30.322265625 59.953468509045528 4900864 2438216
30.322265625 59.9510505967796 4900864 2438328
30.3253442162734 59.949509172234684 4900936 2438400




:

 SELECT GEOMETRY::STGeomFromText('LINESTRING(4900936 2438400, 4901048 2438400, 4901120 2438328, 4901120 2438216, 4901048 2438144, 4900936 2438144, 4900864 2438216, 4900864 2438328, 4900936 2438400 )',0)
      
      









( ) 3 , , 4 .



4 –



( D ) , , , 5 , ( ) ( D ), 5.

.



( E ):

 SELECT GEOMETRY::STGeomFromText('MULTILINESTRING((4901048 2438400, 4901120 2438328),( 4901120 2438216, 4901048 2438144),( 4900936 2438144, 4900864 2438216), (4900864 2438328, 4900936 2438400) )',0)
      
      









5 – ( )



T-SQL PNG Z/X/Y. .

 DECLARE @bbox GEOMETRY DECLARE @rhomb GEOMETRY DECLARE @image VARBINARY(MAX) SELECT @bbox = [tile].[GetTileBounds](15,19144,9524), @rhomb = [tile].[fn_GetSector](30.3277587890625, 59.952259717159905,0,360,440,90) SET @image = [tile].[ShapeTile]( @octagon,15,19144,9524,'4400B050','9601B41E',3) SELECT[tile].[SaveToFolderByZoomXY](@image,'d:/tiles',15,19144,9524) SET @image = [tile].[ShapeTile]( @octagon,15,19143,9524,'4400B050','9601B41E',3) SELECT[tile].[SaveToFolderByZoomXY](@image,'d:/tiles',15,19143,9524) SET @image = [tile].[ShapeTile]( @octagon,15,19145,9524,'4400B050','9601B41E',3) SELECT[tile].[SaveToFolderByZoomXY](@image,'d:/tiles',15,19145,9524) SET @image = [tile].[ShapeTile]( @octagon,15,19144,9523,'4400B050','9601B41E',3) SELECT[tile].[SaveToFolderByZoomXY](@image,'d:/tiles',15,19144,9523) SET @image = [tile].[ShapeTile]( @octagon,15,19144,9525,'4400B050','9601B41E',3) SELECT[tile].[SaveToFolderByZoomXY](@image,'d:/tiles',15,19144,9525)
      
      







PNG :







DrawPartObjectShapeOnTile() ShapeToTileRendering :

 /// <summary> ///      /// </summary> /// <param name="shape">       </param> /// <param name="X">X  </param> /// <param name="Y">Y  </param> /// <param name="Zoom">  </param> /// <param name="argbFill">    ARGB</param> /// <param name="argbStroke"> </param> /// <param name="strokeWidth"> </param> public void DrawPartObjectShapeOnTile(SqlGeometry shape, int X, int Y, int Zoom, string argbFill, string argbStroke, int strokeWidth) { PasteShapeOnTile(CreateColor(argbFill), CreateColor(argbStroke), strokeWidth, CutPolygonByZoomedPixelZeroTile(shape, X, Y, Zoom)); }
      
      







PasteShapeOnTile() .



 private void PasteShapeOnTile(Color fillcolor, Color strokecolor, int width, List<SqlGeometry> geom) { SqlGeometry shape = geom[0]; int geomnum = (int) shape.STNumGeometries(); SqlGeometry stroke = null; SqlGeometry ring; int intnum; if (geom != null) switch (GetOpenGisGeometryType(shape)) { case OpenGisGeometryType.LineString: case OpenGisGeometryType.MultiLineString: DrawMultiLineStringBordered2(shape, fillcolor, strokecolor, width, 1); break; case OpenGisGeometryType.Polygon: intnum = (int) shape.STNumInteriorRing(); ring = shape.STExteriorRing(); // 1.      FillPolygonOnTile(fillcolor, ring.ToPointsArray()); // 2.    if (geomnum >= 1) stroke = geom[1]; for (int i = 1; i <= intnum; i++) { FillTransparentPolygonOnTile(shape.STInteriorRingN(i).ToPointsArray()); } // 3.   if (geom.Count > 1) { stroke = geom[1]; DrawContourOnTile(stroke, strokecolor, width); } break; case OpenGisGeometryType.MultiPolygon: break; } }
      
      







3-7, , CutPolygonByZoomedPixelZeroTile() , .

 /// <summary> ///            /// </summary> /// <param name="poly"></param> /// <param name="X"></param> /// <param name="Y"></param> /// <param name="Z"></param> /// <returns>          </returns> private List<SqlGeometry> CutPolygonByZoomedPixelZeroTile(SqlGeometry poly, int X, int Y, int Z) { return CutZoomedPixelPolygonByZeroTile(_parser.ConvertToZoomedPixelZeroedByTileGeometry(poly,Z,X,Y); }
      
      





GeometryParser «» — X, Y. , :



 /// <summary ///            /// </summary> /// <param name="shape">    </param> /// <param name="zoom">  </param> /// <param name="tileX">   </param> /// <param name="tileY">Y  </param> /// <returns>        </returns> public SqlGeometry ConvertToZoomedPixelZeroedByTileGeometry(SqlGeometry shape,int zoom, int tileX,int tileY) { return CreateGeometryFromZoomedPixelInfo (ConvertToGeometryZoomedPixelsZeroTileShiftedInfo( GetGeometryInfo(shape), zoom, tileX, tileY)); } /// <summary ///            /// </summary> /// <param name="info">   </param> /// <param name="zoom">  </param> /// <param name="x">   </param> /// <param name="y">Y  </param> /// <returns>       </returns> private GeometryZoomedPixelsInfo ConvertToGeometryZoomedPixelsZeroTileShiftedInfo (GeometryInstanceInfo info, int zoom, int x, int y) { int tilezeroshiftX = x*TILE_SIZE; int tilezeroshiftY = y*TILE_SIZE; var result = new GeometryZoomedPixelsInfo(); var pixelCoordsListList = new List<List<GeometryPixelCoords>>(); var geomPixCoordsList = new List<GeometryPixelCoords>(); var coords = new GeometryPixelCoords {InnerRing = false}; OpenGisGeometryType type = info.ShapeType; result.ShapeType = type; switch (type) { case OpenGisGeometryType.Point: PointF[] geopoints = info.Points[0][0].PointList; coords.PixelCoordList = new[] {new Point{X = _conv.FromLongitudeToXPixel(geopoints[0].X, zoom) - tilezeroshiftX, Y = _conv.FromLatitudeToYPixel(geopoints[0].Y, zoom) - tilezeroshiftY } }; geomPixCoordsList.Add(coords); pixelCoordsListList.Add(geomPixCoordsList); break; case OpenGisGeometryType.LineString: coords.PixelCoordList = GetPixelCoordsShifted( info.Points[0][0].PointList, zoom, tilezeroshiftX, tilezeroshiftY); geomPixCoordsList.Add(coords); pixelCoordsListList.Add(geomPixCoordsList); break; case OpenGisGeometryType.Polygon: foreach (var list in info.Points) foreach (GeometryPointSequence pointseq in list) { coords.PixelCoordList = GetPixelCoordsShifted(pointseq.PointList, zoom, tilezeroshiftX, tilezeroshiftY); coords.InnerRing = pointseq.InnerRing; geomPixCoordsList.Add(coords); } pixelCoordsListList.Add(geomPixCoordsList); break; case OpenGisGeometryType.MultiPoint: case OpenGisGeometryType.MultiLineString: case OpenGisGeometryType.MultiPolygon: pixelCoordsListList = GetGeometryPixelCoordsShifted(info.Points, zoom, tilezeroshiftX, tilezeroshiftY); break; case OpenGisGeometryType.GeometryCollection: GeometryInstanceInfo[] geomColl = info.GeometryInstanceInfoCollection; int n = info.GeometryInstanceInfoCollection.Length; var geomPixZoomInfoCollection = new GeometryZoomedPixelsInfo[n]; for (int i = 0; i < n; i++) { var geom = new GeometryZoomedPixelsInfo(); geom.ShapeType = geomColl[i].ShapeType; geom.Points = GetGeometryPixelCoordsShifted(geomColl[i].Points, zoom, tilezeroshiftX, tilezeroshiftY); geomPixZoomInfoCollection[i] = geom; } result.GeometryInstanceInfoCollection = geomPixZoomInfoCollection; break; } if (type != OpenGisGeometryType.GeometryCollection) result.Points = pixelCoordsListList; return result; }
      
      





ShapeToTileRendering CutZoomedPixelPolygonByZeroTile() . . , poly , .



  private List<SqlGeometry> CutZoomedPixelPolygonByZeroTile(SqlGeometry poly, int X, int Y) { List<SqlGeometry> result = new List<SqlGeometry>(); SqlGeometry stroke = null; SqlGeometry contour; SqlGeometry tileLineString; SqlGeometry tobecut; SqlGeometry tile = _conv.GetTilePixelBound(0, 0, 1); var tiled = poly.STIntersection(tile); result.Add(tiled); switch (GetOpenGisGeometryType(tiled)) { case OpenGisGeometryType.Polygon: //         MULTILINESTRING contour = PolygonToMultiLineString(tiled); //        tileLineString = tile.ToLineString(); tobecut = contour.STIntersection(tileLineString); stroke = contour.STDifference(tobecut); break; case OpenGisGeometryType.MultiPolygon: //         MULTILINESTRING contour = MultiPolygonToMultiLineString(tiled); //        tileLineString = tile.ToLineString(); tobecut = contour.STIntersection(tileLineString); stroke = contour.STDifference(tobecut); break; } result.Add(stroke); return result; }
      
      







. tile.FillShapeTiles , @GeoData @FolderPath .

CLR :



BitmapFunctions SQL CLR SqlBitmapOperation :

ShapeTile() PNG xTile, yTile:



ShapeTile()
 [SqlFunction] public static SqlBinary ShapeTile(SqlGeometry shape, SqlInt32 zoom, SqlInt32 xTile, SqlInt32 yTile, SqlString argbFill,SqlString argbStroke,SqlInt32 strokeWidth) { SqlBinary result = null; using (ShapeToTileRendering paster = new ShapeToTileRendering()) { using (MemoryStream ms = new MemoryStream()) { try { paster.DrawPartObjectShapeOnTile(shape, (int) xTile, (int) yTile, (int) zoom, argbFill.ToString(), argbStroke.ToString(), (int) strokeWidth); result = paster.GetBytes(); } catch (System.Exception ex) { string innerMessage = ex.InnerException.Message; throw new Exception(string.Format("zoom: {1}; X:{2}; Y:{3} {0} , inner: {4}", shape, zoom, xTile,yTile, innerMessage)); } return result; } } }
      
      









SqlBitmapOperation TileRendering .

.NET TileRendering .NET :

  • システム
  • Microsoft.SqlServer.Types
  • System.Drawing




msdn.microsoft.com/en-us/library/ms345099.aspx

SqlBitmapOperation TileRendering , , :

 CREATE ASSEMBLY [Microsoft.SqlServer.Types] AUTHORIZATION [dbo] FROM 'd:\SQLCLR\BIN\TileRendering\Microsoft.SqlServer.Types.dll' WITH PERMISSION_SET = UNSAFE GO CREATE ASSEMBLY [System.Drawing] AUTHORIZATION [dbo] FROM 'd:\SQLCLR\BIN\TileRendering\ System.Drawing.dll' WITH PERMISSION_SET = UNSAFE GO CREATE ASSEMBLY [TileRendering] AUTHORIZATION [dbo] FROM 'd:\SQLCLR\BIN\TileRendering\TileRendering.dll' WITH PERMISSION_SET = UNSAFE GO CREATE ASSEMBLY nQuant.Core FROM 'd:\SQLCLR\BIN\TileRendering\ nQuant.Core.dll' WITH PERMISSION_SET = UNSAFE GO CREATE ASSEMBLY SqlBitmapOperation FROM 'd:\SQLCLR\BIN\TileRendering\SqlBitmapOperation.dll' WITH PERMISSION_SET = UNSAFE GO
      
      







SqlBitmapOperation nQuant.Core . PNG 8 .



SqlGeometry Microsoft.SqlServer.Types , Microsoft.SqlServer.Types .

System.Drawing – GDI+ c , EXTERNAL_ACCESS , Sytem.Drawing . , EXTERNAL_ACCESS UNSAFE T-SQL :

 ALTER DATABASE [dataBaseName] SET TRUSTWORTHY ON;
      
      







CLR , , :

 CREATE AGGREGATE [tile].[TileAgg] (@Value [varbinary](max)) RETURNS[varbinary](max) EXTERNAL NAME [SqlBitmapOperation].[TileAgg] GO CREATE AGGREGATE [tile].[IconTileAgg] (@Value [varbinary](max), @PixelX [int], @PixelY [int]) RETURNS[varbinary](max) EXTERNAL NAME [SqlBitmapOperation].[IconTileAgg] GO CREATE FUNCTION [tile].[IconTile](@image [varbinary](max), @zoom [int], @Lon [float], @Lat [float], @xTile [int], @yTile [int], @scale [float]) RETURNS [varbinary](max) WITH EXECUTE AS CALLER AS EXTERNAL NAME [SqlBitmapOperation].[BitmapFunctions].[IconTile] GO --ShapeTile(SqlGeometry shape, SqlInt32 zoom, SqlInt32 xTile, SqlInt32 yTile, SqlString argbFill,SqlString argbStroke,SqlInt32 strokeWidth) CREATE FUNCTION [tile].[ShapeTile](@shape GEOMETRY, @zoom [int], @xTile [int], @yTile [int], @argbFill NVARCHAR(10),@argbStroke NVARCHAR(10), @strokeWidth INT) RETURNS [varbinary](max) WITH EXECUTE AS CALLER AS EXTERNAL NAME [SqlBitmapOperation].[BitmapFunctions].[ShapeTile] GO --SaveToFolderByZoomXY(SqlBinary image, SqlString rootFolderPath, SqlInt32 Zoom, SqlInt32 X,SqlInt32 Y) CREATE FUNCTION tile.SaveToFolderByZoomXY(@image VARBINARY(MAX),@rootFolderPat NVARCHAR(512) , @Zoom [int], @xTile [int], @yTile [int]) RETURNS BIT WITH EXECUTE AS CALLER AS EXTERNAL NAME [SqlBitmapOperation].[BitmapFunctions].[SaveToFolderByZoomXY] GO
      
      







ShapeToTileRendering . 4326 . GeometryParser, PSG3857, . PastShapeOnTile , geom . 256 .

  void PasteShapeOnTile(Color fillcolor,Color strokecolor, int width, List<SqlGeometry> geom) { SqlGeometry shape = geom[0]; int geomnum = (int)shape.STNumGeometries(); SqlGeometry stroke = null; SqlGeometry ring; int intnum; if (geom != null) switch (GetOpenGisGeometryType(shape)) { case OpenGisGeometryType.LineString: case OpenGisGeometryType.MultiLineString: DrawMultiLineStringBordered2(shape, fillcolor, strokecolor, width, 1); break; case OpenGisGeometryType.Polygon: intnum = (int)shape.STNumInteriorRing(); ring = shape.STExteriorRing(); // 1.      FillPolygonOnTile(fillcolor, ring.ToPointsArray()); // 2.    if (geomnum >= 1) stroke = geom[1]; for (int i = 1; i <= intnum; i++) { FillTransparentPolygonOnTile(shape.STInteriorRingN(i).ToPointsArray()); } // 3.   if (geom.Count > 1) { stroke = geom[1]; DrawContourOnTile(stroke, strokecolor, width); } break; case OpenGisGeometryType.MultiPolygon: break; } }
      
      







tile.FillShapeTiles .

tile.FillShapeTiles
 CREATE PROC tile.FillShapeTiles @GeoData GEOMETRY, @fillArgb VARCHAR(20),@strokeArgb VARCHAR(20), @FolderPath NVARCHAR(20), @EndZoom INT = 17, @StartZoom INT = 4, @Thickness INT = 2 AS BEGIN IF @EndZoom < @StartZoom OR @GeoData IS NULL RETURN INSERT INTO tile.tile (Zoom, X,Y,Data) SELECT t.Zoom, t.TileX AS X,t.TileY AS Y, tile.ShapeTile(@GeoData, t.Zoom, t.TileX, t.TileY, @fillArgb, @strokeArgb ,@Thickness) AS Data FROM (SELECT * FROM tile.fn_FetchGeometryTilesZoomDepth(@GeoData,@StartZoom, @EndZoom - @StartZoom)) t SELECT tile.SaveToFolderByZoomXY (Data, @FolderPath ,Zoom,X,Y) FROM tile.Tile END
      
      









, 100 000 , . . , CLR .



tile.FillShapeTilesIntersection() CLR tile.ShapeTile() .PNG , . CLR . , CLR tile.TileAgg(@Data VARBINARY(MAX)) , .PNG , VABINARY(MAX).

CLR :

  • Init();
  • Accumulate(value);
  • Merge(Agg);
  • Terminate()


SQL CLR
 //------------------------------------------------------------------------------ // <copyright file="CSSqlAggregate.cs" company="Microsoft"> // Copyright (c) Microsoft Corporation. All rights reserved. // </copyright> //------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Data.SqlTypes; using Microsoft.SqlServer.Server; using TileRendering; using System.IO; using System.Drawing; using System.Drawing.Imaging; [Serializable] [SqlUserDefinedAggregate(Format.UserDefined, IsInvariantToDuplicates = true, IsInvariantToNulls = true, IsInvariantToOrder = false, IsNullIfEmpty = false, MaxByteSize = -1)] public struct TileAgg : IBinarySerialize { Bitmap _bitmap; ImageFormat _format; Graphics _graphics; ImageCodecInfo _codecInfo; const int TILE_SIZE = 256; Bitmap GetInitialTile() { Bitmap DrawArea = new Bitmap(TILE_SIZE, TILE_SIZE); using (Graphics xGraph = Graphics.FromImage(DrawArea)) { xGraph.FillRectangle(Brushes.Transparent, 0, 0, TILE_SIZE, TILE_SIZE); _graphics = Graphics.FromImage(DrawArea); return DrawArea; } } #region [Aggregate artifacts] public void Init() { _codecInfo = GetEncoderInfo("image/png"); _bitmap = GetInitialTile(); DetectFormat(); } public void Accumulate(SqlBinary Value) { using (MemoryStream ms = new MemoryStream()) { ms.Write(Value.Value, 0, Value.Length); ms.Seek(0, SeekOrigin.Begin); ms.Position = 0; PasteFromStreamImageToTile( ms); } } public void Merge(TileAgg Group) { PasteGroup(Group.Terminate()); } public SqlBinary Terminate() { return GetBytes(); } #endregion [Aggregate artifacts] void PasteFromStreamImageToTile( Stream stream) { using (Bitmap iconImage = new Bitmap(stream, false)) { DetectFormat(); int width = iconImage.Width; int height = iconImage.Height; var area = new Rectangle(0, 0, width, height); CopyRegionIntoImage(iconImage,area, area); } } void CopyRegionIntoImage(Bitmap srcBitmap, Rectangle srcRegion, Rectangle destRegion) { _graphics.DrawImage(srcBitmap, destRegion, srcRegion, GraphicsUnit.Pixel); srcBitmap.Dispose(); } void PasteGroup(SqlBinary Value) { using (MemoryStream ms = new MemoryStream()) { ms.Write(Value.Value, 0, Value.Length); ms.Seek(0, SeekOrigin.Begin); ms.Position = 0; PasteTile(ms); } } void PasteTile(Stream stream) { Rectangle bounds = new Rectangle(0, 0, TILE_SIZE, TILE_SIZE); CopyRegionIntoImage(new Bitmap(stream), bounds, bounds); } byte[] GetBytes() { return _bitmap.ToByteArray(ImageFormat.Png); } #region [IBinarySerialize] public void Read(BinaryReader reader) { _bitmap = new Bitmap(new MemoryStream(reader.ReadBytes((int)reader.BaseStream.Length))); DetectFormat(); } public void Write(BinaryWriter writer) { EncoderParameters encodeParams = new EncoderParameters(1); encodeParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100); _bitmap.Save(writer.BaseStream, _codecInfo, encodeParams); } #endregion [IBinarySerialize] /// <summary> ///    /// </summary> void DetectFormat() { _format = _bitmap.GetImageFormat(); } ImageCodecInfo GetEncoderInfo(string mimeType) { //     string lookupKey = mimeType.ToLower(); ImageCodecInfo foundCodec = null; Dictionary<string, ImageCodecInfo> encoders = Encoders(); if (encoders.ContainsKey(lookupKey)) { //    foundCodec = encoders[lookupKey]; } return foundCodec; } private Dictionary<string, ImageCodecInfo> Encoders() { Dictionary<string, ImageCodecInfo> encoders = new Dictionary<string, ImageCodecInfo>(); foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()) { encoders.Add(codec.MimeType.ToLower(), codec); } return encoders; } }
      
      







tile.FillShapeTilesIntersection tile.Shape. @StartZoom – , @EndZoom — . tile.Shapes.fillArgb tile.Shapes.strokeArgb . : AARRGGBB ,

AA – (), RR – , GG — , BB – . : DDDDFFDD.



 CREATE PROC tile.FillShapeTilesIntersection( @StartZoom INT, @EndZoom INT) AS BEGIN DECLARE @Shape GEOMETRY DECLARE @CurrentZoom INT DECLARE @ObjectTypeID INT DECLARE @fillArgb NVARCHAR(10), @strokeArgb NVARCHAR(10) IF @ObjectTypeID IS NOT NULL BEGIN SET @CurrentZoom = @StartZoom DECLARE shape_cursor CURSOR FOR SELECT o.Shape, fillARGB, strokeARGB FROM tile.Shape o OPEN shape_cursor FETCH NEXT FROM shape_cursor INTO @Shape, @fillArgb, @strokeArgb WHILE @@FETCH_STATUS = 0 BEGIN SET @CurrentZoom = @StartZoom WHILE @CurrentZoom <= @EndZoom BEGIN INSERT INTO tile.tileOverlap (Zoom, X,Y,Data) SELECT t.Zoom, t.TileX AS X,t.TileY AS Y, tile.ShapeTile(@Shape, t.Zoom, t.TileX, t.TileY, @fillArgb, @strokeArgb ,2) AS Data FROM (SELECT * FROM tile.fn_FetchGeometryTiles(@Shape,@CurrentZoom)) t SET @CurrentZoom = @CurrentZoom + 1 END FETCH NEXT FROM shape_cursor INTO @Shape, @fillArgb, @strokeArgb END CLOSE shape_cursor; DEALLOCATE shape_cursor; DELETE tile.TileOverlap END END
      
      









おわりに






要約すると、ここで説明するタイル形成ライブラリはプロトタイプにすぎないことに注意してください。もちろん、データベース側でタイルを生成するのはかなり奇妙なアプローチであり、多くの教育的代替手段があります。しかし、この記事を最後まで読んだ人は、電子地図のタイルモデルの設計についての考えを持ち、SQL Serverの空間データ型の使用方法を知っています。

github ライブラリ



のソースコードライブラリおよびバックアップデータベースのソースコード3.5Mb



All Articles