Import RenewMap project data to a QGIS project.

📘

Check the API docs

https://renewmap.readme.io/reference/list-projects

You will need an API key to get started.

Check the Getting Started page for instructions.

In QGIS, you can run a Python script to fetch RenewMap API data into your project.

  1. Select Plugins > Python Console and then select 📝 Show editor
  2. Create a new blank script using the ➕ icon.
  3. Paste the code below into the blank script, replacing 'YOUR_API_KEY' with your actual API key.
  4. Run the script with the ▶️ icon.
  5. The latest RenewMap data will appear in your project:
    1. 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.

import pandas as pd
import requests
from qgis.core import QgsVectorLayer, QgsField, QgsFeature, QgsGeometry, QgsPointXY, QgsProject
from PyQt5.QtCore import QVariant
from typing import List, Dict
from dataclasses import dataclass

API_KEY = "YOUR_API_KEY" # Replace with your actual API key
BASE_URL = "https://api.renewmap.com.au/api/v1/projects"
DATA_KEY = "projects"

@dataclass
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
    """
    base_url: str = BASE_URL
    limit: int = 1000
    headers: Dict = None

    def __post_init__(self):
        """Initialize default headers if none provided"""
        if self.headers is None:
            self.headers = {
                "accept": "application/json",
                "Authorization": f"Bearer {API_KEY}"
            }

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
    """
    dfs = []
    offset=0
    
    # Fetch data in chunks using pagination
    while True:
        url = f"{config.base_url}?limit={config.limit}&offset={offset}"
        response = requests.get(url, headers=config.headers)
        response.raise_for_status()
        data_dict = response.json()
        df = pd.DataFrame(data_dict[DATA_KEY])

        if df.empty:
            break # No more data available
        
        dfs.append(df)
        offset += config.limit # Increment offset for next request
    
    return pd.concat(dfs, ignore_index=True)


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 = [create_feature(row) for _, row in df.iterrows()]
    provider.addFeatures(features)
    layer.updateExtents()

    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. This data is returned in json format and needs to be unpacked in the script.

import pandas as pd
import requests

from typing import List, Dict
from dataclasses import dataclass

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 = True # Your account needs to have the fields API enabled

@dataclass
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
    """
    base_url: str = BASE_URL
    limit: int = 1000
    headers: Dict = None

    def __post_init__(self):
        """Initialize default headers if none provided"""
        if self.headers is None:
            self.headers = {
                "accept": "application/json",
                "Authorization": f"Bearer {API_KEY}"
            }

def fetch_data(config: APIConfig, add_fields=ADD_FIELDS) -> 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 = f"{config.base_url}?limit={config.limit}&offset={offset}"
        if add_fields:
            url = f"{url}&fields=all"
        response = requests.get(url, headers=config.headers)
        response.raise_for_status()
        data_dict = response.json()
        batch = data_dict['projects']
        # df = pd.DataFrame(data_dict[DATA_KEY])
        if len(batch) == 0:
            break # No more data available
        
        projects_data.extend(batch)
        offset += config.limit # Increment offset for next request
    
    if add_fields:
        projects_data = [flatten_fields(p) for p in projects_data]

    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
            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()
    return layer

config = APIConfig()
df = fetch_data(config)
layer = create_vector_layer(df)
QgsProject.instance().addMapLayer(layer)