Genere puntos en múltiples funciones teniendo en cuenta la distancia mínima

8

Tengo una función que crea aerogeneradores representados como puntos. Esencialmente, usa el código de los puntos aleatorios dentro de la herramienta de polígonos (fijo) aunque con algunos pequeños cambios.

El objetivo es crear puntos aleatorios dentro de los polígonos teniendo en cuenta la distancia mínima especificada. Esto funciona muy bien, especialmente con polígonos que no están cerca de otro (por ejemplo, un solo polígono):

Ejemplo

Sin embargo, si el polígono está cerca o junto a otro polígono (por ejemplo, como se muestra a continuación), los puntos de cada polígono pueden estar dentro de la distancia mínima como se muestra en rojo:

Problema de ejemplo

¿Cómo podría cambiar el código para que esos puntos en rojo no estén cerca de otro de un polígono cercano?

Idealmente, me gustaría que varios puntos sean reemplazados por un solo punto:

Resultado


Aquí está el código que se puede reproducir en la Consola Python , se debe seleccionar una capa de polígono con un CRS relevante antes de ejecutar la función:

import random
from PyQt4.QtCore import QVariant

def checkMinDistance(point, index, distance, points):
    if distance == 0:
        return True
    neighbors = index.nearestNeighbor(point, 1)
    if len(neighbors) == 0:
        return True
    if neighbors[0] in points:
        np = points[neighbors[0]]
        if np.sqrDist(point) < (distance * distance):
            return False
    return True

def generate_wind_turbines(spacing):
    layer = iface.activeLayer()
    crs = layer.crs()
    # Memory layer
    memory_lyr = QgsVectorLayer("Point?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", "Wind turbines for " + str(layer.name()), "memory")
    QgsMapLayerRegistry.instance().addMapLayer(memory_lyr)
    memory_lyr.startEditing()
    provider = memory_lyr.dataProvider()
    provider.addAttributes([QgsField("ID", QVariant.Int)])
    # Variables
    point_density = 0.0001
    fid = 1
    distance_area = QgsDistanceArea()
    # List of features
    fts = []
    # Create points
    for f in layer.getFeatures():
        fGeom = QgsGeometry(f.geometry())
        bbox = fGeom.boundingBox()
        pointCount = int(round(point_density * distance_area.measure(fGeom)))
        index = QgsSpatialIndex()
        points = dict()
        nPoints = 0
        fid += 1
        nIterations = 0
        maxIterations = pointCount * 200
        random.seed()
        while nIterations < maxIterations and nPoints < pointCount:
            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()
            pnt = QgsPoint(rx, ry)
            geom = QgsGeometry.fromPoint(pnt)
            if geom.within(fGeom) and checkMinDistance(pnt, index, spacing, points):
                f = QgsFeature(nPoints)
                f.setAttributes([fid])
                f.setGeometry(geom)
                fts.append(f)
                index.insertFeature(f)
                points[nPoints] = pnt
                nPoints += 1
            nIterations += 1
    provider.addFeatures(fts)
    memory_lyr.updateFields()
    memory_lyr.commitChanges()

generate_wind_turbines(500)

Editar:

Disolver y / o convertir los polígonos en partes individuales no parece ayudar mucho ya que los puntos generados todavía parecen estar dentro de la distancia mínima.

Probado en QGIS 2.18.3 .

José
fuente
1
si es una opción: ¿Ha intentado usar un polígono multiparte como el polígono de entrada?
LaughU
@LaughU: acabo de probarlo con un polígono multiparte pero no se generan puntos. Sería una opción convertir las características de una sola parte en imágenes prediseñadas si el código se puede ajustar para que funcione con polígonos de varias partes.
Joseph
¿Pensó en disolver los polígonos, seguido de una parte multiparte a una capa temporal que utiliza para la generación de puntos? También puede usar un búfer negativo para la capa temporal para evitar superposiciones de símbolos en los bordes.
Mate
@ Mate - Gracias, intenté disolver todos los polígonos antes. Lo intenté nuevamente y lo convertí en una sola parte (no estoy seguro de si esto hace algo ya que ya era una característica única), pero algunos puntos cerca de los bordes están dentro de los puntos en los otros polígonos. Me gustaría evitar el uso de buffers negativos ya que quiero permitir que los puntos estén cerca de los bordes :)
Joseph

Respuestas:

5

Tienes que cambiar dos cosas para que esto funcione. Sin embargo, no obtendrá el máximo de aerogeneradores por área. Para esto, necesitaría ejecutar algunas iteraciones para cada valor y obtener el recuento máximo de puntos.

Me he movido index = QgsSpatialIndex()y points = dict()fuera del bucle for. El código se vería así:

import random
def generate_wind_turbines(spacing):
    layer = self.iface.activeLayer()
    crs = layer.crs()
    # Memory layer
    memory_lyr = QgsVectorLayer("Point?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", "Wind turbines for " + str(layer.name()), "memory")
    QgsMapLayerRegistry.instance().addMapLayer(memory_lyr)
    memory_lyr.startEditing()
    provider = memory_lyr.dataProvider()
    provider.addAttributes([QgsField("ID", QVariant.Int)])
    # Variables
    point_density = 0.0001
    fid = 1
    distance_area = QgsDistanceArea()
    # List of features
    fts = []
    # Create points
    points = dict() # changed from me 
    index = QgsSpatialIndex()# changend from me 
    nPoints = 0 # changed in the edit 
    pointCount = 0 # changed in the edit 

    for f in layer.getFeatures():
        fGeom = QgsGeometry(f.geometry())
        bbox = fGeom.boundingBox()
        # changed here as well 
        pointCount = int(round(point_density * distance_area.measure(fGeom))) + int(pointCount)
        fid += 1
        nIterations = 0
        maxIterations = pointCount * 200
        random.seed()
        while nIterations < maxIterations and nPoints < pointCount:
            rx = bbox.xMinimum() + bbox.width() * random.random()
            ry = bbox.yMinimum() + bbox.height() * random.random()
            pnt = QgsPoint(rx, ry)
            geom = QgsGeometry.fromPoint(pnt)
            if geom.within(fGeom) and checkMinDistance(pnt, index, spacing, points):
                f = QgsFeature(nPoints)
                f.setAttributes([fid])
                f.setGeometry(geom)
                fts.append(f)
                index.insertFeature(f)
                points[nPoints] = pnt
                nPoints += 1
            nIterations += 1
    provider.addFeatures(fts)
    memory_lyr.updateFields()
    memory_lyr.commitChanges()

