Comment créer un tampon orienté en utilisant arcpy?

9

Je voudrais créer un tampon orienté pour chaque polygone de mon fichier de formes en utilisant arcpy. Par orienté, je veux dire que j'ai deux angles a1 et a2 qui contraignent la direction du tampon. Ceci est représenté dans le graphique ci-dessous: entrez la description de l'image ici

Des idées?

WAF
la source
3
Il faudrait plus d'informations sur les angles. De quel axe mesurez-vous les angles? CW ou CCW? Comment localisez-vous chaque angle sur le polygone? Avec quels types de polygones avons-nous affaire? (Un cercle n'est pas un polygone.)
Paul
1
+1 à @Paul mais je pensais qu'un cercle était un polygone jusqu'à ce que je lise ceci .
PolyGeo
+1 aussi! J'ai utilisé le cercle pour illustrer facilement le problème. Les polygones sont les résultats d'une segmentation dans eCognition suivie d'une classification pour identifier une classe. Les angles a1 et a2 dérivent de l'angle d'azimut d'éclairage de l'image satellite segmentée. Dans l'exemple, l'angle d'azimut serait égal à 0, a1 et a2 égal à 0 +/- 15 ° (arbitrairement fixé à 15 °).
WAF du
2
@PolyGeo "Polygon" est utilisé un peu différemment en SIG qu'en mathématiques. Ici , il fait référence à une représentation numérique d'un ( à deux dimensions) région ou sa fermeture . Les régions sont généralement (mais pas toujours) représentées par des approximations polygonales , mais - comme nous savons que nos représentations informatiques ne sont que des approximations - nous supprimons «approximation» et utilisons simplement «polygone».
whuber

Réponses:

20

Sommaire

Cette réponse place la question dans un contexte plus large, décrit un algorithme efficace applicable à la représentation de fichiers de formes d'entités (comme «vecteurs» ou «chaînes de lignes» de points), montre quelques exemples de son application et donne un code de travail pour l'utilisation ou le portage dans un environnement SIG.

Contexte

Ceci est un exemple de dilatation morphologique. En toute généralité, une dilatation "étale" les points d'une région dans leurs quartiers; la collection de points où ils se terminent est la «dilatation». Les applications dans les SIG sont nombreuses: modélisation de la propagation du feu, du mouvement des civilisations, de la propagation des plantes, et bien plus encore.

Mathématiquement, et dans une très grande généralité (mais utile), une dilatation répartit un ensemble de points dans une variété riemannienne (comme un plan, une sphère ou un ellipsoïde). L'étalement est stipulé par un sous-ensemble du faisceau tangent en ces points. Cela signifie qu'à chacun de ces points un ensemble de vecteurs (directions et distances) est donné (j'appelle cela un "voisinage"); chacun de ces vecteurs décrit un chemin géodésique commençant à son point de base. Le point de base est "étalé" aux extrémités de tous ces chemins. (Pour la définition beaucoup plus limitée de «dilatation» qui est classiquement utilisée dans le traitement d'image, voir l'article Wikipedia . La fonction d'étalement est connue sous le nom de carte exponentielle en géométrie différentielle.)

La "mise en mémoire tampon" d'une entité est l'un des exemples les plus simples d'une telle dilatation: un disque de rayon constant (le rayon de la mémoire tampon) est créé (au moins conceptuellement) autour de chaque point de l'entité. L'union de ces disques est le tampon.

Cette question demande le calcul d'une dilatation légèrement plus compliquée où l'étalement ne peut se produire que dans une plage d'angles donnée (c'est-à-dire dans un secteur circulaire). Cela n'a de sens que pour les entités qui ne renferment aucune surface sensiblement incurvée (telles que les petites entités sur la sphère ou l'ellipsoïde ou toutes les entités dans le plan). Lorsque nous travaillons dans le plan, il est également important d'orienter tous les secteurs dans la même direction. (Cependant, si nous modélisons la propagation du feu par le vent, nous souhaiterions que les secteurs soient orientés avec le vent et que leurs tailles puissent également varier avec la vitesse du vent: c'est une des motivations de la définition générale de la dilatation que j'ai donnée. ) (Sur des surfaces courbes comme un ellipsoïde, il est en général impossible d'orienter tous les secteurs dans la "même" direction.)

Dans les circonstances suivantes, la dilatation est relativement facile à calculer:

  • L'entité est dans l'avion (c'est-à-dire que nous dilatons une carte de l'entité et, espérons-le, la carte est assez précise).

  • La dilatation sera constante : l'étalement en chaque point de la caractéristique se produira dans des voisinages congruents de même orientation.

  • Ce quartier commun est convexe. La convexité simplifie et accélère considérablement les calculs.

