2021年7月5日月曜日

国土交通省が提供している行政区域データをマップ・リージョンのレイヤとして表示する

 国土交通省が提供している国土数値情報の行政区域データを、Oracle APEX 21.1のマップ・リージョンのレイヤとして表示させてみました。

使用したデータは以下のサイトよりダウンロードしています。

国土交通省のGISホームページ国土数値情報ダウンロードにアクセスし、行政区域(ポリゴン)のページを開きます。


(画面は2021年6月30日時点のものです)

全国の情報をダウンロードしました。



国土数値情報ダウンロードサイトコンテンツ利用規約はこちらになります。データを利用する前に目を通しておきましょう。

作業はVirtualBox上に作成したOracle Database 18c Express Edition + Oracle APEX 21.1の環境で実施しています。

ダウンロードしたデータを取り込む


全国のデータはN03-20210101_GML.zipとしてダウンロードされました。このZIP圧縮ファイルに含まれるGeoJSON形式のファイルN03-21_210101.geojsonをデータベースに取り込みます。ダウンロードしたZIPファイルはあらかじめ解凍しておきます。

GeoJSONのファイルを以下の手順でデータベースに取り込みます。
  1. BLOB列を持つ表を作成する。
  2. GeoJSONのファイルをBLOB列に保存する。
  3. GeoJSONのファイルに含まれるポリゴンを、Oracle SpatialのSDO_GEOMETRY型で保存する表を作成する。
  4. 保存したGeoJSONのファイルに含まれるポリゴンをOracle SpatialのSDO_GEOMETRY型に変換し、ポリゴン1つを1行として表に書き込む。
以下より、作業手順を記述します。

最初にGeoJSONのファイルをアップロードする表を作成します。SQLワークショップクイックSQLより、表JAR_DATAを定義します。クイックSQLの定義は以下です。

# prefix: jar
# semantics: default
data
content file

SQLの生成SQLスクリプトを保存レビューおよび実行を行います。


続く画面ではアプリケーションの作成ではなく実行を行い、遷移した画面で即時実行をクリックします。


JAR_DATAが作成された時点で、アプリケーションの作成を実行します。確認画面がポップアップするので、そこでもアプリケーションの作成をクリックします。


アプリケーション作成ウィザードのページが開きます。名前行政区域とします。アプリケーションの作成を実行します。


アプリケーションが作成されたら、すぐに実行します。とりあえずGeoJSONのファイルをデータベースにアップロードします。ワークスペースにサインインしたユーザーにてアプリケーションにサインインし、Dataのページから作成を実行します。


Contentとして、GeoJSONのファイルN03-01_210101.geojsonを選択し、作成をクリックします。


とにかくデータベースにBLOBとして保存されていればよいので、設定の調整はしていません。1行保存されていることが確認できれば、今回の用途としては十分です。


SQLワークショップSQLコマンドより以下のDDLを実行し、表JAR_JAPAN_REGION_ADMINSを作成します。GeoJSONのファイルに含まれているポリゴンを保存する表になります。

create table jar_japan_region_admins
(
id number primary key,
prefecture_name varchar2(20) not null, -- N03_001
branch_in_hokkaido varchar2(40), -- N03_002
major_city varchar2(20), -- N03_003
city_name varchar2(40), -- N03_004
admin_code varchar2(6) not null, -- N03_007
geometry sdo_geometry,
vertices number
);


表が作成されたら以下のSQLを実行し、GeoJSONファイルに含まれるポリゴンの情報を、表JAR_JAPAN_REGION_ADMINSにロードします。

insert into jar_japan_region_admins
(
id,
prefecture_name,
branch_in_hokkaido,
major_city, city_name,
admin_code,
geometry
)
select
rownum
,jt.N03_001 prefecture_name
,jt.N03_002 branch_in_hokkaido
,jt.N03_003 major_city
,jt.N03_004 city_name
,nvl(jt.N03_007,'N/A') admin_code
,jt.geometry geometry
from jar_data,
json_table(content, '$.features[*]'
columns(
N03_001 varchar2(255) path '$.properties.N03_001',
N03_002 varchar2(255) path '$.properties.N03_002',
N03_003 varchar2(255) path '$.properties.N03_003',
N03_004 varchar2(255) path '$.properties.N03_004',
N03_007 varchar2(255) path '$.properties.N03_007',
geometry sdo_geometry path '$.geometry'
)
) jt
where jar_data.id = 1;

