├── README.md ├── file1 └── file 2 /README.md: -------------------------------------------------------------------------------- 1 | # Renewable Energy Site Suitability Model 2 | 3 | This project is a GIS-based Streamlit application for identifying optimal sites for renewable energy development (solar, wind, hydro) using a weighted raster overlay model. 4 | 5 | ## Features 6 | - Upload and process raster layers such as solar radiation, slope, distance to roads, and land use. 7 | - Normalize input layers and apply user-defined weights. 8 | - Compute a composite suitability map using Multi-Criteria Decision Analysis (MCDA). 9 | - Visualize the output suitability map interactively. 10 | - Option to save the output as a GeoTIFF raster. 11 | 12 | ## Installation 13 | ```bash 14 | pip install streamlit rasterio numpy matplotlib geopandas 15 | ``` 16 | 17 | ## Folder Structure 18 | ``` 19 | Renewable-Energy-Site-Suitability-Model/ 20 | ├── data/ # Folder for input raster files 21 | ├── output/ # Folder to save output maps 22 | ├── model.py # Main Streamlit app 23 | └── README.md # Project documentation 24 | ``` 25 | 26 | ## Usage 27 | Run the Streamlit app: 28 | ```bash 29 | streamlit run model.py 30 | ``` 31 | 32 | Adjust the weights and paths in the UI to run the suitability model. Ensure all input rasters are spatially aligned. 33 | 34 | ## Example Layers 35 | - `solar_radiation.tif`: Solar potential (higher is better) 36 | - `slope.tif`: Slope data (lower is better) 37 | - `roads_distance.tif`: Distance from roads (lower is better) 38 | - `landuse.tif`: Land use classification (e.g., grassland or barren is suitable) 39 | 40 | ## Notes 41 | - Extendable to wind or hydro energy by replacing the solar input with wind speed or water flow potential. 42 | - Land use reclassification can be adjusted in the script. 43 | 44 | ## License 45 | MIT License 46 | 47 | ## Authors 48 | - Akajiaku – Project Development 49 | 50 | ## Future Work 51 | - Incorporate vector data (e.g., protected areas) 52 | - Add support for cloud-based data sources 53 | - Export reports or dashboards 54 | -------------------------------------------------------------------------------- /file1: -------------------------------------------------------------------------------- 1 | import rasterio 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from rasterio.plot import show 5 | from rasterio.transform import from_origin 6 | 7 | # --- Utility Functions --- 8 | def normalize(array): 9 | """Normalize array to 0-1 scale.""" 10 | array_min, array_max = np.nanmin(array), np.nanmax(array) 11 | return (array - array_min) / (array_max - array_min) 12 | 13 | def weighted_sum(layers, weights): 14 | """Compute weighted overlay.""" 15 | combined = np.zeros_like(layers[0]) 16 | for i in range(len(layers)): 17 | combined += layers[i] * weights[i] 18 | return combined 19 | 20 | # --- Load Raster Data --- 21 | def load_raster(path): 22 | with rasterio.open(path) as src: 23 | data = src.read(1) 24 | profile = src.profile 25 | return data, profile 26 | 27 | # --- Paths to Layers --- 28 | layers_paths = { 29 | "solar": "data/solar_radiation.tif", # Higher is better 30 | "slope": "data/slope.tif", # Lower slope is better 31 | "roads_distance": "data/roads_distance.tif", # Lower is better 32 | "landuse": "data/landuse.tif" # Categorized 33 | } 34 | 35 | # --- Weights for Each Factor --- 36 | weights = { 37 | "solar": 0.4, 38 | "slope": 0.2, 39 | "roads_distance": 0.2, 40 | "landuse": 0.2 41 | } 42 | 43 | # --- Load and Normalize Rasters --- 44 | solar, profile = load_raster(layers_paths["solar"]) 45 | slope, _ = load_raster(layers_paths["slope"]) 46 | roads, _ = load_raster(layers_paths["roads_distance"]) 47 | landuse, _ = load_raster(layers_paths["landuse"]) 48 | 49 | # --- Reclassify Land Use: 1 (suitable), 0 (unsuitable) --- 50 | # e.g., only categories 2 (barren) and 5 (grassland) are suitable 51 | suitable_landuse = np.where(np.isin(landuse, [2, 5]), 1, 0) 52 | 53 | # --- Normalize --- 54 | solar_norm = normalize(solar) 55 | slope_norm = 1 - normalize(slope) # Invert slope: lower = more suitable 56 | roads_norm = 1 - normalize(roads) # Closer to road = better 57 | 58 | # --- Weighted Sum --- 59 | layers = [solar_norm, slope_norm, roads_norm, suitable_landuse] 60 | weights_list = [weights["solar"], weights["slope"], weights["roads_distance"], weights["landuse"]] 61 | suitability = weighted_sum(layers, weights_list) 62 | 63 | # --- Save Output --- 64 | output_path = "output/suitability_map.tif" 65 | profile.update(dtype=rasterio.float32, count=1) 66 | 67 | with rasterio.open(output_path, "w", **profile) as dst: 68 | dst.write(suitability.astype(rasterio.float32), 1) 69 | 70 | # --- Visualization --- 71 | plt.figure(figsize=(10, 6)) 72 | plt.title("Renewable Energy Site Suitability Map") 73 | plt.imshow(suitability, cmap='YlGn') 74 | plt.colorbar(label='Suitability Score (0 - 1)') 75 | plt.xlabel('Column Index') 76 | plt.ylabel('Row Index') 77 | plt.tight_layout() 78 | plt.show() 79 | -------------------------------------------------------------------------------- /file 2: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import rasterio 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | from rasterio.plot import show 6 | 7 | # --- Utility Functions --- 8 | def normalize(array): 9 | array_min, array_max = np.nanmin(array), np.nanmax(array) 10 | return (array - array_min) / (array_max - array_min) 11 | 12 | def weighted_sum(layers, weights): 13 | combined = np.zeros_like(layers[0]) 14 | for i in range(len(layers)): 15 | combined += layers[i] * weights[i] 16 | return combined 17 | 18 | def load_raster(path): 19 | with rasterio.open(path) as src: 20 | data = src.read(1) 21 | profile = src.profile 22 | return data, profile 23 | 24 | # --- Streamlit Interface --- 25 | st.title("Renewable Energy Site Suitability Model") 26 | st.write("Select input raster layers and adjust weights.") 27 | 28 | solar_path = st.text_input("Solar Radiation Raster Path", "data/solar_radiation.tif") 29 | slope_path = st.text_input("Slope Raster Path", "data/slope.tif") 30 | roads_path = st.text_input("Roads Distance Raster Path", "data/roads_distance.tif") 31 | landuse_path = st.text_input("Land Use Raster Path", "data/landuse.tif") 32 | 33 | solar_weight = st.slider("Solar Weight", 0.0, 1.0, 0.4) 34 | slope_weight = st.slider("Slope Weight", 0.0, 1.0, 0.2) 35 | roads_weight = st.slider("Roads Distance Weight", 0.0, 1.0, 0.2) 36 | landuse_weight = st.slider("Land Use Weight", 0.0, 1.0, 0.2) 37 | 38 | if st.button("Run Suitability Model"): 39 | solar, profile = load_raster(solar_path) 40 | slope, _ = load_raster(slope_path) 41 | roads, _ = load_raster(roads_path) 42 | landuse, _ = load_raster(landuse_path) 43 | 44 | # Reclassify land use: 1 (suitable), 0 (unsuitable) 45 | suitable_landuse = np.where(np.isin(landuse, [2, 5]), 1, 0) 46 | 47 | # Normalize 48 | solar_norm = normalize(solar) 49 | slope_norm = 1 - normalize(slope) 50 | roads_norm = 1 - normalize(roads) 51 | 52 | # Compute weighted sum 53 | layers = [solar_norm, slope_norm, roads_norm, suitable_landuse] 54 | weights = [solar_weight, slope_weight, roads_weight, landuse_weight] 55 | suitability = weighted_sum(layers, weights) 56 | 57 | # Display output 58 | st.subheader("Suitability Map") 59 | fig, ax = plt.subplots(figsize=(8, 5)) 60 | cax = ax.imshow(suitability, cmap='YlGn') 61 | fig.colorbar(cax, ax=ax, label='Suitability Score') 62 | st.pyplot(fig) 63 | 64 | # Optionally save output 65 | if st.checkbox("Save Output Raster"): 66 | output_path = st.text_input("Output File Path", "output/suitability_map.tif") 67 | profile.update(dtype=rasterio.float32, count=1) 68 | with rasterio.open(output_path, "w", **profile) as dst: 69 | dst.write(suitability.astype(rasterio.float32), 1) 70 | st.success(f"Saved: {output_path}") 71 | --------------------------------------------------------------------------------