Le blog francophone consacré
aux technologies Esri

Modifier la structure et les données de vos couches d'entités avec l'API Python ArcGIS

Je poursuis ma série de tutoriels consacrés à l'usage de l'API ArcGIS for Python. Aujourd'hui, je vous propose de voir un ensemble d'instructions permettant la modification des données des couches d'entités de votre portail ArcGIS. Le tutoriel ci-dessous montre également comment modifier la structure des couches.

     
Le Notebook présenté ci-dessous est téléchargeable ici.

Tuto 4: Modifier la structure et les données de vos couches d'entités

Ce tutoriel a pour objectif d'illustrer les possibilités de l'API Python ArcGIS en termes de mise à jour des données et de la structure des couches d'entités hébergées sur votre portail ArcGIS. A l'aide des exemples de code ci-dessous, vous pourrez élaborer des scripts plus évolués automatisant la mise à jour des données de vos couches, l'ajout et le calcul de champs, l'ajout de domaines de valeurs, de pièces jointes, ...
In [81]:
from IPython.display import display
from arcgis.gis import GIS

my_gis = GIS('https://www.arcgis.com', 'username', 'password')
In [82]:
my_gis

Lister les champs d'une couche d'entités

Commençons tout d'abord par récupérer la couche d'entités. Par exemple, en utilisant son "ItemID".
In [83]:
my_item = my_gis.content.get('5f227e966acf46d7b2b254f2af85a27e')
my_item
Out[83]:
Salles_de_cinemas_en_France
Salles_de_cinemas_en_FranceFeature Layer Collection by glavenu_esrifrance
Last Modified: octobre 28, 2018
0 comments, 4 views
La séquence d'instructions suivante permet de récupérer la liste des champs de la couche d'entités hébergée.
In [84]:
from arcgis.features import FeatureLayer
my_FL = FeatureLayer.fromitem(my_item,0)
my_fields = my_FL.properties.fields
Profitons-en pour utiliser les capacités de présentation de Jupyter pour présenter cette liste en exploitant les capacités de la syntaxe Markdown !
In [46]:
from IPython.display import Markdown, display
strMarkdown = "Name | Alias | Type | SQL Type"
strMarkdown += "\n" + "-|-|-|-"
for field in my_fields:
    strMarkdown  += "\n" + field.name + " | " + field.alias + " | " + field.type + " | " + field.sqlType

display(Markdown(strMarkdown))
Name Alias Type SQL Type
objectId objectId esriFieldTypeOID sqlTypeOther
ID ID esriFieldTypeInteger sqlTypeOther
Nom Nom esriFieldTypeString sqlTypeOther
Region Region esriFieldTypeString sqlTypeOther
Adresse Adresse esriFieldTypeString sqlTypeOther
Complement Complement esriFieldTypeString sqlTypeOther
Code_INSEE Code_INSEE esriFieldTypeString sqlTypeOther
Commune Commune esriFieldTypeString sqlTypeOther
Pop_Commune Pop_Commune esriFieldTypeInteger sqlTypeOther
Departement Departement esriFieldTypeString sqlTypeOther
Num_Unite_Urbaine Num_Unite_Urbaine esriFieldTypeString sqlTypeOther
Unite_Urbaine Unite_Urbaine esriFieldTypeString sqlTypeOther
Pop_Unite_Urbaine Pop_Unite_Urbaine esriFieldTypeInteger sqlTypeOther
Situation_Geographique Situation_Geographique esriFieldTypeString sqlTypeOther
Ecrans Ecrans esriFieldTypeInteger sqlTypeOther
Fauteuils Fauteuils esriFieldTypeInteger sqlTypeOther
Semaines_activite Semaines_activite esriFieldTypeInteger sqlTypeOther
Seances Seances esriFieldTypeInteger sqlTypeOther
Entrees Entrees esriFieldTypeInteger sqlTypeOther
Tranches_Entrees Tranches_Entrees esriFieldTypeString sqlTypeOther
Proprietaire Proprietaire esriFieldTypeString sqlTypeOther
Programmateur Programmateur esriFieldTypeString sqlTypeOther
AE AE esriFieldTypeString sqlTypeOther
Categorie_Art_et_Essai Categorie_Art_et_Essai esriFieldTypeString sqlTypeOther
AE_RD AE_RD esriFieldTypeString sqlTypeOther
AE_JP AE_JP esriFieldTypeString sqlTypeOther
AE_PR AE_PR esriFieldTypeString sqlTypeOther
Label_Art_et_Essai Label_Art_et_Essai esriFieldTypeString sqlTypeOther
Genre Genre esriFieldTypeString sqlTypeOther
Multiplexe Multiplexe esriFieldTypeString sqlTypeOther
Categorie_Exploitation Categorie_Exploitation esriFieldTypeString sqlTypeOther
Zone_Commune Zone_Commune esriFieldTypeString sqlTypeOther
QPV QPV esriFieldTypeString sqlTypeOther
Nombre_Films_Programmes Nombre de films programmés esriFieldTypeString sqlTypeOther
Nombre_Films Nombre_Films esriFieldTypeString sqlTypeOther
PDM_Entrees_Films_Francais PDM_Entrees_Films_Francais esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Américains PDM_Entrees_Films_Americains esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Europeens PDM_Entrees_Films_Europeens esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Autres PDM_Entrees_Films_Autres esriFieldTypeSingle sqlTypeOther
Films_Art_et_Essai Films_Art_et_Essai esriFieldTypeString sqlTypeOther
Part_Seances_Art_et_Essai Part_Seances_Art_et_Essai esriFieldTypeSingle sqlTypeOther
Part_Entrees_Art_et_Essai Part_Entrees_Art_et_Essai esriFieldTypeSingle sqlTypeOther
Ancien_ID Ancien_ID esriFieldTypeDouble sqlTypeOther

