(token: string, projectId: string, coords: [number, number][])
| 210 | // ── Land Use (ESA WorldCover) ──────────────────────────────────── |
| 211 | |
| 212 | async function computeLandUse(token: string, projectId: string, coords: [number, number][]) { |
| 213 | const geometry = makeGeometry(coords); |
| 214 | |
| 215 | // Load ESA WorldCover v200 |
| 216 | const collection = { |
| 217 | functionInvocationValue: { |
| 218 | functionName: "ImageCollection.load", |
| 219 | arguments: { id: { constantValue: "ESA/WorldCover/v200" } }, |
| 220 | }, |
| 221 | }; |
| 222 | const image = { |
| 223 | functionInvocationValue: { |
| 224 | functionName: "ImageCollection.mosaic", |
| 225 | arguments: { collection }, |
| 226 | }, |
| 227 | }; |
| 228 | const clipped = { |
| 229 | functionInvocationValue: { |
| 230 | functionName: "Image.clip", |
| 231 | arguments: { input: image, geometry }, |
| 232 | }, |
| 233 | }; |
| 234 | |
| 235 | // Use frequencyHistogram reducer to get pixel counts per class |
| 236 | const histResult = await computeValue(token, projectId, reduceRegion(clipped, geometry, "Reducer.frequencyHistogram", 10)); |
| 237 | console.log("Land use histogram:", JSON.stringify(histResult)); |
| 238 | |
| 239 | // ESA WorldCover class codes |
| 240 | const classNames: Record<string, string> = { |
| 241 | "10": "Tree cover", |
| 242 | "20": "Shrubland", |
| 243 | "30": "Grassland", |
| 244 | "40": "Cropland", |
| 245 | "50": "Built-up", |
| 246 | "60": "Bare/sparse", |
| 247 | "70": "Snow/ice", |
| 248 | "80": "Water", |
| 249 | "90": "Wetland", |
| 250 | "95": "Mangroves", |
| 251 | "100": "Moss/lichen", |
| 252 | }; |
| 253 | |
| 254 | // The histogram is in result.Map (the band name) |
| 255 | const hist = histResult?.result?.Map || histResult?.result?.map || {}; |
| 256 | let total = 0; |
| 257 | const counts: Record<string, number> = {}; |
| 258 | for (const [classCode, count] of Object.entries(hist)) { |
| 259 | const n = Number(count); |
| 260 | total += n; |
| 261 | const name = classNames[classCode] || `Class ${classCode}`; |
| 262 | counts[name] = (counts[name] || 0) + n; |
| 263 | } |
| 264 | |
| 265 | if (total === 0) return null; |
| 266 | |
| 267 | const landUse: Record<string, number> = {}; |
| 268 | for (const [name, count] of Object.entries(counts)) { |
| 269 | landUse[name] = Math.round((count / total) * 1000) / 10; |
no test coverage detected