Choosing event venues by attendee travel time, not gut feel

Geocode your attendee list, run an N×M routing matrix against candidate venues, and pick by median and worst-case drive time. REST examples included.

| August 27, 2026
Choosing event venues by attendee travel time, not gut feel

Every venue shortlist ends the same way. The EA who booked last year's offsite says the city-centre hotel worked fine. The head of people-ops says half the team drove forty-five minutes in traffic and arrived stressed. Someone else nominates a ring-road conference centre with free parking. The decision goes to the most senior person in the room, who picks what feels right.

The argument is avoidable. You have a list of attendees. Each one has an address or at least a postcode. Every candidate venue has a coordinate. A routing matrix turns those two facts into a table of drive times — every attendee to every venue — and from that table you can compute median travel time, worst-case travel time, and the share of attendees who would spend more than an hour in the car. The decision becomes a comparison of numbers, not a clash of gut feels.

This post walks through the full pipeline: geocode the attendee list, run the matrix against each shortlisted venue, summarise the results, and render a map for the recommendation deck. The same logic works for choosing a new office location against employee home addresses — the math is identical, only the label changes.

Why travel time beats every other venue metric

Capacity, catering, A/V, natural light — all of those matter, but they are easy to evaluate on a site visit. Travel burden is invisible until the day of the event, and by then you cannot change the venue.

Three concrete failure modes that better data prevents.

The average-distance trap. Average distance from attendee to venue is a number, but it is not the right number. An event where ninety people drive fifteen minutes and ten people drive ninety minutes has a fine average and an unacceptable worst case. Those ten people are exhausted before the first session. Median travel time is more robust — but worst-case (90th or 95th percentile) is the metric that tells you whether you have created a structural barrier for part of your audience.

The public-transport assumption. In a city with dense rail networks, public transport is often faster than driving. In the ring-road conference centres that seem convenient to drivers, there is frequently no viable transit route. Drive-time and walk-time matrices capture the mode you care about. If your attendee population skews toward non-drivers — younger staff, international guests without a hire car — the venue that wins on drive time may lose badly on walk-from-transit time. Running both modes costs nothing extra; the API supports drive and walk.

The fairness problem. If your company has two offices — one in the city, one in a suburb — and you always hold events near one, you are systematically disadvantaging the other group. A matrix quantifies that disadvantage rather than leaving it to accumulate as quiet resentment. People-ops leaders who have started presenting travel-time tables at venue sign-off meetings report that the conversation changes character: it becomes an engineering problem, not a political one.

The pipeline in four stages

Geocode → matrix → summarise → visualise. Each stage is one or two API calls; none requires anything beyond curl or standard HTTP libraries.

Attendee home addresses are personal data

Before the pipeline, the framing. Attendee home addresses — or employee home postcodes — are personal data in every reasonable jurisdiction. The pattern that protects both you and your attendees is:

  1. Geocode the address list once, server-side, and immediately discard the raw addresses from the pipeline's working state.
  2. Retain only the coordinates (lat/lng) and an opaque row ID. No name, no address string, no email attached to a coordinate that leaves your controlled environment.
  3. After the analysis, retain only the aggregated outputs — median travel times, percentile tables, isochrone polygons — not the per-person coordinate set.
  4. Run everything server-side. Do not pass attendee home coordinates to a browser-side script.

The HIPAA-safe geocoding pattern covers the no_record flag that prevents the API from retaining query data server-side. Use it. The event planning team does not need a data-retention compliance review to cost them two weeks; a single request header prevents the problem entirely.

With that framing in place, the pipeline itself is straightforward.

Stage 1 — Geocode the attendee list

The input is a spreadsheet. The output is a list of coordinates and confidence scores. Use the WEB batch tool for a no-code path; use the REST API for a scripted path. Both consume credits proportional to the number of address rows.

The REST path, for a CSV of attendee postcodes or addresses:

# Single address — ground the pattern
curl -s "https://csv2geo.com/api/v1/geocode" \
  --data-urlencode "q=12 Exmouth Market, London EC1R 4QD" \
  --data-urlencode "no_record=1" \
  --data-urlencode "api_key=$CSV2GEO_KEY" \
  | jq '{lat: .results[0].lat, lng: .results[0].lng, confidence: .results[0].confidence}'

In Python for a list of addresses:

