PyQGISでラインに沿って同じサイズのポリゴンを生成しますか?


42

次のステップでAtlasCreatorに使用するために、ラインに沿ってポリゴンを作成したいと思います。

ArcMapには、Strip Map Index Featuresというツールがあります

このツールを使用して、ポリゴンの高さと幅(たとえば8km x 4km)を選択し、ラインに沿って自動的に生成/回転できます。

各ポリゴンの生成された属性の1つは、後でAtlas Generatorで北向き矢印を回転させるために必要な回転角度です。

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

QGIS / pyQGISでこのタスクを解決する方法を誰かが考えていますか?GrassまたはSAGAアルゴリズム、またはカスタムプラグイン内で使用できるprossessing-toolbox-modelも問題ありません;)ある種の概観図としてのすべてのポリゴン/範囲。

Edit2:QGIS以外のソフトウェアをインストールすることなく QGISプラグインで使用できるPyQGISソリューションを探しているので、私は報奨金を提供しています(PostGIS / OracleのようなRDBMSはありません)


4
これはプラグインの楽しいアイデアのように見えます。
alphabetasoup

1
野生の考えとして、Peucker-Douglasの一般化に基づいたものが機能すると思います
plablo09

1
おそらくv.split.length、その後、「DOはポリラインの両端にキャップをしない」オプションを使用してv.bufferその後、セグメントの始点と終点の間に直線を引くと
トーマス・B

