Retail · Insurance · Published 2026-05-24

How many people live within 15 minutes of this address?

A retail siting team picks a candidate location. Before the next meeting they need: the drive-time trade area, the total population inside it, and the median household income. geo_isochrone + population_in_polygon answers in two calls.

Step 1 — Drive-time polygon

mcp.call("geo_isochrone", {
  point: [-77.9111, 34.1659],   // 
  minutes: [15],                 // single ring
  profile: "driving",
})
  // → GeoJSON polygon — the reachable 15-min driving footprint

Step 2 — Aggregate Census variables inside the polygon

One call, area-weighted from Census tracts. The system cache covers ~20 headline ACS variables (population, income, age, race breakdowns, education, housing). Variables in the cache cost nothing extra; variables outside it fall back to the Census API automatically.

mcp.call("population_in_polygon", {
  geometry: isochrone.geometry,
  variables: [
    "B01001_001E",   // total population
    "B19013_001E",   // median household income
    "B01002_001E",   // median age
    "B15003_022E",   // bachelor's degree+
    "B25077_001E",   // median home value
  ],
  year: 2023,
})
  // → {
  //     variables: {
  //       B01001_001E: ,
  //       B19013_001E: ,
  //       B01002_001E: ,
  //       B15003_022E: ,
  //       B25077_001E: ,
  //     },
  //     intersecting_tract_count: ,
  //     tracts_with_data: ,
  //     methodology: "Area-weighted interpolation from us-tracts/tiger-2024.
  //                   For each tract T that intersects the polygon,
  //                   w = area(T ∩ polygon) / area(T).
  //                   Extensive (counts): Σ tract.value × w.
  //                   Intensive (rates/medians): Σ tract.value × w / Σ w.
  //                   Variables sourced from system cache: .
  //                   Variables sourced from live Census API: ."
  //   }

That's the answer. One call returns the demand-side variables you need to size the trade area. Total elapsed: under a second when the system Census cache is warm; a small Census API hit otherwise. The methodology string is suitable for the report's citation block — verbatim provenance for the joined numbers.

Step 3 — Optional render for the deck

// Use the isochrone polygon as a one-off shape, fill with the population value.
mcp.call("ingest_geojson", {
  url: null, inline: isochrone,
  collection: "candidate-site-trade-area", version: "v1",
})

mcp.call("render_map", {
  collection: "candidate-site-trade-area", version: "v1",
  field: "B01001_001E", palette: "sequential",
  title: "15-minute drive-time trade area",
  subtitle: "Aggregated from intersecting Census tracts, area-weighted",
  pin_overlays: [{
    name: "Site",
    color: "#dc2626",
    points: [{ lng: -77.9111, lat: 34.1659, label: "" }],
  }],
  boundary_overlays: [{
    collection: "us-counties", version: "tiger-2024",
    stroke: "#475569", stroke_width: 1.0,
  }],
})

What this used to take

The traditional answer: download isochrones from Mapbox or OpenRouteService, manually export to GeoJSON, ingest into PostGIS, find intersecting tracts via QGIS, pull ACS values per tract from data.census.gov, compute area-weighted aggregates in a spreadsheet. Half a day per site. Then re-do it when the boss asks for a 20-minute ring, or the next site, or median home value too. One call now replaces all of it. The system cache means even at scale (a chain evaluating 50 candidate sites) the Census API doesn't get hit at all.

Try it yourself

Free tier — 50 calls / month, no card.

Get an API key

Tools used: geo_isochrone, population_in_polygon, ingest_geojson, render_map (with pin_overlays + boundary_overlays). population_in_polygon is premium-gated. geo_isochrone requires a Mapbox or OpenRouteService key in the workspace's secrets.