# Carto Trade Area Analysis

> Builds trade area and catchment analysis workflows in CARTO. Triggers when the user mentions trade area, catchment area, isochrone, site selection, where to open, best location, billboard, OOH, audience targeting, drive time, walk time, coverage area, commercial hotspot, site scoring, location ranking, or wants to generate isochrones, score candidate locations, or identify the best sites for retail, advertising, or services.

- Skill: `cartodb/carto-trade-area-analysis` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add cartodb/carto-trade-area-analysis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cartodb/carto-trade-area-analysis/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: cartodb (https://skillmd.com/u/cartodb)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cartodb/carto-trade-area-analysis

---


# Trade Area Analysis

Builds CARTO Workflows that define catchment areas around candidate locations, enrich them with data, and score/rank locations for site selection, billboard placement, or coverage analysis.

**Prerequisites**: Load `carto-create-workflow` for the development process, JSON structure, and validation. It covers both access paths — the MCP server's workflow tools (`create_workflow`, `validate_workflow`, `run_workflow`) when attached, the `carto workflows` CLI otherwise. Routing signals: `carto-basics/references/access-paths.md`.

**Ad-hoc catchments**: for a one-off isochrone around a handful of points, use the `calculate_isolines` MCP tool (available over OAuth, or on a token session whose Allowed APIs include **LDS**) — the workflow patterns below are for table-scale, repeatable analysis.

---

## Instructions

A trade area workflow always follows this pipeline:

```
Candidate Locations → (Filter) → Catchment Areas → Spatial Index → Enrich → Aggregate → Score/Rank → Save
```

### Step 1: Load Candidate Locations

Use `native.gettablebyname` to load stores, billboards, POIs, or any point dataset representing candidate locations.

**Success**: Node outputs a table with a geometry column (e.g. `geom`) and a unique location identifier.

### Step 2: Filter/Limit Candidates (optional)

Use `native.wheresimplified` to narrow candidates (e.g. specific city, category). Use `native.orderby` + `native.limit` for top-N by a metric (e.g. top 50 by revenue).

**Success**: Output contains only the candidate subset relevant to the analysis. Reducing candidates early prevents combinatorial explosion downstream.

### Step 3: Generate Catchment Areas

Choose the catchment method based on the use case:

| Method | Component | When to use | Key params |
|--------|-----------|-------------|------------|
| Isochrones | `native.isolines` | Site selection, retail — realistic road-network areas | `mode`: car/walk; `type`: time/distance; `range`: seconds or meters |
| Buffers | `native.buffer` | Billboard coverage, proximity — simple geometric circles | `distance`: meters |

**Isochrones vs Buffers**: Isochrones follow the road network and produce realistic catchment shapes but require a CARTO LDS API call. Buffers are purely geometric circles — simpler and faster, suitable for proximity analysis or billboard coverage.

**Success**: Each candidate location has an associated polygon representing its catchment area.

### Step 4: Convert Catchment to Spatial Index

Use `native.h3polyfill` or `native.quadbinpolyfill` to tessellate catchment polygons into grid cells. The resolution must match the enrichment dataset you plan to use.

**Success**: Output contains one row per grid cell per location, with both the spatial index column and the location identifier preserved.

### Step 5: Enrich with Data

Join grid cells with a spatial features dataset or use `native.h3enrich`:
- **JOIN approach**: `native.joinv2` or `native.spatialjoin` with a pre-indexed enrichment table
- **Enrich approach**: `native.h3enrich` to pull variables from CARTO's Data Observatory

**Success**: Each grid cell row has enrichment variables (e.g. population, income, foot traffic) attached.

### Step 6: Aggregate Back to Locations

Use `native.groupby` to collapse grid-cell rows back to one row per location:
- **Group by**: the location identifier column
- **Aggregation**: `population,sum,income,avg` (comma-separated column,method pairs)

**Success**: Output has one row per candidate location with aggregated enrichment metrics.

### Step 7: Score and Rank

Three-part pattern (see `carto-composite-scoring` for the full method): **normalize** each variable to [0,1] with `native.normalize`; **composite score** via `native.selectexpression`, e.g. `normalized_population * 0.4 + normalized_income * 0.3 + normalized_traffic * 0.3`; **rank** with `native.orderby` (descending) + `native.limit` (top N).

**Success**: A ranked list of candidate locations with a composite score and the contributing normalized variables.

### Step 8: Save

Use `native.saveastable` to persist the ranked results.

**Success**: Validated workflow that can be uploaded via `create_workflow` (MCP) or `carto workflows create` (CLI).

---

## Gotchas

- **Provider casing & SQL dialect.** This skill uses lowercase column names (`geom`, `population`, `income`, `normalized_population`, etc.) — BigQuery / Databricks / Postgres / Redshift convention. On Snowflake, unquoted identifiers surface UPPERCASE — reference them as `GEOM`, `POPULATION`, `INCOME`, `NORMALIZED_POPULATION`. See `carto-create-workflow/references/providers/<provider>.md` for casing rules and SQL dialect equivalents.
- Isochrone and route components call CARTO's LDS API — they require a valid connection with API access enabled. Buffers do not.
- Cross Join for distance matrices can explode with many locations x many grid cells. Filter and limit candidates first (Step 2) to keep the pipeline manageable.
- H3 resolution must match the enrichment dataset resolution. Check the enrichment table's index column before choosing resolution.
- Buffer distance is in **meters**. Isoline range is in **seconds** (for `type=time`) or **meters** (for `type=distance`).
- The `native.commercialhotspots` component expects `variablecolumns` as a Python-style list string (`['col1', 'col2']`) and `weights` as comma-separated values — inconsistent with other components.
- When aggregating enrichment back to locations (Step 6), ensure the GROUP BY uses the **location identifier**, not the grid cell index. Grouping by the grid cell produces per-cell results instead of per-location.
- When using `native.h3distance` for competitor proximity, the output is grid-based. Join it back to the location table to get per-location distance metrics.

---

## Reference Templates

These files are working examples in this skill directory:

| File | Description |
|------|-------------|
| `isochrones_from_points.json` | Retail stores in Boston — 5-min walk-time isochrones via LDS API. `native.isolines` is on v2 (lowercase wire-value `mode` list, `customoptions` / `traveltime_*` inputs). |
| `identify_best_billboards.json` | Billboard site scoring — buffer, enrich, normalize, weighted composite score, top-N |
| `commercial_hotspots.json` | Commercial hotspot detection — H3 distance to competitors, weighted hotspot analysis |

**Refreshing component versions.** Templates pin each node's `data.version` to what was current when harvested. When a native component bumps versions (e.g. `native.isolines` v1 → v2 added transport modes plus `customoptions` and `traveltime_*` inputs), the older template still parses but flags `verify-remote` warnings and misses new inputs. To refresh, read the live schema — `read_workflow_components` (MCP) or `carto workflows components get <component> --connection <conn> --json` (CLI) — then update the node's `data.version` and `inputs` in lockstep. Cross-reference `Selection` values against `carto-create-workflow/SKILL.md`: `options` may show display labels, but wire values are the lowercase / snake_case forms.

---

## Common Variations

| Variant | How |
|---------|-----|
| Retail site selection | Isochrones (walk/drive) -> H3 polyfill -> enrich with demographics -> score by population + income |
| Billboard/OOH placement | Buffers -> H3 polyfill -> enrich with audience/traffic -> normalize + weighted score -> top-N |
| Commercial hotspot detection | H3 grid -> `native.h3distance` for competitor proximity -> `native.commercialhotspots` with weights and p-value threshold |
| Drive-time coverage analysis | Isochrones (car, multiple ranges) -> union -> dissolve to find total coverage area |
| Walk-time catchment comparison | Isochrones (walk, 5/10/15 min) -> enrich each band -> compare population captured per band |
| Franchise territory planning | Isochrones per candidate -> check overlap -> filter non-overlapping set -> score remaining |