Supprimer un champ

Nous souhaitons maintenant supprimer le champ "Ancien_ID". Pour cela, nous devons tout d'abord créer un objet JSON décrivant la propriété "name" du champ (ou des champs) à supprimer. Ensuite, la méthode "delete_from_definition" de l'objet "FeatureLayerManager" va vous permettre de supprimer effectivement le(s) champ(s) de la couche d'entités ou de la table.
In [47]:
field_to_delete = {'name': 'Ancien_ID'}
my_FL.manager.delete_from_definition({'fields':[field_to_delete]})
Out[47]:
{'success': True}
Reprenons le code précédement utiliser pour accéder à la couche d'entités et lister à nouveau les champs. Le champ "Ancien_ID" a bien été supprimé.
In [48]:
my_FL = FeatureLayer.fromitem(my_item,0)
my_fields = my_FL.properties.fields
strMarkdown = "Name | Alias | Type | SQL Type"
strMarkdown += "\n" + "-|-|-|-"
for field in my_fields:
    strMarkdown  += "\n" + field.name + " | " + field.alias + " | " + field.type + " | " + field.sqlType

display(Markdown(strMarkdown))
Name Alias Type SQL Type
objectId objectId esriFieldTypeOID sqlTypeOther
ID ID esriFieldTypeInteger sqlTypeOther
Nom Nom esriFieldTypeString sqlTypeOther
Region Region esriFieldTypeString sqlTypeOther
Adresse Adresse esriFieldTypeString sqlTypeOther
Complement Complement esriFieldTypeString sqlTypeOther
Code_INSEE Code_INSEE esriFieldTypeString sqlTypeOther
Commune Commune esriFieldTypeString sqlTypeOther
Pop_Commune Pop_Commune esriFieldTypeInteger sqlTypeOther
Departement Departement esriFieldTypeString sqlTypeOther
Num_Unite_Urbaine Num_Unite_Urbaine esriFieldTypeString sqlTypeOther
Unite_Urbaine Unite_Urbaine esriFieldTypeString sqlTypeOther
Pop_Unite_Urbaine Pop_Unite_Urbaine esriFieldTypeInteger sqlTypeOther
Situation_Geographique Situation_Geographique esriFieldTypeString sqlTypeOther
Ecrans Ecrans esriFieldTypeInteger sqlTypeOther
Fauteuils Fauteuils esriFieldTypeInteger sqlTypeOther
Semaines_activite Semaines_activite esriFieldTypeInteger sqlTypeOther
Seances Seances esriFieldTypeInteger sqlTypeOther
Entrees Entrees esriFieldTypeInteger sqlTypeOther
Tranches_Entrees Tranches_Entrees esriFieldTypeString sqlTypeOther
Proprietaire Proprietaire esriFieldTypeString sqlTypeOther
Programmateur Programmateur esriFieldTypeString sqlTypeOther
AE AE esriFieldTypeString sqlTypeOther
Categorie_Art_et_Essai Categorie_Art_et_Essai esriFieldTypeString sqlTypeOther
AE_RD AE_RD esriFieldTypeString sqlTypeOther
AE_JP AE_JP esriFieldTypeString sqlTypeOther
AE_PR AE_PR esriFieldTypeString sqlTypeOther
Label_Art_et_Essai Label_Art_et_Essai esriFieldTypeString sqlTypeOther
Genre Genre esriFieldTypeString sqlTypeOther
Multiplexe Multiplexe esriFieldTypeString sqlTypeOther
Categorie_Exploitation Categorie_Exploitation esriFieldTypeString sqlTypeOther
Zone_Commune Zone_Commune esriFieldTypeString sqlTypeOther
QPV QPV esriFieldTypeString sqlTypeOther
Nombre_Films_Programmes Nombre de films programmés esriFieldTypeString sqlTypeOther
Nombre_Films Nombre_Films esriFieldTypeString sqlTypeOther
PDM_Entrees_Films_Francais PDM_Entrees_Films_Francais esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Américains PDM_Entrees_Films_Americains esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Europeens PDM_Entrees_Films_Europeens esriFieldTypeSingle sqlTypeOther
PDM_Entrees_Films_Autres PDM_Entrees_Films_Autres esriFieldTypeSingle sqlTypeOther
Films_Art_et_Essai Films_Art_et_Essai esriFieldTypeString sqlTypeOther
Part_Seances_Art_et_Essai Part_Seances_Art_et_Essai esriFieldTypeSingle sqlTypeOther
Part_Entrees_Art_et_Essai Part_Entrees_Art_et_Essai esriFieldTypeSingle sqlTypeOther

