QGIS
You can integrate QGIS with the RenewMap API for geospatial and mapping functions.
API Access
You will need an API key to get started.
- Check the Getting Started page for instructions
- 📘 See the API docs
The RenewMap API allows you to integrate up-to-date project data with data analysis platforms such as QGIS. By calling the API directly (rather than using a CSV export), you are guaranteed to always have the most up-to-date data in your GIS tools.
Setup
In QGIS, you can run a Python script to fetch RenewMap API data into your project.
- Select **Plugins **> Python Console and then select 📝 Show editor
- Create a new blank script using the ➕ icon.
- Paste the code below into the blank script, replacing
'YOUR_API_KEY'
with your actual API key. - Run the script with the ▶️ icon.
- The latest RenewMap data will appear in your project:
- A new Point layer called RenewMap with energy project locations and attributes.
** Note: ** the layer is stored in memory and will be lost when you close the project. Re-run the script whenever you want to get the latest data.
If you want to persist the layer to your next session, right click the layer in the Layers pane and select Make permanent. However, this permanent layer will not update with new data.
Projects
import pandas as pd
import requests
import json
from typing import List, Dict
API_KEY = "YOUR_API_KEY" # Replace with your actual API key
BASE_URL = "https://api.renewmap.com.au/api/v1/projects"
DATA_KEY = "projects"
ADD_FIELDS = False # Your account needs to have the fields APdI enabled
headers = {
"accept": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
class APIConfig:
"""Configuration class for API parameters.
Attributes:
base_url: Base URL for the API endpoint
limit: Number of records per request
headers: HTTP headers for API requests
data_key: Key in API response containing the data array
"""
base_url: str = BASE_URL
limit: int = 10000
headers: Dict = headers
add_fields: bool = ADD_FIELDS
data_key: str = DATA_KEY
def get_url(self, offset=0):
url = f"{self.base_url}?limit={self.limit}&offset={offset}"
if self.add_fields:
url = f"{url}&fields=all"
return url
def fetch_data(config: APIConfig) -> pd.DataFrame:
"""
Fetch data from API using pagination.
Args:
config: APIConfig object containing API configuration
Returns:
DataFrame containing all fetched data, with fields flattened if ADD_FIELDS
"""
projects_data = []
offset=0
# Fetch data in chunks using pagination
while True:
url = config.get_url(offset)
response = requests.get(url, headers=config.headers)
response.raise_for_status()
data_dict = response.json()
batch = data_dict['projects']
if len(batch) == 0:
break # No more data available
projects_data.extend(batch)
offset += config.limit # Increment offset for next request
# Flatten the fields data if applicable
if config.add_fields:
projects_data = [flatten_fields(p) for p in projects_data]
print("Flattened fields")
print(f"Fetched {len(projects_data)} projects")
return pd.DataFrame(projects_data)
def flatten_fields(project_json):
"""
Unpacks and formats the fields API extension.
Args:
project_json: the API json response for a project.
Returns:
project_json: the flattened json response
"""
try:
if 'fields' not in project_json:
return project_json
fields = project_json.pop('fields')
for field in fields:
field_name = field['field_name']
field_value = field['value']
# Convert list values to CSV for QGIS
if type(field_value) == list:
field_value = ', '.join([str(i) for i in field_value])
project_json.update({field_name: field_value})
return project_json
except Exception as e:
print(f"⚠️ There was an error flattening fields for {project_json.get('project_name', 'unknown')}: {e}")
return project_json
def create_qgis_fields(df: pd.DataFrame) -> List[QgsField]:
"""
Create QGIS fields based on DataFrame columns.
Args:
df: Input DataFrame with project data
Returns:
List of QgsField objects
"""
# Create longitude and latitude fields
fields = [
QgsField('Longitude', QVariant.Double),
QgsField('Latitude', QVariant.Double)
]
# Create fields for remaining columns
fields.extend([
QgsField(column, QVariant.String if df[column].dtype == 'O' else QVariant.Double)
for column in df.columns if column != 'point'
])
return fields
def create_feature(row: pd.Series) -> QgsFeature:
"""Create QGIS feature from DataFrame row.
Args:
row: Series containing single project data row
Returns:
QgsFeature object with geometry and attributes
"""
feature = QgsFeature()
longitude, latitude = row['point']
# Set point geometry
feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(longitude, latitude)))
# Set attributes including coordinates and other fields
feature.setAttributes([longitude, latitude] + [
row[column] for column in row.index if column != 'point'
])
return feature
def create_vector_layer(df: pd.DataFrame) -> QgsVectorLayer:
"""Create QGIS vector layer from DataFrame.
Args:
df: DataFrame containing project data
Returns:
QgsVectorLayer object with all features
"""
# Create memory layer
layer = QgsVectorLayer("Point?crs=epsg:4326", "RenewMap", "memory")
provider = layer.dataProvider()
# Add fields to layer
fields = create_qgis_fields(df)
provider.addAttributes(fields)
layer.updateFields()
# Add features to layer
features = []
for _, row in df.iterrows():
feature = create_feature(row)
if feature is not None:
features.append(feature)
provider.addFeatures(features)
layer.updateExtents()
print(f"Created layer with {len(features)} features")
return layer
config = APIConfig()
df = fetch_data(config)
layer = create_vector_layer(df)
QgsProject.instance().addMapLayer(layer)
Adding the fields extension
ℹ️ The projects endpoint includes a parameter to return a larger selection of project attributes. To enable, Set ADD_FIELDS = True
at the top of the script above.
N.B. This data is returned in json format and needs to be unpacked in the script. The logic to unpack and reshape the fields data can be found in flatten_fields()
.
Network
import pandas as pd
import requests
from shapely.geometry import shape
from typing import List, Dict
API_KEY = "YOUR_API_KEY" # Replace with your actual API key
BASE_URL = "https://api.renewmap.com.au/api/v1/network"
DATA_KEY = "network"
headers = {
"accept": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
class APIConfig:
"""Configuration class for API parameters.
Attributes:
base_url: Base URL for the API endpoint
limit: Number of records per request
headers: HTTP headers for API requests
data_key: Key in API response containing the data array
"""
base_url: str = BASE_URL
limit: int = 1000
headers: Dict = headers
add_fields: bool = False # Network API doesn't support fields
data_key: str = DATA_KEY
def get_url(self, offset=0):
url = f"{self.base_url}?limit={self.limit}&offset={offset}"
if self.add_fields:
url = f"{url}&fields=all"
return url
def fetch_data(config: APIConfig) -> pd.DataFrame:
"""Fetch data from API using pagination.
Args:
config: APIConfig object containing API configuration
Returns:
DataFrame containing all fetched data
"""
data_list = []
offset = 0
# Fetch data in chunks using pagination
while True:
url = config.get_url(offset)
response = requests.get(url, headers=config.headers)
response.raise_for_status()
data_dict = response.json()
batch = data_dict[config.data_key]
if len(batch) == 0:
break # No more data available
data_list.extend(batch)
offset += config.limit # Increment offset for next request
print(f"Fetched {len(data_list)} items")
return pd.DataFrame(data_list)
def create_qgis_fields(gdf: pd.DataFrame) -> List[QgsField]:
"""Create QGIS fields based on GeoDataFrame columns.
Args:
gdf: Input GeoDataFrame with project data
Returns:
List of QgsField objects
"""
# Loop through columns to dynamically create QgsField objects
fields = []
for column in gdf.columns:
field_type = QVariant.String # Adjust the type based on your data
fields.append(QgsField(column, field_type))
return fields
def create_feature(row: pd.Series) -> QgsFeature:
"""Create QGIS feature from GeoDataFrame row.
Args:
row: Series containing single project data row with geometry
Returns:
QgsFeature object with geometry and attributes
"""
feature = QgsFeature()
# Convert shapely geometry to QgsGeometry
shapely_geom = shape(row['geometry'])
qgs_geometry = QgsGeometry.fromWkt(shapely_geom.wkt)
# Set geometry
feature.setGeometry(qgs_geometry)
# Set attributes for all columns except geometry
attributes = [str(row[column]) for column in row.index if column != 'geometry']
feature.setAttributes(attributes)
return feature
def create_multilinestring_layer(gdf: pd.DataFrame) -> QgsVectorLayer:
"""Create QGIS vector layer from DataFrame.
Args:
df: DataFrame containing project data
Returns:
QgsVectorLayer object with all features
"""
# Create a memory layer for MultiLineStrings
layer = QgsVectorLayer("LineString?crs=epsg:4326", "Network", "memory")
# Add fields to the layer
fields = create_qgis_fields(gdf)
layer.dataProvider().addAttributes(fields)
layer.updateFields()
# Add features to the multilinestring layer
multiline_data = gdf[gdf['geometry'].apply(lambda x: shape(x).geom_type == 'MultiLineString')]
for _, row in multiline_data.iterrows():
feature = create_feature(row)
if not feature.geometry().isNull():
layer.dataProvider().addFeature(feature)
layer.updateExtents()
return layer
def create_multipolygon_layer(gdf: pd.DataFrame) -> QgsVectorLayer:
# Create a memory layer for MultiPolygons
layer = QgsVectorLayer("Polygon?crs=epsg:4326", "Network - Development", "memory")
# Add fields to the layer
fields = create_qgis_fields(gdf)
layer.dataProvider().addAttributes(fields)
layer.updateFields()
# Add features to the multipolygon layer
polygon_data = gdf[gdf['geometry'].apply(lambda x: shape(x).geom_type == 'MultiPolygon')]
for _, row in polygon_data.iterrows():
feature = create_feature(row)
if not feature.geometry().isNull():
layer.dataProvider().addFeature(feature)
layer.updateExtents()
return layer
config = APIConfig()
gdf = fetch_data(config)
multilinestring_layer = create_multilinestring_layer(gdf)
multipolygon_layer = create_multipolygon_layer(gdf)
QgsProject.instance().addMapLayer(multilinestring_layer)
QgsProject.instance().addMapLayer(multipolygon_layer)
Turbines
import pandas as pd
import json
import requests
from typing import Dict, List
API_KEY = "YOUR_API_KEY" # Replace with your actual API key
BASE_URL = "https://api.renewmap.com.au/api/v1/turbines"
DATA_KEY = "turbines"
headers = {
"accept": "application/json",
"Authorization": f"Bearer {API_KEY}"
}
class APIConfig:
"""Configuration class for API parameters.
Attributes:
base_url: Base URL for the API endpoint
limit: Number of records per request
headers: HTTP headers for API requests
data_key: Key in API response containing the data array
"""
base_url: str = BASE_URL
limit: int = 10000
headers: Dict = headers
add_fields: bool = False # Turbine API doesn't support fields
data_key: str = DATA_KEY
def get_url(self, offset=0):
url = f"{self.base_url}?limit={self.limit}&offset={offset}"
if self.add_fields:
url = f"{url}&fields=all"
return url
def fetch_data(config: APIConfig) -> pd.DataFrame:
"""Fetch data from API using pagination.
Args:
config: APIConfig object containing API configuration
Returns:
DataFrame containing all fetched data
"""
data_list = []
offset = 0
# Fetch data in chunks using pagination
while True:
url = config.get_url(offset)
response = requests.get(url, headers=config.headers)
response.raise_for_status()
data_dict = response.json()
batch = data_dict[config.data_key]
if len(batch) == 0:
break # No more data available
data_list.extend(batch)
offset += config.limit # Increment offset for next request
print(f"Fetched {len(data_list)} items")
return pd.DataFrame(data_list)
def create_qgis_fields(df: pd.DataFrame) -> List[QgsField]:
"""Create QGIS fields based on DataFrame columns.
Args:
df: Input DataFrame with project data
Returns:
List of QgsField objects
"""
# Create longitude and latitude fields
fields = [
QgsField('Longitude', QVariant.Double),
QgsField('Latitude', QVariant.Double)
]
# Add other fields from the DataFrame
for column in df.columns:
if column != 'point':
field_type = QVariant.String if df[column].dtype == 'O' else QVariant.Double
fields.append(QgsField(column, field_type))
return fields
def create_feature(row: pd.Series) -> QgsFeature:
"""Create QGIS feature from DataFrame row.
Args:
row: Series containing single turbine data row
Returns:
QgsFeature object with geometry and attributes
"""
feature = QgsFeature()
# Extract longitude and latitude from the 'point' column
longitude, latitude = row['point']
# Set geometry and attributes
point = QgsPointXY(longitude, latitude)
feature.setGeometry(QgsGeometry.fromPointXY(point))
# Prepare attribute values
attributes = [longitude, latitude]
for column in df.columns:
if column != 'point':
value = row[column]
if isinstance(value, list): # Handle arrays of numbers
value = ','.join(map(str, value)) # Convert to comma-separated string
attributes.append(value)
feature.setAttributes(attributes)
return feature
def create_vector_layer(df: pd.DataFrame) -> QgsVectorLayer:
"""Create a QgsVectorLayer from a DataFrame.
Args:
df: Input DataFrame with turbine data
Returns:
QgsVectorLayer object for QGIS map display
"""
# Create qgis layer
layer = QgsVectorLayer("Point?crs=epsg:4326", "Wind Turbine Database", "memory")
provider = layer.dataProvider()
# Add fields to layer
fields = create_qgis_fields(df)
provider.addAttributes(fields)
layer.updateFields()
# Add features to layer
features = [create_feature(row) for _, row in df.iterrows()]
provider.addFeatures(features)
layer.updateExtents()
print(f"Created layer with {len(features)} features")
return layer
config = APIConfig()
df = fetch_data(config)
layer = create_vector_layer(df)
# Add the layer to the map
QgsProject.instance().addMapLayer(layer)
Troubleshooting
❓ Check the Troubleshooting guide for some solutions to common problems.