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

Taras
  • 32,823
  • 4
  • 66
  • 137

2 Answers2

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

MrXsquared
  • 34,292
  • 21
  • 67
  • 117
  • 2
    Nice and clean. – Kadir Şahbaz Feb 19 '21 at 13:16
  • Elegant solution! How can I create the orthogonal lines from the vertices? Where do run the expression? – csheth Jul 20 '21 at 08:42
  • @csheth run the expression in "geometry by expression" from processing toolbox. You can lookup every detail in the model, you can download here: https://github.com/mkoenigb/QGIS_GraphicalModels/blob/master/NestedGrid.model3 – MrXsquared Jul 20 '21 at 08: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 '21 at 09:45
  • I'm trying it on 3.10.4. Will update to 3.16 and try again. – csheth Jul 20 '21 at 09:55
  • 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 '21 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.

MrXsquared
  • 34,292
  • 21
  • 67
  • 117