Ajouter un nouveau champ

L'objectif est de créer un nouveau champ dans lequel nous allons calculer le ratio entre le nombre d'entrée et le nombre de séances dans chaque cinéma. La première étape consiste à décrire, en JSON, les propriétés du nouveau champ. Se référer à la documentation de l'API Rest ArcGIS pour obtenir la liste des propriétés et valeurs supportées.
In [49]:
new_field={'alias': 'Ratio des Entrees-Seances',
 'domain': None,
 'editable': True,
 'length': 10,
 'name': 'ratio_entrees_seances',
 'nullable': True,
 'sqlType': 'sqlTypeOther',
 'type': 'esriFieldTypeDouble'}
Ensuite, vous utiliserez la méthode "add_to_definition" de l'objet "FeatureLayerManager" pour spécifier la liste des nouveaux champs à ajouter à la définition de la couche d'entités (ou de la table).
In [50]:
my_FL.manager.add_to_definition({'fields':[new_field]})
Out[50]:
{'success': True}
Vérifions que le champ a bien été ajouté en affichant le dernier champ de la table attributaire de la couche.
In [51]:
my_FL = FeatureLayer.fromitem(my_item,0)
my_fields = my_FL.properties.fields
last_field=my_fields[len(my_fields)-1]
last_field
Out[51]:
{
  "domain": null,
  "editable": true,
  "alias": "Ratio des Entrees-Seances",
  "nullable": true,
  "defaultValue": null,
  "sqlType": "sqlTypeOther",
  "name": "ratio_entrees_seances",
  "type": "esriFieldTypeDouble"
}

Associer un domaine de valeurs à un champ

Les champs d'une couche d'entités hébergée peuvent être associés à des listes de valeurs, l'équivalents des domaines de valeurs dans les Géodatabases. Voyons comment ajouter une liste de valeurs à un champ en utilisant l'API Python ArcGIS.
Tout d'abord, vous décrirez l'objet JSON du champ que vous voulez mettre à jour, en décrivant le domaine comme ci-dessous. Vous noterez que l'on ne décrit pas totalement l'objet "field", uniquement la propriétés "name" et les autres propriétés devant être modifiées, ici la propriété "domain":
In [91]:
updated_field={
    'name': "Categorie_Art_et_Essai",
    'domain': {
        'type': "codedValue",
        'name': "Categorie_Art_et_Essai_Domain",
        'codedValues': [
            {
            'name': "Non classé",
            'code': "Non classé"
            },
            {
            'name': "Catégorie A",
            'code': "Catégorie A"
            },
            {
            'name': "Catégorie B",
            'code': "Catégorie B"
            }
        ]
    },
    'defaultValue': "Non classé"
}
A l'aide de la méthode "update_definition" vous appliquerez les modifications sur votre champ.
In [92]:
my_FL.manager.update_definition({'fields':[updated_field]})
Out[92]:
{'success': True}

