この記事は、シンプルで生産的な動的Webマップサーバーのプロトタイピングを説明するシリーズの続きで書かれました。 前に 、空間インデックスの配置方法と、空間レイヤーを取得および描画する方法について説明しました 。 これからもう少しエレガントにしましょう。
1つのレイヤーの操作に関しては、多かれ少なかれわかりましたが、実際には何十ものレイヤーが存在する可能性があり、検索範囲に何も入らなくても、それぞれが空間インデックスで要求される必要があります...このプロセスをアーキテクチャ上きれいにし、明らかに役に立たないアクションを取り除くには?
索引付け
可能です、そうではありません。 ただし、このためには、すべての空間レイヤーに対して単一の空間インデックスを作成する必要があります。 インデックスの構造を思い出してください:
- レイヤースペースは長方形のブロックに分割されます
- すべてのブロックには番号が付けられています。
- 各インデックス付きオブジェクトには、それが配置されている1つ以上のブロック番号が割り当てられます
- 検索時、クエリの範囲はブロックに分割され、各ブロックまたは通常の整数インデックスからのブロックの連続チェーンについて、それらに属するすべてのオブジェクトが取得されます
単一のインデックスを作成するには、すべてのレイヤーを1つのグリッドで分割し、同じ方法でブロックに番号を付け、オブジェクト識別子に加えて、テーブル識別子もインデックスに保存する必要があります。 だから:
- OSM Russiaから取得した形状データについて、以前と同様にトレーニングします。
- 池、森林、建物、道路など、比較的人口の多い4つの層を取ります。
- 前と同じように、 OpenLink Virtuosoの OpenSourceエディション、バージョン7.0.0の公式Win_x64ビルドを使用します。これは、以前と同様に開発者のサイトから取得したものです。
- 描画には、組み込みのPL / SQL関数のセットを拡張するGDベースの前述のCプラグインを使用します。
- データ範囲を取ります:xmin = -180、ymin = 35、xmax = 180、ymax = 85度。
- この範囲を緯度と経度で0.1°のブロックに分割します。 したがって、3600X500ブロックのグリッドが得られます。 0.1°は約11 kmで、家のレベルでの描画には少し長めですが、現時点での目標は、特定の条件での最大パフォーマンスではなく、コンセプトチェックです。
- 前と同じようにDBMSにレイヤーをロードします。
- vegetation-polygon.shp:657673要素、ロード時間2 '4' '
- water-polygon.shp:380868要素、ロード時間1 '22' '
- building-polygon.shp:5326421要素、ロード時間15 '42' '
- highway-line.shp:2599083要素、ロード時間7 '56' '
- 現在、データを持つテーブル(例: "xxx"。 "YYY"。 "水ポリゴン")と空間インデックスを持つ関連テーブル(例: "xxx"。 "YYY"。 "水ポリゴン__spx")。 それらがすべて同じパラメーターで形成されているという事実を利用して、単一の空間インデックスにそれらをマージします
一般的なインデックスの作成には2'16 ''かかります。 コンストラクト(挿入...選択...)の代わりにカーソルを使用するのは、OpenSourceバージョンでは、ログ内の1つのトランザクションのログのサイズが50 MBに制限されているためです。 この制限は、再組み立てによって簡単に解消されますが、それは別の話です。registry_set ('__spx_startx', '-180'); registry_set ('__spx_starty', '35'); registry_set ('__spx_nx', '3600'); registry_set ('__spx_ny', '500'); registry_set ('__spx_stepx', '0.1'); registry_set ('__spx_stepy', '0.1'); drop table "xxx"."YYY"."total__spx"; create table "xxx"."YYY"."total__spx" ( "node_" integer, "tab_" integer, "oid_" integer, primary key ("node_", "tab_", "oid_")); create procedure mp_total_spx () { for select "node_", "oid_" from "xxx"."YYY"."water-polygon__spx" do { insert into "xxx"."YYY"."total__spx" ("oid_","tab_","node_") values ("oid_", 1, "node_"); commit work; } for select "node_", "oid_" from "xxx"."YYY"."vegetation-polygon__spx" do { insert into "xxx"."YYY"."total__spx" ("oid_","tab_","node_") values ("oid_", 2, "node_"); commit work; } for select "node_", "oid_" from "xxx"."YYY"."highway-line__spx" do { insert into "xxx"."YYY"."total__spx" ("oid_","tab_","node_") values ("oid_", 3, "node_"); commit work; } for select "node_", "oid_" from "xxx"."YYY"."building-polygon__spx" do { insert into "xxx"."YYY"."total__spx" ("oid_","tab_","node_") values ("oid_", 4, "node_"); commit work; } }; mp_total_spx ();
- これで、以前と同じ方法でインデックスを操作するように設定します
create procedure "xxx"."YYY"."total__spx_enum_items_in_box" ( in minx double precision, in miny double precision, in maxx double precision, in maxy double precision) { declare nx, ny integer; nx := atoi (registry_get ('__spx_nx')); ny := atoi (registry_get ('__spx_ny')); declare startx, starty double precision; startx := atof (registry_get ('__spx_startx')); starty := atof (registry_get ('__spx_starty')); declare stepx, stepy double precision; stepx := atof (registry_get ('__spx_stepx')); stepy := atof (registry_get ('__spx_stepy')); declare sx, sy, fx, fy integer; sx := cast (floor ((minx - startx)/stepx) as integer); fx := cast (floor ((maxx - startx)/stepx) as integer); sy := cast (floor ((miny - starty)/stepy) as integer); fy := cast (floor ((maxy - starty)/stepy) as integer); declare ress any; ress := vector(dict_new (), dict_new (), dict_new (), dict_new ()); for (declare iy integer, iy := sy; iy <= fy; iy := iy + 1) { declare ixf, ixt integer; ixf := nx * iy + sx; ixt := nx * iy + fx; for select "node_", "tab_", "oid_" from "xxx"."YYY"."total__spx" where "node_" >= ixf and "node_" <= ixt do { dict_put (ress["tab_" - 1], "oid_", 0); } } result_names('oid', 'tab'); declare ix integer; for (ix := 0; ix < 4; ix := ix + 1) { declare arr any; arr := dict_list_keys (ress[ix], 1); gvector_digit_sort (arr, 1, 0, 1); foreach (integer oid in arr) do { result (oid, ix + 1); } } }; create procedure view "xxx"."YYY"."v_total__spx_enum_items_in_box" as "xxx"."YYY"."total__spx_enum_items_in_box" (minx, miny, maxx, maxy) ("oid" integer, "tab" integer);
- 正しい動作を確認します。
select count(*) from "xxx"."YYY"."v_total__spx_enum_items_in_box" as a where a.minx = 82.963 and a.miny = 54.9866 and a.maxx = 82.98997 and a.maxy = 55.0133; select count(*) from "xxx"."YYY"."v_vegetation-polygon_spx_enum_items_in_box" ... select count(*) from "xxx"."YYY"."v_water-polygon_spx_enum_items_in_box" ... select count(*) from "xxx"."YYY"."v_building-polygon_spx_enum_items_in_box" ... select count(*) from "xxx"."YYY"."v_highway-line_spx_enum_items_in_box" ...
インデックスのパフォーマンスを測定します。
一般的なインデックスの古い[35 ... 45.50 ... 60]°の正方形で、10,000回のランダム検索を実行します。 すべてのリクエストは1つの接続で実行されます。 シングルコアでパフォーマンスを表示します。
リクエストサイズ | 単一インデックスの時間 | 昔 |
---|---|---|
0.01° | 7'35 '' | 8'26 '' |
0.1° | 8'25 '' | 9'20 '' |
0.5° | 15'11 '' | 16'7 '' |
地図を描く
まず最初に、 古い方法でそれを描画しますので、比較するものがあります。
create procedure mk_test_gif( in cminx double precision, in cminy double precision, in cmaxx double precision, in cmaxy double precision) { declare img any; img := img_create (512, 512, cminx, cminy, cmaxx, cmaxy, 1); declare cl integer; declare bg integer; { cl := img_alloc_color (img, 0, 0, 255); bg := img_alloc_color (img, 0, 0, 255); whenever not found goto nf; for select blob_to_string(Shape) as data from xxx.YYY."water-polygon" as x, xxx.YYY."v_water-polygon_spx_enum_items_in_box" as a where a.minx = cminx and a.miny = cminy and a.maxx = cmaxx and a.maxy = cmaxy and x."_OBJECTID_" = a.oid and x.maxx_ >= cminx and x.minx_ <= cmaxx and x.maxy_ >= cminy and x.miny_ <= cmaxy do { img_draw_polygone (img, data, cl, bg); } nf:; ... , img_draw_polyline. ... } declare ptr integer; ptr := img_tostr (img); img_destroy (img); declare image any; image := img_fromptr(ptr); string_to_file('test.gif', image, -2); return; }; mk_test_gif(35., 50., 45., 60.);
作業には17.2秒かかります。
さて、約束どおり、1回の選択で。 頭に浮かぶ最初のもの
集合体 。
集計は、カーソルが開かれたときに作成され、結果の各行で呼び出され、カーソルが閉じられたときにファイナライズされるオブジェクトです。
create function mapx_agg_init (inout _agg any) {;}; create function mapx_agg_acc ( inout _agg any, in _tab integer, in _oid integer ) { if (_agg is null) { declare img any; img := img_create (512, 512, _tab[0], _tab[1], _tab[2], _tab[3], 1); _agg := img; return 0; } else { return case when _tab = 4 then (img_draw_polygone(_agg, ( select blob_to_string(bl.Shape) from "xxx"."YYY"."building-polygon" as bl where bl."_OBJECTID_" = _oid), 255, 255)) when _tab = 3 then (img_draw_polyline(_agg, ( select blob_to_string(hw.Shape) from "xxx"."YYY"."highway-line" as hw where hw."_OBJECTID_" = _oid), 100, 100)) when _tab = 2 then (img_draw_polygone(_agg, ( select blob_to_string(vg.Shape) from "xxx"."YYY"."vegetation-polygon" as vg where vg."_OBJECTID_" = _oid), 10, 10)) when _tab = 1 then (img_draw_polygone(_agg, ( select blob_to_string(wt.Shape) from "xxx"."YYY"."water-polygon" as wt where wt."_OBJECTID_" = _oid), 50, 50)) else 1 end; } }; create function mapx_agg_final (inout _agg any) returns integer { declare ptr integer; ptr := img_tostr (_agg); img_destroy (_agg); declare image any; image := img_fromptr(ptr); string_to_file('nskx_ii.gif', image, -2); return 1; }; create aggregate mapx_agg (in _tab integer, in _oid integer) returns integer from mapx_agg_init, mapx_agg_acc, mapx_agg_final; create procedure mk_testx_ii_gif( in cminx double precision, in cminy double precision, in cmaxx double precision, in cmaxy double precision) { declare cnt integer; select mapx_agg(tab, oid) into cnt from ( select * from (select vector(cminx, cminy, cmaxx, cmaxy) as tab, 0 as oid) as f1 union all (select tab, oid from xxx.YYY."v_total__spx_enum_items_in_box" as a where a.minx = cminx and a.miny = cminy and a.maxx = cmaxx and a.maxy = cmaxy) ) f_all; } mk_testx_ii_gif(35., 50., 45., 60.);
残念ながら、初期化パラメーターを集約に渡すための通常の方法はありません。そのため、データと初期化文字列から結合を取り除いて、コンストラクターではなく、最初の行を取得するときに、トリックに移動する必要があります。
どうしてそれができるのか、注意深い読者は、1つの選択が約束されたと言うでしょう、そして、それらの全部があります! 実際、行識別子は集計に整然と表形式で送られるため、見かけのサブクエリは実際には結合ハンドによって編成されます。
したがって、実行時間は42秒です。 Nda。
別の試み
create procedure mk_testx_gif( in cminx double precision, in cminy double precision, in cmaxx double precision, in cmaxy double precision) { declare img any; img := img_create (512, 512, cminx, cminy, cmaxx, cmaxy, 1); declare cnt, cnt2 integer; declare cl1, bg1 integer; cl1 := img_alloc_color (img, 0, 0, 255); bg1 := img_alloc_color (img, 0, 0, 255); declare cl2, bg2 integer; cl2 := img_alloc_color (img, 0, 255, 0); bg2 := img_alloc_color (img, 0, 255, 0); declare cl3, bg3 integer; cl3 := img_alloc_color (img, 255, 100, 0); bg3 := img_alloc_color (img, 255, 100, 0); declare cl4, bg4 integer; cl4 := img_alloc_color (img, 255, 0, 0); bg4 := img_alloc_color (img, 255, 0, 0); select sum ( case when geom is null then 0 when geom_type = 2 then (img_draw_polyline(img, geom, cl, bg)) else (img_draw_polygone(img, geom, cl, bg)) end ) into cnt from ( select case when a.tab = 4 then ( select blob_to_string(bl.Shape) from "xxx"."YYY"."building-polygon" as bl where bl."_OBJECTID_" = a.oid) when a.tab = 3 then ( select blob_to_string(hw.Shape) from "xxx"."YYY"."highway-line" as hw where hw."_OBJECTID_" = a.oid) when a.tab = 2 then ( select blob_to_string(vg.Shape) from "xxx"."YYY"."vegetation-polygon" as vg where vg."_OBJECTID_" = a.oid) when a.tab = 1 then ( select blob_to_string(wt.Shape) from "xxx"."YYY"."water-polygon" as wt where wt."_OBJECTID_" = a.oid) else '' end as geom, case when a.tab = 3 then 2 else 1 end as geom_type, case when a.tab = 4 then cl4 when a.tab = 3 then cl3 when a.tab = 2 then cl2 when a.tab = 1 then cl1 else 0 end as cl, case when a.tab = 4 then bg4 when a.tab = 3 then bg3 when a.tab = 2 then bg2 when a.tab = 1 then bg1 else 0 end as bg from xxx.YYY."v_total__spx_enum_items_in_box" as a where a.minx = cminx and a.miny = cminy and a.maxx = cmaxx and a.maxy = cmaxy ) f_all; declare ptr integer; ptr := img_tostr (img); img_destroy (img); declare image any; image := img_fromptr(ptr); string_to_file('testx.gif', image, -2); return; }; mk_testx_gif(35., 50., 45., 60.);
このクエリは17.4秒実行されます。 したがって、オプティマイザーは、隠れた結合を認識し、美しさをあまり失うことなく要求を実行することができました。 インデックス自体の作業のわずかな増加は、リクエストの複雑さの増加によって食い尽くされました。
結果は次のとおりです。
この説明のない画像には数百万のオブジェクトがあります。
結論
1つのリクエストで地図を描くことは難しくありません。 パフォーマンスをあまり失うことなく、これを実行することさえできました。 確かに、それも勝つことに失敗し、その理由はデータ構造にあるようです。 また、この構造は、従来のGISで使用するために強化されています。
たとえば、テーブル「highway-line」には、属性が異なるさまざまなタイプのレイヤーが数十個含まれています。 通常、このようなテーブルは、同じ物理テーブルを参照し、フィルターが異なるすべての道路レイヤーの基礎として機能します。 もちろん、2ダースよりも1つのテーブルで作業する方が便利です(この点もこの作業の動機の1つでした)。 繰り返しますが、これらすべてのレイヤーに共通の空間インデックスがあります。
しかし、欠点もあります。 それでも、各レイヤーを描画するには、個別のSQLクエリを実行する必要があります。 つまり 1つのインデックスがありますが、それでもいくつかの検索があります。 最も勝てるのはページのキャッシュです。 追加のインデックスが必要です-レコードのタイプ。それを検索してサンプルを横断することも必要です。 さらに、異なるタイプのオブジェクトが混在しているため、同じタイプのオブジェクトが並んで(1ページに)表示される可能性が低く、それによって読み取りの総数が増加します。
たとえば、上記のように、「高速道路」テーブルをタイプごとに多数のサブテーブルに分散し、それらすべてを1つの空間インデックスに結合するとどうなりますか? インデックスの操作はこれから変更されることはなく、インデックスを1回検索するだけで済みます。 データの操作は加速するだけです データの局所性が向上します。同じタイプのデータが空間的に近い頻度でディスクに並んで表示されます。 また、検索範囲にデータの種類がない場合、単に処理されません。 どれだけ多くても、有用なデータの読み取りには影響しません。
そして最後の観測。 インデックス自体はかなり奇妙なオブジェクトです。 関係代数にも関係計算にも近いものはありません。 これは、クエリプロセッサがそれらをより効率的に実行できるようにする実装固有の拡張機能です。 私たちの場合、インデックスにはデータにないいくつかのセマンティクスがロードされます。 マルチテーブルインデックスは、レイヤー間の関係を表します。実際、特定のインデックスには、一緒に描画したいレイヤーが含まれます。
一方、インデックスをテーブルとして認識することはできません(実際はテーブルですが、これは特定のDBMS内に留まることを余儀なくされるため、これは必要な手段です)。 その値はテーブル識別子です。 これはメタテーブルであり、クエリプランはこのメタデータに依存します。
繰り返しますが、伝統的に、クエリプロセッサは、使用するインデックスを自由に選択できます。 しかし、これは私たちの場合ではありません。 いくつかのマルチテーブルインデックスを作成し、プライマリデータストリームのソースであるインデックスを明示的に示すことができます。 オプティマイザーがそれについてどう考えているかに関係なく。 関係モデルの「ドライ理論」を通して「生命の木は常に緑」であることがわかります。