def checkMinDistance( point, index, distance, points):
    if distance == 0:
        return True
    neighbors = index.nearestNeighbor(point, 1)
    if len(neighbors) == 0:
        return True
    if neighbors[0] in points:
        np = points[neighbors[0]]
        if np.sqrDist(point) < (distance * distance):
            return False
    return True

EDITAR:

Joseph tenía razón. mis cambios solo funcionaron para un área realmente pequeña. He probado y encontrado una nueva solución moviendo dos variables fuera del ciclo for y cambié la pointCountvariable.

Lo he probado con 500m y este es el resultado (dos intentos diferentes):

ingrese la descripción de la imagen aquí

LaughU
fuente
1
Bien, gracias por notarlo! Estoy seguro de que esto mejora la eficiencia en gran medida =)
Joseph
1
@Joseph tenías razón. Lo probé con áreas pequeñas que funcionaban. He agregado algunos cambios menores y funciona para mí ahora. Avíseme si todavía necesita mejorar :)
LaughU
1
¡Interesante! Probaré esto mañana e informaré pero el resultado se ve muy bien;)
Joseph
1
Si, esto es bueno! Su script también es más rápido y aún entrega los resultados que estaba buscando. Le otorgaré la recompensa eventualmente, espero que no le importe, pero edité su código muy ligeramente;)
Joseph
1
@Joseph No, no me importa;) Estaba probando el código y olvidé limpiar el desorden, también conocido como espacios en blanco
LaughU el
4

Un método podría ser crear otra función que haga lo siguiente:

  1. Genere buffers con el mismo radio que el espacio alrededor de todos los puntos y los almacene en una capa de polígono.
  2. Cree una lista que contenga la identificación de todos los buffers que se cruzan entre sí.
  3. Elimina los búferes usando esta lista de identificación.
  4. Genera el centroide de los almacenamientos intermedios restantes y los almacena en una capa de puntos.

Esta es la función que utilicé:

def wind_turbine_spacing_checker(layer, spacing):
    # Create buffers for points
    poly_layer =  QgsVectorLayer("Polygon?crs=epsg:27700", 'Buffers' , "memory")
    pr = poly_layer.dataProvider() 
    pr.addAttributes([QgsField("ID", QVariant.Int)])
    feat_list = []
    for f in layer.getFeatures():
        poly = QgsFeature()
        f_buffer = f.geometry().buffer((spacing / 2), 99)
        f_poly = poly.setGeometry(QgsGeometry.fromPolygon(f_buffer.asPolygon()))
        poly.setAttributes([1])
        poly.setGeometry(f_buffer)
        feat_list.append(poly)

    pr.addFeatures(feat_list)
    poly_layer.updateExtents()
    poly_layer.updateFields()
    QgsMapLayerRegistry.instance().addMapLayers([poly_layer])

    # Get pairs of intersecting buffer features
    features = [feat for feat in poly_layer.getFeatures()]
    ids = []
    for feat in poly_layer.getFeatures():
        for geat in features:
            if feat.id() != geat.id():
                if geat.geometry().intersects(feat.geometry()):
                    ids.append([feat.id(), geat.id()])

    # Set/sort list and get id of intersecting feature(s)
    for x in ids:
        x.sort()

    ids_sort = set(tuple(x) for x in ids)
    ids_list = [list(x) for x in ids_sort]
    ids_firstItem = [item[0] for item in ids_list]
    final_list = list(set(ids_firstItem))

    # Use ids from final_list to remove intersecting buffer features
    with edit(poly_layer):
        poly_layer.deleteFeatures(final_list)

    # Create new point layer and get centroids from buffers
    # (using final_list to delete the original features may not delete those points where the buffer interesects
    # so best to obtain the centroid of the buffers and output this as a new file)
    result_layer = QgsVectorLayer('Point?crs=epsg:27700&field=id:string', 'Result' , 'memory')
    result_layer.startEditing()
    for feat in poly_layer.getFeatures():
        centroid = feat.geometry().centroid()
        name = feat.attribute("ID")
        centroid_feature = QgsFeature(poly_layer.fields())
        centroid_feature.setGeometry(centroid)
        centroid_feature['ID'] = name
        result_layer.addFeature(centroid_feature)

    result_layer.commitChanges()
    QgsMapLayerRegistry.instance().addMapLayer(result_layer)

La función se puede ejecutar inmediatamente al final de la generate_wind_turbines()función utilizando:

...
memory_lyr.commitChanges()
wind_turbine_spacing_checker(memory_lyr, spacing)

Esto da los resultados como se muestra en la imagen de la pregunta. Probablemente no sea la más eficiente de las soluciones, pero parece funcionar.


Algunos ejemplos donde los puntos rojos son los generados inicialmente y los puntos mostrados como turbinas con un límite son el resultado final:

  • generate_wind_turbines(500)

    escenario 1


  • generate_wind_turbines(1000)

    Escenario 2

José
fuente