To integrate Google Maps with a Raspberry Pi for GPS tracking, you need a UART GPS module (like the Adafruit MTK3339), the gpsd daemon for NMEA parsing, and the googlemaps Python library for reverse geocoding and distance matrix APIs. This build targets the Raspberry Pi 5 (4GB variant) running Raspberry Pi OS (Bookworm 64-bit), utilizing the hardware PL011 UART for reliable 9600-baud NMEA polling without dropping packets under load.

Difficulty: Intermediate | Time: 2 Hours | Cost: ~$115 USD

Project Spec Sheet & Hardware BOM

Before firing up the soldering iron, verify you have the exact components listed below. Substituting an I2C GPS module will require a completely different polling script, and using a Pi Zero 2 W will introduce thermal throttling if you attempt to run a local Chromium kiosk alongside the tracker daemon.

ComponentExact Variant / ModelEst. Cost (2026)
Single Board ComputerRaspberry Pi 5 (4GB RAM)$60.00
GPS ModuleAdafruit Ultimate GPS Breakout v3 (MTK3339)$39.95
Power SupplyOfficial 27W USB-C PD Power Supply (5V/5A)$12.00
Storage32GB microSD (SanDisk Extreme Class 10 A2)$9.00
Wiring22 AWG solid core jumper wires (Female-to-Female)$4.00
Indicator Hardware5V SPDT Relay Module (Optocoupler isolated)$3.50

GPIO Pin Mapping Table

The Raspberry Pi 5 uses the standard 40-pin header. We are tapping the primary UART (/dev/ttyAMA0) for the GPS data stream and a standard GPIO for a geofence trigger relay.

Pi 5 Physical PinBCM GPIO / FunctionGPS / Relay Module PinWire Color
Pin 13.3V PowerGPS VINRed
Pin 6Ground (GND)GPS GND & Relay GNDBlack
Pin 8GPIO 14 (TXD)GPS RXYellow
Pin 10GPIO 15 (RXD)GPS TXGreen
Pin 12GPIO 18 (PCM_CLK)Relay IN (Signal)Blue
Bench Tip: The Adafruit Ultimate GPS has a built-in patch antenna, but if you are mounting this inside a metal vehicle chassis, you must use the u.FL connector to route an active external antenna. Without line-of-sight to the sky, the MTK3339 will fail to achieve a 3D fix, and your Python script will hang waiting for valid GPRMC sentences.

Hardware Assembly & Serial Configuration

The Raspberry Pi OS routes the system console to the serial port by default. If you do not disable this, the Linux boot logs will overwhelm the GPS module's RX line, and you will receive garbage data.

  1. Wire the GPS Module: Connect the 3.3V, GND, TX, and RX pins according to the mapping table above. Double-check that Pi TX goes to GPS RX, and Pi RX goes to GPS TX.
  2. Disable Serial Console: Open a terminal and run sudo raspi-config. Navigate to Interface Options > Serial Port. Select No for "Would you like a login shell to be accessible over serial?" and Yes for "Would you like the serial port hardware to be enabled?".
  3. Reboot and Verify: Restart the Pi. After reboot, verify the UART is live by running cat /dev/ttyAMA0. You should see raw NMEA 0183 sentences scrolling by (e.g., $GPGGA,123519,4807.038,N...). Press Ctrl+C to exit.
  4. Install gpsd: Install the GPS daemon to handle the serial buffering and socket parsing. Run sudo apt install gpsd gpsd-clients python3-gps.
  5. Configure gpsd: Edit the configuration file with sudo nano /etc/default/gpsd. Set the device to DEVICES="/dev/ttyAMA0" and ensure GPSD_OPTIONS="-n" is set so it polls immediately upon startup.
  6. Restart the Daemon: Run sudo systemctl restart gpsd and verify with cgps -s. If you see a 3D fix and satellite count, your hardware layer is solid.

Python GPS Polling & Google Maps API Integration

This script reads the gpsd socket, extracts the latitude and longitude, and passes it to the Google Maps Geocoding API to resolve a physical street address. If the coordinates cross a predefined geofence boundary, it triggers the GPIO 18 relay.

Prerequisites: Install the Google Maps Python client via pip: pip3 install googlemaps. You will need a Google Cloud Platform (GCP) API key with the Geocoding API enabled and a linked billing account.

import gps
import googlemaps
import time
import math
from gpiozero import LED
from googlemaps.exceptions import ApiError

# --- Hardware Pin Definitions ---
GEOFENCE_RELAY_PIN = 18
relay = LED(GEOFENCE_RELAY_PIN)

# --- Configuration ---
GOOGLE_API_KEY = 'YOUR_GCP_API_KEY_HERE'
# Target geofence center (e.g., your workshop)
TARGET_LAT = 37.7749
TARGET_LON = -122.4194
GEOFENCE_RADIUS_METERS = 150.0

# Initialize clients
gmaps = googlemaps.Client(key=GOOGLE_API_KEY)
session = gps.gps("localhost", "2947")
session.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)