1
私はこの質問に対して賞金を開始したいのですが、まだ十分な評判がありません;(
Berlinmapper

2
「ラベルフォローライン」の実装には、再利用可能なコードが含まれている場合があります。長方形は、等幅フォントのグリフの足跡のようなものです。
user30184

回答:


29

興味深い質問です!それは私が自分で試してみたいものでしたので、試してみました。

PostGRES / POSTGISでは、一連のポリゴンを生成する関数を使用してこれを行うことができます。

私の場合、鉄道線を表す1つの機能(MULTILINESTRING)を持つテーブルがあります。CRSをメートル単位で使用する必要があります。osgb(27700)を使用しています。4km x 2kmの「ページ」を作成しました。

ここでは、結果を見ることができます...緑色のものは道路網であり、鉄道の周りの1kmのバッファにクリップされています。これはポリゴンの高さにうまく対応しています。

postgisが生成したストリップマップ

ここに関数があります...

CREATE OR REPLACE FUNCTION getAllPages(wid float, hite float, srid integer, overlap float) RETURNS SETOF geometry AS
$BODY$
DECLARE
    page geometry; -- holds each page as it is generated
    myline geometry; -- holds the line geometry
    startpoint geometry;
    endpoint geometry;
    azimuth float; -- angle of rotation
    curs float := 0.0 ; -- how far along line left edge is
    step float;
    stepnudge float;
    currpoly geometry; -- used to make pages
    currline geometry;
    currangle float;
    numpages float;
BEGIN
    -- drop ST_LineMerge call if using LineString 
    -- replace this with your table.
    SELECT ST_LineMerge(geom) INTO myline from traced_osgb; 
    numpages := ST_Length(myline)/wid;

    step := 1.0/numpages;
    stepnudge := (1.0-overlap) * step; 
    FOR r in 1..cast (numpages as integer)
    LOOP
        -- work out current line segment

        startpoint :=  ST_SetSRID(ST_Line_Interpolate_Point(myline,curs),srid);
        endpoint :=  ST_SetSRID(ST_Line_Interpolate_Point(myline,curs+step),srid);
        currline := ST_SetSRID(ST_MakeLine(startpoint,endpoint),srid);

        -- make a polygon of appropriate size at origin of CRS
        currpoly := ST_SetSRID(ST_Extent(ST_MakeLine(ST_MakePoint(0.0,0.0),ST_MakePoint(wid,hite))),srid);

        -- then nudge downwards so the midline matches the current line segment
        currpoly := ST_Translate(currpoly,0.0,-hite/2.0);

        -- Rotate to match angle
        -- I have absolutely no idea how this bit works. 
        currangle := -ST_Azimuth(startpoint,endpoint) - (PI()/2.0) + PI();
        currpoly := ST_Rotate(currpoly, currangle);

        -- then move to start of current segment
        currpoly := ST_Translate(currpoly,ST_X(startpoint),ST_Y(startpoint));

        page := currpoly;

        RETURN NEXT page as geom; -- yield next result
        curs := curs + stepnudge;
    END LOOP;
    RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;

この機能を使用する

以下に例を示します。4km x 2kmページ、epsg:27700および10%のオーバーラップ

select st_asEwkt(getallpages) from getAllPages(4000.0, 2000.0, 27700, 0.1);

これを実行した後、PgAdminIIIからcsvファイルにエクスポートできます。これをQGISにインポートできますが、レイヤーのCRSを手動で設定する必要があるかもしれません-QGISはEWKTのSRIDを使用してレイヤーCRSを設定しません:/

方位属性の追加

これはおそらくpostgisで簡単に実行でき、QGIS式で実行できますが、コードを記述する必要があります。このようなもの...

create table pages as (
    select getallpages from getAllPages(4000.0, 2000.0, 27700, 0.1)
);

alter table pages add column bearing float;

update pages set bearing=ST_Azimuth(ST_PointN(getallpages,1),ST_PointN(getallpages,2));

注意事項

少しハッキングされており、1つのデータセットでテストする機会しかありませんでした。

そのベアリング属性の更新で選択する必要のある2つの頂点を100%確信することはqueryできません。実験する必要があるかもしれません。

現在のラインセグメントに一致するようにポリゴンを回転させるために、このような複雑な数式を実行する必要がある理由がわからないことを認めなければなりません。ST_Rotate()でST_Azimuth()の出力を使用できると思いましたが、一見そうではありません。


あなたの答えは本当に素晴らしいです、私は確かに何かを試してみます私にとっての1つの制限は、私が取り組んでいるプロジェクトにpostgresを使用できないことであり、サーバー側に依存しないものが必要です。そのようなものをpyQGISで再現するための優れたロジック。
ベルリンマッパー

2
その場合は、QgsGeometryクラスご覧ください。PostGISのジオメトリ操作のサブセットがあり、pyQGISルートに行きたい場合の出発点として適しています。このアルゴリズムは、pyQGIS ...に移植する必要があります
スティーブン・ケイ

3
私は、PostGISのために使用したアプローチだと思うST_Simplifyを基準線を生成し、セグメントにラインを破壊してから使用してST_Bufferによって及びST_Envelopeが短く、より効率的です。
マティアスクーン

@Matthias Kuhn:線をセグメントに分割すると、同じサイズの線を取得できますが、必ずしも同じサイズのポリゴンを取得できるわけではありません。たとえば、線がかなり「曲がっている」場合、ポリゴンはおそらく短くなりますよね?
ベルリンマッパー

2
ソリューションとスクリプトのPyQGISバージョンをテストしましたが、残っているいくつかの小さな問題を解決する方法はありますか:bit.ly/1KL7JHn
ベルリンマッパー

12

さまざまなソリューションがあります。そして、これは単純なポリラインと複数の選択されたエンティティで動作します

ブロック図:

  1. パラメーター

    1. 生成の方向を選択し、インデックスを読み取ります(左から右、北から南...)
    2. オブジェクトサイズを設定する

    shape = (4000,8000) # (<width>,<length>)
    1. 重ね合わせ係数の定義(デフォルトでは10%?)
  2. 初期化
    1. ポリラインの順序付け(開始点と終了点の比較)順序付けは、選択した方向によって異なります>頂点順序付けフィーチャクラスを作成しますOrderNodes
  3. OrderNodesのループ

    1. 最初のポイントをアンカーとして作成します

    2. 各頂点について、dict x、y、idに追加し、ベクトルを計算します

    3. (長さとベクトルの向きを超えて)重ね合わせを減らして(10%/ 2)ポリゴンを生成します> 5%左ポリゴン5%右ポリゴン同じアンカーポイント
    4. 先行する頂点がポリゴンから外れた場合、またはベクトルlenがシェイプの長さよりも長い場合に停止します
    5. 以前の適切なソリューションでポリゴンを生成し、最後の適切な位置でアンカーポイントを設定します
    6. 新しいループを実行し、dict x、y、idをリセットして、次のポリゴンオブジェクトを生成します。

この提案が本当に明確でないか、コメントされていない場合は、この提案を変更できます。


これは洗練されたように聞こえますが、モデラーまたはPyQGISでこれを使用する方法がまだわからないことを認めざるを得ません。ところで:重ね合わせ係数とは何ですか?
ベルリンマッパー

この場合、スーパーポスト8000​​ x 10%のポリゴンの一部である@Berlinmapper。他のポリゴンを選択するか、ポリゴン間に固定距離の上位を作成できます。あなたは隅に、次のタイルのページを表示するために、すべてのアトラスのそれを見ることができます
GeoStoneMarten

ソリューションはpyQGISまたは処理ツールボックスで使用することを意図していますか?それは素晴らしいサウンドが、私はまだ続行する方法がわからない
Berlinmapper

1
@Berlinmapper pyQGISを使用してプロセススクリプトを作成し、処理ツールボックスまたはQGISプラグインで入力および出力パラメーターを設定する必要があると思います。arcgistoolboxと同じです。実際にそれをしてテストする時間はありません。
GeoStoneMarten

12

スティーブン・ケイズはpyqgisで答えます。スクリプトを実行する前に、レイヤー内の行を選択するだけです。スクリプトはラインマージをサポートしていないため、マルチラインストリングのレイヤーで動作できません

#!python
# coding: utf-8

# https://gis.stackexchange.com/questions/173127/generating-equal-sized-polygons-along-line-with-pyqgis
from qgis.core import QgsMapLayerRegistry, QgsGeometry, QgsField, QgsFeature, QgsPoint
from PyQt4.QtCore import QVariant


def getAllPages(layer, width, height, srid, overlap):
    for feature in layer.selectedFeatures():
        geom = feature.geometry()
        if geom.type() <> QGis.Line:
            print "Geometry type should be a LineString"
            return 2
        pages = QgsVectorLayer("Polygon?crs=epsg:"+str(srid), 
                      layer.name()+'_id_'+str(feature.id())+'_pages', 
                      "memory")
        fid = QgsField("fid", QVariant.Int, "int")
        angle = QgsField("angle", QVariant.Double, "double")
        attributes = [fid, angle]
        pages.startEditing()
        pagesProvider = pages.dataProvider()
        pagesProvider.addAttributes(attributes)
        curs = 0
        numpages = geom.length()/(width)
        step = 1.0/numpages
        stepnudge = (1.0-overlap) * step
        pageFeatures = []
        r = 1
        currangle = 0
        while curs <= 1:
            # print 'r =' + str(r)
            # print 'curs = ' + str(curs)
            startpoint =  geom.interpolate(curs*geom.length())
            endpoint = geom.interpolate((curs+step)*geom.length())
            x_start = startpoint.asPoint().x()
            y_start = startpoint.asPoint().y()
            x_end = endpoint.asPoint().x()
            y_end = endpoint.asPoint().y()
            # print 'x_start :' + str(x_start)
            # print 'y_start :' + str(y_start)
            currline = QgsGeometry().fromWkt('LINESTRING({} {}, {} {})'.format(x_start, y_start, x_end, y_end))
            currpoly = QgsGeometry().fromWkt(
                'POLYGON((0 0, 0 {height},{width} {height}, {width} 0, 0 0))'.format(height=height, width=width))
            currpoly.translate(0,-height/2)
            azimuth = startpoint.asPoint().azimuth(endpoint.asPoint())
            currangle = (startpoint.asPoint().azimuth(endpoint.asPoint())+270)%360
            # print 'azimuth :' + str(azimuth)
            # print 'currangle : ' +  str(currangle)

            currpoly.rotate(currangle, QgsPoint(0,0))
            currpoly.translate(x_start, y_start)
            currpoly.asPolygon()
            page = currpoly
            curs = curs + stepnudge
            feat = QgsFeature()
            feat.setAttributes([r, currangle])
            feat.setGeometry(page)
            pageFeatures.append(feat)
            r = r + 1

        pagesProvider.addFeatures(pageFeatures)
        pages.commitChanges()
        QgsMapLayerRegistry.instance().addMapLayer(pages)
    return 0

layer = iface.activeLayer()
getAllPages(layer, 500, 200, 2154, 0.4)

1
すばらしいです。ソリューションをテストしました。これらの問題を解決する方法は、解決策にまだあります:bit.ly/1KL7JHn
ベルリンマッパー

おそらく、ここでいくつかの「インスピレーション」がある:github.com/maphew/arcmapbook/blob/master/Visual_Basic/...
トーマス・B

ありがとう。ArcMapツールの仕組みを理解するための素晴らしいリソースです。残念ながら、私はVBには慣れていませんが、おそらく他の誰かがそれを使用して回答/コメントを投稿できます;)
Berlinmapper

