Al een tijd migreren op bestanden gebaseerde gegevens naar het web en we zijn allemaal gewend om handmatig te downloaden wat we nodig hebben en het van daaruit te gebruiken in onze desktopsoftware zoals ArcGIS Pro.<\/P>
Deze blog gaat over het automatiseren<\/STRONG> van die ervaring, waarbij de download- en verwerkingsstappen worden vervangen door gemakkelijk te begrijpen, goed gedefinieerde en deelbare tools die uw informatieproduct op aanvraag leveren<\/STRONG>.<\/P>Aan twee uiteinden van de dataschaal heb ik wereldwijde politieke indelingen en bouwvergunningactiviteiten in Vancouver, Canada. De eerste dataset is groot<\/STRONG> en afkomstig uit een AWS S3 bucket, de laatste is klein<\/STRONG> en afkomstig van een open data portal. Beide worden ingelezen met notebooks in ArcGIS Pro met dezelfde basis tooling.<\/STRONG><\/P>Eerst de kleine dataset:<\/P>
Bouwvergunningen Vancouver<\/span><\/span><\/P>De grote dataset:<\/P>
Wereldwijde politieke indelingen<\/span><\/span><\/P>En in het middengebied, de politieke indelingen voor alleen Duitsland:<\/P>
Een overzicht van politieke indelingen voor Duitsland<\/span><\/span><\/P>Al deze datasets werden op vergelijkbare wijze ingelezen maar met opmerkelijke verschillen die we hieronder zullen bespreken.<\/P>Als u niet bekend bent met GeoParquet<\/STRONG><\/A>, zult u dat worden, en ik maak het mijn bekende formaat<\/STRONG> van keuze voor dit bericht. Andere veelvoorkomende cloud-bestandstypen zoals CSV, Excel en JSON zouden ook werken. Met "cloud" bedoel ik niet alleen waar u ze vandaan haalt, maar formaten die zich lenen voor remote query zonder dat u een lokale kopie hoeft te downloaden en te inspecteren.<\/P>Hoewel ik zeg dat dezelfde basis tooling toegang kan krijgen tot de grote data op S3 en de kleine data op een website<\/STRONG>, zijn de mogelijkheden van de respectieve servers belangrijk. S3 weet hoe bestanden te leveren volgens queries tegen hive storage, terwijl een website alleen weet hoe hele bestanden als downloads te leveren.<\/P>Laten we eerst kijken hoe we de kleinere dataset aanpakken - bouwvergunningen, door geautomatiseerde download van een website.<\/P>De data bevindt zich in een open data portal<\/STRONG><\/A>, als u door de site surft ziet u opties voor downloadformaten.<\/P>De data wordt geautomatiseerd in de notebook VancouverBuildingPermits<\/STRONG>. Als u de code inspecteert ziet u dat ik gebruik maak van DuckDB<\/STRONG><\/A> om te helpen - het is een ruimtelijk ingeschakelde SQL database en webclient. U zult zien dat het downloaden van data en conversie naar feature class heel eenvoudig is - de tooling leidt het schema af van de Parquet bron plus SQL statements<\/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>Als u de code inspecteert in de download ziet u enkele extra stappen die ik gebruikte om tekstveldbreedtes af te ronden op factoren van 10, een onnodige versiering in dit geval maar gewoon om te laten zien hoe u dit doet als u verwacht dat de data in de toekomst bewerkt wordt en u geen afkapping van waarden wilt.<\/P><P>Dus dat is een eenvoudig geval van het inlezen van een klein cloud-resident bestand in ArcGIS met minimale controle over het schema - let nogmaals op dat standaard SQL statements beschikbaar zijn om uw weergave van het cloud-gebaseerde bestand te definiëren. Vergelijk deze aanpak met het definiëren van het schema met geoprocessing in <A title="Refining the schema for local well-known files" href="https:\/\/community.esri.com\/"><STRONG>dit gerelateerde bericht<\/STRONG><\/A>.<\/P><P>Laten we opschalen naar wereldwijde schaaldata met behulp van de <STRONG>Division_Area<\/STRONG>-notebook, gebruikmakend van data van <A title="Overture" href="https:\/\/overturemaps.org\/"><STRONG>Overture Maps Foundation<\/STRONG><\/A>.<\/P><P> <\/P><pre class="lia-code-sample language-python"><code># Haal de Division_Area laag op van 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;")
# Maak de DuckDB relatie
# Voeg elke gewenste where-clausule toe, bijvoorbeeld "country = 'GB'"
# De order-by expressie helpt om kleine divisies bovenop grote weer te geven, maar is duur
print(f"Verwerking gestart om {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)
# Maak de output feature class met precieze schema controle
print('Output feature class aanmaken op {}'.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)
# Schrijf de uitvoer
print('Schrijven van uitvoer op {}'.format(getNow()))
met arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version",
"subtype","primary_name","class","region",
"country","driving_side","shape@"]) als iCursor:
rij = duckDivisions.fetchone()
i = 1
terwijl rij:
als i % 100000 == 0:
print(f"Ingevoegde {i} Division_Areas rijen...")
rij = list(rij)
rij[-1] = arcpy.FromWKB(rij[-1])
iCursor.insertRow(rij)
i+=1
rij = duckDivisions.fetchone()
del iCursor
# Repareer geometrieën, verwijder null geometrieën
print(f"Repareren van eventuele slechte geometrieën op {getNow()}")
arcpy.management.RepairGeometry(
in_features=arcDivisions,
delete_null="DELETE_NULL",
validation_method="OGC")
print(f"Klaar op {getNow()}")
De belangrijkste verschillen tussen deze wereldwijde schaal data ingest en die voor de bouwvergunningen is dat de bron wordt verkregen door een wildcard query op S3 hive opslag, en het schema handmatig wordt gedefinieerd. De tooling is hetzelfde.< /P>
Dus dat is een blik op gebruikersgerichte ingest van data op grote en kleine schaal, maar wat als je een data-eigenaar bent en je wilt oneindig flexibele cloud well-known file-gebaseerde data levering aanbieden aan een breed publiek van gebruikers zonder dat zij de moeite hoeven te nemen om tooling te bouwen? Hier komt het mid-scale voorbeeld van politieke divisies voor Duitsland om de hoek kijken.< /P>
De clientbibliotheek die we gebruiken - DuckDB - is ook een opslagformaat, maar één met ruimtelijke database mogelijkheden, inclusief views, dus voor het geval van cloud-gesourcede data is het eenvoudig om een data product aan te bieden dat alleen een view bevat die on-demand door elke eindgebruiker wordt gelezen. Dit betekent dat, voor de inspanning van het uitzoeken van een view-definitie, een data product van elke schaal kan worden geleverd in een klein databasebestand.< /P>
De blog download bevat zo'n data product - mydivisionview.duckdb - dat is gemaakt door de notebook MakeViewDatabase en kan worden ingelezen door iedereen met de notebook ReadViewDatabase. In dit geval kan dan een bestand van slechts 268KB data leveren op nationale (of zelfs wereldwijde) schaal. Dit is de kracht van cloud-native well-known files die on-demand vanuit de cloud worden gelezen. Als de datasetbron verandert, zal dezelfde DuckDB database altijd toegang hebben tot de nieuwste data.< /P>
Hieronder staat de code voor ReadViewDatabase. Als je mydivisionview.duck db in je project hole map kopieert, levert het binnen enkele seconden (15 op mijn machine) de inhoud van de view in je project standaard geodatabase:
import arcpy
van datetime import datetime
import duckdb
import os
def getNow():
return str(datetime.utcnow().replace(microsecond=0))
print(f"Gegevens extraheren op {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)
voor 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)
met arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version","subtype",
"primary_name","class","region","country",
"driving_side","shape@"]) als iCursor:
rij = duckDivisions.fetchone()
i = 1
terwijl rij:
#als i % 1000 == 0:
# print('Ingevoegde {} My_Division_Area rijen'.format(i))
rij = list(rij)
rij[-1] = arcpy.FromWKB(rij[-1])
iCursor.insertRow(rij)
i+=1
rij = duckDivisions.fetchone()
del iCursor
conn.close()
print(f"Gegevens geëxtraheerd op {getNow()}")Ik sluit af met een paar samenvattende punten:< /P>
- Kleine cloud well-known files in de cloud zijn gemakkelijk te verwerken.< /LI>
- Onbeperkte schaal cloud well-known files in objectopslag zijn gemakkelijk te verwerken.< /LI>
- Cloud well-known file data van elke schaal in elke view-definitie kan gemakkelijk worden gedeeld in kleine containers.< /LI>
< /P>
< /P>
< /P>