沿着PyQGIS生成相等大小的多边形?


42

我想沿着一条线创建多边形,以在下一步中将它们用于AtlasCreator。

ArcMap有一个称为“ 带状地图索引要素”的工具。

使用此工具,我可以选择多边形的高度和宽度(例如8km x 4km),并自动沿线生成/旋转它们。

每个多边形生成的属性之一是随后需要在Atlas Generator中旋转我的向北箭头的旋转角度。

在此处输入图片说明

有谁知道如何在QGIS中/使用pyQGIS解决此任务?也可以在自定义插件中使用的Grass或SAGA算法或prosssings-toolbox-model也很好;)Edit1:我不仅需要打印范围,而且还需要多边形本身,因为我想用所有多边形/范围作为某种总览图。

Edit2:我提供了一个赏金,因为我仍在寻找一种可以在QGIS-Plugin中使用的PyQGIS解决方案,而无需在QGIS之外安装软件(没有像PostGIS / Oracle这样的RDBMS)


4
对于插件来说,这似乎是一个有趣的主意。
alphabetasoup

1
作为一个疯狂的想法,我认为基于Peucker-Douglas概括的某些方法可能会起作用
plablo09 2015年

1
也许是v.split.length,然后在各段的起点和终点之间画一条直线,然后使用选项“不要在折线的末端制作大写字母”的v.buffer
Thomas B

1
我希望就这个问题开始悬赏,但是我还没有足够的声誉;(
Berlinmapper 2015年

2
“标签跟随行”实现中可能会有一些可重用的代码。您的矩形就像某些等宽字体的字形的足迹。
user30184

Answers:


29

有趣的问题!这是我想尝试的东西,因此尝试一下。

您可以在PostGRES / POSTGIS中使用生成一组多边形的函数来执行此操作。

就我而言,我有一张表,其中包含一个代表铁路线的要素(MULTILINESTRING)。它需要以米为单位使用CRS,我正在使用osgb(27700)。我已经完成了4公里x 2公里的“页面”。

在这里,您可以看到结果...绿色的东西是公路网,被修剪到铁路周围1公里的缓冲区内,与多边形的高度非常吻合。

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' ;

使用此功能

这是一个例子。4公里x 2公里页面,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));

注意事项

这有点累人,只有一次可以测试一个数据集。

不能100%确定需要在该方位属性更新上选择哪个两个顶点query..可能需要进行实验。

我必须承认,我不知道为什么我需要做一个复杂的公式来旋转多边形以匹配当前线段。我以为我可以在ST_Rotate()中使用ST_Azimuth()的输出,但似乎不能。


您的回答确实很棒,我会尽力而为。对我的一个限制是,我不能为正在从事的项目使用postgres,并且需要不依赖服务器端的东西。但是也许我可以使用您的用pyQGIS复制类似内容的绝佳逻辑。
Berlinmapper 2015年

2
如果是这样,请看一下QgsGeometry类。它具有PostGIS几何运算的子集,如果您要使用pyQGIS路线,它将是一个很好的起点。该算法应可移植到pyQGIS。–
Steven Kay

3
我认为对于Postgis,使用ST_Simplify生成参考线并将线断开成段,然后使用ST_BufferST_Envelope的方法会更短,更有效。
Matthias Kuhn

@Matthias Kuhn:如果我将线分割成线段,我可以获得相等尺寸的线,但不一定也获得相等尺寸的多边形。例如,如果线很“弯曲”,则多边形可能会更短,不是吗?
Berlinmapper

2
我测试了您的解决方案和脚本的PyQGIS版本,任何想法如何解决剩下的一些小问题:bit.ly/1KL7JHn
Berlinmapper '16

12

有不同的解决方案。这可以与简单的折线和多个选定的实体一起使用