Cette question s'inscrit dans de telles circonstances spécialisées: elle demande la dilatation de polygones arbitraires par des secteurs circulaires dont les origines (les centres des disques dont ils sont issus) se situent aux points de base. À condition que ces secteurs ne s'étendent pas sur plus de 180 degrés, ils seront convexes. (Les secteurs plus grands peuvent toujours être divisés en deux en deux secteurs convexes; l'union des deux dilatations plus petites donnera le résultat souhaité.)


la mise en oeuvre

Parce que nous effectuons des calculs euclidiens - faisant l'étalement dans le plan - nous pouvons dilater un point simplement en traduisant le voisinage de dilatation en ce point. (Pour pouvoir le faire, le quartier a besoin d'une originequi correspondra au point de base. Par exemple, l'origine des secteurs dans cette question est le centre du cercle à partir duquel ils sont formés. Cette origine se trouve à la limite du secteur. Dans l'opération de tamponnage SIG standard, le voisinage est un cercle dont l'origine est en son centre; maintenant l'origine se trouve à l'intérieur du cercle. Le choix d'une origine n'est pas un gros problème de calcul, car un changement d'origine ne fait que déplacer la dilatation entière, mais cela peut être un gros problème en termes de modélisation des phénomènes naturels. La sectorfonction dans le code ci-dessous illustre comment une origine peut être spécifiée.)

Dilater un segment de ligne peut être délicat, mais pour un voisinage convexe, nous pouvons créer la dilatation comme l'union des dilatations des deux extrémités avec un parallélogramme soigneusement choisi. (Dans l'intérêt de l'espace, je ne m'arrêterai pas pour prouver des affirmations mathématiques comme celle-ci, mais j'encourage les lecteurs à essayer leurs propres preuves car c'est un exercice perspicace.) Voici une illustration utilisant trois secteurs (montrés en rose). Ils ont des rayons unitaires et leurs angles sont donnés dans les titres. Le segment de ligne lui-même a une longueur de 2, est horizontal et est représenté en noir:

Dilatations de segment

Les parallélogrammes sont trouvés en localisant les points roses qui sont aussi loin que possible du segment dans la direction verticale uniquement . Cela donne deux points inférieurs et deux points supérieurs le long de lignes parallèles au segment. Il suffit de réunir les quatre points en un parallélogramme (représenté en bleu). Remarquez, à droite, comment cela a du sens même lorsque le secteur lui-même n'est qu'un segment de ligne (et non un vrai polygone): là, chaque point du segment a été traduit dans une direction à 171 degrés à l'est du nord sur une distance allant de 0 à 1. L'ensemble de ces points d'extrémité est le parallélogramme illustré. Les détails de ce calcul apparaissent dans la bufferfonction définie dans dilate.edgesle code ci-dessous.

Pour dilater une polyligne , nous formons l'union des dilatations des points et segments qui la forment. Les deux dernières lignes dilate.edgeseffectuent cette boucle.

La dilatation d'un polygone nécessite d'inclure l'intérieur du polygone ainsi que la dilatation de sa frontière. (Cette assertion fait quelques hypothèses sur le quartier de dilatation. L'une est que tous les quartiers contiennent le point (0,0), ce qui garantit que le polygone est inclus dans sa dilatation. Dans le cas des quartiers variables, il suppose également que la dilatation de tout intérieur le point du polygone ne s'étendra pas au-delà de la dilatation des points limites. C'est le cas pour les voisinages constants.)

Examinons quelques exemples de la façon dont cela fonctionne, d'abord avec un nonagone (choisi pour révéler les détails) puis avec un cercle (choisi pour correspondre à l'illustration de la question). Les exemples continueront d'utiliser les trois mêmes quartiers, mais seront réduits à un rayon de 1/3.

Dilatations d'un nonagone

Sur cette figure, l'intérieur du polygone est gris, les dilatations ponctuelles (secteurs) sont roses et les dilatations de bord (parallélogrammes) sont bleues.

Dilatations d'un cercle

Le "cercle" n'est vraiment qu'un 60-gon, mais il se rapproche bien d'un cercle.


Performance

Lorsque l'entité de base est représentée par N points et le voisinage de dilatation par M points, cet algorithme nécessite un effort O (N M) . Cela doit être suivi en simplifiant le désordre des sommets et des arêtes dans l'union, ce qui peut nécessiter un effort O (N M log (N M)): c'est quelque chose à demander au SIG de faire; nous ne devrions pas avoir à programmer cela.

