GPS & GeoURI Validation in Python
Four input formats, coordinate range checks, and automatic DMS conversion — in one API call. Here's why parsing GPS strings yourself is harder than it looks.
In this guide
1. Why GPS validation is harder than it looks
GPS coordinates look deceptively simple — just two numbers representing latitude and longitude. In practice, users enter coordinates in at least four different formats, each with its own syntax for degrees, minutes, seconds, and direction. Google Maps outputs decimal degrees, marine navigation devices use degrees and decimal minutes, aviation charts prefer degrees-minutes-seconds, and modern QR codes embed geo: URIs defined by RFC 5870.
A naive float() call handles exactly one of these formats. It raises a ValueError on DMS strings like 52°13'46.92"N, ignores direction letters, cannot distinguish latitude from longitude in a GeoURI, and produces no error when a value exceeds the valid range. The result is subtle bugs that only surface when real users paste coordinates from unfamiliar sources.
On top of format variety, there are Unicode traps. Copy-pasting from Wikipedia or a PDF often introduces prime symbols (U+2032) instead of apostrophes, double primes (U+2033) instead of quotation marks, and ring-above characters (U+02DA) instead of degree signs. These look-alike characters break strict parsers silently.
The IsValid GPS API handles all of this in a single call — four formats, Unicode normalization, direction letters, range validation, and automatic DMS conversion. You send the raw user input; it returns structured, validated coordinates.
2. Four input formats
The API auto-detects which format the input uses. You do not need to tell it — just pass the raw string and the response includes the detected format field.
| Format | Name | Example | Description |
|---|---|---|---|
| DD | Decimal Degrees | 52.2297 21.0122 | Most common in APIs and databases. Plain decimal numbers with optional ° symbol. |
| DDM | Degrees Decimal Minutes | 52°13.782' 21°0.732' | Common in marine navigation and older GPS devices. Degrees plus decimal minutes. |
| DMS | Degrees Minutes Seconds | 52°13'46.92" 21°0'43.92" | Traditional cartographic format. Used in aviation, hiking maps, and government surveys. |
| GeoURI | RFC 5870 URI | geo:52.2297,21.0122 | Standardized URI scheme. Used in QR codes, vCards, and mobile apps. Supports optional altitude and parameters. |
Each format can include direction letters (N/S/E/W) either before or after the number. For example, N52.2297 E21.0122 and 52.2297N 21.0122E are both valid DD inputs.
There is also a dash-separated DMS variant: 52-13-46.92 N 21-0-43.92 E. This format uses hyphens instead of degree, minute, and second symbols. Because hyphens could also indicate negative values, this variant requires an explicit direction letter to avoid ambiguity.
3. Coordinate ranges
Valid GPS coordinates must fall within strict geographic bounds. Anything outside these ranges does not correspond to a real point on Earth's surface.
| Component | Range | Notes |
|---|---|---|
| Latitude | -90 to +90 | +90 is the North Pole, -90 is the South Pole |
| Longitude | -180 to +180 | +180 and -180 both refer to the antimeridian (International Date Line) |
| Minutes | 0 to <60 | Must be less than 60 in DMS and DDM formats |
| Seconds | 0 to <60 | Must be less than 60 in DMS format |
The API rounds all coordinates to 6 decimal places in the response. This is intentional — 6 decimal places of latitude or longitude correspond to roughly 11 centimeters of ground distance, which is more precision than most applications need.
4. Unicode normalization
One of the most common reasons coordinate parsing fails in production is Unicode look-alike characters. When users copy coordinates from Wikipedia articles, PDF documents, or desktop publishing software, the degree, prime, and double-prime symbols are often replaced by visually similar but technically different Unicode code points.
The API normalizes the following characters before parsing, so you never need to pre-process them yourself:
| Input character | Code point | Normalized to |
|---|---|---|
| ′ (prime) | U+2032 | ' (apostrophe, U+0027) |
| ″ (double prime) | U+2033 | " (double quote, U+0022) |
| ˚ (ring above) | U+02DA | ° (degree sign, U+00B0) |
| ° (degree sign) | U+00B0 | ° (normalized, U+00B0) |
This is a real problem in practice. Consider the following Python code — these three strings look identical but contain different bytes:
# These look the same but use different Unicode code points: a = '52°13\'46.92"N' # ASCII quotes — works b = '52°13′46.92″N' # prime + double prime — common in Wikipedia c = '52˚13\'46.92"N' # ring above instead of degree — common in PDFs # Without normalization, b and c would fail to parse. # The IsValid API normalizes all of them to the same canonical form.
Because the API handles normalization server-side, you can pass raw clipboard content or OCR output directly. No find-and-replace preprocessing needed.
5. Direction letters — N / S / E / W
Direction letters indicate which hemisphere a coordinate falls in. They can appear in several positions depending on the format and regional convention:
Before the number (prefix)
N52.2297 E21.0122 — The direction letter comes first, immediately followed by the numeric value. This is common in aviation and military coordinate systems (ICAO format).
After the number (suffix)
52.2297N 21.0122E — The direction letter follows the numeric value. This is the most common convention in cartographic notation and DMS coordinates: 52°13'46.92"N 21°0'43.92"E.
Negative numbers instead of S / W
-33.8688 151.2093 — Without direction letters, a negative latitude means South and a negative longitude means West. This is the standard in most programming APIs and databases. Sydney, Australia is at approximately -33.87° latitude.
The S and W direction letters produce negative coordinates internally. The API always returns the sign-based lat and lon values (negative for south and west) along with the explicit latDir and lonDir fields.
# All three refer to the same point — Sydney, Australia: validate_gps("-33.8688 151.2093") # negative lat = South validate_gps("S33.8688 E151.2093") # S prefix = South validate_gps("33.8688S 151.2093E") # S suffix = South # Response for all three: # { "lat": -33.8688, "lon": 151.2093, "latDir": "S", "lonDir": "E", ... }
6. The API response
Valid coordinate — Warsaw, Poland (DD format)
{ "valid": true, "format": "dd", "lat": 52.2297, "lon": 21.0122, "latDir": "N", "lonDir": "E", "dms": { "lat": "52°13'46.92\"N", "lon": "21°0'43.92\"E" } }
Invalid coordinate — latitude out of range
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the input is a valid coordinate pair |
| format | string | Detected format: "dd", "ddm", "dms", or "geo-uri"(only present when valid: true) |
| lat | number | Latitude in decimal degrees, rounded to 6 decimal places (only present when valid: true) |
| lon | number | Longitude in decimal degrees, rounded to 6 decimal places (only present when valid: true) |
| latDir | string | "N" or "S"(only present when valid: true) |
| lonDir | string | "E" or "W"(only present when valid: true) |
| dms | object | Coordinates converted to DMS notation with lat and lon string fields. Always included when valid: true |
7. Python code example
Using the requests library — the de facto standard for HTTP in Python. Install it with pip install requests.
# gps_validator.py import os import requests API_KEY = os.environ["ISVALID_API_KEY"] BASE_URL = "https://api.isvalid.dev" def validate_gps(coords: str) -> dict: """Validate GPS coordinates using the IsValid API. Args: coords: Coordinate string in any format (DD, DDM, DMS, GeoURI). Returns: Validation result as a dictionary. Raises: requests.HTTPError: If the API returns a non-2xx status. """ response = requests.get( f"{BASE_URL}/v0/gps", params={"value": coords}, headers={"Authorization": f"Bearer {API_KEY}"}, ) response.raise_for_status() return response.json() # ── Example usage ────────────────────────────────────────────────────────────────────── result = validate_gps('52°13\'46.92"N 21°0\'43.92"E') if not result["valid"]: print("Invalid coordinates") else: print(f"Format : {result['format']}") # "dms" print(f"Lat : {result['lat']}") # 52.2297 print(f"Lon : {result['lon']}") # 21.0122 print(f"DMS lat: {result['dms']['lat']}") # "52°13'46.92"N" print(f"DMS lon: {result['dms']['lon']}") # "21°0'43.92"E"
Validating multiple formats in a single batch:
from concurrent.futures import ThreadPoolExecutor inputs = [ "52.2297 21.0122", # DD "52°13.782' 21°0.732'", # DDM "geo:52.2297,21.0122", # GeoURI "91.0000 21.0122", # Invalid — lat > 90 ] with ThreadPoolExecutor(max_workers=4) as pool: results = list(pool.map(validate_gps, inputs)) for coord, r in zip(inputs, results): if r["valid"]: print(f"{coord} → {r['format']} ({r['lat']}, {r['lon']})") else: print(f"{coord} → INVALID")
In a Flask application, you might use it like this to validate location data:
# app.py (Flask) from flask import Flask, request, jsonify app = Flask(__name__) @app.post("/location") def location(): data = request.get_json() try: result = validate_gps(data["coordinates"]) except requests.RequestException: return jsonify(error="GPS validation service unavailable"), 502 if not result["valid"]: return jsonify(error="Invalid GPS coordinates"), 400 return jsonify( lat=result["lat"], lon=result["lon"], dms=result["dms"], format=result["format"], )
8. cURL examples
Decimal Degrees — Warsaw:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/gps?value=52.2297%2021.0122"
DMS with direction letters — Warsaw:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/gps?value=52%C2%B013%2746.92%22N%2021%C2%B00%2743.92%22E"
GeoURI (RFC 5870) — Warsaw:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/gps?value=geo%3A52.2297%2C21.0122"
9. Edge cases to handle
Poles
Latitude ±90 is valid. Both the North Pole (90°N) and the South Pole (90°S) are legitimate coordinates. Longitude is technically meaningless at the poles — all meridians converge — but the API accepts any longitude value paired with ±90 latitude without error.
# North Pole — all longitudes are equivalent here validate_gps("90.0 0.0") # valid, lat: 90, latDir: "N" validate_gps("90.0 180.0") # valid, same physical point # South Pole validate_gps("-90.0 0.0") # valid, lat: -90, latDir: "S"
Antimeridian
Longitude ±180 is valid. Both 180° and -180° refer to the same meridian — the International Date Line roughly follows this line through the Pacific Ocean. The API accepts both values without treating them differently.
# Both refer to the same meridian validate_gps("0.0 180.0") # valid, lon: 180, lonDir: "E" validate_gps("0.0 -180.0") # valid, lon: -180, lonDir: "W" # But 180.001 is out of range validate_gps("0.0 180.001") # {"valid": False}
Altitude in GeoURI
The GeoURI scheme (RFC 5870) supports an optional third component for altitude in meters: geo:52.2297,21.0122,123. The API accepts this format and validates the latitude and longitude, but the altitude value is not included in the response — only lat and lon are returned.
# GeoURI with altitude (123 meters) result = validate_gps("geo:52.2297,21.0122,123") # Altitude is accepted but not returned in the response print(result["lat"]) # 52.2297 print(result["lon"]) # 21.0122 print(result["format"]) # "geo-uri"
DMS with dashes
The dash-separated DMS format uses hyphens as separators instead of degree, minute, and second symbols: 52-13-46.92 N 21-0-43.92 E. This format requires an explicit direction letter. Without it, the parser cannot distinguish a dash-separated DMS value from a negative decimal degree — is -33-51 negative 33 degrees 51 minutes, or just the number -33.51 with a misplaced separator?
# Dash-separated DMS — direction letter required validate_gps("52-13-46.92 N 21-0-43.92 E") # valid, format: "dms" # Without direction letters, dashes are ambiguous # The API interprets plain negative numbers as DD format: validate_gps("-33.8688 151.2093") # valid, format: "dd"
Summary
See also
Try GPS validation instantly
Free tier includes 100 API calls per day. No credit card required. All four coordinate formats supported.