Skip to content

Map Viewport Loading

This page describes how to efficiently load data into interactive map applications using the Solar Lake Enterprise API.

The API provides optimized bounding box endpoints designed specifically for viewport-based loading.


Overview

For map applications, the typical workflow is:

  1. User pans or zooms the map
  2. Frontend calculates the visible bounding box
  3. API request loads data for that bounding box
  4. Map updates with returned features

There are two levels of viewport endpoints:

  • GET endpoints → simple
  • POST endpoints → advanced (filtering, projection, exclusion, pagination)

Choosing the Right Endpoint

Use GET /bbox/... when:

  • You only need basic viewport loading
  • Small geographical extent
  • No filtering is required
  • You want a simple request format

Example:

GET /bbox/h3s?min_lon=11.5&min_lat=48.1&max_lon=11.51&max_lat=48.11&subscription_key=YOUR_KEY

Use POST /bbox/... when:

  • You need filtering
  • You want to reduce payload using options.select
  • You want incremental loading
  • You need pagination
  • You want fine-grained control

Example:

POST /bbox/buildings?subscription_key=YOUR_KEY
{
  "bbox": {
    "min_lon": 11.5,
    "min_lat": 48.1,
    "max_lon": 11.51,
    "max_lat": 48.11
  },
  "filter": {
    "pv": false,
    "roof_area": { "$gte": 80 }
  },
  "options": {
    "select": ["building_id", "roof_area", "pv", "geometry"],
    "limit": 5000
  }
}

Viewport Strategy Options

Depending on your frontend, you may choose different data layers.

1️⃣ Aggregated Layer (H3 Hexagons)

Use:

GET /bbox/h3s

or

POST /bbox/h3s

Advantages:

  • Smaller payload
  • Scales well at low zoom levels
  • Suitable for choropleth maps
  • Includes geometry + aggregate metrics

Typical fields:

  • h3_id
  • count_buildings
  • sales_opportunity_score
  • share_owned
  • geometry

Optional: Rendering Empty H3 Cells

Map clients may optionally request empty H3 cells within the viewport.

This can be useful when rendering a faint grid overlay or background shading that helps users visually understand areas without residential buildings.

Example request:

{
  "bbox": { ... },
  "include_empty_cells": "geometries"
}

Recommended visualization approach:

  • Render normal cells (items) using the chosen color scale
  • Render empty cells (empty_items) with a light neutral color or faint outlines
  • Disable hover and interaction for empty cells

This allows users to distinguish between:

  • areas with residential buildings (cells with data)
  • areas without residential buildings (empty cells)

Performance Considerations

Returning empty cells increases the response size because additional geometries must be transmitted.

For best performance:

  • enable empty cells only when needed
  • use options.select to limit returned fields
  • avoid very large bounding boxes

2️⃣ Postcode or Municipality Layer

Use:

GET /bbox/postcodes
GET /bbox/municipalities

or

POST /bbox/postcodes
POST /bbox/municipalities

Good for:

  • Administrative boundary visualization
  • Regional dashboards
  • Sales territory analysis
  • Planning of mailings

3️⃣ Building Layer (Detailed Views)

Use:

GET /bbox/buildings

or

POST /bbox/buildings

Recommended only at higher zoom levels due to payload size.

Always use projection:

{
  "options": {
    "select": ["building_id", "roof_area", "pv", "geometry"]
  }
}

Incremental Loading (Avoid Re-Downloading Data)

When users pan slightly, you do not need to reload the entire viewport.

The advanced POST endpoints support exclusion of already loaded areas:

{
  "bbox": {
    "min_lon": 11.5,
    "min_lat": 48.1,
    "max_lon": 11.51,
    "max_lat": 48.11
  },
  "exclude": {
    "exclude_bboxes": [
      {
        "min_lon": 11.505,
        "min_lat": 48.105,
        "max_lon": 11.51,
        "max_lat": 48.11
      }
    ]
  },
  "options": {
    "select": ["building_id", "geometry"]
  }
}

This allows efficient incremental viewport updates.


A typical map integration looks like this:

  1. Track current viewport bounds
  2. Debounce map move events (e.g., 200–400 ms)
  3. Cancel previous in-flight request
  4. Send new bbox request
  5. Render returned GeoJSON

Pseudo-flow:

onMapMoveEnd():
    bbox = getMapBounds()
    requestData(bbox)

Performance Recommendations

For smooth map interaction:

  • Always use options.select to minimize payload
  • Avoid extremely large bounding boxes
  • Use aggregate layers for low zoom levels
  • Switch to building layer only when zoomed in
  • Use incremental loading when possible

Understanding BBox Responses

Bounding box responses include metadata:

{
  "h3_resolution": 7,
  "cells": 120,
  "returned": 5000,
  "items": [...]
}

Where:

  • h3_resolution → resolution used for aggregation
  • cells → number of grid cells covering the bbox
  • returned → number of records returned
  • items → actual data

This metadata can be used for diagnostics or analytics but is not required for rendering.


Common Pitfalls

Large Payloads

Returning full building records for large areas may impact performance.
Use projection.

Excessive Requests

Debounce map movement to avoid flooding the API.

Geometry Handling

All geometries are GeoJSON.
Make sure your mapping library expects [lon, lat].


Summary

For interactive maps:

  • Use aggregate layers at low zoom
  • Use building layer at high zoom
  • Prefer POST endpoints for flexibility
  • Always use projection
  • Implement incremental loading for best performance