Skip to content
How-to10 min read

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.

Fields in a successful metadata response
FieldWhat it gives youHow we use it
statusWhether a panorama was found, and if not, whyThe coverage flag itself; everything else is conditional on it
locationLatitude and longitude of the panorama that was matchedSnap distance: how far the returned panorama sits from the point you asked about
dateCapture month, typically as YYYY-MMImagery age, seasonality control and vintage consistency across the sample
pano_idAn identifier for the matched panoramaDeduplication within a run, and churn detection between runs
copyrightAttribution string for the panoramaSeparates 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

Statuses and what they actually mean for an audit
StatusInterpretationCorrect handling
OKA panorama was found within the radiusRecord it, and record the snap distance
ZERO_RESULTSNo panorama near the requested locationA genuine coverage gap; count it as one
NOT_FOUNDThe location or panorama id could not be resolvedCheck the input; do not silently merge with genuine gaps
OVER_QUERY_LIMITRate or usage limits were hitBack off and retry; never count as a coverage gap
REQUEST_DENIEDThe request was not authorisedA configuration problem, not a data finding; stop the run
INVALID_REQUESTRequired parameters were missing or malformedFix 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.
360 degree street-level panorama of Fifth Avenue in New York, showing buildings, traffic and pedestrians
The panorama behind one OK status. The audit does not fetch this; it establishes that it exists, where it sits and when it was captured, before any image request is billed.

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.

Street-level panorama of a sampled point captured in April 2026
One capture month for a single point. The metadata date is what lets you group points by vintage before analysis rather than discovering the mix afterwards.

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

  1. State the sampling interval, radius and source parameter at the top. Without them the coverage rate is a number with no definition behind it.
  2. Break coverage down by district and road class, since the gaps are almost never evenly distributed.
  3. Show the age distribution as a histogram of capture months, not as a single average.
  4. Flag the comparison units that fail your freshness or vintage-consistency thresholds, and price the fallback for them separately.
  5. 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.

More from the blog

Keep reading

Next step

Need this done rather than explained?

If the article describes a problem you are facing, tell us the specifics and we will scope it with a fixed price.