Une relation porte la description de ce qui se passe lorsque des entités interagissent les unes avec les autres.
Ocelet fait un usage important du concept de graphe d'interaction. Un graphe d'interaction est d'abord un graphe (au sens mathématique du terme) c'est à dire un ensemble d'entités (les sommets du graphe) et un ensemble de liens entre des entités (les arètes du graphe).
C'est à travers la définition d'une relation qu'il est possible de construire un exemplaire (ou plusieurs) de graphe d'interction. Cette définition permet d'abord d'indiquer la nature du graphe : on indique les catégories d'entités qui sont susceptibles d'interagir; elle permet ensuite d'indiquer ce qui se passe lorsque ces entités interagissent les unes avec les autres : on décrit des fonctions d'interaction.
L'utilisation dans un scénario se fait principalement à travers l'appel de fonctions prédéfinies (construction du graphe), puis de fonctions de filtrage et d'interactions que l'on a défini.
Définition d'une relation
relation
Nom de la relation<
Entité1 role1,
Entité2 role2>
{
définitions de fonctions d'interactions, de filtrage, et de propriétés d'arètes}
Le Nom de la relation doit impérativement commencer par une lettre (majuscule de préférence) mais peut éventuellement être suivi par des chiffres, et ne pas contenir d'espace ni de caractères spéciaux.
On peut placer dans une définition de relation autant de propriétés et de fonctions de filtrage et d'interaction que l'on veut.
Dans cette définition, Entité1 et Entité2 sont des catégories d'entités qui doivent être définies par ailleurs. Il ne s'agit pas d'instances mais bien du nom générique de catégories d'entités, (c'est la raison pour laquelles nous avons mis la première lettre en majuscule). Les mots role1 et role2 sont des identifiants que vous pouvez choisir, ils vont être utilisés pour représenter une instance d'entité (un sommet du graphe) dans les fonctions d'interaction. Autrement dit, quand on va définir une fonction d'interaction, on pourra utiliser l'identifiant role1 comme si c'était un variable de type Entité1 et on fera de même avec role2 en tant qu'instance d'Entité2. Chaque arète du graphe d'interaction aura dans ce cas deux extrémités : role1 et role2, et la définition de fonctions d'interactions va permettre de décrire ce qui se passe entre ces deux extrémités au moment d'une interaction entre elles.
Définition d'une fonction d'interaction
interaction
nom de la fonction(
(Type argument (,
Type argument)* )?)
{
code du corps de la fonction
(agg
{
opérations d'aggrégation}
)?}
Le nom de la fonction doit impérativement commencer par une lettre (minuscule de préférence) mais peut éventuellement être suivi par des chiffres, et ne pas contenir d'espace ni de caractères spéciaux.
On peut optionnellement déclarer un ou plusieurs arguments qu'il est possible de passer à la fonction (la notation (...)?
utilisée ici signifie 0 ou 1 occurence). Dans ce cas chaque argument sera déclaré par : Type argument
. Le Type est soit un type simple d'Ocelet (Integer, Double, Point, etc.), soit un type composite, soit une catégorie d'entité, soit un datafacer, soit même une autre relation. L'argument est un nom de variable que vous choisissez, il doit commencer par une lettre en minuscule.
Si on déclare plusieurs arguments, ils doivent être séparés par une virgule : Type1 arg1, Type2 arg2, Type3 arg3
etc. (La notation (...)*
utilisée dans la définition de syntaxe signifie 0 occurence ou plus).
Le code du corps de la fonction contient une série d'instructions du langage, d'appels à des fonctions usuelles, ou à des fonctions d'aggrégation. Ce code correspond à ce qui se passe sur chaque arète du graphe lorsqu'une entité role1 interagit avec une entité role2.
Il est important de noter que dans le code d'une fonction d'interaction, les propriétés des entités role1 et role2 ne sont pas modifiées directement si on leur affecte une nouvelle valeur. Dans le principe on lit l'état n et on écrit l'état n+1 des propriétés. La modification effective n'intervient qu'après application de la fonction d'interaction à toutes les arètes du graphe.
Dans certains cas, c'est à travers un opérateur d'aggrégation que cette affectation a lieu. Une syntaxe particulière doit être utilisée à la fin de la fonction pour indiquer sur quelles propriétés on fera appel à des opérateurs d'aggrégation :
agg
{
( role.
propriété<<
|+<<
opérateur d'aggrégation)*}
On peut déclarer l'usage d'opérateurs d'aggrégation sur autant de propriétés que nécessaire. C'est ce qu'indique la notation (...)*
. Les opérateurs d'aggrégation peuvent être soit un de ceux qui sont prédéfinis dans Ocelet (Random, Max, Min, Mean, etc.) soit un opérateur que vous aurez défini vous même.
La notation <<
| +<<
signifie que l'on peut utiliser soit le sigle <<
soit le sigle +<<
. Dans le premier cas seules les nouvelles valeurs affectées à la propriété (les valeurs de l'état n+1) sont passées dans la liste des valeurs candidates à l'opérateur d'affectation. Dans le second cas, la valeur précédente (l'état n) de la propriété est elle aussi présente dans la liste des valeurs candidates. Voir la section consacrée à l'usage des opérateurs d'aggrégation pour davantage de détails.
Définition d'une propriété d'arète
On peut si nécessaire attacher des propriétés aux arètes d'un graphe d'interaction. Il suffit de déclarer ces propriété dans la définition de la relation. On pourra ensuite mettre à jour ou lire la valeur de ces propriétés d'arc dans le code des fonctions d'interaction comme s'il s'agissait de variables. On déclarera chaque propriété de la façon suivante :
property
Type nom
Le Type d'une propriété est soit un type simple d'Ocelet ( Integer, Double, Point, etc.), soit un type composite défini par vous même sous la forme d'une structure.
Le nom d'une propriété doit impérativement commencer par une lettre (minuscule de préférence) mais peut éventuellement être suivi par des chiffres, et ne pas contenir d'espace ni de caractère spéciaux.
Définition d'une fonction de filtrage
Les filtres sont des fonctions qui permettent de conserver certaines arètes d'un graphe d'interaction. Si par exemple on souhaite exécuter une fonction d'interaction sur une partie seulement d'un graphe, il est pratique de faire appel à un filtre pour n'appliquer cette fonction qu'aux arètes qui répondent à certains critères.
Un filtre est donc une fonction qui doit déterminer si chacune des arètes visitées doit être conservée ou non et renvoyer une valeur de type Boolean : soit true
(si on conserve l'arète) soit false
(si on ne la conserve pas). La syntaxe de définition d'un filtre sur les arètes est la suivante :
filter
nom du filtre{
code du filtre}
Le nom du filtre doit impérativement commencer par une lettre (minuscule de préférence) mais peut éventuellement être suivi par des chiffres, et ne pas contenir d'espace ni de caractère spéciaux.
Le code du filtre est le corps de la fonction, il doit renvoyer une valeur soit true
(si on conserve l'arète) soit false
(si on ne la conserve pas).
On fait référence à un ou plusieurs types d'entité dans la définition d'une Relation. C'est la raison pour laquelle nous avons aussi des définitions d'entités dans les exemples présentés ci-dessous.
entity Parcelle {
Integer id
Integer ocsol
MultiPolygon geom
String culture
}
entity MntCellule{
property Cell cell
property Double altitude
}
entity Hexagone{
property Cell cell
property Double valeur
property Integer os
}
relation Voisin<Parcelle p1, Parcelle p2> {
// Vrai si p1 et p2 ont la même occupation du sol que l'argument os fourni
filter memeOcSol(Integer os) {
return ((p1.ocsol == os) && (p2.ocsol == os))
}
// Vrai si la distance entre p1 et p2 est inférieure à dmax
filter distance(Double dmax) {
return (p1.geom.distance(p2.geom) <= dmax)
}
// Dessine une arète de ce graphe dans un fichier Kml
// KmlOut est ici un Datafacer de type KmlExport
interaction drawEdge(KmlOut ko, Date beg, Date end){
let pc1 = Point|xy(p1.geom.centroid.x,p1.geom.centroid.y)
let pc2 = Point|xy(p2.geom.centroid.x,p2.geom.centroid.y)
let arc = Line|points(pc1,pc2)
ko.addGeometry("Voisins",p1.id+" -- "+p2.id,beg,end,arc,"vois",0)
}
}
entity Exploitant {
// L'exploitant choisit quelle culture mettre sur la parcelle
// connaissant l'indentifiant de la parcelle et la date.
service String getSemis(Integer idParc, Date datesemis) {
...
}
}
relation Explotation<Exploitant ex, Parcelle p> {
// La culture utilisée sur la parcelle est mise à jour
interaction Seme(Date dateSemis) {
p.culture = ex.getSemis(p.id, dateSemis)
}
}
relation MntToHexagon<MntCellule mnt, Hexagone hexagone>{
// La valeur en altitude est attribuée à la propriété valeur des entités Hexagones (moyenne par aggregation)
interaction setValeurMnt(){
hexagone.valeur = mnt.altitude
}agg{
hexagone.valeur << Mean
}
}
relation HexagoneEtParcelle<Hexagone hexagone, Parcelle parcelle>{
// Attribution de l'occupation du sol aux Hexagones venant du parcellaires
interaction setOcs(){
hexagone.os = parcelle.ocsol
}
}
relation HexagonVoisins<Hexagone hexagone1, Hexagone hexagone2>{
// Moyenne de la valeur des hexagones voisins sur l'hexagone central
interaction moyenneDesVoisins(){
hexagone1.valeur = hexagone2.valeur
hexagone2.valeur = hexagone1.valeur
}agg{
hexagone1.valeur << Mean
hexagone2.valeur << Mean
}
}
La définition d'une Relation permet de décrire un type de graphe d'interaction. Cela permet de construire une ou plusieurs variables contenant un exemplaire de graphe de ce type là (un graphe d'interaction est une instance de relation).
scenario MonModele {
// On obtient une liste de parcelles à partir d'un datafacer Shapefile
let shpData = new ShpDatafacer
let lparc = shpData.readAll()
// Creation d'un graphe de type Voisin
let graphVoisinage = new Voisin
// Ajoute une liste de Parcelles au graphe
graphVoisinage.addAllParcelle(lparc)
...
}
Fonctions disponibles sur les graphes d'interaction
Lorsque l'on a initialisé une variable avec un graphe d'interaction, on dispose de deux sortes de fonctions qu'il est possible d'appliquer à ce graphe :
Fonctions prédéfinies
connect()
: ajoute une arète retenue par un filtre au graphedisconnect()
: retire une arète parcourue du graphegetComplete()
: parcours les arètes (virtuelles) du graphe complet. Les arètes du graphe complet ne sont pas réellement ajoutées au graphe.Integer size()
: renvoie le nombre d'arètes du grapheLes fonctions générées à partir de la définition de la relation
// Construit des arètes entre les parcelles voisines
// (moins de 30m de distance entre elles)
graphVoisinage.complete.distance(30).connect()
Graphes d'interaction dont une entité contient un type Cell
Lors de l'utilisation d'une relation avec des entités non définies par une propriété de type Cell il faut explicitement, pour chaque entité utiliser la fonction connect(). Dans le cas d'une relation avec au moins une entité ayant une propriété Cell si les autres entités sont définies avec une Geometry ou un type Cell la connection est implicite par correspondance spatiale ou par voisinage. Il existe trois types de graphes implicites selon les formes de représentations utilisées :
Fonctions prédéfinies pour une relation avec le même type d'entités cellulaires
connect(List<Entité> entités)
: Connecte automatiquement les entités cellulaires avec leurs voisines
Integer size()
: renvoie le nombre d'arètes du grapheLes fonctions générées à partir de la définition de la relation
createSquares(Geometry geom, Double resolutionX, Double resolutionY)
: Création d'entités cellulaires de formes carrée ou rectangulaire. La résolution correspond au côté du carré. L'ensemble des entités est généré sur la zone correpondant a l'enveloppe de la géométrie passée en paramètre.createHexagons(Geometry geom, Double resolution)
: Création d'entités cellulaires de formes hexagonales régulière. La résolution correspond au côté d'un hexagone. L'ensemble des entités est généré sur la zone correpondant a l'enveloppe de la géométrie passée en paramètre.createTriangles(Geometry geom, Double resolution)
: Création d'entités cellulaires de forme triangulaire. La résolution correspond au côté du triangle. L'ensemble des entités est généré sur la zone correpondant a l'enveloppe de la géométrie passée en paramètre.getAllNom_Entité()
: renvoie la liste d'entités cellulaires créées avec une méthode createSquares(...), createHexagons(...) ou createTriangles(...), où Nom_Entité est le nom utilisé dans la définition du type d'entité. extendedMoore(Integer distance)
: Créer une distance de voisinage étendue en nombre de pixel sous la forme d'un voisinage de Moore.extendedCircularMoore(Integer distance)
: Créer une distance circulaire de voisinage étendue en nombre de pixel sous la forme d'un voisinage de Moore.Fonctions prédéfinies pour une relation avec des types différents d'entités cellulaires
connect(List<Entité1> entités1, List<Entités2> entités2)
: Connecte automatiquement les entités cellulaires de la première liste avec les entités cellulaires de la deuxième par correspondance spatiale.
Integer size()
: renvoie le nombre d'arètes du grapheFonctions prédéfinies pour une relation avec entités cellulaires et des entités ayant une propriété de type Géométrique
connect(List<Entité1> entités1, List<Entités2> entités2)
: Connecte automatiquement les entités cellulaires avec les entités géométriques par correspondance spatiale.Integer size()
: renvoie le nombre d'arètes du grapheExemple d'utilisation dans un scénario
scenario MonModele {
// On obtient une liste de parcelles à partir d'un datafacer Shapefile
let shpData = new ShpDatafacer
let lparc = shpData.readAll()
// Creation d'un graphe de type Voisin
let graphVoisinage = new Voisin
// Ajoute une liste de Parcelles au graphe
graphVoisinage.addAllParcelle(lparc)
//On obtient une liste de MntCellule à partir d'un datafacer RasterFile dont l'emprise est calculée sur celle du Shapefile
let mnt = new Mnt
let mntList = mnt.readAllMntCellule(shpData.boundaries)
//On créait une relation de voisinnages entre les entités Hexagones
let hexaVoisins = new HexagonVoisins
//On créait les entités à partir de la relation précédente sur l'emprise du shapefile (hexagones avec des cotés de 100 mètres)
hexaVoisins.createHexagons(shpData.boundaries, 100.0)
//On obtient une liste d'Hexagones à partir de la relation
let hexaList = hexaVoisins.getAllHexagone
// Création de la relation entre Hexagones et Parcelles
let hexaParcelle = new HexagoneEtParcelle
hexaParcelle.connect(hexaList, lparc)
//On lance l'interaction pour attributer l'occupation du sol des parcelles aux hexagones
hexaParcelle.setOcs
// Création de la relation entre Hexagones et MntCellule
let mntHexa = new MntToHexagon
mntHexa.connect(mntList, hexaList)
//On lance l'interaction pour attributer l'altitude des MntCellules aux hexagones
mntHexa.setValeurMnt
//On lance l'interaction sur les hexagones voisins pour calculer la moyenne de leurs valeurs sur l'hexagone central
hexaVoisins.moyenneDesVoisins
...
}