L'effort de calcul pourrait être amélioré à O (M + N) pour les entités de base convexes (car vous pouvez déterminer comment contourner la nouvelle frontière en fusionnant de manière appropriée les listes de sommets décrivant les limites des deux formes d'origine). Cela ne nécessiterait pas non plus de nettoyage ultérieur.

Lorsque le voisinage de dilatation change lentement de taille et / ou d'orientation au fur et à mesure que vous progressez autour de l'entité de base, la dilatation du bord peut être étroitement approchée à partir de la coque convexe de l'union des dilatations de ses extrémités. Si les deux quartiers de dilatation ont des points M1 et M2, cela peut être trouvé avec un effort O (M1 + M2) en utilisant un algorithme décrit dans Shamos & Preparata, Computational Geometry . Par conséquent, en laissant K = M1 + M2 + ... + M (N) le nombre total de sommets dans les N quartiers de dilatation, nous pouvons calculer la dilatation en temps O (K * log (K)).

Pourquoi voudrions-nous aborder une telle généralisation si tout ce que nous voulons, c'est un simple tampon? Pour les grandes entités terrestres, un voisinage de dilatation (tel qu'un disque) qui a en réalité une taille constante peut avoir une taille variable sur la carte où ces calculs sont effectués. Ainsi, nous obtenons un moyen de faire des calculs précis pour l'ellipsoïde tout en continuant à profiter de tous les avantages de la géométrie euclidienne.


Code

Les exemples ont été produits avec ce Rprototype, qui peut facilement être porté dans votre langage préféré (Python, C ++, etc.). Dans sa structure, elle est parallèle à l'analyse rapportée dans cette réponse et n'a donc pas besoin d'explication séparée. Les commentaires clarifient certains détails.

(Il peut être intéressant de noter que les calculs trigonométriques ne sont utilisés que pour créer les exemples d'entités - qui sont des polygones réguliers - et les secteurs. Aucune partie des calculs de dilatation ne nécessite de trigonométrie.)

#
# Dilate the vertices of a polygon/polyline by a shape.
#
dilate.points <- function(p, q) {
  # Translate a copy of `q` to each vertex of `p`, resulting in a list of polygons.
  pieces <- apply(p, 1, function(x) list(t(t(q)+x)))
  lapply(pieces, function(z) z[[1]]) # Convert to a list of matrices
}
#
# Dilate the edges of a polygon/polyline `p` by a shape `q`. 
# `p` must have at least two rows.
#
dilate.edges <- function(p, q) {
  i <- matrix(c(0,-1,1,0), 2, 2)       # 90 degree rotation
  e <- apply(rbind(p, p[1,]), 2, diff) # Direction vectors of the edges
  # Dilate a single edge from `x` to `x+v` into a parallelogram
  # bounded by parts of the dilation shape that are at extreme distances
  # from the edge.
  buffer <- function(x, v) {
    y <- q %*% i %*% v # Signed distances orthogonal to the edge
    k <- which.min(y)  # Find smallest distance, then the largest *after* it
    l <- (which.max(c(y[-(1:k)], y[1:k])) + k-1) %% length(y)[1] + 1
    list(rbind(x+q[k,], x+v+q[k,], x+v+q[l,], x+q[l,])) # A parallelogram
  }
  # Apply `buffer` to every edge.
  quads <- apply(cbind(p, e), 1, function(x) buffer(x[1:2], x[3:4]))
  lapply(quads, function(z) z[[1]]) # Convert to a list of matrices
}
#----------------------- (This ends the dilation code.) --------------------------#
#
# Display a polygon and its point and edge dilations.
# NB: In practice we would submit the polygon, its point dilations, and edge 
#     dilations to the GIS to create and simplify their union, producing a single
#     polygon.  We keep the three parts separate here in order to illustrate how
#     that polygon is constructed.
#
display <- function(p, d.points, d.edges, ...) {
  # Create a plotting region covering the extent of the dilated figure.
  x <- c(p[,1], unlist(lapply(c(d.points, d.edges), function(x) x[,1])))
  y <- c(p[,2], unlist(lapply(c(d.points, d.edges), function(x) x[,2])))
  plot(c(min(x),max(x)), c(min(y),max(y)), type="n", asp=1, xlab="x", ylab="y", ...)
  # The polygon itself.
  polygon(p, density=-1, col="#00000040")
  # The dilated points and edges.
  plot.list <- function(l, c) lapply(l, function(p) 
                  polygon(p, density=-1, col=c, border="#00000040"))
  plot.list(d.points, "#ff000020")
  plot.list(d.edges, "#0000ff20")
  invisible(NULL) # Doesn't return anything
}
#
# Create a sector of a circle.
# `n` is the number of vertices to use for approximating its outer arc.
#
sector <- function(radius, arg1, arg2, n=1, origin=c(0,0)) {
  t(cbind(origin, radius*sapply(seq(arg1, arg2, length.out=n), 
                  function(a) c(cos(a), sin(a)))))
}
#
# Create a polygon represented as an array of rows.
#
n.vertices <- 60 # Inscribes an `n.vertices`-gon in the unit circle.
angles <- seq(2*pi, 0, length.out=n.vertices+1)
angles <- angles[-(n.vertices+1)]
polygon.the <- cbind(cos(angles), sin(angles))
if (n.vertices==1) polygon.the <- rbind(polygon.the, polygon.the)
#
# Dilate the polygon in various ways to illustrate.
#
system.time({
  radius <- 1/3
  par(mfrow=c(1,3))
  q <- sector(radius, pi/12, 2*pi/3, n=120)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="-30 to 75 degrees")

  q <- sector(radius, pi/3, 4*pi/3, n=180)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="-150 to 30 degrees")

  q <- sector(radius, -9/20*pi, -9/20*pi)
  d.points <- dilate.points(polygon.the, q)
  d.edges <- dilate.edges(polygon.the, q)
  display(polygon.the, d.points, d.edges, main="171 degrees")
})

