HOCO AUTO-HOCO External Developer Demo

This page demonstrates how an external developer can discover public completed AUTO-HOCO runs, extract the AUTO-HOCO ID, build the Firebase Storage metadata URL, fetch the GeoJSON, and display the GeoJSON on a map.

Step-by-step developer workflow

1
Connect to the HOCO Firebase project using the Firebase Web SDK.
2
Query Firestore collection: hoco_requests
3
Filter only public completed AUTO-HOCO runs: public == true status == "completed" type == "automated"
4
Read the Firestore document ID. The document ID is the AUTO-HOCO run ID: AUTO-1782803122360-England-Day1-2
5
Parse the AUTO-HOCO run ID: AUTO = automated run 1782803122360 = autoCode England = region Day1-2 = forecastDay
6
Build the Firebase Storage metadata URL: https://firebasestorage.googleapis.com/v0/b/hoco-3b23e.firebasestorage.app/o/forecasts%2F{runId}.geojson
7
Fetch metadata, read downloadTokens, build the final GeoJSON URL, then display the GeoJSON on a map.
External developers should not guess AUTO-HOCO numeric codes. They should discover available runs from Firestore first.

Firebase Web config

Copy this config into a Firebase Web SDK app to connect to the HOCO project. Access is controlled by Firestore and Storage rules.

const firebaseConfig = {
  apiKey: "AIzaSyA8IYTApDl8vhZENPJXIw48rR_SUhfFPVQ",
  authDomain: "hoco-3b23e.firebaseapp.com",
  projectId: "hoco-3b23e",
  storageBucket: "hoco-3b23e.firebasestorage.app",
  messagingSenderId: "409513272369",
  appId: "1:409513272369:web:a857d9b039d53710f8a110"
};

Available HOCO regions

Active regions can be used in the region filter. Inactive regions are listed for future compatibility.

const HOCO_REGIONS = [
  { id: 'England', name: 'England', status: 'active', west: -10.234, south: 48.788, east: 5.471, north: 56.578 },
  { id: 'Scotland', name: 'Scotland (Beta)', status: 'active', west: -9.4482, south: 55.9161, east: 0, north: 60 },
  { id: 'France', name: 'Coming Soon', status: 'inactive', west: -7.9761, south: 42.1448, east: 12.0850, north: 53.2849 },
  { id: 'Spain', name: 'Coming Soon', status: 'inactive', west: -9.5, south: 36.0, east: 4.5, north: 43.8 },
  { id: 'Germany', name: 'Germany (Beta)', status: 'active', west: 0.9558, south: 46.9809, east: 16.9409, north: 55.8567 },
  { id: 'Poland', name: 'Poland (Beta)', status: 'active', west: 11.7142, south: 47.9787, east: 27.1252, north: 55.4243 },
  { id: 'Baltics', name: 'Baltics (Beta)', status: 'active', west: 11.6949, south: 52.7695, east: 36.4801, north: 63.4481 }
];

JavaScript example code external developers can follow

This compact example discovers AUTO-HOCO runs from Firestore and builds Firebase Storage metadata URLs.

<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import {
  getFirestore,
  collection,
  query,
  where,
  orderBy,
  limit,
  getDocs
} from "https://www.gstatic.com/firebasejs/10.8.0/firebase-firestore.js";

const firebaseConfig = {
  apiKey: "AIzaSyA8IYTApDl8vhZENPJXIw48rR_SUhfFPVQ",
  authDomain: "hoco-3b23e.firebaseapp.com",
  projectId: "hoco-3b23e",
  storageBucket: "hoco-3b23e.firebasestorage.app",
  messagingSenderId: "409513272369",
  appId: "1:409513272369:web:a857d9b039d53710f8a110"
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);

function parseAutoHocoRunId(runId) {
  const match = String(runId).match(/^AUTO-(\d+)-(.+)-(Day\d+-\d+)$/);
  return {
    autoCode: match?.[1] || null,
    region: match?.[2] || null,
    forecastDay: match?.[3] || null
  };
}

function buildMetadataUrl(runId) {
  const bucket = "hoco-3b23e.firebasestorage.app";
  const objectPath = `forecasts/${runId}.geojson`;
  return `https://firebasestorage.googleapis.com/v0/b/${bucket}/o/${encodeURIComponent(objectPath)}`;
}