import csv, os, time
import requests

API = "https://csv2geo.com/api/v1/geocode"
KEY = os.environ["CSV2GEO_KEY"]

def geocode_attendees(input_path, output_path):
    with open(input_path) as fin, open(output_path, "w", newline="") as fout:
        reader = csv.DictReader(fin)
        writer = csv.DictWriter(fout, fieldnames=["row_id", "lat", "lng", "confidence"])
        writer.writeheader()
        for row in reader:
            r = requests.get(
                API,
                params={
                    "q": row["address"],
                    "no_record": "1",      # PII-safe: do not log the query
                    "api_key": KEY,
                },
                timeout=15,
            )
            r.raise_for_status()
            result = r.json()["results"]
            if not result:
                continue
            best = result[0]
            if best["confidence"] < 0.7:
                # Low confidence — flag for manual review, do not feed to matrix
                continue
            writer.writerow({
                "row_id": row["row_id"],
                "lat": best["lat"],
                "lng": best["lng"],
                "confidence": best["confidence"],
            })
            time.sleep(0.05)  # gentle pacing on a long list

geocode_attendees("attendees.csv", "attendees_coords.csv")

The no_record=1 parameter is the critical line. It tells the API not to retain the query string server-side. The raw address never touches a log you do not control. Confidence filtering at 0.7 drops the ambiguous results before they poison the matrix — a low-confidence geocode placed in the wrong city produces a travel-time result that is confidently wrong. Retain those rows separately and chase them up before the analysis, not after.

For a non-code path, the WEB batch tool accepts the same CSV and returns the same output without touching a terminal. Credit cost is the same: one credit per address row.

Stage 2 — Run the routing matrix

The routing matrix endpoint accepts N origins and M destinations and returns an N×M table of travel times. For venue selection, the origins are your attendee coordinates and the destinations are your shortlisted venue coordinates.

API shape:

POST /api/v1/matrix

Body parameters:

| Parameter | Notes | |---|---| | origins | |-separated lat,lng pairs — the attendee coordinates | | destinations | |-separated lat,lng pairs — the venue coordinates | | mode | drive or walk | | api_key | standard auth |

A worked example for 200 attendees against 3 candidate venues:

import requests, os, json

MATRIX_API = "https://csv2geo.com/api/v1/matrix"
KEY = os.environ["CSV2GEO_KEY"]

def run_matrix(origins, destinations, mode="drive"):
    """
    origins: list of (lat, lng) tuples
    destinations: list of (lat, lng) tuples
    Returns: list of lists — matrix[i][j] = travel time in seconds, attendee i to venue j
    """
    origins_str = "|".join(f"{lat},{lng}" for lat, lng in origins)
    dests_str   = "|".join(f"{lat},{lng}" for lat, lng in destinations)
    r = requests.post(
        MATRIX_API,
        data={
            "origins":      origins_str,
            "destinations": dests_str,
            "mode":         mode,
            "api_key":      KEY,
        },
        timeout=60,
    )
    r.raise_for_status()
    return r.json()["durations"]  # seconds, shape [N][M]

# Load attendee coords from stage 1 output
with open("attendees_coords.csv") as f:
    reader = csv.DictReader(f)
    attendee_coords = [(float(row["lat"]), float(row["lng"])) for row in reader]

# Shortlisted venues — you collected these during the site-visit phase
venue_coords = [
    (51.5074, -0.1278),   # Venue A: city centre hotel
    (51.4700, -0.4543),   # Venue B: ring-road conference centre
    (51.5155, -0.0922),   # Venue C: East London warehouse space
]

matrix = run_matrix(attendee_coords, venue_coords, mode="drive")

The same call in Node:

const API = 'https://csv2geo.com/api/v1/matrix';
const KEY = process.env.CSV2GEO_KEY;

async function runMatrix(origins, destinations, mode = 'drive') {
  const body = new URLSearchParams({
    origins:      origins.map(([lat, lng]) => `${lat},${lng}`).join('|'),
    destinations: destinations.map(([lat, lng]) => `${lat},${lng}`).join('|'),
    mode,
    api_key: KEY,
  });
  const r = await fetch(API, { method: 'POST', body });
  if (!r.ok) throw new Error(`matrix ${r.status}`);
  const json = await r.json();
  return json.durations;  // seconds, shape [N][M]
}

