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
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
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
<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
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
Map waiting for AUTO-HOCO GeoJSON.
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());