42 votos

Generación de polígonos de igual tamaño a lo largo de la línea con PyQGIS

Me gustaría crear polígonos a lo largo de una línea para utilizarlos en AtlasCreator en un siguiente paso.

ArcMap tiene una herramienta llamada Características del índice del mapa de franjas .

Con esta herramienta puedo elegir la altura y la anchura de mis polígonos (digamos 8km x 4km) y producirlos/rotarlos a lo largo de la línea automáticamente.

Uno de los atributos generados de cada polígono es el ángulo de rotación que necesito para girar mis flechas del norte en el Generador de Atlas después.

enter image description here

¿Alguien tiene una idea de cómo resolver esta tarea en QGIS / con PyQGIS?

Los algoritmos de GRASS o SAGA o un modelo de caja de herramientas de evaluación que se pueda utilizar dentro de un plugin personalizado también estaría bien.

Necesito no sólo la impresión de extensiones sino también de los propios polígonos ya que quiero imprimir un mapa con todos los polígonos/extensiones como una especie de mapa general. Estoy buscando una solución de PyQGIS que pueda ser utilizada en un plugin de QGIS. sin necesidad de instalar un software aparte de QGIS (sin RDBMS como PostGIS / Oracle).

30voto

Mat Puntos 196

Interesante pregunta. Es algo que he querido probar yo mismo, así que lo he intentado.

Esto se puede hacer en PostGRES/POSTGIS con una función que genera un conjunto de polígonos.

En mi caso, tengo una tabla con una característica (un MULTILINEAL) que representa una línea ferroviaria. Necesita usar un CRS en metros, estoy usando osgb (27700). He hecho "páginas" de 4km x 2km.

Aquí puedes ver el resultado... lo verde es la red de carreteras, recortada a un buffer de 1km alrededor del ferrocarril, que corresponde a la altura de los polígonos muy bien.

postgis generated strip map

Esta es la función...

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

Utilizando esta función

Este es un ejemplo; páginas de 4km x 2km, epsg:27700 y 10% de solapamiento

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

Después de ejecutar esto, puede exportar desde PgAdminIII a un archivo csv. Usted puede importar esto en QGIS, pero es posible que tenga que establecer el CRS manualmente para la capa - QGIS no utiliza el SRID en EWKT para establecer el CRS de la capa para usted :/

Añadir atributo de rodamiento

Esto es probablemente más fácil de hacer en postgis, se puede hacer en las expresiones de QGIS, pero tendrá que escribir algo de código. Algo como esto...

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

Advertencias

Es un poco improvisado, y sólo tuve la oportunidad de probarlo en un conjunto de datos.

No estoy 100% seguro de cuáles son los dos vértices que debes elegir en esa actualización de atributos del rodamiento query .. podría necesitar experimentar.

Debo confesar que no tengo ni idea de por qué tengo que hacer una fórmula tan enrevesada para rotar el polígono para que coincida con el segmento de línea actual. Pensé que podría utilizar la salida de ST_Azimuth() en ST_Rotate(), pero parece que no.

13voto

Jed Puntos 6

La respuesta de @Steven Kays en PyQGIS.

Sólo tiene que seleccionar las líneas en su capa antes de ejecutar el script. El script no admite la fusión de líneas, por lo que no puede funcionar en capas con multilíneas

#!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)

11voto

Carra Puntos 6832

Hay diferentes soluciones. Y esto puede funcionar con una simple polilínea y múltiples entidades seleccionadas

diagrama de bloques:

  1. Parámetros

    1. seleccionar la orientación para la generación y el índice de lectura (de izquierda a derecha, de norte a sur...)

    2. establecer el tamaño del objeto

      shape = (4000,8000) # (<width>,<length>)

    3. definir el coeficiente de superposición (10% por defecto)

  2. init
    1. La ordenación de la polilínea (comparar el punto inicial y final) depende de su elección de orientación > crear una característica de ordenación de vérticesclase OrderNodes
  3. bucle en OrderNodes

    1. crear su primer punto como ancla

    2. para cada vértice se añade en dict x,y,id y se calcula un vector

    3. generar polígono (sobre la longitud y la orientación del vector) con superposición reductora ( 10% /2) > 5% polígono izquierdo 5% polígono derecho con el mismo punto de anclaje

    4. Se detiene cuando un punto de vértice precedente está fuera del polígono o si la longitud del vector es > a la longitud de la forma

    5. Genere el polígono con la solución buena anterior y establezca el punto de anclaje con la última posición buena

    6. Realiza un nuevo bucle y restablece los dict x,y,id para generar el siguiente objeto poligonal.

Puedes cambiar esta proposición si no es realmente clara o comentar.

4voto

Nick Puntos 3115

Las dos respuestas (en el momento de la publicación) son ingeniosas y están bien explicadas. Sin embargo, también hay una solución MUY simple pero eficaz posible para esto (suponiendo que acepte todos sus mapas alineados con el Norte hacia arriba de la manera tradicional, en lugar de una dirección del Norte al azar basada en el río). Si quieres rotaciones, es posible pero un poco más complejo (ver abajo).

En primer lugar, eche un vistazo a mi post aquí . Esto le da un cómo para crear coberturas de mapas para Atlas. El método que quieres es una adaptación del 'Flujo de trabajo 2' en el how-to. Divida su característica lineal por vértices o longitud y amortigüe las características por cualquier cantidad. La cantidad de búfer dictará parcialmente el solapamiento (pero véase más adelante), pero lo más importante es que crea una característica con un área. Puede utilizar cualquier número de plugins para dividir las líneas, pero GRASS v.split.length y v.split.vert son buenas opciones (disponibles en Processing Toolbox).

Una vez activada la Generación de Atlas en Map Composer y seleccionada su capa con buffer, vuelva a la pestaña de elementos y seleccione su objeto de mapa. Marque "Controlado por Atlas" y, en su caso, optaría por la función Margen alrededor. Esto controlará el solapamiento entre los mapas (alternativamente, usted puede preferir la escala fija).

Puede previsualizar su Atlas utilizando el botón de previsualización del Atlas en la barra de herramientas superior del compositor y ver cuántas páginas producirá. Puedes elegir exportar todas las páginas en un solo PDF o en archivos separados.

Para conseguir que el mapa gire a lo largo de la línea, hay un campo de rotación en las propiedades del elemento del compositor de mapas. Tendrá que establecer una expresión (utilice el pequeño botón a la derecha del cuadro de rotación). Seleccione variable como su opción y luego Editar. Aparecerá un constructor de expresiones y allí podrá acceder a la geometría o a los campos de las características del atlas. A continuación, puede construir una expresión para girar el mapa de acuerdo con la rotación de las características (puede calcular el rumbo utilizando los puntos de inicio y final de cada segmento de línea y un poco de trigonometría). Repite el mismo proceso para rotar la flecha del Norte (utilizando la misma expresión o variable precalculada).

3voto

Joel Samuel Puntos 6

Sé que es una pregunta antigua, pero se ha creado un nuevo plugin para solucionar este problema. https://plugins.qgis.org/plugins/polystrip/

No soy el creador y no me atribuyo el mérito del plugin.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X