しばらくの間、ファイルベースのデータはウェブに移行しており、私たちは皆、必要なものを手動でダウンロードし、ArcGIS Proのようなデスクトップソフトウェアでそこから作業を進めることに慣れています。<\/P>
このブログは、その体験を自動化することについてであり、ダウンロードと処理のステップを、理解しやすく明確で共有可能なツーリングによって情報製品をオンデマンドで提供するものに置き換えるものです。<\/P>
データ規模の両端には、世界中の政治区分とカナダ・バンクーバーの建築許可活動があります。前者のデータセットはから取得される大きなものであり、後者はオープンデータポータルから取得される小さなものです。どちらもArcGIS Proのノートブックを使い、同じ基本的なツーリングで取り込みます。<\/P>
まずは小さなデータセットから:<\/P>
バンクーバー建築許可<\/span><\/P>大きなデータセット:<\/P>
世界中の政治区分<\/span><\/P>そして中間地点として、ドイツだけの政治区分:<\/P>
ドイツの政治区分のビュー<\/span><\/P>これらすべてのデータセットは類似して取り込まれましたが、以下で議論する注目すべき違いがあります。<\/P>GeoParquetに馴染みがない場合はこれから馴染むでしょうし、この投稿では私のお気に入りのよく知られたフォーマットにしています。他の一般的なクラウドファイルタイプであるCSV、Excel、JSONも同様に機能します。「クラウド」とは、それらを取得する場所だけでなく、ローカルコピーをダウンロードして検査せずにリモートクエリが可能なフォーマットを指します。<\/P>S3上の大規模データとウェブサイト上の小規模データに同じ基本的なツーリングでアクセスできると言いましたが、それぞれのサーバーの能力が重要です。S3はHiveストレージに対するクエリに応じてファイルを配信できますが、ウェブサイトはファイル全体のダウンロードのみを提供します。<\/P>まずは小さなデータセット、建築許可をウェブサイトから自動ダウンロードする方法を見てみましょう。<\/P>このデータはオープンデータポータルにあり、サイトを閲覧するとダウンロード形式の選択肢が表示されます。<\/P>このデータはノートブックVancouverBuildingPermitsで自動化されています。コードを見るとDuckDBを使用していることがわかります。これは空間対応SQLデータベース兼ウェブクライアントです。データのダウンロードとフィーチャークラスへの変換は非常に簡単で、ツーリングはParquetソースとSQL文からスキーマを推測します。<\/P> <\/P>import arcpy
from arcgis.features import GeoAccessor, GeoSeriesAccessor
import duckdb
import math
import os
arcpy.env.overwriteOutput = True
url = r"https:\/\/opendata.vancouver.ca\/api\/explore\/v2.1\/catalog\/datasets\/issued-building-permits\/exports\/parquet?lang=en&refine=issuedate%3A%222024%22&timezone=America%2FLos_Angeles"
conn = duckdb.connect()
conn.sql("set force_download=true;")
conn.sql("install httpfs;load httpfs;")
conn.sql("install spatial;load spatial;")
conn.sql(f"create temp view permitsView as select * from read_parquet('{url}');")
bldgPermits = conn.sql("""select permitnumber as Permit_Number,
try_cast (permitnumbercreateddate as date) as Permit_Number_Created_Date,
try_cast (issuedate as date) as Issue_Date,
permitelapseddays::int as Permit_Elapsed_Days,
projectvalue::float as Project_Value,
typeofwork as Type_Of_Work,
address as Address,
projectdescription as Project_Description,
permitcategory as Permit_Category,
applicant as Applicant,
applicantaddress as Applicant_Address,
propertyuse as Property_Use,
specificusecategory as Specific_Use_Category,
buildingcontractor as Building_Contractor,
buildingcontractoraddress as Building_Contractor_Address,
issueyear as Issue_Year,
geolocalarea as Geo_Loc_Area,
yearmonth as Year_Month,
ST_AsText(ST_GeomFromWKB(geom)) as SHAPE
from permitsView;""")
df = bldgPermits.df()
df.spatial.set_geometry("SHAPE",sr=4326,inplace=True)
aprx = arcpy.mp.ArcGISProject("CURRENT")
gdb = aprx.defaultGeodatabase
out_fc = "Issued_Building_Permits"
location = os.path.join(gdb,out_fc)
df.spatial.to_featureclass(location,sanitize_columns=False)<\/code><\/pre><P> <\/P><P>コードを見るとテキストフィールド幅を10の倍数に丸める追加ステップがありますが、このケースでは不要な装飾です。ただし将来編集される可能性がある場合や値の切り捨てを避けたい場合には役立ちます。<\/P><P>これはスキーマ制御が最小限ながら、小さなクラウド上ファイルをArcGISに取り込む単純な例です。繰り返しますが標準SQL文でクラウド由来ファイルのビュー定義が可能です。この方法と<A title="Refining the schema for local well-known files" href="https:\/\/community.esri.com\/t5\/etl-patterns-blog\/etl-pattern-working-with-local-well-known-files\/?ba-p=1515000" target="_blank">こちら関連投稿</A>でジオプロセシングによるスキーマ定義との比較もできます。<\/P><P>次にグローバル規模データへ進みます。ノートブック<STRONG>Division_Area</STRONG>を使い、<A title="Overture" href="https:\/\/overturemaps.org\/download\/?rel=noopener noreferrer nofollow noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferrer noopener noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer noreferer nop... 出力を書き込む
print('出力を書き込み中 {}'.format(getNow()))
with arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version",
"subtype","primary_name","class","region",
"country","driving_side","shape@"]) as iCursor:
row = duckDivisions.fetchone()
i = 1
while row:
if i % 100000 == 0:
print(f"{i} 行の Division_Areas を挿入しました...")
row = list(row)
row[-1] = arcpy.FromWKB(row[-1])
iCursor.insertRow(row)
i+=1
row = duckDivisions.fetchone()
del iCursor
# ジオメトリを修復し、null ジオメトリを削除する
print(f"不正なジオメトリを修正中 {getNow()}")
arcpy.management.RepairGeometry(
in_features=arcDivisions,
delete_null="DELETE_NULL",
validation_method="OGC")
print(f"完了しました {getNow()}")
このグローバルスケールのデータ取り込みと建築許可の取り込みとの主な違いは、ソースが S3 hive ストレージ上のワイルドカードクエリで取得されていることと、スキーマが手動で定義されていることです。 ツールは同じです。
これで大規模および小規模のユーザー向けデータ取り込みの概要を見ましたが、もしあなたがデータ所有者で、ユーザーがツールを構築する手間をかけずに幅広いユーザーに無限に柔軟なクラウドの well-known file ベースのデータ配信を提供したい場合はどうでしょうか? ここでドイツの政治区分という中規模の例が登場します。
私たちが使用しているクライアントライブラリ DuckDB はストレージフォーマットでもあり、ビューを含む空間データベース機能も備えているため、クラウドソースのデータの場合、任意のエンドユーザーがオンデマンドで読み取るビューのみを含むデータ製品を提供することが簡単です。 つまり、ビュー定義を考える労力だけで、あらゆる規模のデータ製品を小さなデータベースファイルで配信できます。
ブログダウンロードにはそのようなデータ製品 - mydivisionview.duckdb - が含まれており、ノートブック MakeViewDatabase によって作成され、誰でもノートブック ReadViewDatabase を使って取り込むことができます。 この場合、わずか268KBのファイルで国レベル(あるいはグローバルレベル)のデータを配信できます。 これはクラウドネイティブな well-known file をクラウドからオンデマンドで読み取る力です。 もしデータセットソースが変わっても同じ DuckDB データベースは常に最新のデータにアクセスします。
以下は ReadViewDatabase のコードです。mydivisionview.duckdb ファイルをプロジェクトのホールフォルダーにコピーすれば、数秒(私の環境では15秒)でビュー内容をプロジェクトのデフォルトジオデータベースに配信します:
import arcpy
from datetime import datetime
import duckdb
import os
def getNow():
return str(datetime.utcnow().replace(microsecond=0))
print(f"{getNow()} にデータ抽出中")
arcpy.env.overwriteOutput = True
aprx = arcpy.mp.ArcGISProject("CURRENT")
homeFolder = aprx.homeFolder
gdb = aprx.defaultGeodatabase
sR = arcpy.SpatialReference(4326)
duckDB = os.path.join(homeFolder,"MyDivisionView.duckdb")
conn = duckdb.connect(duckDB)
conn.sql("install spatial;load spatial;")
conn.sql("install httpfs;load httpfs;")
conn.sql("set enable_object_cache=true;")
duckDivisions = conn.sql(f"""select id,
xmin::float as xmin,
ymin::float as ymin,
xmax::float as xmax,
ymax::float as ymax,
version::short as version,
subtype,
primary_name,
class,
region,
country,
driving_side,
geometry
from MyDivisionView;""")
arcDivisions = arcpy.management.CreateFeatureclass(gdb,
out_name="My_Division_Areas",
geometry_type="POLYGON",
spatial_reference=sR).getOutput(0)
arcpy.management.AddField(in_table=arcDivisions,field_name="id",field_type="TEXT",field_length=32)
for f in ["xmin","ymin","xmax","ymax"]:
arcpy.management.AddField(in_table=arcDivisions,field_name=f,field_type="FLOAT")
arcpy.management.AddField(in_table=arcDivisions,field_name="version",field_type="SHORT")
arcpy.management.AddField(in_table=arcDivisions,field_name="subtype",field_type="TEXT",field_length=20)
arcpy.management.AddField(in_table=arcDivisions,field_name="primary_name",field_type="TEXT",field_length=100)
arcpy.management.AddField(in_table=arcDivisions,field_name="class",field_type="TEXT",field_length=6)
arcpy.management.AddField(in_table=arcDivisions,field_name="region",field_type="TEXT",field_length=10)
arcpy.management.AddField(in_table=arcDivisions,field_name="country",field_type="TEXT",field_length=2)
arcpy.management.AddField(in_table=arcDivisions,field_name="driving_side",field_type="TEXT",field_length=10)
with arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version","subtype",
"primary_name","class","region","country",
"driving_side","shape@"]) as iCursor:
row = duckDivisions.fetchone()
i = 1
while row:
#if i % 1000 == 0:
# print('Inserted {} My_Division_Area rows'.format(i))
row = list(row)
row[-1] = arcpy.FromWKB(row[-1])
iCursor.insertRow(row)
i+=1
row = duckDivisions.fetchone()
del iCursor
conn.close()
print(f"{getNow()} にデータ抽出完了")最後にいくつかまとめます:
- クラウド上の小さな well-known file は簡単に取り込めます。
- オブジェクトストレージ上の無制限スケールの well-known file も簡単に取り込めます。
- 任意のスケールの well-known file データは任意のビュー定義で、小さなコンテナで簡単に共有できます。