Depuis un certain temps, les données basées sur des fichiers migrent vers le web et nous sommes tous habitués à télécharger manuellement ce dont nous avons besoin et à continuer à partir de là dans notre logiciel de bureau comme ArcGIS Pro.<\/P>
Ce blog parle de l'automatisation<\/STRONG> de cette expérience, en remplaçant les étapes de téléchargement et de traitement par des outils faciles à comprendre, bien définis et partageables qui livrent votre produit d'information à la demande<\/STRONG>.<\/P>À deux extrémités de l'échelle des données, j'ai les divisions politiques mondiales et l'activité des permis de construire à Vancouver, Canada. Le premier jeu de données est important<\/STRONG> et provient d'un bucket AWS S3, le second est petit<\/STRONG> et provient d'un portail de données ouvertes. Ils sont tous deux ingérés en utilisant des notebooks dans ArcGIS Pro avec les mêmes outils de base.<\/STRONG><\/P>Le petit jeu de données d'abord :<\/P>
Permis de construire à Vancouver<\/span><\/span><\/P>Le grand jeu de données :<\/P>
Divisions politiques mondiales<\/span><\/span><\/P>Et au milieu, les divisions politiques pour seulement l'Allemagne :<\/P>
Une vue des divisions politiques pour l'Allemagne<\/span><\/span><\/P>Tous ces jeux de données ont été ingérés de manière similaire mais avec des différences notables que nous discuterons ci-dessous.<\/P>Si vous ne connaissez pas GeoParquet<\/A>, vous allez le découvrir, et j'en fais mon format bien connu<\/STRONG> préféré pour ce post. D'autres types courants de fichiers cloud comme CSV, Excel et JSON fonctionneraient aussi. Par "cloud", je veux dire non seulement où vous allez les chercher mais aussi des formats qui se prêtent à une requête distante sans que vous ayez à télécharger et inspecter une copie locale.<\/P>Bien que je dise que les mêmes outils de base peuvent accéder aux grandes données sur S3 et aux petites données sur un site web<\/STRONG>, les capacités des serveurs respectifs comptent. S3 sait comment livrer des fichiers selon des requêtes contre un stockage hive, tandis qu'un site web sait juste comment livrer des téléchargements de fichiers entiers.<\/P>Voyons comment aborder d'abord le plus petit jeu de données - les permis de construire, par téléchargement automatisé depuis un site web.<\/P>Les données résident dans un portail de données ouvertes<\/STRONG><\/A>, si vous naviguez sur le site vous verrez les options de format de téléchargement.<\/P>Les données sont automatisées dans le notebook VancouverBuildingPermits<\/STRONG>. Si vous inspectez le code, vous remarquerez que j'utilise DuckDB<\/A> pour aider - c'est une base de données SQL spatiale activée et un client web. Vous verrez que le téléchargement des données et la conversion en classe d'entités est très simple - l'outil déduit le schéma à partir de la source Parquet plus les instructions SQL<\/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>Si vous inspectez le code dans le téléchargement, vous verrez quelques étapes supplémentaires que j'ai utilisées pour arrondir la largeur des champs texte à des facteurs de 10, une fantaisie inutile dans ce cas mais juste pour vous montrer comment faire cela si vous prévoyez que les données soient modifiées à l'avenir et que vous ne voulez pas la troncature des valeurs.<\/P><P>C'est donc un cas simple d'ingestion d'un petit fichier résidant dans le cloud dans ArcGIS avec un contrôle minimal sur le schéma - notez encore une fois que les instructions SQL standard sont disponibles pour définir votre vue du fichier source cloud. Comparez cette approche à la définition du schéma en utilisant le géotraitement dans <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">ce post lié<\/A>.<\/P><P>Passez à l'échelle mondiale avec le notebook <STRONG>Division_Area<\/STRONG>, utilisant les données de la <A title="Overture" href="https:\/\/overturemaps.org\/download\/" target="_blank" rel="noopener nofollow noreferrer">Overture Maps Foundation<\/A>.<\/P><P> <\/P><pre class="lia-code-sample language-python"><code># Obtenir la couche Division_Area depuis 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;")
# Créer la relation DuckDB
# Insérer toute clause where désirée, par exemple "country = 'GB'"
# L'expression order-by aide à afficher les petites divisions au-dessus des grandes, mais est coûteuse
print(f"Traitement commencé à {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)
# Créer la classe d'entités en sortie avec un contrôle précis du schéma
print('Création de la classe d'entités en sortie à {}'.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)
pour 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)
# Écrire la sortie
print('Écriture de la sortie à {}'.format(getNow()))
avec arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version",
"subtype","primary_name","class","region",
"country","driving_side","shape@"]) comme iCursor :
row = duckDivisions.fetchone()
i = 1
while row :
if i % 100000 == 0 :
print(f"Inséré {i} lignes Division_Areas...")
row = list(row)
row[-1] = arcpy.FromWKB(row[-1])
iCursor.insertRow(row)
i+=1
row = duckDivisions.fetchone()
del iCursor
# Réparer les géométries, supprimer les géométries nulles
print(f"Correction des géométries incorrectes à {getNow()}")
arcpy.management.RepairGeometry(
in_features=arcDivisions,
delete_null="DELETE_NULL",
validation_method="OGC")
print(f"Terminé à {getNow()}")
Les principales différences entre cette ingestion de données à l'échelle mondiale et celle pour les permis de construire sont que la source est obtenue par une requête générique sur le stockage S3 hive, et que le schéma est défini manuellement. Les outils sont les mêmes.< /P>
Ceci est donc un aperçu de l'ingestion de données orientée utilisateur à grande et petite échelle, mais que faire si vous êtes un propriétaire de données et que vous souhaitez offrir une livraison de données basée sur des fichiers well-known cloud infiniment flexible à un large éventail d'utilisateurs sans qu'ils aient à se donner la peine de créer des outils ? C'est là qu'intervient l'exemple à moyenne échelle des divisions politiques pour l'Allemagne.< /P>
La bibliothèque cliente que nous utilisons - DuckDB - est aussi un format de stockage, mais avec des capacités de base de données spatiale, y compris des vues, donc dans le cas des données provenant du cloud il est simple d'offrir un produit de données qui contient uniquement une vue qui est lue à la demande par n'importe quel utilisateur final. Cela signifie, pour l'effort de définir une vue, qu'un produit de données de n'importe quelle échelle peut être livré dans un petit fichier de base de données.< /P>
Le téléchargement du blog contient un tel produit de données - mydivisionview.duckdb - qui a été créé par le notebook MakeViewDatabase et peut être ingéré par n'importe qui utilisant le notebook ReadViewDatabase. Dans ce cas, un fichier de seulement 268 Ko peut fournir des données à l'échelle nationale (ou même mondiale). C'est la puissance des fichiers well-known cloud natifs lus à la demande depuis le cloud. Si la source du jeu de données change, la même base DuckDB accédera toujours aux dernières données.< /P>
Ci-dessous se trouve le code pour ReadViewDatabase. Si vous copiez mydivisionview.duck db dans votre dossier projet hole il livrera le contenu de la vue dans votre géodatabase par défaut du projet en quelques secondes (15 sur ma machine) :
import arcpy
from datetime import datetime
import duckdb
import os
def getNow():
return str(datetime.utcnow().replace(microsecond=0))
print(f"Extraction des données à {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)
pour f dans ["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)
avec arcpy.da.InsertCursor(arcDivisions,["id","xmin","ymin","xmax","ymax","version","subtype",
"primary_name","class","region","country",
"driving_side","shape@"]) comme iCursor :
row = duckDivisions.fetchone()
i = 1
while row :
#if i % 1000 == 0:
# print('Inséré {} lignes My_Division_Area'.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"Données extraites à {getNow()}")Je terminerai par quelques points résumés :
- Les petits fichiers well-known cloud dans le cloud sont facilement ingérés.
- Les fichiers well-known cloud illimités dans le stockage d'objets sont facilement ingérés.
- Toute donnée fichier well-known cloud à n'importe quelle échelle dans n'importe quelle définition de vue peut être facilement partagée dans de petits conteneurs.