框图:

  1. 参量

    1. 选择生成方向并读取索引(从左到右,从北到南...)
    2. 设定物件大小

    shape = (4000,8000) # (<width>,<length>)
    1. 定义叠加系数(默认为10%?)
  2. 在里面
    1. 排序折线(比较起点和终点)的排序取决于您的方向选择>创建顶点排序要素类OrderNodes
  3. 在OrderNodes上循环

    1. 创建您的第一点作为锚点

    2. 对于每个顶点,将其添加到字典x,y,id上并计算向量

    3. 通过减少重叠(10%/ 2)> 5%左多边形5%右多边形(具有相同锚点)来生成多边形(在长度和矢量方向上)
    4. 当先前的顶点不在多边形范围内或向量len>到形状长度时,停止
    5. 使用先前的好解生成多边形,并使用最后的好位置设置锚点
    6. 执行新的循环并重置字典x,y,id以生成下一个多边形对象。

如果不清楚或没有评论,您可以更改此提议。


这听起来很复杂,但我不得不承认我还不知道如何将其用于建模器或PyQGIS。顺便说一句:什么是叠加系数?
Berlinmapper 2015年

@Berlinmapper,在这种情况下是多边形的一部分,其超级位置为8000 x 10%。您可以选择其他多边形,也可以在多边形之间创建固定距离叠加。您可以在所有地图集中看到该图片,以指示角落中的下一个图块页面
GeoStoneMarten 2015年

您的解决方案是否打算与pyQGIS或处理工具箱一起使用?听起来不错,但我仍然不知道如何进行
Berlinmapper 2015年

1
@Berlinmapper我认为您需要使用pyQGIS来创建流程脚本并在处理工具箱或QGIS插件中设置输入和输出参数。与arcgistoolbox相同。我实际上没有时间进行测试。
GeoStoneMarten

12

史蒂文·凯斯(Steven Kays)用pyqgis回答。只需在运行脚本之前选择图层中的行即可。该脚本不支持换行,因此无法在具有multilinestring的图层上使用

#!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
Berlinmapper '16


Thanks.great的资源,了解ArcMap工具的工作原理。不幸的是,我不习惯VB,但也许有人可以用它来发布答案/评论;)
Berlinmapper,2016年

4

这两个答案(在发布时)非常巧妙,并得到了很好的解释。但是,还有一个非常简单但有效的解决方案(假设您将接受所有以传统方式与北向上对齐的地图,而不是基于河流的随机北向)。如果您想旋转,则可以,但是稍微复杂一点(请参阅底部)。

首先在这里看看我的帖子。这为您提供了创建Atlas地图覆盖的方法。您想要的方法是方法中“工作流2”的改编。通过顶点或长度拆分线性特征,并以任意数量缓冲特征。缓冲的量将部分决定重叠(但请参见下文),但更重要的是,它创建具有面积的要素。您可以使用任意数量的插件来分割行,但是GRASS v.split.length和v.split.vert是不错的选择(在Processing Toolbox中可用)。

在Map Composer中启用Atlas Generation并选择了缓冲层后,切换回items选项卡并选择map对象。选中“由Atlas控制”,在您的用例中,我会选择“功能周围的余量”。这将控制您在地图之间的重叠(或者,您可能更喜欢固定比例尺)。

您可以使用作曲家顶部工具栏中的“预览图集”按钮预览图集,并查看将产生多少页。请注意,您可以选择将所有页面导出为单个PDF或单独的文件。

为了使地图沿直线旋转,在Map Composer项属性中有一个旋转字段。您将需要设置一个表达式(使用旋转框右侧的小按钮)。选择变量作为选项,然后选择编辑。将弹出一个表达式构建器,您可以在其中访问图集要素的几何或字段。然后,您可以构建一个Express,以根据要素的旋转来旋转地图(可以使用每个线段的起点和终点以及一点点Trig来计算方位角)。重复相同的过程以旋转您的向北箭头(使用相同的表达式或预先计算的变量)。


感谢您的解决方案。但我认为这样一来,我将无法获得要打印的单个范围的多边形。我需要他们制作所有打印范围的“概览图”。
Berlinmapper 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.