4

2つの回答(投稿時)は独創的で、十分に説明されています。ただし、これには非常にシンプルですが効果的な解決策もあります(川に基づいたランダムな北方向ではなく、従来の方法で北に整列したすべてのマップを受け入れると仮定します)。回転が必要な場合は可能ですが、もう少し複雑です(下を参照)。

最初に私の投稿を見てください。これにより、Atlasのマップカバレッジを作成する方法がわかります。あなたが望む方法は、ハウツーの「ワークフロー2」です。線形フィーチャを頂点または長さで分割し、任意の量でフィーチャをバッファします。バッファリングする量によって、オーバーラップが部分的に決まります(ただし、以下を参照)が、さらに重要なことは、エリアを持つフィーチャを作成することです。任意の数のプラグインを使用して行を分割できますが、GRASS v.split.lengthおよびv.split.vertは適切なオプションです(Processing Toolboxで使用可能)。

Map ComposerでAtlas Generationを有効にし、バッファレイヤーを選択したら、アイテムタブに戻り、マップオブジェクトを選択します。「Atlasによって制御されている」をチェックします。ユースケースでは、機能周辺のマージンを選択します。これにより、マップ間のオーバーラップが制御されます(または、固定縮尺を選択することもできます)。

コンポーザの上部ツールバーにある[アトラスのプレビュー]ボタンを使用してアトラスをプレビューし、生成されるページ数を確認できます。すべてのページを単一のPDFでエクスポートするか、個別のファイルとしてエクスポートするかを選択できます。

マップを線に沿って回転させるために、Map Composerアイテムプロパティに回転フィールドがあります。式を設定する必要があります(回転ボックスの右側にある小さなボタンを使用します)。オプションとして変数を選択し、編集します。式ビルダーがポップアップ表示され、そこでアトラスフィーチャのジオメトリまたはフィールドにアクセスできます。次に、フィーチャの回転に従ってマップを回転するエクスプレスを構築できます(各ラインセグメントの開始点と終了点、および少しのトリガーを使用して方位を計算できます)。同じプロセスを繰り返して、北矢印を回転させます(同じ式または事前に計算された変数を使用)。


このソリューションをありがとう。しかし、この方法では、印刷したい単一の範囲のポリゴンを取得できないと思います。すべての印刷範囲で「概要マップ」も作成する必要があります。
Berlinmapper
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.