async function fetchAutoHocoRuns() {
  const q = query(
    collection(db, "hoco_requests"),
    where("public", "==", true),
    where("status", "==", "completed"),
    where("type", "==", "automated"),
    orderBy("completedAt", "desc"),
    limit(20)
  );

  const snapshot = await getDocs(q);

  return snapshot.docs.map((docSnap) => {
    const runId = docSnap.id;
    const parsed = parseAutoHocoRunId(runId);

    return {
      runId,
      ...parsed,
      metadataUrl: buildMetadataUrl(runId),
      ...docSnap.data()
    };
  });
}

const runs = await fetchAutoHocoRuns();
console.log(runs);
</script>

Python example code external developers can follow

This Python example uses the Firestore REST API to discover AUTO-HOCO runs, then reads Firebase Storage metadata and downloads the final GeoJSON.

Install dependency first: pip install requests
import re
import json
import requests
from urllib.parse import quote

API_KEY = "AIzaSyA8IYTApDl8vhZENPJXIw48rR_SUhfFPVQ"
PROJECT_ID = "hoco-3b23e"
DATABASE_ID = "(default)"
STORAGE_BUCKET = "hoco-3b23e.firebasestorage.app"

HOCO_REGIONS = [
    { "id": "England", "name": "England", "status": "active", "west": -10.234, "south": 48.788, "east": 5.471, "north": 56.578 },
    { "id": "Scotland", "name": "Scotland (Beta)", "status": "active", "west": -9.4482, "south": 55.9161, "east": 0, "north": 60 },
    { "id": "France", "name": "Coming Soon", "status": "inactive", "west": -7.9761, "south": 42.1448, "east": 12.0850, "north": 53.2849 },
    { "id": "Spain", "name": "Coming Soon", "status": "inactive", "west": -9.5, "south": 36.0, "east": 4.5, "north": 43.8 },
    { "id": "Germany", "name": "Germany (Beta)", "status": "active", "west": 0.9558, "south": 46.9809, "east": 16.9409, "north": 55.8567 },
    { "id": "Poland", "name": "Poland (Beta)", "status": "active", "west": 11.7142, "south": 47.9787, "east": 27.1252, "north": 55.4243 },
    { "id": "Baltics", "name": "Baltics (Beta)", "status": "active", "west": 11.6949, "south": 52.7695, "east": 36.4801, "north": 63.4481 }
]

def parse_auto_hoco_run_id(run_id):
    match = re.match(r"^AUTO-(\d+)-(.+)-(Day\d+-\d+)$", run_id)

    if not match:
        return {
            "autoCode": None,
            "regionFromId": None,
            "forecastDay": None
        }

    return {
        "autoCode": match.group(1),
        "regionFromId": match.group(2),
        "forecastDay": match.group(3)
    }

def firestore_value_to_python(value):
    if "stringValue" in value:
        return value["stringValue"]

    if "booleanValue" in value:
        return value["booleanValue"]

    if "integerValue" in value:
        return int(value["integerValue"])

    if "doubleValue" in value:
        return float(value["doubleValue"])

    if "timestampValue" in value:
        return value["timestampValue"]

    if "arrayValue" in value:
        return [
            firestore_value_to_python(item)
            for item in value.get("arrayValue", {}).get("values", [])
        ]

    if "mapValue" in value:
        return {
            key: firestore_value_to_python(item)
            for key, item in value.get("mapValue", {}).get("fields", {}).items()
        }

    return None

def firestore_doc_to_python(document):
    fields = document.get("fields", {})

    data = {
        key: firestore_value_to_python(value)
        for key, value in fields.items()
    }

    document_path = document.get("name", "")
    run_id = document_path.split("/")[-1]

    data["runId"] = data.get("id") or run_id

    return data

def build_storage_metadata_url(run_id):
    object_path = f"forecasts/{run_id}.geojson"
    encoded_path = quote(object_path, safe="")
    return f"https://firebasestorage.googleapis.com/v0/b/{STORAGE_BUCKET}/o/{encoded_path}"

def build_geojson_download_url_from_metadata(metadata):
    bucket = metadata["bucket"]
    name = metadata["name"]

    token = metadata.get("downloadTokens", "").split(",")[0]
    encoded_name = quote(name, safe="")

    return f"https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{encoded_name}?alt=media&token={token}"

def fetch_storage_metadata(metadata_url):
    response = requests.get(metadata_url)
    response.raise_for_status()
    return response.json()

def fetch_geojson(download_url):
    response = requests.get(download_url)
    response.raise_for_status()
    return response.json()

