How to Audit Google Street View Coverage and Freshness Before You Collect
Metadata requests are free and consume no image quota. Running them first turns "is there coverage?" from an assumption into a measured number.
- Author
- HuiTu Technology
- Published
- Updated
The most expensive way to discover that a study area has patchy street-view coverage is to find out halfway through image collection. The second most expensive is to find out at analysis time, when a third of your sample points turn out to carry imagery from three different years.
There is a cheap way instead. The Street View Static API has a metadata endpoint that answers whether imagery exists at a location, where the nearest panorama actually sits and roughly when it was captured. Google documents these requests as free of charge and as not consuming image quota, which means a full coverage audit of a metro area costs request time and nothing else. Doing it first is the single highest-return hour in a street-view project.
What the metadata endpoint returns
You send the same location, radius and source parameters you would send to the image endpoint. You get back a small JSON object instead of a JPEG.
| Field | What it gives you | How we use it |
|---|---|---|
| status | Whether a panorama was found, and if not, why | The coverage flag itself; everything else is conditional on it |
| location | Latitude and longitude of the panorama that was matched | Snap distance: how far the returned panorama sits from the point you asked about |
| date | Capture month, typically as YYYY-MM | Imagery age, seasonality control and vintage consistency across the sample |
| pano_id | An identifier for the matched panorama | Deduplication within a run, and churn detection between runs |
| copyright | Attribution string for the panorama | Separates official platform capture from user-contributed panoramas |
Design the sample before you send anything
A coverage audit is a sampling exercise, and a badly designed sample produces a confident wrong answer. Three decisions do most of the work.
- Sample along the road network, not on a square grid. A grid puts points in the middle of blocks and parks, where no panorama should exist, and reports the resulting gaps as missing coverage.
- Fix one interval and record it. Twenty to fifty metres suits urban work. The interval has to be identical in every district you intend to compare, or your coverage rates are not comparable.
- Set the radius deliberately. A generous radius makes coverage look excellent by matching panoramas far from the point you asked about; a tight radius is honest but rejects legitimate matches on wide roads. We usually run 30 m and keep the snap distance so the choice can be re-examined later.
The source parameter matters too. Requesting outdoor imagery excludes interior panoramas, which otherwise contaminate a streetscape audit with shop interiors and station concourses that happen to sit near your sample point.
A minimal audit script
Nothing clever is required. Read points, probe each one, write a row per point, and keep every field the response gave you rather than reducing it to a boolean on the way in.
import csv
import os
import time
import requests
API = "https://maps.googleapis.com/maps/api/streetview/metadata"
KEY = os.environ["GOOGLE_MAPS_API_KEY"] # never hard-code a key into the script
session = requests.Session()
def probe(lat, lon, radius=30, source="outdoor"):
"""Metadata request: free of charge, and it consumes no image quota."""
params = {
"location": f"{lat},{lon}",
"radius": radius, # snap distance in metres; keep it tight
"source": source, # "outdoor" excludes indoor and interior panoramas
"key": KEY,
}
for attempt in range(4):
response = session.get(API, params=params, timeout=10)
if response.status_code == 200:
return response.json()
time.sleep(2 ** attempt) # back off, then retry
return {"status": "REQUEST_FAILED"}
with open("sample_points.csv") as source_file, \
open("coverage.csv", "w", newline="") as out_file:
writer = csv.writer(out_file)
writer.writerow(
["point_id", "status", "pano_id", "capture_month", "pano_lat", "pano_lon"]
)
for row in csv.DictReader(source_file):
meta = probe(row["lat"], row["lon"])
located = meta.get("location") or {}
writer.writerow([
row["point_id"],
meta.get("status"),
meta.get("pano_id", ""),
meta.get("date", ""), # YYYY-MM, when the platform returns it
located.get("lat", ""),
located.get("lng", ""),
])Two habits are worth carrying over from any collection pipeline: never hard-code the key, and store the raw status rather than collapsing it early. A run that recorded only "covered / not covered" cannot later distinguish a genuine coverage gap from a run that quietly hit a quota ceiling.
Read the status field carefully
| Status | Interpretation | Correct handling |
|---|---|---|
| OK | A panorama was found within the radius | Record it, and record the snap distance |
| ZERO_RESULTS | No panorama near the requested location | A genuine coverage gap; count it as one |
| NOT_FOUND | The location or panorama id could not be resolved | Check the input; do not silently merge with genuine gaps |
| OVER_QUERY_LIMIT | Rate or usage limits were hit | Back off and retry; never count as a coverage gap |
| REQUEST_DENIED | The request was not authorised | A configuration problem, not a data finding; stop the run |
| INVALID_REQUEST | Required parameters were missing or malformed | Fix the caller; these rows are not evidence about coverage |
The numbers an audit should produce
Turn the response table into a small set of figures you can put in front of whoever is funding the collection. These are the ones that change decisions.
- Coverage rate: the share of sample points returning OK, reported per district and per road class rather than as one headline number.
- Median snap distance, plus the 90th percentile. A rising tail means panoramas are being matched from adjacent streets.
- Imagery age distribution: median capture month and the share older than your freshness threshold.
- Vintage spread within each comparison unit. A district whose points span 2019 to 2026 cannot be compared cleanly against one captured entirely in 2025.
- Capture month mix, which is the seasonality control any vegetation indicator will need later.
- Unique panorama count against sample point count. A ratio far below one means your interval is finer than the panorama spacing and you are paying for duplicates.

Freshness is a project constraint, not a detail
The capture month is the field people skim past and then regret. Retail frontage turns over fast enough that four-year-old imagery misreports what is trading. Vegetation indicators computed from a leaf-off January capture are not comparable with a July one, and the difference is usually larger than the differences between the streets you are studying.

Panorama ids move; plan for it
A panorama id identifies the imagery you were served, not the place. Platforms re-shoot streets, re-process panoramas and retire old ones, so an id captured in one audit can stop resolving later. Treat it as a run-scoped value: use it to deduplicate within a run and key your own database on the sample point identifier instead. A changed id between two audits is only a review flag — confirm the capture date and returned location before calling it new imagery, because reprocessing can also change identifiers.
Report it so the decision is obvious
- State the sampling interval, radius and source parameter at the top. Without them the coverage rate is a number with no definition behind it.
- Break coverage down by district and road class, since the gaps are almost never evenly distributed.
- Show the age distribution as a histogram of capture months, not as a single average.
- Flag the comparison units that fail your freshness or vintage-consistency thresholds, and price the fallback for them separately.
- Estimate image-request volume and cost directly from the OK count, which is now a measurement rather than a guess.
The output of a good audit is often a smaller project than the one that was proposed: three districts covered properly instead of five covered unevenly. That is a better result than discovering the same thing after the invoices arrive.