Seit einiger Zeit wandern dateibasierte Daten ins Web, und wir sind alle daran gewöhnt, manuell herunterzuladen, was wir brauchen, und es dann in unserer Desktop-Software wie ArcGIS Pro weiterzuverarbeiten.<\/P>
Dieser Blog handelt davon, diese Erfahrung zu automatisieren<\/STRONG>, die Schritte des Herunterladens und Verarbeitens durch leicht verständliche, klar definierte und teilbare Werkzeuge zu ersetzen, die Ihr Informationsprodukt auf Abruf liefern<\/STRONG>.<\/P>An zwei Enden der Datenskala habe ich weltweite politische Einteilungen und Baugenehmigungsaktivitäten in Vancouver, Kanada. Der erstgenannte Datensatz ist groß<\/STRONG> und stammt aus einem AWS S3 Bucket, der letztere ist klein<\/STRONG> und stammt aus einem Open Data Portal. Beide werden mit Notebooks in ArcGIS Pro mit denselben grundlegenden Werkzeugen eingelesen.<\/STRONG><\/P>Zuerst der kleine Datensatz:<\/P>
Vancouver building permits<\/span><\/span><\/P>Der große Datensatz:<\/P>
Worldwide political divisions<\/span><\/span><\/P>Und im mittleren Bereich die politischen Einteilungen nur für Deutschland:<\/P>
A view of political divisions for Germany<\/span><\/span><\/P>Alle diese Datensätze wurden ähnlich eingelesen, aber mit bemerkenswerten Unterschieden, die wir unten besprechen werden.<\/P>Wenn Sie mit GeoParquet<\/A> nicht vertraut sind, werden Sie es sein, und ich mache es zu meinem widely used format<\/STRONG> der Wahl für diesen Beitrag. Andere gängige Cloud-Dateitypen wie CSV, Excel und JSON würden ebenfalls funktionieren. Mit "Cloud" meine ich nicht nur den Ort, an dem Sie sie abrufen, sondern Formate, die sich für Remote-Abfragen eignen, ohne dass Sie eine lokale Kopie herunterladen und inspizieren müssen.<\/P>Während ich sage, dass dasselbe grundlegende Werkzeug sowohl auf die großen Daten auf S3 als auch auf die kleinen Daten auf einer Website zugreifen kann<\/STRONG>, sind die Fähigkeiten der jeweiligen Server wichtig. S3 weiß, wie man Dateien entsprechend Abfragen gegen Hive-Speicher liefert, während eine Website nur weiß, wie man ganze Dateien zum Download bereitstellt.<\/P>Lassen Sie uns zuerst den kleineren Datensatz angehen - Baugenehmigungen durch automatisierten Download von einer Website.<\/P>Die Daten befinden sich in einem open data portal<\/STRONG><\/A>, wenn Sie die Seite durchsuchen, sehen Sie Optionen für das Download-Format.<\/P>Die Daten werden im Notebook VancouverBuildingPermits<\/STRONG> automatisiert verarbeitet. Wenn Sie den Code untersuchen, werden Sie feststellen, dass ich DuckDB<\/A> verwende - es ist eine räumlich aktivierte SQL-Datenbank und Web-Client. Sie werden sehen, dass der Daten-Download und die Umwandlung in eine Feature-Class sehr einfach sind - das Werkzeug leitet das Schema aus der Parquet-Quelle plus SQL-Anweisungen ab<\/STRONG>.<\/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>Wenn Sie den Code im Download untersuchen, sehen Sie einige zusätzliche Schritte, die ich verwendet habe, um Textfeldbreiten auf Vielfache von 10 aufzurunden – eine unnötige Verzierung in diesem Fall, aber nur um Ihnen zu zeigen, wie man das macht, falls Sie erwarten, dass die Daten zukünftig bearbeitet werden und keine Wertkürzungen wünschen.<\/P><P>Das ist also ein einfacher Fall des Einlesens einer kleinen cloud-resident Datei in ArcGIS mit minimaler Kontrolle über das Schema – beachten Sie erneut, dass Standard-SQL-Anweisungen verfügbar sind, um Ihre Ansicht der cloud-basierten Datei zu definieren. Vergleichen Sie diesen Ansatz mit der Definition des Schemas mittels Geoverarbeitung in <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">diesem verwandten Beitrag<\/A>.<\/P><P>Lassen Sie uns zur globalen Skala-Daten mit dem <STRONG>Division_Area<\/STRONG>-Notebook übergehen und Daten von der <A title="Overture" href="https:\/\/overturemaps.org\/download\/" target="_blank" rel="noopener nofollow noreferrer">Overture Maps Foundation<\/A> verwenden.<\/P><P> <\/P><pre class="lia-code-sample language-python"><code># Get the Division_Area layer from Overture Maps Foundation
import arcpy
from datetime import datetime
import duckdb
import os
aprx = arcpy.mp.ArcGISProject("CURRENT")
homeFolder = aprx.homeFolder
gdb = aprx.defaultGeodatabase
release = "2024-07-22.0"
def getNow():
return str(datetime.utcnow().replace(microsecond=0))
arcpy.env.overwriteOutput = True
sR = arcpy.SpatialReference(4326)
conn = duckdb.connect()
conn.sql("install spatial;load spatial;")
conn.sql("install httpfs;load httpfs;")
conn.sql("set s3_region='us-west-2';")
conn.sql("set enable_object_cache=true;")
# Make the DuckDB relation
# Insert any desired where clause, for example "country = 'GB'"
# The order-by expression helps to display small divisions on top of large ones, but is expensive
print(f"Processing starting at {getNow()}")
whereExp = "1 = 1"
sql = f"""select id,
bbox.xmin as xmin,
bbox.ymin as ymin,
bbox.xmax as xmax,
bbox.ymax as ymax,
version,
subtype,
names.primary as primary_name,
class,
region,
country,
norms.driving_side as driving_side,
geometry
from read_parquet('s3:\/\/_overturemaps-us-west-2\/_release\/_{release}\/_theme=divisions\/_type=division_area\/*',filename=true, hive_partitioning=1)
where {whereExp}
order by ST_Area(ST_GeomFromWKB(geometry)) desc;"""
duckDivisions = conn.sql(sql)
# Make the output feature class with precise schema control
print('Creating output feature class at {}'.format(getNow()))
arcDivisions = arcpy.management.CreateFeatureclass(out_path=gdb,
out_name="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)
# Schreibe die Ausgabe
print('Schreibe Ausgabe um {}'.format(getNow()))
mit arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version",
"subtype","primary_name","class","region",
"country","driving_side","shape@"]) als iCursor:
row = duckDivisions.fetchone()
i = 1
während row:
wenn i % 100000 == 0:
print(f"Eingefügte {i} Division_Areas Zeilen...")
row = list(row)
row[-1] = arcpy.FromWKB(row[-1])
iCursor.insertRow(row)
i+=1
row = duckDivisions.fetchone()
del iCursor
# Repariere Geometrien, entferne null Geometrien
print(f"Korrigiere fehlerhafte Geometrien um {getNow()}")
arcpy.management.RepairGeometry(
in_features=arcDivisions,
delete_null="DELETE_NULL",
validation_method="OGC")
print(f"Fertig um {getNow()}")
Die Hauptunterschiede zwischen diesem globalen Datenimport und dem für die building permits sind, dass die Quelle durch eine Wildcard-Abfrage auf S3 hive storage erhalten wird, und das Schema manuell definiert ist. Das Tooling ist dasselbe.< /P>
Das ist also ein Blick auf benutzerorientierten Datenimport in großem und kleinem Maßstab, aber was ist, wenn Sie ein Datenbesitzer sind und eine unendlich flexible cloud well-known file-basierte Datenbereitstellung für eine breite Nutzergruppe anbieten möchten, ohne dass diese sich die Mühe machen müssen, eigenes Tooling zu entwickeln? Hier kommt das Beispiel mittleren Maßstabs der politischen Divisionen für Deutschland ins Spiel.< /P>
Die Client-Bibliothek, die wir verwenden - DuckDB - ist auch ein Speicherformat, aber eines mit spatial database capabilities, einschließlich Views, sodass es im Fall von cloud-sourced data einfach ist, ein Datenprodukt anzubieten, das nur eine View enthält, die von jedem Endnutzer on-demand gelesen wird. Das bedeutet, dass mit dem Aufwand einer View-Definition ein Datenprodukt beliebigen Umfangs in einer winzigen Datenbankdatei geliefert werden kann.< /P>
Der Blog-Download enthält ein solches Datenprodukt - mydivisionview.duckdb - das durch das Notebook MakeViewDatabase erstellt wurde und von jedem mit dem Notebook ReadViewDatabase eingelesen werden kann. In diesem Fall kann also eine Datei von nur 268KB Daten nationalen (oder sogar globalen) Umfangs liefern. Das ist die Kraft von cloud-native well-known files, die on-demand aus der Cloud gelesen werden. Wenn sich die Datensatzquelle ändert, greift dieselbe DuckDB-Datenbank immer auf die neuesten Daten zu.< /P>
Unten steht der Code für ReadViewDatabase. Wenn Sie mydivisionview.duck db in Ihren project hole Ordner kopieren, liefert er den Inhalt der View in Ihre projektspezifische Standard-Geodatabase in Sekunden (15 auf meinem Rechner):
import arcpy
from datetime import datetime
import duckdb
import os
def getNow():
return str(datetime.utcnow().replace(microsecond=0))
print(f"Extrahiere Daten um {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)
für 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)
mit arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version","subtype",
"primary_name","class","region","country",
"driving_side","shape@"]) als iCursor:
row = duckDivisions.fetchone()
i = 1
während row:
#wenn i % 1000 == 0:
# print('Eingefügte {} My_Division_Area Zeilen'.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"Daten extrahiert um {getNow()}")Ich beende mit einigen Zusammenfassungspunkten:< /P>
- Kleine cloud well-known files in der Cloud sind leicht einzulesen.< /LI>
- Cloud well-known files unbegrenzten Umfangs in object storage sind leicht einzulesen.< /LI>
- Daten beliebigen Umfangs aus cloud well-known files in jeder View-Definition können leicht in winzigen Containern geteilt werden.< /LI>
< /P>
< /P>
< /P>