最後にwhere jar_data.id = 1としてアップロードしたファイルを選択しています。複数回ファイルをアップロードしている場合は1でない場合もあるので、そのような場合は適時変更します。

json_tableのカラムgeometryとして、geometry sdo_geometry path '$.geometry'を指定しています。この指定によって、GeoJSONファイル内のgeometryとして与えられている座標の配列を、Oracle SpatialのSDO_GEOMETRY型の列に取り込んでいます。


今回の実行では121,158行のデータが、表JAR_JAPAN_REGION_ADMINSに取り込まれました。

ロードされたデータを検査します。マニュアルのこちらに記載されている手順に従いSDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXTファンクションを実行します。許容差0.05とします。マニュアルによると、測地データの場合は0.05m、つまり5cmより近ければ同じ点と認識されます。

とりあえず、最初の10行だけ検査します。

begin
for r in (
select geometry from jar_japan_region_admins order by id
fetch first 10 rows only
)
loop
dbms_output.put_line(sdo_geom.validate_geometry_with_context(r.geometry,0.05));
end loop;
end;

結果は以下のようになります。
13356 [Element <1>] [Coordinate <661>][Ring <1>]
13367 [Element <1>] [Ring <1>]
13367 [Element <1>] [Ring <1>]
13367 [Element <1>] [Ring <1>]
13367 [Element <1>] [Ring <1>]
13356 [Element <1>] [Coordinate <448>][Ring <1>]
13356 [Element <1>] [Coordinate <514>][Ring <1>]
13367 [Element <1>] [Ring <1>]
13367 [Element <1>] [Ring <1>]
13367 [Element <1>] [Ring <1>]
先頭の数値はORAエラーの番号です。ORA-13356、ORA-13367など、対象としたすべての行でエラーが発生しています。実は、ロードしたすべての行でエラーが発生しています。

エラーを解消するために、SDO_UTIL.RECTIFY_GEOMETRYを、すべてのロードしたデータに対して実行します。元のデータを残すため、列GEOM_Rを追加します。

alter table jar_japan_region_admins add (geom_r sdo_geometry);

データの修正を実行します。処理に時間がかかるのでsqlplusなどのコマンドラインのツール、もしくはSQLワークショップSQLスクリプトより実行することが望ましいです。

update jar_japan_region_admins set geom_r = sdo_util.rectify_geometry(geometry,0.05);

再度、検査を行います。今度は全行を対象にします。
(sqlplusより実行する場合はset serveroutput onの実行や、最後の/を忘れずに。)

declare
cnt number := 0;
l_message varchar2(4000);
begin
for r in (select id, geom_r geometry from jar_japan_region_admins order by id)
loop
l_message := sdo_geom.validate_geometry_with_context(r.geometry,0.05);
if l_message <> 'TRUE' then
dbms_output.put_line('ID: ' || r.id || ' ' || l_message);
cnt := cnt + 1;
end if;
end loop;
dbms_output.put_line('Error Count: ' || cnt);
end;

Error Countとして0が返ってくれば、データの修正は完了です。

領域を表すポリゴンがどの程度複雑なのかを確認するため、頂点の数を列VERTICESとして記録しておきます。

update jar_japan_region_admins set vertices = sdo_util.getnumvertices(geom_r);


マップを表示するページの作成


ロードされたデータを、一旦、マップ・リージョンで表示させてみます。アプリケーション・ビルダーにて、先ほど作成したアプリケーション、行政区域を開きます。

ページ作成ウィザードを起動します。アプリケーション・ビルダーよりページの作成をクリックします。


ページ・タイプコンポーネントとし、マップをクリックします。


ページ名行政区域コードとします。へ進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。へ進みます。


表/ビューの名前として、先ほどデータをロードした表JAR_JAPAN_REGION_ADMINSを選択します。へ進みます。


