CADとGISをつなぐ ── LibreDWG × GDALでDWGをQGIS/ArcGIS pro 対応GeoPackageへ変換する
AutoCADのDWGを、そのままGISで使いたいことって結構あります。
河川図や道路図、施設台帳、測量図など、元データはCADで管理されているけれど、解析や地図表示はQGISやArcGIS Proでやりたい。そんな場面はかなり多いです。
直接DWGを読み込みことはできますが、一筋縄ではいかない場合が多々あります。
今回は、GNU LibreDWGとGDAL/OGRを組み合わせて、DWGをGeoPackageへ変換するDocker環境を作りました。
最終的な流れはこんな感じです。
QGISだけで使うのであれば、もっと簡単な構成でも動きます。
ただ、実際にやってみると、
- 大きなDWGでも落とさず変換したい
- 日本語を文字化けさせたくない
- CADの単位のmmをGISのmへ直したい
- ArcGIS ProでもPoint / Line / Polygonをきちんと表示したい
- 変換後のFeature数も確認したい
といった問題が次々に出てきました。
この記事では、最終版の使い方だけではなく、途中でどこにハマって、なぜ今の構成になったのかも含めてまとめます。
作ったもの
公開版の名前は DWG2GeoPackage v0.2.0 にしました。
ホスト側で必要なのは、基本的にDockerとDocker Composeだけです。LibreDWGやGDALはUbuntu 24.04のコンテナ内へ入れています。
ざっくり言うと、
DWG2GeoPackag処理の流れ
- DWG
↓ - GNU LibreDWG 0.14
↓ - R2018 DXF
↓ - GDAL/OGR
↓ - GeoPackage
という流れです。
ただし、実際にはDXFをそのままGDALへ渡して終わり、というわけではありません。
途中で、
文字コード
CAD単位
DXF構造
Geometry type
Feature数
RTree
などを確認して、問題がなければGeoPackageとして確定します。
最初は「GeoJSONにすればいいのでは?」と思っていました
最初はかなり単純に考えていました。
LibreDWGにはGeoJSON出力があるので、
DWG
↓
GeoJSON
でよいのでは、と思っていました。
小さいデータなら、これでも十分動きます。
ところが、約44MBの実DWGで試したところ、生成されたGeoJSONが約1GBまで大きくなりました。
しかも、最後まで正常なJSONとして読めないケースも出ました。
GeoJSONは便利ですが、CADの大きなデータを中間形式として毎回作るには少し重いです。
そこで途中から、
DWG
↓
DXF
↓
GDAL
↓
GeoPackage
へ切り替えました。
GeoPackageはSQLiteベースなので、1ファイルで扱えて、空間インデックスも持てます。QGISでもArcGIS Proでも扱いやすいです。
ここから少しずつ問題を潰していきました。
LibreDWGは0.14を固定しています
今回使っているのはGNU LibreDWG 0.14です。
Dockerfileでは、単に、
ARG LIBREDWG_TAG=0.14
とするだけではなく、commitも固定しています。
d9468ae948b8f07a08efa756c19f8916052358c0
Docker build時にはGit tagが存在することを確認して、clone後のcommitもチェックします。
CAD変換は、ライブラリ側の変更で挙動が変わると原因を追うのが大変です。
なので今回は、
どのLibreDWGで確認したか
をはっきり残すようにしました。
中間形式にはR2018 DXFを使っています
LibreDWGからGDALへ渡す中間形式にはR2018 DXFを使っています。
dwg2dxf --as r2018
DXFにすると、GDALのDXF driverからCAD entityをOGR geometryとして読み出せます。
例えば、
POINT -> Point
LINE -> LineString
SOLID / 3DFACE -> Polygon
HATCH -> Polygon
といった形です。
今回やりたいのは、AutoCADの見た目を完全コピーすることではありません。
CADの中にあるGISで使いたいgeometryを取り出すことが目的なので、DXFを境界にすると扱いやすくなりました。
実DWGでDXF構造エラーにも当たりました
実データを試している途中で、LibreDWGが出力したDXFをezdxfで読むと、
Expected DXF entity INSERT or SEQEND
というエラーが出ました。
INSERTとATTRIBの並びで、最後のSEQENDが欠けるケースです。
ここで、
ogr2ogr -skipfailures
を使えば、無理やり先へ進めることはできます。
ただ、それだと何件落ちたのか分からないまま成果物ができてしまいます。
業務データでは、これはちょっと怖いです。
そこで今回は、原因が分かっている、
INSERT
ATTRIB
SEQEND
の終端だけを補正するようにしました。
DXFをraw byteの2行ペアとして読み、必要な場所にだけ、
0
SEQEND
を追加します。
それ以外の壊れ方は勝手に直しません。
補正後にGDALのpreflightが通らなければ、そこで止めます。
日本語の文字化けもありました
次にハマったのが日本語です。
DXFヘッダを見ると、
$DWGCODEPAGE
ANSI_932
となっていたので、最初はGDALへCP932を指定していました。
ところが、GeoPackageを見ると、
R02xxx市河川網図.bds
のような日本語が文字化けしていました。
そこでDXFのbyte列を直接確認してみると、LibreDWGが実際に出力していた日本語はUTF-8でした。
つまり、
ヘッダ: ANSI_932
実データ: UTF-8
という状態でした。
ヘッダだけを信じてCP932変換したため、逆に文字化けしていたわけです。
現在はDXF全体をUTF-8としてチェックしてから、
DXF_ENCODING=UTF-8
をGDALへ渡しています。
これで、実データのレイヤ名やTEXT属性も正常に残ることを確認できました。
CADのmmとGISのmは別物です
実DWGを最初にGeoPackageへ変換したとき、Extentがこうなりました。
X: -68,000,375
Y: -133,500,285
EPSG:6676の平面直角座標として見ると、明らかに桁が大きいです。
DXFヘッダを確認すると、
$INSUNITS = 4
でした。
これはmillimetersです。
つまり、CAD側はmmで座標を持っていました。
0.001倍すると、
X: -68,000.375
Y: -133,500.285
となり、期待していたmetre座標になります。
最初は $INSUNITS を見て自動変換する実装も試しました。
ただ、実務のCADには、
unitless
ヘッダと実際の運用単位が違う
独自ルールで座標を持っている
といったデータもあります。
なので公開版では、倍率を明示的に渡すようにしました。
mmからmなら、
--scale-to-m 0.001
です。
意味はシンプルで、
GIS座標[m] = CAD座標 × scale-to-m
です。
すでにmetre単位なら、
--scale-to-m 1
でOKです。
$INSUNITS はログに出しますが、最終的にはユーザーが指定した倍率を優先します。
QGISでは見えるのに、ArcGIS ProではLineしか見えませんでした
これは少し意外でした。
QGISでは問題なく表示できていたGeoPackageをArcGIS Proで開くと、Lineしか見えないことがありました。
GeoPackageの中を確認すると、PointやPolygonはちゃんと入っています。
原因は、GeoPackageのレイヤ構成でした。
GDALのDXF driverは、CAD entityを基本的に1つの entities レイヤとして返します。
そのままGeoPackageへ書くと、
geometry_type_name = GEOMETRY
の混在レイヤになります。
QGISはこの形でも扱えますが、ArcGIS Proではgeometry typeごとにfeature classを分けた方が安定します。
そこで、最終的にはこうしました。
sample.gpkg
├── entities_point
│ └── POINT Z
├── entities_multipoint
│ └── MULTIPOINT Z
├── entities_line
│ └── MULTILINESTRING Z
└── entities_polygon
└── MULTIPOLYGON Z
空のgeometry typeは作りません。
分類にはOGR SQLの OGR_GEOMETRY を使っています。
例えばLineなら、
SELECT *
FROM entities
WHERE OGR_GEOMETRY='LINESTRING'
OR OGR_GEOMETRY='MULTILINESTRING'
です。
そして出力型は、
MULTILINESTRINGZ
と明示します。
ここで PROMOTE_TO_MULTI だけを使う方法も試しましたが、GDAL 3.8.4ではGeoPackage側の宣言型が GEOMETRY のまま残るケースがありました。
なので現在は、型をはっきり指定しています。
RTreeの名前も固定ではありませんでした
ArcGIS Pro対応まで進めて、もう大丈夫だろうと思ったところで、E2Eテストにこんなエラーが出ました。
Missing spatial index table: rtree_entities_point_geom
最初は、
空間インデックスが作られていない?
と思いました。
でも実際には、
rtree_entities_point__ogr_geometry_
というRTreeが存在していました。
GeoPackageのRTree名は、
rtree_<table>_<geometry_column>
です。
つまり、geometry columnを geom と決め打ちしていた検証コードの方が間違っていました。
今は、
SELECT table_name, column_name
FROM gpkg_geometry_columns
から実際のgeometry列名を取得して確認しています。
もし本当にRTreeがなければ、GDALの CreateSpatialIndex() で作成して、もう一度チェックします。
最終的な構成
ここまで直して、最終的な流れはこうなりました。
変換するだけではなく、どこまで通ったかをログに残すようにしています。
サンプルDWGも用意しました
記事を読んでそのまま試せるように、小さな sample.dwg を作れるようにしました。
サンプルの座標は、JGD2011 / Japan Plane Rectangular CS VIII、EPSG:6676として扱う値です。
単位はmetreです。
中身は3entityだけです。
| 種類 | CAD entity | Layer | 座標 |
|---|---|---|---|
| Point | POINT | SAMPLE_POINT |
(-65000, -131000) |
| Line | LINE | SAMPLE_LINE |
(-65100,-131100) → (-64900,-130900)
|
| Polygon | SOLID | SAMPLE_POLYGON |
Point周辺の60m四方 |
PolygonにはSOLIDを使っています。
この方が、Point / Line / Polygonの3種類をシンプルに確認できます。
なお、DWGの中にEPSG:6676そのものをGISのCRS情報として埋め込んでいるわけではありません。
EPSG:6676として使う座標値で作図して、GeoPackageへ変換するときにEPSGを指定するという考え方です。
EPSGコードは必ず設定しなければなりません。
インストール
公開版の dwg2geopackage_v0.2.0.zip は、こちらからダウンロードできます。
dwg2geopackage_v0.2.0.zip をダウンロード
ダウンロードしたZIPを展開して、ディレクトリへ移動します。
unzip dwg2geopackage_v0.2.0.zip
cd dwg2geopackage_v0.2.0
chmod +x ./scripts/*.sh
その後、E2Eテストを実行します。
./scripts/verify_docker.sh
この中では、
Docker build
↓
sample.dxf
↓ dxf2dwg
sample.dwg
↓
sample.gpkg
↓
Point=1 / Line=1 / Polygon=1 を確認
↓
upstream DWG E2E
↓
pytest
まで一気に確認します。
sample.dwg は、適当なバイナリを置いているわけではありません。
リポジトリに入っている sample.dxf から、Pinned LibreDWGで生成します。
サンプルだけ作りたい場合は、
docker compose build
./scripts/generate_sample_dwg.sh
でも作れます。
sample.dwgを変換してみます
サンプルはmetre単位なので、
--scale-to-m 1
です。
./scripts/dwg2geopackage.sh \
--epsg6676 \
--scale-to-m 1 \
samples/sample.dwg \
out \
sample
生成されるのは、
out/sample.gpkg
です。
中には、
entities_point
entities_line
entities_polygon
の3feature classが入ります。
QGISでもArcGIS Proでも、そのまま別レイヤとして追加できます。
mm単位の実DWGを変換する場合
今回の検証に使った実DWGはmm単位でした。
その場合は、
./scripts/dwg2geopackage.sh \
--epsg6676 \
--scale-to-m 0.001 \
"/home/tys/dwg/input/DMxxx2500.DWG" \
"/home/tys/dwg/output"
とします。
実データでは、約72.9万Featureを持つGeoPackageまで確認しました。
ログには、
DXF INSUNITS : 4 (millimeters)
Header scale : 0.001 (advisory only)
Scale to m : 0.001 (explicit runtime parameter)
と出るので、CAD側の情報と実際に指定した倍率を見比べられます。
DXFも残したい場合
GeoPackageと一緒にDXFも残したい場合は、--dxf を付けます。
./scripts/dwg2geopackage.sh \
--epsg6676 \
--scale-to-m 0.001 \
--dxf \
input.dwg \
output
この場合、
input.gpkg -> scale-to-mを適用
input.dxf -> 元のCAD座標
になります。
DXFはCAD用なので、座標は元のままです。
GeoJSONも必要なら、
--geojson
を追加できます。
ただ、大きなDWGではGeoJSONがかなり大きくなるので、普段はGeoPackageだけでよいと思います。
変換後もチェックしています
このツールでは、
ogr2ogrが終了コード0だったから成功
だけでは終わりにしていません。
変換後に、
SQLite integrity_check
EPSG
geometry_type_name
Feature数
元DXFとのFeature総数一致
Extent
scale-to-mの反映
RTree
gpkg_rtree_index
Z/M次元
を確認しています。
また、Featureを落として強引に通す、
-skipfailures
は使っていません。
何か読めないものがあれば、基本的には止めてログを確認します。
業務データでは、この方が後から説明しやすいです。
ディレクトリ構成
公開版はこんな構成です。
dwg2geopackage_v0.2.0/
├── Dockerfile
├── docker-compose.yml
├── README.md
├── CHANGELOG.md
├── samples/
│ ├── sample.dxf
│ └── sample.dwg
├── scripts/
│ ├── dwg2geopackage.sh
│ ├── generate_sample_dwg.sh
│ ├── convert_all_in_container.sh
│ ├── run_e2e.sh
│ └── verify_docker.sh
├── src/
│ └── dwg2geopackage/
└── tests/
Python側も、できるだけ役割を分けています。
例えば、
DXF metadata確認
DXF構造補正
geometry分類
GeoPackage spatial index確認
を別moduleにしています。
問題が起きたときに、
どの段階で止まったのか
を追いやすくするためです。
このツールで全部のDWGを完全再現できるわけではありません
DWGはかなり広いフォーマットです。
今回の仕組みは、AutoCADの見た目を完全に再現することを目的にはしていません。
複雑なブロック表現や、
MATERIAL
TABLESTYLE
高度なHATCH
特殊object
などはLibreDWG側の対応状況にも依存します。
今回の目的は、あくまで、
CADにあるGISで使いたいgeometry
↓
Point / Line / Polygon
↓
GeoPackage
です。
図面表現を100%そのまま残したい場合は、別の要件として考えた方がよいです。
DWGを直接処理できると、その後がかなり楽になります
実務では、
DWG受領
↓
誰かが手作業でGISへ変換する
↓
QGISやArcGIS Proで使う
という流れが残っていることがあります。
ここをDockerで再現できるようにすると、
DWG受領
↓
自動変換
↓
GeoPackage
↓
PostGIS / WebGIS / QGIS / ArcGIS Pro
へつなげやすくなります。
今回作ったのは、その入口部分です。
将来的には、
将来への機能拡張
DWGアップロード
↓
EPSG指定
↓
scale指定
↓
非同期変換
↓
DBへ保存
↓
Openlayersで表示
↓
GeoPackageダウンロード
のようなWeb APIにも発展できます。
まずはCLIとDockerで、同じ変換を何度実行しても同じ検証を通るところまで固めました。
CADとGISの間に残っている手作業を少し減らすには、ちょうどよい土台になったと思います。
また、GISからCADへの変換も可能ですので、それを機能に追加してもよいかもしれません。
2026-09-26 追記
この記事で紹介していたDWG→GeoPackage変換ツールを v0.3.4 へ更新しました。
v0.3.4では、単にGeoPackageを作るだけではなく、
- 元DWGと同名のDXFを必ず残す
- ArcGIS Pro用のPRJ / WLDを必ず作る
- DXFはCAD座標を変更しない
- GeoPackageだけ
--scale-to-mを適用する- EPSGを実行時に指定する
- DXF→GeoPackageの各段階をPythonモジュールとして分離する
- 変換後のGeoPackageを自動検証する
という形へ整理しました。
以前の記事で作っていた「DWGをGISへ持っていく実験」から一歩進み、
CAD側の確認用DXFとGIS利用用GeoPackageを同時に作る実運用向けの構成になっています。
改訂版については、下記に記事として作成しています。
https://qiita.com/rino_yume/items/0e1326ae8585e4f90c7c
参考
- GNU LibreDWG
https://www.gnu.org/software/libredwg/ - LibreDWG
dwgwrite
https://github.com/LibreDWG/libredwg/blob/master/programs/dwgwrite.1 - LibreDWG
dxf2dwg
https://github.com/LibreDWG/libredwg/blob/master/programs/dxf2dwg.1 - GDAL DXF driver
https://gdal.org/en/stable/drivers/vector/dxf.html - GDAL GeoPackage driver
https://gdal.org/en/stable/drivers/vector/gpkg.html - GDAL ogr2ogr
https://gdal.org/en/stable/programs/ogr2ogr.html - GDAL OGR SQL dialect
https://gdal.org/en/stable/user/ogr_sql_dialect.html - GeoPackage RTree Spatial Index Extension
https://www.geopackage.org/spec/#extension_rtree