Note: for large attendee lists (several hundred or more), split the origins into batches and make multiple matrix calls rather than one enormous request. Each batch produces a partial matrix; concatenate the rows. The venue column count stays fixed across batches so the concatenation is straightforward.

Stage 3 — Summarise by venue

The matrix gives you raw seconds. Translate to minutes and compute the statistics that matter for a recommendation.

import statistics

MINUTES = 60  # seconds per minute

def summarise_venue(col_index, matrix, venue_name):
    times_min = [
        row[col_index] / MINUTES
        for row in matrix
        if row[col_index] is not None   # null = no route found
    ]
    if not times_min:
        return {"venue": venue_name, "error": "no routes"}

    times_min.sort()
    n = len(times_min)
    p90_idx = int(n * 0.90)
    over_60 = sum(1 for t in times_min if t > 60)

    return {
        "venue":         venue_name,
        "n":             n,
        "median_min":    round(statistics.median(times_min), 1),
        "p90_min":       round(times_min[p90_idx], 1),
        "pct_over_60":   round(100 * over_60 / n, 1),
        "max_min":       round(max(times_min), 1),
    }

venue_names = ["City Centre Hotel", "Ring-Road Conference Centre", "East London Warehouse"]

summaries = [
    summarise_venue(j, matrix, venue_names[j])
    for j in range(len(venue_names))
]

for s in sorted(summaries, key=lambda x: x["median_min"]):
    print(
        f"{s['venue']:35s}  "
        f"median {s['median_min']:5.1f} min  "
        f"p90 {s['p90_min']:5.1f} min  "
        f"{s['pct_over_60']:4.1f}% > 60 min"
    )

The output table is the recommendation. A venue with a lower median but a higher p90 is worth discussion — it might be convenient for most people while being punishing for a specific regional cluster. The share of attendees facing more than 60 minutes of driving is the metric that tends to land hardest in the approval conversation: "this venue means one in eight people will spend more than an hour each way" is hard to wave away.

The same summary table becomes the first slide of the venue recommendation deck. No cartographic skill required; the numbers carry the argument.

Stage 4 — Visualise with isolines and a static map

Numbers in a table convince the analytical stakeholders. A map convinces everyone else. Two more API calls produce the visual.

Isolines — a polygon showing the area reachable from a venue within a given drive time — answer the question "how many of our attendees are within 30/45/60 minutes of this venue?" visually. The isoline endpoint returns a GeoJSON polygon; you can paste it straight into any mapping tool or render it with the Static Maps endpoint.

# 60-minute drive isoline around Venue A
curl -s "https://csv2geo.com/api/v1/isoline" \
  --data-urlencode "lat=51.5074" \
  --data-urlencode "lng=-0.1278" \
  --data-urlencode "contours=30,45,60" \
  --data-urlencode "mode=drive" \
  --data-urlencode "api_key=$CSV2GEO_KEY"

Returns nested GeoJSON polygons — one per contour value. The 60-minute polygon tells you the catchment area; overlay your attendee scatter plot and count the dots inside.

Static Maps — a rendered PNG of a map at a given bounding box, with optional pins. For the recommendation deck, one static map per shortlisted venue, showing the venue pin and the attendee scatter coloured by travel-time band (green < 30 min, amber 30-60 min, red > 60 min), makes the comparison visceral. No GIS expertise needed; the endpoint accepts marker arrays and renders the image server-side.

curl -s -o "venue_a_map.png" \
  "https://csv2geo.com/api/v1/staticmap?center=51.5074,-0.1278&zoom=10&size=800x600&api_key=$CSV2GEO_KEY"

For a presentation-quality map with coloured attendee pins, the Static Maps API accepts marker definitions in the query parameters. Consult the endpoint documentation for marker syntax — the pattern is one parameter per pin or a batch marker array, depending on count.

Handling the office-location variant

The exact same pipeline answers "where should we open our next office, given where our employees live?" Replace "attendee list" with "employee home postcode list" and "candidate venues" with "candidate office sites." The matrix, the summary, and the isoline visualisation are all identical.

The PII handling is more sensitive here, because you are processing employment-context data rather than event-attendance data. The no_record=1 flag is non-negotiable. Aggregate all outputs before they leave your controlled environment — the deliverable to leadership is a summary table and maps, never a file that pairs an employee name with a coordinate. Keep the geocoded coordinates only for the duration of the analysis job; write the aggregate results to a permanent store; delete the intermediate files.

