最小距離を考慮して、複数のフィーチャにポイントを生成します


8

ポイントで表される風力タービンを作成する機能があります。基本的には、多少の変更はありますが、ポリゴン(固定)ツールランダムポイントからのコードを使用します。

目標は、指定された最小距離を考慮して、ポリゴン内にランダムなポイントを作成することです。これは特に、別のポリゴンに近接していないポリゴン(単一のポリゴンなど)で非常にうまく機能します。

例

ただし、ポリゴンが別のポリゴンに近接または隣接している場合(たとえば、以下に示すように)、各ポリゴンのポイントは、赤で示すように最小距離内にある可能性があります。

問題の例

赤のポイントが近くのポリゴンの別のポイントに近づかないようにコードを変更するにはどうすればよいですか?

理想的には、複数のポイントを1つのポイントに置き換えたいです。

結果


Pythonコンソールで再現できるコードを次に示します。関数を実行する前に、関連するCRSでポリゴンレイヤーを選択する必要があります。

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)

編集:

生成されたポイントが最小距離内にあるように見えるので、ポリゴンをディゾルブまたはシングルパートに変換することはあまり役に立たないようです。

QGIS 2.18.3でテスト済み


1
オプションの場合:マルチパートポリゴンを入力ポリゴンとして使用しようとしましたか?
LaughU 2017年

@LaughU-マルチパートポリゴンでテストしましたが、ポイントが生成されません。マルチパートポリゴンで機能するようにコードを調整できる場合は、シングルパートフィーチャをマルチパートに変換するオプションになります。
ジョセフ

ポリゴンをディゾルブしてから、マルチパートからシングルパートにポイント生成に使用する一時的なレイヤーを作成することを考えましたか?一時レイヤーにネガティブバッファーを使用して、エッジでのシンボルの重複を回避することもできます。
マット

@Matte-ありがとう、私は以前にすべてのポリゴンを溶解しようとしました。もう一度試してシングルパートに変換しました(すでに単一の機能だったため、これが何かを実行するかどうかは不明です)が、エッジの近くのいくつかのポイントが他のポリゴンのポイント内にあります。ポイントがエッジの近くになるようにしたいので、ネガティブバッファーの使用は避けたい:)
Joseph

回答:


5

これを機能させるには、2つの点を変更する必要があります。ただし、エリアあたりの風力タービンの最大数は得られません。このため、各値に対していくつかの反復を実行し、最大ポイント数を取得する必要があります。

私が移動した index = QgsSpatialIndex()points = dict()forループの外。コードは次のようになります。

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

編集:

ジョセフは正しかった。私の変更は本当に小さな領域でのみ機能しました。私は周りをテストし、2つの変数をforループから移動してpointCount変数を変更することにより、新しい解決策を見つけました。

私は500mでテストしましたが、これが結果です(2つの異なる試行)。

ここに画像の説明を入力してください


1
わかりました。ご連絡いただきありがとうございます。これにより効率が大幅に向上することを確信しています=)
Joseph

1
@ジョセフあなたが正しかった。動作する小さな領域でテストしました。いくつかのマイナーな変更を追加しましたが、今はうまくいきます。それでも改善が必要な場合は
お知らせください

1
面白い!私はこれを明日テストして報告しますが、結果は本当に良さそうです;)
ジョセフ

1
うん、これはいい!スクリプトも高速で、探していた結果が得られます。最終的に報奨金を授与します。問題ないことを願っていますが、コードを少しだけ編集しました;)
ジョセフ

1
@ジョセフいいえ、私は気にしません;)私はコードでテストしていて、混乱(別名ホワイトスペース)をクリーンアップするのを忘れていました
LaughU

4

1つの方法は、次のことを行う別の関数を作成することです。

  1. すべてのポイントの周囲の間隔と同じ半径のバッファーを生成し、これらをポリゴンレイヤーに保存します。
  2. 互いに交差するすべてのバッファのIDを含むリストを作成します。
  3. このIDリストを使用してバッファを削除します。
  4. 残りのバッファーの重心を生成し、それらをポイントレイヤーに格納します。

これは私が使った機能です:

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)

関数は、次のgenerate_wind_turbines()コマンドを使用して、関数の最後ですぐに実行できます。

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

これにより、質問の画像に示すような結果が得られます。おそらく最も効率的なソリューションではありませんが、うまくいくようです。


赤い点が最初に生成されたものであり、境界付きのタービンとして表示された点が最終的​​な結果であるいくつかの例:

  • generate_wind_turbines(500)

    シナリオ1


  • generate_wind_turbines(1000)

    シナリオ2

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.