Calculer les valeurs d'un champ

Commençons par un premier exemple dans lequel nous souhaitons mettre le champ AE (Année Exploitation) à 2017 pour les cinémas d'Ile-de-France et 2016 pour les autres régions:
In [52]:
my_FL.calculate("Region LIKE 'ILE-DE-FRANCE'",{"field": "AE", "value" : "2017"})
Out[52]:
{'success': True, 'updatedFeatureCount': 90}
In [53]:
my_FL.calculate("Region NOT LIKE 'ILE-DE-FRANCE'",{"field": "AE", "value" : "2016"})
Out[53]:
{'success': True, 'updatedFeatureCount': 911}
Ci-dessous, un deuxième exemple visant à calculer la valeur du champ "ratio_entrees_seances" par une division du nombre d'entrées par le nombre de séances, tout cela à l'aide d'une expression SQL. Le calcul sera, cette fois-ci, appliqué à tous les enregistrements de la couche d'entités:
In [54]:
my_FL.calculate("1=1",{"field": "ratio_entrees_seances", "sqlExpression" : "CAST(Entrees AS FLOAT) / CAST(Seances AS FLOAT)"})
Out[54]:
{'success': True, 'updatedFeatureCount': 1005}

Ajouter une entité à votre couche

Pour ajouter un nouvel enregistrement dans une couche d'entités, il vous faut tout d'abord créer l'objet "Feature" en JSON. Vous noterez que dans notre exemple, ne n'avons pas listé et renseigné tous les champs de la table.
In [55]:
new_feature={"geometry": {"y": 68540528, "x": 6404123}, 
             "attributes": {"Nom": "CINEMA UGC VELIZY2","Commune": "Vélizy-Villacoublay", "Code_Insee": "78640",
                            "Region": "ILE-DE-France", "Fauteuils": 750, "Seances": 12}}
La méthode "edit_features" permet d'exécuter l'opération de mise à jour. On notera que l'argument "adds" permet de fournir plusieurs entités à l'aide d'un tableau d'objet "feature". A noter également que cette méthode "edit_feature" permet en une seule opération de réaliser des ajouts, des mises à jour et des suppressions.
In [56]:
my_FL.edit_features(adds=[new_feature])
Out[56]:
{'addResults': [{'globalId': None,
   'objectId': 1007,
   'success': True,
   'uniqueId': 1007}],
 'deleteResults': [],
 'updateResults': []}

Mettre à jour une entité de votre une couche

Pour mettre à jour des entités existantes, il vous faudra tout d'abord les récupérer, par exemple, à l'aide d'une requête ci-dessous. A noter que l'on peut aussi construire entièrement (ou partiellement) l'objet "feature" en JSON.
In [57]:
my_featureset = my_FL.query(where="Nom LIKE 'CINEMA UGC VELIZY2'")
feature_to_update = my_featureset.features[0]
Mettre ensuite à jour les différents attributs de ces entités.
In [58]:
feature_to_update.attributes['Adresse']='Centre commercial Vélizy 2'
feature_to_update.attributes['Multiplexe']='Oui'
Nous utilisons à nouveau la méthode "edit_features" avec l'argument "updates" pour mettre à jour la liste d'objets "feature" à actualiser dans la couche d'entités.
In [59]:
my_FL.edit_features(updates= [feature_to_update])
Out[59]:
{'addResults': [],
 'deleteResults': [],
 'updateResults': [{'globalId': None,
   'objectId': 1002,
   'success': True,
   'uniqueId': 1002}]}

Supprimer une entité dans une couche

La suppression d'une (ou plusieurs) entité(s) se fait de différentes manières. Vous pouvez utiliser, comme précédement, la méthode "edit_features" peut être utilisée en fournissant la liste des entités en JSON avec leur propriété "ObjectId". Vous pouvez également utiliser la méthode "delete_features" et fournir une requête définissant les entités à supprimer.
In [60]:
my_FL.delete_features(where="Nom LIKE 'CINEMA UGC VELIZY2'")
Out[60]:
{'deleteResults': [{'globalId': None,
   'objectId': 1002,
   'success': True,
   'uniqueId': 1002},
  {'globalId': None, 'objectId': 1007, 'success': True, 'uniqueId': 1007}]}

Activer les pièces-jointes sur une couche d'entité

