Placement algorithm introduction¶
This notebook demonstrates how to use GLAES to systematically place objects (such as wind turbines) within a predefined study area.
Workflow Overview:¶
- Create file with available lands like in 00_basic_workflow.
- Distribute item placements within the eligible regions
- basic placement
- spare the edges of a region
- direction-dependent placements
- get placement coordinates
- use wind direction data for placements
- spatially varying raster
- individual placement areas
# Import GLAES
import geokit as gk
import matplotlib.pyplot as plt
import numpy as np
import glaes as gl
%matplotlib inline
# other imports
from copy import copy
1. Create the land eligibility analysis¶
Here the eligibile land file from the first example notebook is recreated, for details have a look into 00_basic_workflow.
# Choose a region to operate on (Here, a predefined region for Aachen, Germany is used)
regionPath = gl._test_data_["aachenShapefile.shp"]
# Initialize ExclusionCalculator object
ec = gl.ExclusionCalculator(regionPath, srs=3035, pixelRes=100)
# Apply exclusions
ec.excludeRasterType(gl._test_data_["clc-aachen_clipped.tif"], value=(12, 22))
ec.excludeVectorType(
gl._test_data_["aachenRoads.shp"], where="type='motorway' OR type='primary' OR type='trunk'", buffer=200
)
# Draw result
ec.draw()
Memory usage during calc: 241.55859375 MB
Memory usage during calc: 264.6484375 MB
<Axes: >
Copy the ec object after land eligibility analysis.
# make a copy of the ec object so we can "reset" the ec object after distributing placements and have a "clean" ec again
ec_backup = copy(ec)
2. Distribute item placements within the eligible regions¶
Basic Placement¶
separation specifies the minimal distance between two objects.
# Do placements
ec.distributeItems(separation=1000)
# Draw result
ec.draw()
<Axes: >
Spare the edges of a region¶
When placements are created separately for neighbouring regions, points close to the shared region borders could end up being placed too close to each other. Setting avoidRegionBorders=True prevents this by keeping placements away from the outer boundary of the current region.
The distance kept from the region border is half of the specified separation distance. For example, with separation=1000, placements are kept at least 500 m away from the region boundary.
If different axial and transverse separation distances are provided as a tuple, the border distance is calculated from half of their mean value.
Unlike an exclusion of the region edge, avoidRegionBorders=True does not modify the eligibility raster itself. It only restricts where distributeItems() may place items.
# reset the ec object to remove the "old" placements from above
ec = copy(ec_backup)
# repeat the step above and set the argument avoidRegionBorders to True
ec.distributeItems(separation=1000, avoidRegionBorders=True)
# Draw result. In comparison to above you can see that no placements are distributed towards the edges of the region, you can also see that less items were distributed
ec.draw()
Memory usage during calc: 277.40625 MB
<Axes: >
Direction-dependent placements¶
By default, a single separation distance is used, meaning that the minimum spacing between two objects is the same in every direction.
Alternatively, two separation distances can be specified using separation=(axial, transverse), axialDirection:
- axial separation: the minimum spacing along one chosen axis,
- transverse separation: the minimum spacing along the axis perpendicular to it.
The orientation of the axial axis is defined by the axialDirection parameter. The angle is measured counter-clockwise from East. This follows the standard mathematical convention and differs from the common azimuth convention, where angles are typically measured clockwise from North.
|
axialDirection = 45°
axial
/
/
----------●----------
/
/
↙
transverse transverse
↖ ↘
|
Changing axialDirection simply rotates the two separation directions. The first value always belongs to the axial direction, and the second value to the perpendicular (transverse) direction.
# reset the ec object again
ec = copy(ec_backup)
# do placements with "separation=(axial, transverse), axialDirection"
ec.distributeItems(separation=(800, 600), axialDirection=45, avoidRegionBorders=True)
# Draw result
ec.draw()
350 Memory usage during calc: 280.64453125 MB
<Axes: >
Get the placements as XY coordinates¶
# print only the first 5 placements
print(ec.itemCoords[:5])
[[4053200.01 3098499.99] [4053550. 3098000.01] [4054800.01 3097999.99] [4051400.01 3097899.99] [4054099.99 3097700.01]]
Use wind direction data for placements¶
ec.distributeItems(
(1400, 600), # separation distances
axialDirection=r"data/ERA5_wind_direction_100m_mean_Aachen.tif",
outputSRS=gk.srs.EPSG4326,
)
ec.draw()
<Axes: >
Create a spatially varying raster and use it to apply location-dependent spacing when distributing items¶
Instead of using a uniform separation distance, distributeItems() supports the sepScaling parameter to vary the spacing between items spatially. When sepScaling is used, a single separation value must be provided. The effective local separation is calculated by multiplying the specified separation value by the corresponding value of the scaling raster:
effective separation = separation × local sepScaling value
In the subsequent example, a raster is created with values increasing from 50 to 200 and use it as sepScaling. The raster is named rotor_diam_raster to represent spatially varying wind turbine rotor diameters. With separation=8.0, the resulting spacing therefore ranges from 400 to 1600 (North to South), corresponding to eight times the local rotor diameter.
# Create a raster with values increasing from 50 to 200 from top to bottom
mat = np.zeros_like(ec.region.mask, dtype=np.uint8)
for i in range(mat.shape[0]):
mat[i, :] = (200 - 50) * i / (mat.shape[0]) + 50
# Show the raster
h = plt.imshow(mat)
plt.colorbar(h)
plt.show()
# Convert the array into a raster used to scale separation distances
rotor_diam_raster = ec.region.createRaster(output="tmp/rotor_diameter.tif", data=mat)
# Place items using a variable spacing:
# effective spacing = separation × local raster value
points = ec.distributeItems(separation=8.0, sepScaling=rotor_diam_raster)
ec.draw()
<Axes: >
Individual placement areas¶
Additionally, the whole area can be divided into one area for each placement by using the distributeAreas function. Those can be saved as well.
To call this function, first use the distributeItems function to create placements as elaborated above.
# 1) # Create the placement points and save them as a shapefile
points = ec.distributeItems(separation=1000)
ec.saveItems(output="tmp/individual_placement_points.shp")
# 2) Create placement areas (polygons around placed items) and save them as a shapefile
areas = ec.distributeAreas(minArea=300000)
ec.saveAreas(output="tmp/individual_placement_areas.shp")
# 3) Visualisation
ec.draw()
<Axes: >