def haversine_distance(lat1, lon1, lat2, lon2):
    """Calculate the great-circle distance between two points in meters."""
    R = 6371000  # Earth radius in meters
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    delta_phi = math.radians(lat2 - lat1)
    delta_lambda = math.radians(lon2 - lon1)
    a = math.sin(delta_phi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(delta_lambda/2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    return R * c

def get_reverse_geocode(lat, lon):
    """Fetch street address from Google Maps API with error handling."""
    try:
        result = gmaps.reverse_geocode((lat, lon))
        if result:
            return result[0]['formatted_address']
    except ApiError as e:
        print(f"[ERROR] Google Maps API rejected request: {e}")
    except Exception as e:
        print(f"[ERROR] Network or Timeout failure: {e}")
    return "Address Unresolved"

print("Tracker initialized. Waiting for 3D GPS fix...")

try:
    while True:
        report = session.next()
        if report['class'] == 'TPV':
            if hasattr(report, 'lat') and hasattr(report, 'lon'):
                current_lat = report.lat
                current_lon = report.lon
                
                # Check Geofence
                distance = haversine_distance(current_lat, current_lon, TARGET_LAT, TARGET_LON)
                if distance <= GEOFENCE_RADIUS_METERS:
                    relay.on()
                    status = "INSIDE GEOFENCE"
                else:
                    relay.off()
                    status = "OUTSIDE GEOFENCE"
                
                # Poll Google Maps every 5th fix to avoid API rate limits
                if int(time.time()) % 5 == 0:
                    address = get_reverse_geocode(current_lat, current_lon)
                    print(f"[{status}] Lat: {current_lat:.5f}, Lon: {current_lon:.5f} | {address}")
                    
        time.sleep(0.5)

except KeyboardInterrupt:
    print("\nTracker stopped by user.")
    relay.off()
    session.close()

Debugging `REQUEST_DENIED` and Serial Lockups

When integrating the Google Maps API with embedded hardware, the most common failure mode is authentication rejection. If your script outputs the following exact error string, your hardware is fine, but your GCP configuration is blocking the request:

googlemaps.exceptions.ApiError: REQUEST_DENIED (This API project is not authorized to use this API.)

Ranked Causes for REQUEST_DENIED

  1. Geocoding API Not Enabled: By default, new GCP projects do not enable the Geocoding API. Navigate to the GCP Console > APIs & Services > Library, search for "Geocoding API", and click Enable.
  2. API Key IP Restrictions: If you set HTTP referrer or IP address restrictions on your API key in the GCP credentials page, the Pi's outbound NAT IP must be whitelisted. For mobile vehicle trackers, you must remove IP restrictions or use a backend proxy.
  3. Missing Billing Account: Google Maps Platform requires an active billing account with a valid credit card on file, even if you are operating within the $200 monthly free tier.

The First Three Things to Check When the Build Fails

If the script hangs, outputs no data, or throws socket errors, run through this diagnostic triage before rewriting your code:

  1. Verify the gpsd Socket: Run sudo systemctl status gpsd. If it shows active (running) but cgps shows no data, the daemon is running but the serial port is locked. Ensure no other process (like a stray Python script) is holding /dev/ttyAMA0 open.
  2. Check Raw UART Traffic: Run cat /dev/ttyAMA0. If the terminal is blank, your TX/RX wires are swapped, or the GPS module lacks 3.3V power. If you see garbage characters, your baud rate is mismatched (the MTK3339 defaults to 9600; ensure /etc/default/gpsd isn't forcing 4800).
  3. Validate API Key Permissions: Test the API key directly from the Pi's terminal using curl:
    curl "https://maps.googleapis.com/maps/api/geocode/json?latlng=40.714,-74.005&key=YOUR_API_KEY". If this returns a JSON error, the issue is strictly in the GCP console.

Extending and Simplifying the Build

Depending on your deployment environment, you may need to strip this project down to its bare essentials or scale it up for fleet tracking.

How to Simplify (Offline Data Logging)

If you are deploying this in an area without cellular Wi-Fi and do not want to pay for a 4G LTE HAT, drop the Google Maps API entirely. Modify the Python script to append the raw current_lat and current_lon variables to a local CSV file on the microSD card. When the Pi returns to your workshop, you can export the CSV and bulk-import it into Google Maps Pro or Google Earth via KML conversion.

How to Extend (Fleet Telemetry via LoRaWAN)

For long-range tracking where cellular is too expensive, integrate a LoRaWAN concentrator like the RAK2245. Instead of polling Google Maps directly from the Pi, have the Pi package the NMEA data into a JSON payload and transmit it via MQTT over LoRa to a central gateway. The gateway server, which has stable fiber internet, then handles the Google Maps API calls and renders the dashboard, saving your Pi from managing complex web sockets.

Frequently Asked Questions

How to display offline google maps on raspberry pi without internet?

The official Google Maps JavaScript API strictly requires an active internet connection to fetch vector tiles and cannot be cached for offline use due to licensing restrictions. To display offline maps on a Pi touchscreen kiosk, you must pivot to open-source tile servers. Install TileServer GL via Docker, download OpenStreetMap (OSM) MBTiles for your specific region, and render them locally using a lightweight browser like Chromium in kiosk mode. You can still overlay your custom GPS coordinate markers on top of the local OSM tiles using Leaflet.js.

Can I use google maps raspberry pi for real-time vehicle tracking?

Yes, but you must account for API costs and latency. The Google Maps Geocoding API costs roughly $5.00 per 1,000 requests. If your Python script polls the API every second, you will burn through the $200 monthly free credit in less than 12 hours of continuous driving. For real-time vehicle tracking, use the Pi to push raw coordinates via WebSocket to a custom Node.js backend, and use the Google Maps JavaScript API on the frontend dashboard to draw the polyline. Only call the reverse-geocoding API when the vehicle's ignition is turned off to log the final parking address.

Why is my google maps raspberry pi GPS coordinates lagging by 5 seconds?

A 5-second lag is almost always caused by gpsd buffering or a mismatch in the serial polling rate. By default, the Adafruit MTK3339 outputs NMEA sentences at 1Hz (once per second). If your Python loop is sleeping for too long, or if you are using the software UART (/dev/ttyS0) instead of the hardware UART (/dev/ttyAMA0), the kernel will buffer the serial data, introducing massive latency. Ensure you are using the hardware UART, set time.sleep() to no more than 0.2 in your loop, and verify that no other background services are monopolizing the Pi's CPU scheduler.