Pour pouvoir ajouter des pièces-jointes (attachments en anglais) aux entités de votre couche, vous devez tout d'abord activer le stockage des pièces-jointes sur la couche. Pour cela, nous passons à nouveau par la modification de la définition de la couche.
In [61]:
attachment_JSON={'hasAttachments': True}
my_FL.manager.update_definition(attachment_JSON)
Out[61]:
{'success': True}

Ajouter des pièces-jointes

Maintenant que la couche dispose de la structure pour le stockage de pièces-jointes, la méthode "add" sur la propriété "attachments" permet d'ajouter votre fichier. Vous noterez que le premier argument de la méthode "add" correspond à l'objectID de l'entité. Recherchons le cinéma "Caméo Commanderie" à Nancy pour lui associer une photo.
In [62]:
my_featureset = my_FL.query(where="Nom LIKE 'Caméo Commanderie Nancy'")
feature_oid = my_featureset.features[0].get_value('objectId')
my_FL.attachments.add(feature_oid, '/Users/glavenu/Documents/SIG/temp/cameo_nancy.jpg')
Out[62]:
{'addAttachmentResult': {'globalId': None, 'objectId': 1, 'success': True}}

Lister les pièces-jointes d'une entité

Pour lister les pièces-jointes d'une entité, il suffira de rechercher l'entité puis d'utiliser la méthode
In [65]:
my_featureset = my_FL.query(where="Nom LIKE 'Kinépolis Nancy'")
feature_oid = my_featureset.features[0].get_value('objectId')
my_attachements=my_FL.attachments.get_list(feature_oid)
my_attachements
Out[65]:
[{'contentType': 'image/jpeg',
  'id': 2,
  'keywords': '',
  'name': 'kinepolis_nancy1.jpg',
  'parentObjectId': 1005,
  'size': 115106},
 {'contentType': 'image/jpeg',
  'id': 3,
  'keywords': '',
  'name': 'kinepolis_nancy2.jpg',
  'parentObjectId': 1005,
  'size': 9671},
 {'contentType': 'image/jpeg',
  'id': 4,
  'keywords': '',
  'name': 'kinepolis_nancy3.jpg',
  'parentObjectId': 1005,
  'size': 22707},
 {'contentType': 'image/jpeg',
  'id': 5,
  'keywords': '',
  'name': 'kinepolis_nancy4.jpg',
  'parentObjectId': 1005,
  'size': 109055}]

Supprimer des pièces-jointes

Ci-dessous, un exemple de code permettant de supprimer la dernière pièce-jointe de l'entité listé précédement. . La méthode "delete" appliquée à la propriété "attachments" permet de supprimer une pièce jointe en précisant l'ObjectID de l'entité et l'ID de la pièce-jointe.
Pour mémoire, en Python, l'index -1 permet d'accéder au dernier élément d'un tableau ou d'une liste
In [66]:
my_FL.attachments.delete(my_attachements[-1]['parentObjectId'],my_attachements[-1]['id'])
Out[66]:
{'deleteAttachmentResults': [{'globalId': None,
   'objectId': 5,
   'success': True}]}
In [68]:
my_attachements=my_FL.attachments.get_list(feature_oid)
my_attachements
Out[68]:
[{'contentType': 'image/jpeg',
  'id': 2,
  'keywords': '',
  'name': 'kinepolis_nancy1.jpg',
  'parentObjectId': 1005,
  'size': 115106},
 {'contentType': 'image/jpeg',
  'id': 3,
  'keywords': '',
  'name': 'kinepolis_nancy2.jpg',
  'parentObjectId': 1005,
  'size': 9671},
 {'contentType': 'image/jpeg',
  'id': 4,
  'keywords': '',
  'name': 'kinepolis_nancy3.jpg',
  'parentObjectId': 1005,
  'size': 22707}]

Télécharger les pièces-jointes

Pour être complet sur la manipulation des pièces-jointes, vois les instruction permettant de les télécharger en local.
In [79]:
img=my_FL.attachments.download(oid=feature_oid, attachment_id=4)
img
Out[79]:
['/var/folders/8q/lxsbl08d1sj4lsjypx2hkk580000gn/T/kinepolis_nancy3.jpg']
Si la pièce-jointe est une image et que vous souhaitez la visualiser dans votre Notebook, vous pouvez utiliser les instructions suivantes:
In [80]:
from IPython.core.display import Image, display
img_path=img[0]
display(Image(img_path))

Partager cet article:

Rejoindre la discussion

    Les commentaires à propos de cet article: