Coverage for hml/rasterizeShapefile.py: 2%
46 statements
« prev ^ index » next coverage.py v7.8.1, created at 2025-05-23 08:31 +0000
« prev ^ index » next coverage.py v7.8.1, created at 2025-05-23 08:31 +0000
1#!/usr/bin/env python3
3# Define function ...
4def rasterizeShapefile(sfObj, /, *, nx = 1024, ny = 1024, px = 1024.0):
5 """
6 Rasterize a ShapeFile.
8 Arguments:
9 sfObj -- a shapefile.Reader of a ShapeFile
11 Keyword arguments:
12 px -- pixel size (default 1024.0)
13 nx -- number of x pixels (default 1024)
14 ny -- number of y pixels (default 1024)
16 Note:
17 This function only works for ShapeFiles that solely exist in the (positive,
18 positive) quadrant.
19 """
21 # Import standard modules ...
22 import multiprocessing
24 # Import special modules ...
25 try:
26 import numpy
27 except:
28 raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None
29 try:
30 import shapefile
31 except:
32 raise Exception("\"shapefile\" is not installed; run \"pip install --user pyshp\"") from None
33 try:
34 import shapely
35 import shapely.geometry
36 import shapely.validation
37 except:
38 raise Exception("\"shapely\" is not installed; run \"pip install --user Shapely\"") from None
40 # Import sub-functions ...
41 from .rasterizePolygon import rasterizePolygon
43 # Check argument ...
44 if not isinstance(sfObj, shapefile.Reader):
45 raise TypeError("\"sfObj\" is not a shapefile.Reader")
47 # Initialize counter and global grid ...
48 n = 0 # [#]
49 globalGrid = numpy.zeros((ny, nx), dtype = numpy.float32) # [m2]
51 # Create a pool of workers ...
52 with multiprocessing.Pool(maxtasksperchild = 1) as pObj:
53 # Initialize list ...
54 results = []
56 # Loop over shape+record pairs ...
57 for shapeRecord in sfObj.iterShapeRecords():
58 # Crash if this shape+record is not a shapefile polygon ...
59 if shapeRecord.shape.shapeType != shapefile.POLYGON:
60 raise Exception("\"shape\" is not a POLYGON") from None
62 # Convert shapefile.Shape to shapely.geometry.polygon.Polygon or
63 # shapely.geometry.multipolygon.MultiPolygon ...
64 poly = shapely.geometry.shape(shapeRecord.shape)
65 if not poly.is_valid:
66 print(f"WARNING: Skipping a shape as it is not valid ({shapely.validation.explain_validity(poly)}).")
67 n += 1 # [#]
68 continue
69 if poly.is_empty:
70 n += 1 # [#]
71 continue
73 # Add rasterization job to worker pool ...
74 results.append(
75 pObj.apply_async(
76 rasterizePolygon,
77 (poly,),
78 {
79 "px" : px,
80 },
81 )
82 )
84 print(f"INFO: {n:,d} records were skipped because they were invalid")
86 # Loop over results ...
87 for result in results:
88 # Get result ...
89 ix1, iy1, localGrid = result.get()
91 # Check result ...
92 if not result.successful():
93 raise Exception("\"multiprocessing.Pool().apply_async()\" was not successful") from None
95 # Loop over x-axis ...
96 for ix in range(localGrid.shape[1]):
97 # Loop over y-axis ...
98 for iy in range(localGrid.shape[0]):
99 # Add local grid to global grid ...
100 globalGrid[iy1 + iy, ix1 + ix] += localGrid[iy, ix] # [m2]
102 # Close the pool of worker processes and wait for all of the tasks to
103 # finish ...
104 # NOTE: The "__exit__()" call of the context manager for
105 # "multiprocessing.Pool()" calls "terminate()" instead of
106 # "join()", so I must manage the end of the pool of worker
107 # processes myself.
108 pObj.close()
109 pObj.join()
111 # Return answer ...
112 return globalGrid