For teams that want a no-code path for the geocoding stage, the WEB batch tool accepts the address CSV and returns coordinates without any scripting. The routing matrix stage currently requires a REST call, but a non-technical planner can hand the coordinate file to an engineer for that single step, then take the summary table back to their own workflow.

How to present the results to stakeholders

The analysis produces three artefacts. Use all three.

A summary table (median, p90, share over 60 minutes, per venue) as the first slide. This is the decision-support artefact — it should stand alone without explanation. If the senior decision-maker looks at only one thing, it should be this table.

Isoline maps (one per shortlisted venue, 30/45/60-minute contours, with attendee scatter) as the second and third slides. These make the geographic fairness argument visible. A venue whose 60-minute isoline excludes a dense cluster of attendees is obviously problematic in a way that does not survive a table column.

A "what if" sensitivity slide — run the matrix for two modes (drive + walk-from-transit) and show how the rankings change. If the city-centre venue jumps dramatically in the walk-from-transit ranking, that matters for non-drivers in a way the drive-time table alone conceals.

One slide to avoid: the map that shows all attendee home locations without aggregation. It tells the audience more about where individuals live than the venue decision requires. Present the isochrone ring, the summary table, and the count-of-attendees-per-band. Leave the coordinate scatter internal.

Step-by-step implementation guide

Step 1: Prepare and clean the attendee address list

Before geocoding, run a basic consistency pass on the input data. Standardise country codes, remove rows where the address field is blank or contains placeholder text ("TBC", "remote", "N/A"), and verify that postcodes follow the expected format for each country in the list. Rows that fail these checks go into a manual-review queue, not into the geocoding job. A bad address geocoded with false confidence produces a travel-time estimate that is wrong in a way that looks right — far more damaging than a flagged null.

The output of this step is a clean CSV with columns row_id, address. The row_id is your only handle back to the attendee record and should be opaque — a hash or a sequential integer, not a name or email.

Step 2: Geocode the clean list with PII-safe parameters

Run the geocoding job as shown in Stage 1 above. Two parameters are mandatory: no_record=1 to prevent server-side query logging, and a confidence threshold to filter ambiguous results. Log the confidence score for every row. Any row with confidence below 0.7 goes to manual review before it enters the matrix.

The output of this step is attendees_coords.csv containing row_id, lat, lng, confidence. At this point the address strings are no longer needed in the pipeline. Delete them from working memory; do not write them to the coordinate file.

Step 3: Build and run the routing matrix

Collect the coordinates of your shortlisted venues — typically two to five candidates. Run a single matrix call (or a batch of calls if your attendee list is large) for drive mode. If transit accessibility matters for your audience, run a second call for walk mode. Store the raw duration matrices indexed by venue.

The latency of a large matrix call scales with the product of origin count and destination count. For a 300-attendee list against 4 venues, the matrix is 1,200 cells — a single call, returning in a few seconds. For a 2,000-employee list against 5 office sites, consider batching origins into groups of 500 to keep individual call timeouts comfortable.

Step 4: Compute the summary statistics

Run the summarise_venue function (or equivalent) for every venue column. Produce a table with at minimum: median travel time, 90th percentile travel time, percentage of attendees facing more than 60 minutes, and maximum travel time. Sort by median. Flag any venue where the p90 exceeds 90 minutes — that venue has a hard accessibility problem for a specific segment of your audience, even if the median looks acceptable.

If you are doing the office-location variant, add a "commute days per year" column: multiply the per-employee annual commute time (two-way × working days) to convert travel time into hours lost per year per employee. That number tends to focus leadership attention on the decision in a way that minutes per trip does not.

Step 5: Generate isoline maps for the shortlist

For each venue that survives the statistics review (typically the top two or three by median), run the isoline endpoint with contours at 30, 45, and 60 minutes in drive mode. Overlay the attendee scatter, coloured by band. Export the Static Maps PNG for each venue.

Write a one-paragraph plain-English interpretation of each map: "Venue B places 68% of attendees within 45 minutes, but the eastern cluster of 34 people faces 75-90 minutes each way. If that cluster represents a team that will attend quarterly, the cumulative travel burden is significant." That paragraph, next to the map, is what gets the venue decision made in a single meeting rather than three.

