Épaisseur de paroi minimale d'un polygone non convexe avec des trous


9

Quelle est la façon la plus efficace de trouver l'épaisseur de paroi minimale (valeur et emplacement) d'une zone de polygone complexe et non convexe, y compris des trous? Voir l'exemple d'un polygone en bleu, avec l'épaisseur de paroi minimale en rouge, bien que dans ce cas l'emplacement soit ambigu, si les deux lignes adjacentes sont parallèles.

entrez la description de l'image ici

Jusqu'à présent, nous avons essayé:

  • Subdiviser des lignes de polygone et trouver des lignes point-point minimales à l'intérieur du polygone (force brute, pas efficace pour les polygones complexes avec> 10'000 points)
  • Triangulation de Delaunay et recherche d'arêtes minimales à l'intérieur du polygone. Pas assez précis, réalisable uniquement s'il est combiné avec la subdivision des lignes polygonales en premier. Voici un exemple (Nr 3), où la triangulation de Delaunay ne trouverait pas d'arêtes simplex en rouge mais manquerait l'épaisseur de paroi minimale dans la boîte verte:
    entrez la description de l'image ici
  • Augmentation itérative du tampon d'érosion pour trouver l'encart minimal, où le polygone d'érosion se divise en plusieurs parties = la moitié de l'épaisseur minimale de la paroi. Le problème est de trouver l'emplacement de l'épaisseur de paroi minimale avec cette approche par la suite. De plus, l'érosion ne se divise pas toujours en plusieurs parties et manque des "impasses". Voici un exemple (Nr 2) qui s'érode en une ligne et donne la mauvaise épaisseur de paroi minimale:
    entrez la description de l'image ici
  • Trouver d'abord l'axe médian, puis rechercher le cercle minimum sur l'axe médian qui recouvre mais ne chevauche pas la zone du polygone. Edit: Problématique sont les nombreux «mauvais candidats» sur l'axe médian: par exemple. (Nr 1) le cercle A serait faux, le cercle B indiquerait l'épaisseur de paroi minimale correcte:
    entrez la description de l'image ici

Obtenez la distance entre toutes les paires de lignes pour trouver les plus proches.
bugmenot123

Alors, quel était le problème avec l'approche de l'axe médial?
Hornbydd

1
@Hornbydd: Le problème était qu'il y a beaucoup de cercles sur l'axe médian qui touchent les coins mais ne définissent pas l'épaisseur de la paroi. Voir le deuxième exemple : le cercle A serait faux, le cercle B serait l'emplacement correct de l'épaisseur de paroi minimale. Donc, l'axe médial ressemble à un détour informatique coûteux et ne fournit pas la bonne réponse ...
Oliver Staubli

1
Si vous faites de l'érosion jusqu'à ce que le polygone dégénère en deux polygones se touchant en un point, alors l'emplacement sera là où un cercle de rayon identique au tampon centré au point touche le polygone d'origine. C'est une hypothèse présentée sans preuve mais je ne vois pas de contre-exemple ...
Spacedman

1
@OliverStaubli Ma suggestion est de vérifier non seulement les bords des triangles delaunay, mais aussi les hauteurs de ces triangles qui ont un bord sur la frontière et les deux autres à l'intérieur du polygone. Dans l'exemple N ° 3, la hauteur du triangle sous le carré vert est ce que vous recherchez. (selon les contraintes de la triangulation, vous devrez peut-être également filtrer certains des candidats dans des triangles obtus)
mkadunc

Réponses:


1

L'une des méthodes les plus efficaces pour trouver l'épaisseur de paroi minimale (valeur et emplacement) d'une zone de polygone complexe et non convexe, y compris des trous, pourrait être en utilisant une couche régulièrement espacée (ou aléatoire) de points pour déterminer, d'abord, le segment le plus proche avec le contexte pour chaque point et, ensuite, le point d'intersection entre le segment incrémentiel et le polygone latéral opposé; basé dans les directeurs cosinus.

Des distances incrémentielles peuvent être utilisées jusqu'à ce que le premier segment atteigne et recoupe un polygone latéral (l'épaisseur de paroi minimale).

Pour essayer mon approche, j'ai cloné votre polygone avec des trous et créé une couche de points aléatoires à l'intérieur du polygone avec 100 points; comme on peut le voir sur l'image suivante:

entrez la description de l'image ici

Le code utilisé par PyQGIS se présente comme suit:

import math

def azimuth(point1, point2):
   return point1.azimuth(point2) #in degrees

def cosdir_azim(azim):
   azim = math.radians(azim)
   cosa = math.sin(azim)
   cosb = math.cos(azim)
   return cosa,cosb

registry = QgsMapLayerRegistry.instance()    
polygon = registry.mapLayersByName('polygon_with_holes')
point_layer = registry.mapLayersByName('Random_points')
points = [ feat.geometry().asPoint() for feat in point_layer[0].getFeatures() ]
feat_polygon = polygon[0].getFeatures().next()

#producing rings polygons
rings_polygon = feat_polygon.geometry().asPolygon()
segments = []
epsg = point_layer[0].crs().authid()
uri = "LineString?crs=" + epsg + "&field=id:integer""&index=yes"
mem_layer = QgsVectorLayer(uri,
                           'increasing_segments',
                           'memory')