def fetch_auto_hoco_catalogue(region=None, forecast_day=None, limit_count=20):
    url = (
        f"https://firestore.googleapis.com/v1/projects/{PROJECT_ID}"
        f"/databases/{DATABASE_ID}/documents:runQuery?key={API_KEY}"
    )

    filters = [
        {
            "fieldFilter": {
                "field": { "fieldPath": "public" },
                "op": "EQUAL",
                "value": { "booleanValue": True }
            }
        },
        {
            "fieldFilter": {
                "field": { "fieldPath": "status" },
                "op": "EQUAL",
                "value": { "stringValue": "completed" }
            }
        },
        {
            "fieldFilter": {
                "field": { "fieldPath": "type" },
                "op": "EQUAL",
                "value": { "stringValue": "automated" }
            }
        }
    ]

    if region:
        filters.append({
            "fieldFilter": {
                "field": { "fieldPath": "region" },
                "op": "EQUAL",
                "value": { "stringValue": region }
            }
        })

    body = {
        "structuredQuery": {
            "from": [
                { "collectionId": "hoco_requests" }
            ],
            "where": {
                "compositeFilter": {
                    "op": "AND",
                    "filters": filters
                }
            },
            "orderBy": [
                {
                    "field": { "fieldPath": "completedAt" },
                    "direction": "DESCENDING"
                }
            ],
            "limit": limit_count
        }
    }

    response = requests.post(url, json=body)
    response.raise_for_status()

    raw_results = response.json()
    runs = []

    for item in raw_results:
        document = item.get("document")

        if not document:
            continue

        run = firestore_doc_to_python(document)
        run_id = run["runId"]
        parsed = parse_auto_hoco_run_id(run_id)

        catalogue_item = {
            "runId": run_id,
            "autoCode": parsed["autoCode"],
            "region": run.get("region") or parsed["regionFromId"],
            "forecastDay": parsed["forecastDay"],
            "weather": run.get("weather"),
            "startTime": run.get("startTime"),
            "endTime": run.get("endTime"),
            "createdAt": run.get("createdAt"),
            "completedAt": run.get("completedAt"),
            "metadataUrl": build_storage_metadata_url(run_id),
            "existingGeojsonUrl": run.get("geojsonUrl")
        }

        if forecast_day and catalogue_item["forecastDay"] != forecast_day:
            continue

        runs.append(catalogue_item)

    return runs

if __name__ == "__main__":
    runs = fetch_auto_hoco_catalogue(
        region="England",
        forecast_day=None,
        limit_count=10
    )

    print(f"Found {len(runs)} AUTO-HOCO runs")

    for run in runs:
        print(json.dumps(run, indent=2))

    if not runs:
        raise SystemExit("No AUTO-HOCO runs found.")

    selected_run = runs[0]

    print("\nSelected run:")
    print(selected_run["runId"])

    metadata = fetch_storage_metadata(selected_run["metadataUrl"])

    print("\nStorage metadata:")
    print(json.dumps(metadata, indent=2))

    download_url = build_geojson_download_url_from_metadata(metadata)

    print("\nGeoJSON download URL:")
    print(download_url)

    geojson = fetch_geojson(download_url)

    feature_count = len(geojson.get("features", []))
    print(f"\nGeoJSON loaded. Feature count: {feature_count}")

    output_file = f"{selected_run['runId']}.geojson"

    with open(output_file, "w", encoding="utf-8") as f:
        json.dump(geojson, f)

    print(f"Saved GeoJSON to {output_file}")

Fetch AUTO-HOCO catalogue

Waiting.

Results

Working GeoJSON map example

Click “Fetch metadata and display GeoJSON on map” on any result. The GeoJSON polygons will be drawn below.

Map waiting for AUTO-HOCO GeoJSON.

Once the GeoJSON is fetched, use a map library such as Leaflet and add the GeoJSON as a layer.

const layer = L.geoJSON(geojson, {
  style: function(feature) {
    const risk = Number(
      feature.properties?.riskValue ??
      feature.properties?.peak_risk ??
      feature.properties?.risk ??
      0
    );

    return {
      color: getRiskColor(risk),
      weight: 2,
      fillColor: getRiskColor(risk),
      fillOpacity: 0.35
    };
  },
  onEachFeature: function(feature, layer) {
    layer.bindPopup(JSON.stringify(feature.properties, null, 2));
  }
}).addTo(map);

map.fitBounds(layer.getBounds());