レイヤタイプポリゴンジオメトリ列GEOM_R (Sdo_Geometry)ツールチップ列PREFECTURE_NAME (Varchar2)を選択します。ファセット検索ページの作成ONにします。作成をクリックします。


マップ・リージョンを含んだページが作成されます。このままでは、すべてのポリゴンをマップに表示してしまいます。ファセットを追加することにより、マップ上に表示するポリゴンを制限します。

最初に都道府県を指定するファセットを作成します。左ペインのファセット上でコンテキスト・メニューを表示させ、ファセットの作成を実行します。識別名前P4_PREFECTURE_NAMEタイプとしてチェック・ボックス・グループを選択します。ラベル都道府県とします。LOVタイプ個別値NULLオプションを含めるOFFにします。このファセットのソースとして、ファセットの名前P4_PREFECTURE_NAMEより、PREFECTURE_NAMEが自動的に選ばれます。


続いて支庁のファセットを作成します。識別名前P4_BRANCH_IN_HOKKAIDO、タイプはチェック・ボックス・グループです。ラベル支庁とします。LOVタイプ個別値NULLオプションを含めるONにします。支庁は北海道だけにある行政区域なので、ほとんどの場合はNULLです。NULL表示値として- 指定なし -を入力します。ソースは自動的に選択されます。依存先ファセットとしてP4_PREFECTURE_NAMEタイプ次と等しいを選択し、北海道を入力します。


続いて郡・政令指定都市を選択するファセットを作成します。識別名前P4_MAJOR_CITYとします。その他の設定は支庁とほとんど同じですが、依存先は設定しません。


市区町村のファセットを作成します。識別名前P4_CITY_NAMEとします。それ以外はP4_MAJOR_CITYと同じです。


最後に行政区域コードのファセットを作成します。識別名前P4_ADMIN_CODEとします。列ADMIN_CODEにはデータロード時に、NULLの代わりにN/Aを投入しています。そのためNULLであることはないので、LOVNULLオプションを含めるOFFにします。


ファセットの設定については以上です。

マップの初期表示として、世界地図ではなく日本全体が表示されるようにします。マップAttributesを開き、初期位置およびズームとして、タイプ静的値経度139.69167緯度35.68944ズーム・レベル3.8を指定します。


