Create a Reverse Geolocator with Pico W and GPS Module
Introduction
In this guide, we will walk through how to build a reverse geolocator using the Raspberry Pi Pico W and a GPS module. This project allows you to fetch the human-readable address of your current location by processing GPS data and querying the OpenCage Geocoder API. Perfect for hobbyists and students, this tutorial makes use of MicroPython to program the Pico W.
Parts and Tools Required
- NEO-6M GPS Module - A reliable GPS module for accurate positioning.
- Raspberry Pi Pico W - The microcontroller for this project.
- Soldering Iron Kit - For soldering the GPS module headers.
- Breadboard - For making connections.
- Jumper Wires - For making connections.
What is GPS?
GPS, or Global Positioning System, is a satellite-based navigation system that provides location and time information anywhere on Earth. It works by using a network of satellites that transmit signals to GPS receivers, like the NEO-6M module used in this project. By analyzing the signals from multiple satellites, the GPS receiver calculates its exact position in terms of latitude, longitude, and altitude.
This technology has become essential in many applications, including navigation, surveying, and scientific research. In this project, we utilize GPS to determine our geographical coordinates, which are then converted into human-readable addresses using the OpenCage Geocoder API.
Connections
The GPS module needs to be soldered to add headers before it can be connected to the Pico W. After soldering, use the following connections:
- GPS Module TX to Pico W Pin 1 (RX)
- GPS Module RX to Pico W Pin 0 (TX)
- GPS Module VCC to Pico W 3.3V
- GPS Module GND to Pico W GND
Ensure you solder the GPS module carefully to avoid damaging the headers or PCB.
Setting Up an OpenCage Account
To use the OpenCage Geocoder API for reverse geocoding, you need an API key. Follow these steps to set up an account:
- Visit the OpenCage Geocoder API website.
- Click on Sign Up to create a free account.
- After registering, log in to your account and navigate to the API Keys section.
- Generate a new API key and copy it. You will use this key in the code to authenticate your API requests.
Note: The free tier provides limited requests per day. Upgrade if you need more requests.
Code
Below is the complete code for this project. This code reads GPS data, processes it to extract coordinates, and uses the OpenCage Geocoder API to retrieve the corresponding city and country.
import machine
import time
import urequests
import network
# Initialize UART for GPS communication
uart = machine.UART(0, baudrate=9600, tx=machine.Pin(0), rx=machine.Pin(1))
def connect_internet(name, password):
try:
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect(name, password)
for i in range(0, 10):
if not sta_if.isconnected():
time.sleep(1)
print("Connected to Wi-Fi")
except Exception as e:
print('There was an issue connecting to WIFI')
print(e)
def reverse_geocode(latitude, longitude):
"""
Convert latitude and longitude to a human-readable address using OpenCage API.
"""
api_key = "Put your key here" # Your provided API key
# Manually construct the URL with query parameters
full_url = f"https://api.opencagedata.com/geocode/v1/json?q={latitude}+{longitude}&key={api_key}"
# Validate coordinates
if not (-90 <= latitude <= 90) or not (-180 <= longitude <= 180):
print("Invalid GPS coordinates.")
return "Unknown", "Unknown"
try:
print(f"Requesting URL: {full_url}") # Debugging log
response = urequests.get(full_url, timeout=5)
# Check if the request was successful
if response.status_code == 200:
data = response.json()
print(f"API Response: {data}") # Debugging log
if data.get('results'):
components = data['results'][0].get('components', {})
city = components.get('city') or components.get('town') or components.get('village') or "Unknown"
country = components.get('country', "Unknown")
return city, country
else:
print("No results found in API response.")
return "Unknown", "Unknown"
elif response.status_code == 403:
print("Error: API key is invalid or blocked.")
return "Unknown", "Unknown"
elif response.status_code == 429:
print("Error: Rate limit exceeded. Try again later.")
return "Unknown", "Unknown"
else:
print(f"Unexpected API error: {response.status_code}, {response.text}")
return "Unknown", "Unknown"
except Exception as e:
print(f"Error during API request: {e}")
return "Unknown", "Unknown"
def validate_checksum(sentence):
if '*' not in sentence:
return False # Invalid sentence format
# Split the sentence into data and checksum parts
data, checksum = sentence.split('*')
if not checksum:
return False
# Calculate checksum by XORing all characters in the data part
calculated_checksum = 0
for char in data:
calculated_checksum ^= ord(char)
return calculated_checksum == int(checksum, 16)
def extract_coordinates(sentence):
"""
Extract coordinates and altitude from GPGGA sentence.
Returns a tuple: (latitude, longitude, altitude) or None if invalid.
"""
# Split the sentence by commas
print(sentence)
parts = sentence.split(",")
# Validate the sentence
if parts[0] != "GPGGA":
raise ValueError("Input is not a valid GPGGA sentence.")
# Check GPS signal validity
gps_quality = parts[6]
if gps_quality != "1":
return None
# Extract latitude
raw_lat = parts[2]
lat_direction = parts[3]
latitude = float(raw_lat[:2]) + float(raw_lat[2:]) / 60 # Convert to decimal degrees
if lat_direction == "S":
latitude = -latitude
# Extract longitude
raw_lon = parts[4]
lon_direction = parts[5]
longitude = float(raw_lon[:3]) + float(raw_lon[3:]) / 60 # Convert to decimal degrees
if lon_direction == "W":
longitude = -longitude
# Extract altitude
altitude = float(parts[9])
city, country = reverse_geocode(latitude, longitude)
return latitude, longitude, altitude, city, country
def convert_to_decimal(raw, direction):
"""
Convert raw NMEA latitude/longitude to decimal format.
"""
if not raw:
return None
degrees = int(raw[:2])
minutes = float(raw[2:])
decimal = degrees + (minutes / 60)
if direction in ['S', 'W']:
decimal = -decimal
return decimal
def read_gps(read_coordinates_only=False):
buffer = b""
while True:
if uart.any(): # Check if there's data available in UART buffer
data = uart.read() # Read the data from UART
if data:
buffer += data # Append data to buffer
# Process complete NMEA sentences
while b'\r\n' in buffer: # Look for sentence endings
raw_line, buffer = buffer.split(b'\r\n', 1) # Split buffer at sentence boundary
line = raw_line.decode('utf-8') # Decode from bytes to string
if line.startswith('$'):
lines = line.split('$')
for line in lines:
if line == '' or not validate_checksum(line):
continue
if read_coordinates_only:
if line.startswith('GPGGA'):
coordinates = extract_coordinates(line)
if coordinates:
latitude, longitude, altitude, city, country = coordinates
print(f"Latitude: {latitude}, Longitude: {longitude}, Altitude: {altitude} m, City: {city}, Country: {country}")
else:
print("GPS signal not available")
else:
print(line) # Print the full NMEA sentence
time.sleep(1) # Add a small delay to prevent CPU overuse
try:
print("Reading GPS data...")
# Pass `True` to only print coordinates and altitude, or `False` for all data
connect_internet('SMA', 'mmmyellow')
read_gps(read_coordinates_only=True)
except KeyboardInterrupt:
print("Stopped")
Sample Log
Below is a sample output log showcasing the GPS data and reverse geocoding results:
GPGGA,042820.00,4152.68463,N,08739.23793,W,1,05,2.68,308.3,M,-33.8,M,,*62
Requesting URL: https://api.opencagedata.com/geocode/v1/json?q=41.87808+-87.65397&key=5b9111b71c5f47e194c1d9b8c7d896a9
Latitude: 41.87808, Longitude: -87.65397, Altitude: 308.3 m, City: Chicago, Country: United States
GPGGA,042821.00,4152.68536,N,08739.23883,W,1,05,2.68,310.1,M,-33.8,M,,*67
Requesting URL: https://api.opencagedata.com/geocode/v1/json?q=41.87809+-87.65398&key=5b9111b71c5f47e194c1d9b8c7d896a9
Latitude: 41.87809, Longitude: -87.65398, Altitude: 310.1 m, City: Chicago, Country: United States
GPGGA,042824.00,4152.68823,N,08739.24100,W,1,05,2.68,318.9,M,-33.8,M,,*6E
Requesting URL: https://api.opencagedata.com/geocode/v1/json?q=41.87814+-87.65401&key=5b9111b71c5f47e194c1d9b8c7d896a9
Latitude: 41.87814, Longitude: -87.65401, Altitude: 318.9 m, City: Chicago, Country: United States
GPGGA,042827.00,4152.69062,N,08739.24131,W,1,05,2.68,323.1,M,-33.8,M,,*63
Requesting URL: https://api.opencagedata.com/geocode/v1/json?q=41.87818+-87.65402&key=5b9111b71c5f47e194c1d9b8c7d896a9
Latitude: 41.87818, Longitude: -87.65402, Altitude: 323.1 m, City: Chicago, Country: United States
Testing
Once the connections are made and the code is uploaded to the Pico W, power it up. Open the serial console to view the GPS data and reverse geocoding results. You should see the latitude, longitude, altitude, city, and country printed out in real time.
Conclusion
Congratulations! You've successfully created a reverse geolocator using the Raspberry Pi Pico W and a GPS module. This project demonstrates the powerful capabilities of GPS and MicroPython, and there’s so much more you can explore. Consider expanding this project by logging data, integrating with other APIs, or adding real-time mapping features.
If you enjoyed this tutorial, subscribe to our YouTube channel for more hands-on tech projects and tutorials. Interested in taking your projects to the next level or need professional help? Hire us on Upwork through ShillehTek consulting. Let’s bring your ideas to life!
Thank you for following along, and we look forward to seeing what you create. Share your projects and experiences with us!