Step 6: Archive the aggregate outputs; delete the working data

After the recommendation is accepted and the venue is booked, archive the summary tables and the map images. Delete the intermediate files: the geocoded coordinate CSV, the raw matrix output. The aggregate is all you need for the record; the per-person coordinate set is a liability after the analysis is complete.

Document the data-handling steps in your event-planning runbook so the next person who runs the analysis inherits the right pattern without having to reconstruct it.

Pricing and scale

The free tier allows 3,000 API calls per day — enough to geocode a 3,000-attendee list, run the matrix against four candidate venues, and generate isolines, all within one day's budget, with no payment details required.

For a larger analysis — a 10,000-employee home-address file for an office-location decision — the paid tier starts at $54/month for 100,000 calls. The geocoding job (10,000 credits), the matrix runs, and the isoline calls sit comfortably within that bracket. Full pricing at csv2geo.com/pricing/api.

Credit accounting for the venue-selection pipeline:

| Step | Credits | |---|---| | Geocode 500 attendee addresses | 500 | | Drive matrix, 500×4 venues | 2,000 | | Walk matrix, 500×4 venues | 2,000 | | Isolines, 4 venues × 3 contours | 12 | | Static maps, 4 venues | 4 | | Total | 4,516 |

For repeat events — same employee population, new shortlist of venues — the geocoding step is free on cache hits for coordinates geocoded within the last 30 days. See Caching geocoding results — 90% cost reduction for the caching pattern that makes repeat analysis nearly free.

Frequently Asked Questions

How many candidate venues can I compare in a single matrix call? The matrix endpoint accepts multiple destinations in a single call, so you can compare all your shortlisted venues simultaneously. Practical limits depend on your origin count — for most event-planning use cases (under 1,000 attendees, under 10 venues) a single call handles the full analysis. For larger employee-address projects, batch the origins.

Does the matrix support public transport, not just driving? The routing matrix supports drive and walk modes. Public transport routing requires real-time schedule data and varies significantly by city — that is not a mode the matrix API currently covers. For a mixed audience of drivers and non-drivers, run both drive and walk matrices and present the comparison; the gap between the two rankings tells you which venues are transit-hostile.

What if an attendee's address geocodes to low confidence — a postcode centroid rather than a street address? A postcode centroid is often sufficient for travel-time estimation, because the error introduced by moving an origin point a few hundred metres within the same postcode is negligible at the city scale of a venue decision. The exception is rural postcodes that cover large geographic areas. Flag low-confidence results by country — rural postcodes in the UK cover different scales than urban postcodes in Japan, for example — and manually verify the outliers before feeding them to the matrix.

Should I include attendees who are flying in rather than driving? This analysis covers drive-time and walk-time from local home addresses. Attendees who are flying in have a travel burden that is dominated by flight time and hotel proximity, not by the venue's position relative to local home addresses. Model those separately — typically a manual check of whether the venue is within reasonable distance of the relevant airport — rather than mixing flying attendees into the drive-time matrix.

How do I handle attendees who have not yet confirmed attendance? Run the analysis on the confirmed attendee list, and separately run a sensitivity check using the full invited list. If the venue ranking changes substantially between the two scenarios, note it in the recommendation. In practice, confirmed and invited populations usually cluster in the same geographic pattern.

Can this pipeline inform a recurring event — for example, an annual conference with a rotating city? Yes, and it is particularly valuable for that use case. Each year, geocode the current member or employee list (it will have changed), run the matrix against candidate cities (represented by a central coordinate such as a convention centre), and compare year-over-year how the geographic distribution of your audience is shifting. Over three or four years, that trend data is worth more than any single venue decision.

Is there a no-code path for planners who are not comfortable with REST APIs? The geocoding stage is available through the WEB batch tool — upload the CSV, download the coordinate file, no scripting required. The routing matrix currently requires a REST call or a hand-off to an engineer. A one-page handoff document that describes the coordinate-file format and the three matrix parameters is usually enough for a planner to brief a developer in fifteen minutes.

Related Articles

---

*I.A. / CSV2GEO Creator*

Ready to geocode your addresses?

Use our batch geocoding tool to convert thousands of addresses to coordinates in minutes. Start with 100 free addresses.

Try Batch Geocoding Free →