prov = mem_layer.dataProvider()
length = 10
pt2 = 0 
k = 0
while pt2 == 0:
    for i, point in enumerate(points):
        #determining closest distance to vertex or side polygon
        dist1 = feat_polygon.geometry().closestSegmentWithContext(point)[0]
        #determining point with closest distance to vertex or side polygon
        pt = feat_polygon.geometry().closestSegmentWithContext(point)[1]
        cosa, cosb = cosdir_azim(azimuth(pt, point))
        #extending segment in opposite direction based in director cosine and length 
        op_pt  = QgsPoint(point.x() + (length*cosa), point.y() + (length*cosb))
        segments.append([pt,op_pt])
        geom = QgsGeometry.fromPolyline([point,op_pt])
        for ring in rings_polygon:
            geom_ring = QgsGeometry.fromPolyline(ring)
            if geom.intersects(geom_ring):
                pt3 = geom.intersection(geom_ring)
                pt2 = pt3.distance(QgsGeometry.fromPoint(point))
                ms = [pt3.asPoint(), pt]
    length += 100
    k += 1
new_segments = segments[len(segments) -1 - len(segments)/k: len(segments) - 1]

feats = [ QgsFeature() for i in range(len(new_segments)) ]
for i,feat in enumerate(feats):
    feat.setAttributes([i])
    geom = QgsGeometry.fromPolyline(new_segments[i])
    feat.setGeometry(geom)

prov.addFeatures(feats)
QgsMapLayerRegistry.instance().addMapLayer(mem_layer)
minimum_segment = QgsGeometry().fromPolyline(ms).exportToWkt()
print minimum_segment, k

et il produit une couche mémoire de distances incrémentielles (uniquement à des fins de visualisation) et imprime une épaisseur de paroi minimale au format WKT.

Après avoir exécuté le code sur la console Python de QGIS, j'ai obtenu le résultat de l'image suivante:

entrez la description de l'image ici

On peut observer qu'une seule distance incrémentielle a atteint le côté opposé en premier dans la zone attendue.

Le format WKT imprimé (pour une épaisseur de paroi minimale) est utilisé avec le plugin QuickWKT de QGIS pour visualiser ce segment à l'image suivante:

entrez la description de l'image ici

La légère inclinaison a été produite parce que "le segment le plus proche avec le contexte" était joint à un sommet; au lieu du polygone latéral. Cependant, il peut être évité avec une exception de code ou plusieurs points.


1

Deux autres idées à jeter dans le pot:

  1. Pixellisez votre polygone et utilisez une transformation de distance (renvoie l'image de la distance la plus courte entre chaque pixel différent de zéro et un pixel nul). Squelettez votre image tramée, puis prenez les valeurs de l'image transformée à distance le long du squelette. De cet ensemble, vous aurez quelques minimums qui devraient correspondre à votre largeur la plus étroite. Cet ensemble peut être utilisé comme points de recherche initiaux pour ensuite implémenter votre approche par force brute. Je dois noter que le squelette coupera les coins des objets, et à ces endroits, la transformation de distance approchera de zéro (à mesure que vous vous rapprocherez de la limite de l'objet). Cela peut être problématique, mais représente un problème avec votre problème - pourquoi t la plus petite largeur doit-elle être dans un coin (et être essentiellement nulle)? Vous pouvez résoudre ce problème en définissant un seuil sur la distance la plus courte autour du périmètre entre les deux points (s'ils se trouvent sur le même objet). Vous pouvez utiliser une transformation de distance géodésique sur l'ensemble des pixels de périmètre pour trouver rapidement cette valeur.

    Cette méthode vous oblige à prendre une décision sur la résolution du polygone tramé, ce qui introduit une certaine dépendance à l'échelle. Et si vous choisissez une résolution trop élevée, la transformation de distance peut prendre beaucoup de temps. Mais en général, ils sont assez rapides. Cette méthode peut ne pas vous donner la précision souhaitée, mais elle peut au moins vous donner un ensemble beaucoup plus restreint d'emplacements à vérifier.

  2. Votre méthode de force brute n'est pas un mauvais point de départ. J'ai eu un problème similaire où je devais trouver toutes les intersections d'une (longue) ligne avec lui-même, et j'ai été en mesure d'accélérer considérablement le temps de recherche en utilisant un algorithme de recherche d'arbre kd (j'ai utilisé la recherche de plage dans Matlab à l'époque) pour trouver pointe d'abord dans un quartier. De cette façon, vous ne forcez que brutalement un petit sous-ensemble du nombre total de points.


Merci @jon. Les deux approches prometteuses. J'envisageais déjà kdTree mais j'espérais que le problème décrit a une solution bien connue de "copier-coller" :-) Il faudra creuser plus profondément ...
Oliver Staubli

0

L'approche de l'axe médian est correcte, il vous suffit d'un critère pour ignorer les mauvais cercles: chaque cercle de l'axe médian touche la surface en deux points (ou plus). Imaginez des vecteurs du centre du cercle à ces points de la surface et observez que l'angle entre est de 180 ° pour le bon cercle B et de seulement 90 ° pour le mauvais cercle A.

entrez la description de l'image ici


0

Un algorithme de dessin polygonal générique fonctionne en triant les segments de ligne de haut en bas et en les parcourant pour tracer des lignes orthogonales de pixels. Cette méthode de décomposition des polygones (sans courbures) étant très rapide, elle peut être utilisée comme base. Au lieu d'aller uniquement de haut en bas, vous pouvez aller à 0, 30, 60 et 90 degrés, et trouver votre section orthogonale la plus courte (= épaisseur de paroi minimale!), Il vous suffit de la calculer une fois par point et non pour tout type de «résolution en pixels».

Voir algorithme de remplissage de polygone-ligne-infographie-infographie

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.