10

How can I create something like this as a layer in QGIS?

I currently have one large Grid set with "ID"s, and I'm wanting to create nested layers of 5 x 5 grids 5 levels down. (so for example grid A is 62500m x 62500m, and within grid A I want 25 tiles of 12500m x 1500m, and in each of those I want 25 tiles of 500m x 500m, and in each of those I want 25 tiles of 100m x 100m) with an "ID" specific to the tile it's embedded within (ex.: 2_3_4_5_A)

enter image description here

2 Answers 2

15

This answer was given to the original question prior the edit:

Here is a graphical model you can use:

enter image description here

So basically, you first need to create your parent grid as rectangles and order them with an SQL statement, that allows to order by X asc and Y desc: select *, ROW_NUMBER() OVER(ORDER BY ST_MinY(geometry) desc, ST_MinX(geometry) asc) as id FROM input1 (see @JGH's answer here: https://gis.stackexchange.com/a/317621/107424). Now assign the new feature ids as ids ("id" = $id). Then densify these rectangles (expression here @Subgrids - 1) and extract the vertices. From these vertices, which contain an angle field, you can now create orthogonal lines by using this expression:

CASE
 WHEN "angle" in (90)
  THEN make_line($geometry,make_point($x,$y-@VerticalSpacing))
 WHEN "angle" in (0)
  THEN make_line($geometry,make_point($x+@HorizontalSpacing,$y))
END

The lines will exactly fill one parent grid. You can use these to divide your parent grid into a child grid. Then you need to order the child-grid-cells similar as before: select *, ROW_NUMBER() OVER(ORDER BY ST_MinY(geometry) desc, ST_MinX(geometry) asc) as id FROM input1.

Finally add the child-grid-id by using this expression: to_string(id) || '_' || to_string(array_find(array_agg("id"||'_'||-$id,"id"),"id"||'_'||-$id)+1) as requested at Increment value based on grouped field. Done.

enter image description here

If you want to use Letters instead of Numbers, you need to implement a letter-lookup such as array_get(string_to_array('A,B,C,D,E,F'),$id+1) or as I explained here: https://gis.stackexchange.com/a/401200/107424

The model works with metric and degree units. Tested in QGIS 3.16.1. Proof:

enter image description here


For reference, here the model exported as Python script:

"""
Model exported as python.
Name : NestedGrid
Group : GISSE
With QGIS : 31601
"""

from qgis.core import QgsProcessing
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingMultiStepFeedback
from qgis.core import QgsProcessingParameterCrs
from qgis.core import QgsProcessingParameterExtent
from qgis.core import QgsProcessingParameterNumber
from qgis.core import QgsProcessingParameterFeatureSink
from qgis.core import QgsExpression
import processing


class Nestedgrid(QgsProcessingAlgorithm):

    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterCrs('CRS', 'CRS', defaultValue='EPSG:3857'))
        self.addParameter(QgsProcessingParameterExtent('Extent', 'Extent', defaultValue=None))
        self.addParameter(QgsProcessingParameterNumber('HorizontalSpacing', 'Horizontal Spacing of Parent Grid', type=QgsProcessingParameterNumber.Double, minValue=0, defaultValue=10000))
        self.addParameter(QgsProcessingParameterNumber('VerticalSpacing', 'Vertical Spacing of Parent Grid', type=QgsProcessingParameterNumber.Double, minValue=0, defaultValue=10000))
        self.addParameter(QgsProcessingParameterNumber('Subgrids', 'Subgrids (x*x)', type=QgsProcessingParameterNumber.Integer, minValue=0, defaultValue=3))
        self.addParameter(QgsProcessingParameterFeatureSink('Parent_grid', 'Parent_Grid', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, supportsAppend=True, defaultValue=None))
        self.addParameter(QgsProcessingParameterFeatureSink('Child_grid', 'Child_Grid', type=QgsProcessing.TypeVectorAnyGeometry, createByDefault=True, supportsAppend=True, defaultValue=None))

    def processAlgorithm(self, parameters, context, model_feedback):
        # Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
        # overall progress through the model
        feedback = QgsProcessingMultiStepFeedback(9, model_feedback)
        results = {}
        outputs = {}

        # Create grid
        # Create the original parent grid
        alg_params = {
            'CRS': parameters['CRS'],
            'EXTENT': parameters['Extent'],
            'HOVERLAY': 0,
            'HSPACING': parameters['HorizontalSpacing'],
            'TYPE': 2,
            'VOVERLAY': 0,
            'VSPACING': parameters['VerticalSpacing'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['CreateGrid'] = processing.run('native:creategrid', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Execute SQL
        # Order the Grid 1) from West to East and 2) from North to South
        alg_params = {
            'INPUT_DATASOURCES': outputs['CreateGrid']['OUTPUT'],
            'INPUT_GEOMETRY_CRS': None,
            'INPUT_GEOMETRY_FIELD': '',
            'INPUT_GEOMETRY_TYPE': None,
            'INPUT_QUERY': 'select *, ROW_NUMBER() OVER(ORDER BY  ST_MinY(geometry) desc, ST_MinX(geometry) asc) as id\nFROM input1',
            'INPUT_UID_FIELD': '',
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ExecuteSql'] = processing.run('qgis:executesql', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Refactor fields
        # Create the id's regarding the new sorting of the features
        alg_params = {
            'FIELDS_MAPPING': [{'expression': '$id','length': 42,'name': 'id','precision': 0,'type': 10},{'expression': '$id','length': 20,'name': 'parentid','precision': 0,'type': 4}],
            'INPUT': outputs['ExecuteSql']['OUTPUT'],
            'OUTPUT': parameters['Parent_grid']
        }
        outputs['RefactorFields'] = processing.run('native:refactorfields', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Parent_grid'] = outputs['RefactorFields']['OUTPUT']

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # Densify by count
        # Densify the grid: add extra vertices that will be used as origin for the orthogonal lines used for splitting the grid
        alg_params = {
            'INPUT': outputs['RefactorFields']['OUTPUT'],
            'VERTICES': QgsExpression(' @Subgrids - 1').evaluate(),
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['DensifyByCount'] = processing.run('native:densifygeometries', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        # Extract vertices
        # Extract the (extra) vertices. They will have an angle field, we can use to identify the needed vertices for the split
        alg_params = {
            'INPUT': outputs['DensifyByCount']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ExtractVertices'] = processing.run('native:extractvertices', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(5)
        if feedback.isCanceled():
            return {}

        # Geometry by expression
        # Create orthogonal lines at the desified vertices of the parent grid.
        # The lines will exactly fill one parent grid and can be used to divide it into the child grid at the next step.
        alg_params = {
            'EXPRESSION': 'CASE\r\n WHEN \"angle\" in (90) --270\r\n  THEN make_line($geometry,make_point($x,$y-@VerticalSpacing ))\r\n WHEN \"angle\" in (0) -- 180\r\n  THEN make_line($geometry,make_point($x+@HorizontalSpacing,$y))\r\nEND',
            'INPUT': outputs['ExtractVertices']['OUTPUT'],
            'OUTPUT_GEOMETRY': 1,
            'WITH_M': False,
            'WITH_Z': False,
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['GeometryByExpression'] = processing.run('native:geometrybyexpression', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(6)
        if feedback.isCanceled():
            return {}

        # Split with lines
        # Cut the Parent Grid into the Child Grid
        alg_params = {
            'INPUT': outputs['RefactorFields']['OUTPUT'],
            'LINES': outputs['GeometryByExpression']['OUTPUT'],
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['SplitWithLines'] = processing.run('native:splitwithlines', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(7)
        if feedback.isCanceled():
            return {}

        # Execute SQL
        # Order the Grid 1) from West to East and 2) from North to South, see: https://gis.stackexchange.com/a/317621/107424
        alg_params = {
            'INPUT_DATASOURCES': outputs['SplitWithLines']['OUTPUT'],
            'INPUT_GEOMETRY_CRS': None,
            'INPUT_GEOMETRY_FIELD': '',
            'INPUT_GEOMETRY_TYPE': None,
            'INPUT_QUERY': 'select *, ROW_NUMBER() OVER(ORDER BY  ST_MinY(geometry) desc, ST_MinX(geometry) asc) as id\nFROM input1',
            'INPUT_UID_FIELD': '',
            'OUTPUT': QgsProcessing.TEMPORARY_OUTPUT
        }
        outputs['ExecuteSql'] = processing.run('qgis:executesql', alg_params, context=context, feedback=feedback, is_child_algorithm=True)

        feedback.setCurrentStep(8)
        if feedback.isCanceled():
            return {}

        # Refactor fields
        # Create the Child IDs, see: https://gis.stackexchange.com/a/376725/107424
        alg_params = {
            'FIELDS_MAPPING': [{'expression': 'to_string(id) || \'_\' || to_string(array_find(array_agg(\"id\"||\'_\'||-$id,\"id\"),\"id\"||\'_\'||-$id)+1)','length': 42,'name': 'id','precision': 0,'type': 10},{'expression': 'id','length': 20,'name': 'parentid','precision': 0,'type': 4},{'expression': 'array_find(array_agg(\"id\"||\'_\'||-$id,\"id\"),\"id\"||\'_\'||-$id)+1','length': 20,'name': 'childid','precision': 0,'type': 4}],
            'INPUT': outputs['ExecuteSql']['OUTPUT'],
            'OUTPUT': parameters['Child_grid']
        }
        outputs['RefactorFields'] = processing.run('native:refactorfields', alg_params, context=context, feedback=feedback, is_child_algorithm=True)
        results['Child_grid'] = outputs['RefactorFields']['OUTPUT']
        return results

    def name(self):
        return 'NestedGrid'

    def displayName(self):
        return 'NestedGrid'

    def group(self):
        return 'GISSE'

    def groupId(self):
        return 'GISSE'

    def createInstance(self):
        return Nestedgrid()
6
  • 2
    Nice and clean. Feb 19, 2021 at 13:16
  • Elegant solution! How can I create the orthogonal lines from the vertices? Where do run the expression?
    – csheth
    Jul 20, 2021 at 8:42
  • @csheth run the expression in "geometry by expression" from processing toolbox. You can lookup every detail in the model, you can download here: github.com/mkoenigb/QGIS_GraphicalModels/blob/master/…
    – MrXsquared
    Jul 20, 2021 at 8:48
  • @MrXsquared I ran the expression on the vertices layer as you suggest, made sure the "angle" column was created. Unfortunately the "geometry by expression" does not create an polygon/line/point. In addition the modeller does not open the NestedGrid.model3 file throwing a "AttributeError: 'NoneType' object has no attribute 'svgIconPath' " error message. So I can't explore the details in the model yet.
    – csheth
    Jul 20, 2021 at 9:45
  • 1
    I updated to 3.16.8 and your model opened and ran without any errors. Perhaps I need to modify the steps because I am trying to create child grids on a parent grid I have already created.
    – csheth
    Jul 20, 2021 at 13:19
1

You can use ProcessX Plug-In. It has an algorithm "Create Nested Grid", you can find in your processing toolbox --> processx --> vector - creation --> create nested grid:

enter image description here

This Algorithm creates a nested grid, where the gridsize is specified for the parentgrid. Each childgrid has half the x- and y-spacing of its parent. Note that the parentgrid is created at last, so it lies on top of its childgrids. Change your symbology to make the childgrids visible. You can also choose giving one or both axis letters as ids instead of numbers. These letters follow the Excel-Style-Column-Naming. Each childgrid also has the id of its parent it lies within assigned.

To get your example result, choose 2 subgrids and 3 as X- and Y-Factor:

enter image description here

Disclaimer: I am the author of this Plug-In.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.