マップ上のポリゴンの表示を調整します。外観塗りつぶしの不透明度0.3にします。ストロークの色は赤(#ff0000)とします。加えて、ツールチップ拡張フォーマットONにし、HTML式として以下を設定します。

<b>都道府県</b>: &PREFECTURE_NAME.
{if BRANCH_IN_HOKKAIDO/}<br><b>支庁</b>: &BRANCH_IN_HOKKAIDO.{endif/}
{if MAJOR_CITY/}<br><b>郡・政令指定都市</b>: &MAJOR_CITY.{endif/}
{if CITY_NAME/}<br><b>市区町村</b>: &CITY_NAME.{endif/}
{if ADMIN_CODE/}<br><b>行政区域コード</b>: &ADMIN_CODE.{endif/}

表示する項目がNULLの場合には、項目自体をツールチップから除いています。


全体で12万行を超えるデータがあるため、検索条件がないと表示に時間がかかります。パフォーマンスを調整するため、レイヤー行政区域コード処理する最大行数10000に制限します。行政区域がもっとも多い北海道のすべての行政区域が表示される行数です。


以上で一旦完了です。ページを実行して、作成したマップを表示してみます。検索条件がないと、検索件数の上限に達します。


検索条件を指定することにより、その地域の行政区域が地図に重なって表示されます。



国土交通省から入手したデータをそのまま表示することはできました。

行政区域が内陸だったり離島などが含まれていなければ、行政区域コードひとつに付き、ひとつのポリゴンが定義されています。

そうでない場合、例えば長崎県の五島市では1,674のポリゴンがひとつの行政区域に含まれています。


ジオメトリを簡略化する


行政区域の詳細な表示より表示速度を重視する場合は、SDO_UTIL.SIMPLIFY(またはSIMPLIFYVW)ファンクションを適用することができます。ポリゴンに含まれる頂点の数を少なくする(間引く)ことにより、表示のパフォーマンスが向上します。

簡略化したジオメトリを保存する列GEOM_Sを追加します。

alter table jar_japan_region_admins add (geom_s sdo_geometry);

ジオメトリの簡略化を行い、結果を列GEOM_Sへ保存します。閾値として10m許容差は0.05を指定しました。Oracle Database 18c Express Editonで2分程度の時間がかかっています。

update jar_japan_region_admins set geom_s = sdo_util.simplify(geom_r,10,0.05);

簡略化された結果をマップに重ね合わせて表示することにより、結果を確認します。

ページ・デザイナにて先ほど作成した行政区域コードのページを開きます。マップのレイヤー行政区域コード上でコンテキスト・メニューを表示させ、重複を実行します。重複の結果作成されたレイヤー識別名前簡略化とし、列のマッピングジオメトリ列GEOM_Sへ変更します。


レイヤーを追加したページを実行し、簡略化された表示を確認します。青森県を表示してみます。

五所川原市の一部と八戸市の部分の表示が薄いことが確認できます。


行政区域コードの表示を外すと、簡略化されたレイヤーでは上記の行政区域が表示されていないことがわかります。


期待通りに簡略化されなかったデータを特定するため、行政区域コードのツールチップの表示にIDを追加します。

<b>都道府県</b>: &PREFECTURE_NAME.
{if BRANCH_IN_HOKKAIDO/}<br><b>支庁</b>: &BRANCH_IN_HOKKAIDO.{endif/}
{if MAJOR_CITY/}<br><b>郡・政令指定都市</b>: &MAJOR_CITY.{endif/}
{if CITY_NAME/}<br><b>市区町村</b>: &CITY_NAME.{endif/}
{if ADMIN_CODE/}<br><b>行政区域コード</b>: &ADMIN_CODE.{endif/}
<br><b>ID</b>: &ID.


ページを実行し、表示されない領域のIDを確認します。


簡略化の結果、全国では表示されない領域が27ありました。これらは個別にデータの修正が必要になります。

表示されない簡略化されたデータを修正する


簡略化された結果を確認してみます。最初にジオメトリを頂点に分解して、マップ・リージョンに表示させてみます。

頂点のデータを保存する表JAR_VARTICESを作成します。列X, Y, IDを含みます。

create table jar_vertices(x number, y number, id number);

テーブル・ファンクションのSDO_UTIL.GETVERTICESを呼び出し、問題のあるIDのデータを表にロードします。先程の五所川原市の一部の領域(ID: 9898)を対象にします。

insert into jar_vertices(x,y,id)
select p.x, p.y, p.id
from jar_japan_region_admins r,
table(sdo_util.getvertices(r.geom_s)) p
where r.id = 9898;

434行の点のデータがロードされました。このデータを表示するマップ・リージョンのページを作成します。先ほどと同様に、ページの作成を開始し、タイプとしてマップを選択します。ページ名は頂点の確認とします。へ進みます。


ナビゲーション・メニュー・エントリの作成は行うように指定します。続いて表の指定が求められます。表/ビューの名前としてJAR_VERTICESを選択します。へ進みます。


ポイントを選択します。ジオメトリ列タイプ2つの数値列経度列X緯度列Yになります。ツールチップ列としてID(Number)を選択します。作成をクリックするとページが作成されます。実行すると以下のように表示されます。

領域は表示されませんが、領域を形成できる頂点のデータは含まれていることが確認できます。


個々の点が表示されるため、どの辺りに問題があるのか確認する手掛かりが得られます。

始点と思われる位置のIDは19です。


終点は始点と一致するため、その1つ前のIDの433を確認します。


ではIDが1の頂点はどこにあるのかというと、ここでした。


ジオメトリに不明な点があるので、ジオメトリの内容を直接確認します。簡略化したジオメトリに含まれるエレメント数をSDO_UTIL.GETNUMELEMファンクションにより取得し、それぞれのエレメントのジオメトリ・タイプを確認します。

declare
l_geom sdo_geometry;
l_geom_s sdo_geometry;
l_elem_cnt number;
begin
select geom_s into l_geom from jar_japan_region_admins where id = 9898;
l_elem_cnt := sdo_util.getnumelem(l_geom);
for i in 1..l_elem_cnt
loop
l_geom_s := sdo_util.extract(l_geom, i);
dbms_output.put_line('type: ' || l_geom_s.sdo_gtype);
end loop;
end;

以下の結果が得られます。
type: 2002
type: 2003
type: 2003
type: 2003
type: 2003

文が処理されました。
先頭のSDO_GTYPEが2002というのは線であり、ポリゴンではありません。Oracle APEXのマップ・リージョンのレイヤーにはポリゴンが指定されているため、ジオメトリが点や線を含むと問題が起こるようです。

国土交通省のデータをロードした直後のデータのジオメトリのタイプはすべて2003、つまり単純なポリゴンです。SDO_UTIL.RECTIFY_GEOMETRYファンクションを実行したり、SDO_UTIL.SIMPLIFYファンクションを実行すると、結果のタイプは2004(COLLECTION)や2007(MULTIPOLYGON)に変わることがあります。ジオメトリのタイプがコレクション(2004)に変わった際に点(2001)や線(2002)が含まれることがあると、Oracle APEXのマップ・リージョンでは(ポリゴンではないため)、地図上にポリゴンとして表示されないようです。

SDO_UTIL.RECTIFY_GEOMETRYやSDO_UTIL.SIMPLIFYの結果はジオメトリとしては正しいため、SDO_GEOM.VALIDATE_GEOMETRY_WITH_CONTEXTの結果はTRUEとなります。Oracle APEXのマップ・リージョンはSDO_GEOMETRY型として正しいかどうかに関係なく、座標の数値を読み出すことさえできれば描画できます。実際にGeoJSONをロードした直後、ジオメトリとしては不正であっても、ポリゴンは描画できていました。

少し強引ですが、ジオメトリがコレクション(2004)の場合、ジオメトリにポリゴンだけが含まれるように修正します。set serveroutput on、/による実行、commitなども忘れずに実行します。

declare
    l_geom sdo_geometry;
    l_geom_s sdo_geometry;
    l_geom_t sdo_geometry;
    l_elem_cnt integer;
    b_polygon_only boolean;
    l_message varchar2(4000);
begin
    for r in (
        select * from jar_japan_region_admins order by id
    )
    loop
        l_geom := r.geom_s;
        if l_geom.sdo_gtype = 2004 then
            l_elem_cnt := sdo_util.getnumelem(l_geom);

            -- コレクションにポリゴン以外が含まれているか確認する。
            b_polygon_only := true;
            for i in 1..l_elem_cnt
            loop
                l_geom_s := sdo_util.extract(l_geom, i);
                if l_geom_s.sdo_gtype <> 2003 then
                    b_polygon_only := false;
                    exit;
                end if;
            end loop;

            -- ポリゴン以外が含まれているときは、ジオメトリからそれらを取り除く。
            if not b_polygon_only then
                dbms_output.put_line('id:' || r.id);
                l_geom_t := null;
                for i in 1..l_elem_cnt
                loop
                    l_geom_s := sdo_util.extract(l_geom, i);
                    if l_geom_s.sdo_gtype = 2003 then
                        if l_geom_t is null then
                            l_geom_t := l_geom_s;
                        else
                            l_geom_t := sdo_util.append(l_geom_t,l_geom_s);
                        end if;
                    end if;
                end loop;
                if l_message <> 'TRUE' then
                    dbms_output.put_line('ID:' || r.id || ' ' || l_message);
                else
                    dbms_output.put_line('ID:' || r.id || ' fixed.');
                    update jar_japan_region_admins set geom_s = l_geom_t where id = r.id;
                end if;
            end if;
        end if;
    end loop;
end;
/

修正したデータをマップから確認します。


青森県の五所川原市と八戸市が、簡略化したデータで領域が表示されるようになっています。

青森県以外で表示がされていなかった領域についても、簡略化したデータで領域が表示されています。

最後に簡略化したジオメトリが持っている頂点の数をアップデートします。列VERT_Sを追加します。

alter table jar_japan_region_admins add (vert_s number);

頂点の数をアップデートします。

update jar_japan_region_admins set vert_s = sdo_util.getnumvertices(geom_s);

全体として削減された頂点の数を確認してみます。

select sum(vertices) "元の頂点数", sum(vert_s) "簡略化後", sum(vertices)/sum(vert_s) "比率"
from jar_japan_region_admins

結果として、1/8強の頂点が削減されています。

元の頂点数簡略化後比率
1474803617792678.28882680339712926727691796678070239037


行政区域コード単位で集約する


ひとつの行政区域を表示するたびに何千行もデータベースから行を取り出すのも効率がよくないため、行政区域コードごとにまとめてみます。同一の行政区域コードのポリゴンにはほとんど重なりがないため(重なりがあるなら、最初から1つのポリゴンになっている)、
SDO_UTIL.APPENDファンクションを使用することができます。

以下のDDLを実行し、まとめた結果を保存する表JAR_JAPAN_REGION_CITIESを作成します。

create table jar_japan_region_cities
(
prefecture_name varchar2(20) not null,
branch_in_hokkaido varchar2(40),
major_city varchar2(20),
city_name varchar2(40),
admin_code varchar2(6) not null,
geometry sdo_geometry,
row_count number,
vertices number
);


最初に元々、行政区域コード1つあたり1行、つまりポリゴンがひとつだけ登録されているデータを表JAR_JAPAN_REGION_ADMINSより見つけ、表JAR_JAPAN_REGION_CITIESへコピーします。

簡略化したジオメトリ(列GEOM_S)をソースとして処理を行います。

コピーを行うために、以下のMERGE文を実行しました。

merge into jar_japan_region_cities c
using
(
select
prefecture_name, branch_in_hokkaido, major_city, city_name
,admin_code, geom_s geometry, vert_s vertices
from jar_japan_region_admins
where
(prefecture_name, admin_code)
in
(
select prefecture_name, admin_code
from jar_japan_region_admins
group by prefecture_name, admin_code
having count(*) = 1
)
) a
on (c.prefecture_name = a.prefecture_name and c.admin_code = a.admin_code)
when matched then
update set
branch_in_hokkaido = a.branch_in_hokkaido
,major_city = a.major_city
,city_name = a.city_name
,geometry = a.geometry
,row_count = 1
,vertices = a.vertices
when not matched then
insert (prefecture_name, branch_in_hokkaido, major_city, city_name
,admin_code, geometry, row_count, vertices)
values (a.prefecture_name, a.branch_in_hokkaido, a.major_city, a.city_name
,a.admin_code, a.geometry, 1, a.vertices)
;


1213行が表JAR_JAPAN_REGION_CITIESに挿入されました。

残りとなる、ひとつ以上のポリゴンを含む行政区域コードを一行にまとめます。

普通に実行すると非常に長い時間がかかるため、まとめる対象を削減します。10,000平方メートル(100m四方)以上の領域のみを対象とします。そのために面積を計算します。

面積を保持する列AREAを追加します。

alter table jar_japan_region_admins add (area number);

面積を計算して、列AREAをアップデートします。

update jar_japan_region_admins set area = sdo_geom.sdo_area(geom_s);

作業ログを記録する表JAR_VALIDATE_CITIESを作成します。

create table jar_validate_cities
(
prefecture_name varchar2(20) not null,
branch_in_hokkaido varchar2(40),
major_city varchar2(20),
city_name varchar2(40),
admin_code varchar2(6),
message varchar2(4000)
);

実際には重なり合う領域も存在します。そのため、重なりが無いときはSDO_UTIL.APPENDを使い、重なりがあるときはSDO_GEOM.SDO_UNIONを使うようにコードを書いています。重なりの確認には、SDO_GEOM.RELATEファンクションを使用します。

以下のPL/SQLコードを実行します。処理に長時間かかるため、sqlplusでデータベースに繋いで実行しました。SQLワークショップSQLスクリプトとしても実行できるでしょう。

declare
    l_geom   sdo_geometry;
    l_geom_a sdo_geometry;
    l_row_count number;
    l_vertices number;
    l_message varchar2(4000);
begin
    delete from jar_validate_cities;
    commit;
    for c in (
        -- まとめる数が少ない行政区分からひとつひとつまとめていく。
        select prefecture_name, branch_in_hokkaido, major_city, city_name, admin_code
        from jar_japan_region_admins
        where (prefecture_name, admin_code) not in (
              -- すでにまとめ済みの行政区分は除外する
              select prefecture_name, admin_code from jar_japan_region_cities
        )
        group by prefecture_name, branch_in_hokkaido, major_city, city_name, admin_code
        order by count(*) asc
    )
    loop
        l_geom := null;
        for r in
        (
             select prefecture_name, admin_code, geom_s geometry
             from jar_japan_region_admins
             where prefecture_name = c.prefecture_name
               and admin_code = c.admin_code
               and area > 10000
        )
        loop
            if l_geom is null then
                -- 最初の1行は初期化に使用する。
                l_geom := r.geometry;
                l_row_count := 1;
                continue;
            end if;
            -- ポリゴン、行数、頂点の数を集計する
            if sdo_geom.relate(l_geom, 'disjoint', r.geometry, 0.05) = 'TRUE' then
                l_geom_a := sdo_util.append(l_geom, r.geometry);
            else
                l_geom_a := sdo_geom.sdo_union(l_geom, r.geometry, 0.05);
            end if;
            l_geom := l_geom_a;
            l_row_count := l_row_count + 1;
         end loop;
         l_vertices := sdo_util.getnumvertices(l_geom);
         -- 結果を保存する。途中で停止できるよう毎回commitする。
         l_message := sdo_geom.validate_geometry_with_context(l_geom, 0.05);
         insert into jar_validate_cities(prefecture_name, branch_in_hokkaido,
             major_city, city_name, admin_code, message)
         values(
             c.prefecture_name, c.branch_in_hokkaido,
             c.major_city, c.city_name, c.admin_code, l_message);
         insert into jar_japan_region_cities
            (prefecture_name, branch_in_hokkaido, major_city, city_name
            ,admin_code, geometry, row_count, vertices)
         values(
            c.prefecture_name, c.branch_in_hokkaido, c.major_city, c.city_name,
            c.admin_code, l_geom, l_row_count, l_vertices);
         commit;
     end loop;
end;
/

処理が終了したら、エラーがないか確認します。表JAR_VALIDATE_CITIESのmessageとしてTRUE以外が保存されている行を検索します。

select count(*) from jar_validate_cities where message <> 'TRUE';

結果が0であれば、データのまとめは正常に完了しています。

まとめたデータを使って、マップを表示するページを作成します。先ほど作成したページのコピーを作成します。作成メニューより、コピーとしてのページを実行します。


次のコピーとしてのページを作成として、このアプリケーションのページを選択します。へ進みます。


新規ページ名APPENDとしました。へ進みます。


ナビゲーションのプリファレンスとして、新規ナビゲーション・メニュー・エントリの作成を選択します。へ進みます。


データ・ソースとなる表はページ作成後に変更します。この時点でのアイテムなどの変更は不要です。何も変更せず、コピーを実行します。


ページがコピーされたら、マップ・リージョンのソース表名JAR_JAPAN_REGION_CITIESへ変更します。


レイヤー行政区域コード列のマッピングジオメトリ列GEOMETRYに変更します。レイヤーの簡略化削除します。


以上で完了です。作成したページを実行します。全体の行数が減ったため、全国の地図が表示されるようになりました。



都道府県単位で集約する


Oracle Spatialが提供する空間集計ファンクションのSDO_AGGR_UNIONを使用して、都道府県ごとにポリゴンを集約してみます。

以下のDDLを実行し、集約した結果を保存する表JAR_JAPAN_REGION_PREFECTURESを作成します。

create table jar_japan_region_prefectures
(
prefecture_name varchar2(20) not null,
geometry sdo_geometry,
row_count number,
vertices_sum number,
vertices number
);

表を作成したら、以下のPL/SQLコードを実行し、頂点の合計が少ない都道府県より順番に、集計処理を実行します。これも時間がかかるので、sqlplusなどで接続して実行する必要があります。

declare
    l_pref_name varchar2(20);
    l_geom sdo_geometry;
    l_row_count number;
    l_vertices_sum number;
    l_vertices number;
    l_errm varchar2(4000);
begin
    for c in (
        select prefecture_name
        from jar_japan_region_cities
        where prefecture_name not in (
              select prefecture_name from jar_japan_region_prefectures
        )
        group by prefecture_name
        order by sum(vertices) asc
    )
    loop
        begin
            select
                prefecture_name
                ,sdo_aggr_union(sdoaggrtype(geometry, 0.05))
                ,count(*)
                ,sum(vertices)
             into l_pref_name, l_geom, l_row_count, l_vertices_sum
             from jar_japan_region_cities
             where prefecture_name = c.prefecture_name
             group by prefecture_name;
        exception
            when others then
                l_errm := sqlerrm;
                dbms_output.put_line(l_errm); 
                continue;
        end;
        -- 
        l_vertices := sdo_util.getnumvertices(l_geom);
        insert into jar_japan_region_prefectures
            (prefecture_name, geometry, row_count, vertices_sum, vertices)
        values(l_pref_name, l_geom, l_row_count, l_vertices_sum, l_vertices);
        commit;
    end loop;
end;
/

メモリに余裕がある(PGA_AGGREGATE_TARGETの値が大きい)環境であれば、都道府県ごとにFORループで回す必要はないと思われます。

先ほどと同様にコピーとしてのページを実行し、マップを表示するページを作成します。都道府県単位で集約したデータをソースとすることにより、集約したポリゴンをマップに表示させます。

手順として異なる部分のみを示します。

コピー元ページとして6.APPENDを選択し、新規ページ名都道府県とします。


ページが作成されたら、マップ・リージョンのソース表名JAR_JAPAN_REGION_PREFECTURESへ変更します。


レイヤーの名前行政区域コードから都道府県に変更します。


ファセットのP7_BRANCH_IN_HOKKAIDOP7_MAJOR_CITYP7_CITY_NAMEP7_ADMIN_CODE削除します。


ファセットP7_PREFECTURE_NAMEの属性を調整します。リスト・エントリ件数の計算OFFにします。集約後は、都道府県の件数はつねに1になるためです。また、最大表示エントリ47にします。


以上でページを実行してみます。


以上で、作業は完了です。

Oracle Spatialというか地理情報処理に詳しい方によると、今まで紹介してきた処理は平面直角座標系に変換したうえで実施すると良い、とのことでした。

Oracle Spatialは色々な機能を提供しています。地理情報処理の知識が必要な部分はあり簡単ではありませんが、学ぶだけの価値はありそうです。本記事内では、ポリゴンの結合、面積の計算、ジオメトリの位置関係の確認などの機能を使用しています。これらの機能を本当に簡単に、SQLから呼び出すことが出来ています。

本記事で作成したアプリケーションを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/japan-region-map.sql

Oracle APEXによるアプリケーション開発の参考になれば幸いです。


追記

記事中にも記載していますが、国土交通省国土数値情報ダウンロードサイトからダウンロードした行政区域データを使用して、こちらの記事を書いています。
データのダウンロードは以下のURLから行なっています。
https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N03-v3_0.html

参考

行政区域コードのまとめ処理の進捗を確認するために使用したスクリプトは以下です。
set pages 1000 lines 180 trims on trimo on
col cnt format 99999999
col prefecture_name format a12
col major_city format a20
col city_name format a30
col admin_code format a6

select 
    prefecture_name
    ,branch_in_hokkaido
    ,major_city
    ,city_name
    ,admin_code
    ,count(*) cnt, sum(vertices)
from jar_japan_region_admins
where (prefecture_name, admin_code) not in (
    select prefecture_name, admin_code from jar_japan_region_cities
)
and 
area > 10000
group by
    prefecture_name
    ,branch_in_hokkaido
    ,major_city
    ,city_name
    ,admin_code
order by 6 desc;

都道府県のまとめ処理の進捗を確認するために使用したスクリプトは以下です。北海道のまとめはデータ数が多いため、長時間(30分程度)はかかります。
set pages 1000 lines 120 trims on trimo on
col cnt format 99999999
col prefecture_name format a12
col admin_code format a6
select prefecture_name, row_count, vertices_sum, vertices
from jar_japan_region_prefectures;