Le temps de calcul pour cet exemple (d'après la dernière figure), avec N = 60 et M = 121 (à gauche), M = 181 (au milieu) et M = 2 (à droite), était d'un quart de seconde. Cependant, la plupart de cela était pour l'affichage. En règle générale, ce Rcode gérera environ N M = 1,5 million par seconde (en ne prenant que 0,002 seconde environ pour effectuer tous les exemples de calcul illustrés). Néanmoins, l'apparition du produit M N implique des dilatations de nombreuses figures ou des figures compliquées via un voisinage détaillé pouvant prendre un temps considérable, alors attention! Benchmarkez le timing des petits problèmes avant d'en aborder un gros. Dans de telles circonstances, on pourrait envisager une solution basée sur un raster (qui est beaucoup plus facile à implémenter, ne nécessitant essentiellement qu'un seul calcul de voisinage.)

whuber
la source
Wow, c'est très détaillé et fascinant. Je n'en attendais pas moins.
Paul
1

C'est assez large, mais vous pourriez:

  1. tampon le polygone d'origine
  2. trouver le point d'origine des rayons "orientés" à créer sur la frontière du polygone (une sorte de point de tangence?)
  3. créer / prolonger une ligne à partir de ce point jusqu'à une distance au-delà du tampon en utilisant l'angle discuté dans les commentaires de la question.
  4. coupez cette ligne avec le tampon et le polygone d'origine. Cela pourrait probablement être fait en même temps que 3) avec des arguments appropriés pour étendre.
  5. extraire le nouveau polygone "tampon orienté" de l'ensemble de polygones résultant
Roland
la source
Je pense que l'OP signifie un "tampon orienté" dans le sens d'une dilatation morphologique de chaque forme par un secteur d'un cercle. (Cette description donne immédiatement une solution raster; mais comme les fichiers de formes sont au format vectoriel, une solution vectorielle serait souhaitable. C'est délicat à faire.)
whuber
Espérons que le PO clarifiera ce point. Je suis entré dans ma ligne de pensée basée sur le graphique, qui n'est pas toujours le plus sûr. Quoi qu'il en soit, bien que l'on puisse placer une forme irrégulière de cellules par rapport à un emplacement calculé (je l'ai fait dans gridedit ... je me sens vieux!), Je penserais qu'une solution vectorielle serait plus propre / exploiterait mieux les fonctions vectorielles d'Arc . La méthode générale est probablement analogue, quel que soit le modèle de données. Peut-être un peu plus de codage pour l'utilisateur du côté raster.
Roland
En fait, aucun codage n'est nécessaire du côté raster :-). Cela peut être fait de plusieurs manières, y compris des statistiques focales avec un quartier correctement défini. Je conviens qu'une solution vectorielle est préférable ici: plus propre et plus précise. Cependant, pour les ensembles de données extrêmement volumineux ou complexes, cela pourrait s'enliser, alors qu'une solution raster sera rapide, il vaut donc toujours la peine de savoir comment procéder dans les deux sens.
whuber
Réflexion sur les focales , mais je ne savais pas si la forme + l'angle de l'OP serait difficile à combiner dans un seul quartier .
Roland
Seul le secteur doit être décrit par le quartier, ce qui est facile à faire .
whuber