11

I have a function which creates Solar Photovolatic panels represented as polygons. Essentially, it creates a rectangular grid where the user can specify the following parameters:

  • Length
  • Width
  • Horizontal distance
  • Vertical distance

The code is based on the plugin FeatureGridCreator but focusing only on the polygon aspect. It works well for the most part, especially when creating polygons with large dimensions (e.g. 10m length and width; 10m horizontal and vertical distances).

But I noticed a couple of issues:

  1. When specifying polygons for dimensions less than 2m for both length and width, no polygons were created.

  2. When specifying polygons with differing dimensions (e.g. 5m length and 7m width), the dimensions were not the same when measured with the Measure Line tool. For these dimensions, the length and width were shown to be 4m and 6m respectively.

    Example of differing length and width

The CRS used for both the projection and layer is EPSG:27700 although I wouldn't have thought this would be a problem.

So does anyone have any idea what could be causing these issues? I am also open to suggestions as to how the code could be improved or even replaced with a better alternative.


Here is the code which can be reproduced in the Python Console, a polygon layer must be selected with a relevant CRS before running the function:

from PyQt4.QtCore import QVariant
from math import ceil

def generate_pv_panels(length, width, distance_x, distance_y):
    # Define layer properties
    layer = iface.activeLayer()
    crs = layer.crs()
    memory_lyr = QgsVectorLayer("Polygon?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", "PV panels for " + str(layer.name()), "memory")
    QgsMapLayerRegistry.instance().addMapLayer(memory_lyr)
    memory_lyr.startEditing()
    provider = memory_lyr.dataProvider()
    provider.addAttributes([QgsField("ID", QVariant.Int)])
    fid = 0
    start_x = 0
    start_y = 0
    # Ensure polygons are not created 'within each other'
    if distance_x < (length / 1000):
        distance_x = (length / 1000)
    if distance_y < (width / 1000):
        distance_y = (width / 1000)
    fts = []
    for f in layer.getFeatures():
        fid += 1
        bbox = f.geometry().boundingBox()
        start_x = bbox.xMinimum() + float(distance_x / 2)
        start_y = bbox.yMinimum() + float(distance_y / 2)
        for row in range(0, int(ceil(bbox.height() / distance_y))):
            for column in range(0, int(ceil(bbox.width() / distance_x))):
                fet = QgsFeature()
                geom_type = pv_panel_size(length, width, start_x, start_y)
                if f.geometry().contains(geom_type):
                    fet.setGeometry(geom_type)
                    fet.setAttributes([fid])
                    fts.append(fet)
                start_x += distance_x + (length / 1000)
            start_x = bbox.xMinimum() + float(distance_x / 2)
            start_y += distance_y + (width / 1000)
    provider.addFeatures(fts)
    memory_lyr.updateFields()
    memory_lyr.commitChanges()

def pv_panel_size(length, width, x, y):
    # Length & width measured in mm; x & y measured in m
    l = length / 2000
    w = width / 2000
    return QgsGeometry.fromRect(QgsRectangle(x - l, y - w, x + l, y + w))

generate_pv_panels(10000, 10000, 100, 100)
11
+300

Your algorithm makes sense, but it seems that your problem is due to a rounding error when you divide by 2000 (divide by integer, which explains why a number smaller than two gives 0, and all distances are rounded to even values)

You should change the integer division with a float division

l = length / 2000

should be

l = length / 2000. # the . makes sure that you divide by a decimal value

or

l = float(length) / 2000

Note that this gives you the exact dimensions entered by the form, but you could decide to round the size of your parcels at one meter if you prefer:

l = float(length/1000) / 2

Note that you should also check the rounding at the start coordinates, but I do not know if this rounding is on purpose.

start_x = bbox.xMinimum() + float(distance_x) / 2
  • I think this has solved the issues I mentioned (ironically, I noticed some new issues occuring but think they have been resolved). Will continue testing this further and report back. Many thanks :) – Joseph Sep 7 '17 at 15:21
  • Yup, I believe your solution has worked. Thank you again ;) – Joseph Sep 8 '17 at 11:33
3

Thanks to @radouxju, here is the final code which also takes into account the horizontal and vertical distances being zero:

from PyQt4.QtCore import QVariant
from math import ceil

def generate_pv_panels(length, width, distance_x, distance_y):
    # Define layer properties
    layer = iface.activeLayer()
    crs = layer.crs()
    memory_lyr = QgsVectorLayer("Polygon?crs=epsg:" + unicode(crs.postgisSrid()) + "&index=yes", "PV panels for " + str(layer.name()), "memory")
    QgsMapLayerRegistry.instance().addMapLayer(memory_lyr)
    memory_lyr.startEditing()
    provider = memory_lyr.dataProvider()
    provider.addAttributes([QgsField("ID", QVariant.Int)])
    # Define variables
    fid = 0
    start_x = 0
    start_y = 0
    state_x = False
    state_y = False
    # Ensure polygons are not created 'within each other' if distance is zero;
    # Instead they will align on the bounding box
    if distance_x == 0:
        distance_x = (length / 1000)
        state_x = True
    if distance_y == 0:
        distance_y = (width / 1000)
        state_y = True
    fts = []
    for f in layer.getFeatures():
        fid += 1
        bbox = f.geometry().boundingBox()
        start_x = bbox.xMinimum() + float(distance_x / 2)
        start_y = bbox.yMinimum() + float(distance_y / 2)
        for row in range(0, int(ceil(bbox.height() / distance_y))):
            for column in range(0, int(ceil(bbox.width() / distance_x))):
                fet = QgsFeature()
                geom_type = pv_panel_size(length, width, start_x, start_y)
                if f.geometry().contains(geom_type):
                    fet.setGeometry(geom_type)
                    fet.setAttributes([fid])
                    fts.append(fet)
                if state_x == False:
                    start_x += distance_x + (length / 1000)
                else:
                    start_x += distance_x
            start_x = bbox.xMinimum() + float(distance_x / 2)
            if state_y == False:
                start_y += distance_y + (width / 1000)
            else:
                start_y += distance_y
    provider.addFeatures(fts)
    memory_lyr.updateFields()
    memory_lyr.commitChanges()

def pv_panel_size(length, width, x, y):
    # Length & width measured in mm; x & y measured in m
    l = float(length) / 2000
    w = float(width) / 2000
    return QgsGeometry.fromRect(QgsRectangle(x - l, y - w, x + l, y + w))

  • Using generate_pv_panels(5500, 5000, 20, 1):

    Scenario 1


  • Using generate_pv_panels(5500, 5000, 20, 0):

    Scenario 2

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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