How to Convert Degrees, Minutes and Seconds to Decimal Degrees in Python
On this page
Problem statement
Coordinates arrive in many shapes: 21°18'25.5"N 157°51'30"W from a document, 211825N 1575130W from a government gazetteer, 21 18.425 N from a GPS unit. GIS software wants decimal degrees, 21.307083 and -157.858333. The arithmetic is easy, degrees plus minutes over 60 plus seconds over 3,600, but real data adds hemisphere letters that carry the sign, compact formats with no separators, decimal minutes, and the reverse conversion back to DMS for display.
This page converts the compact DMS fields of the US Geological Survey's Geographic Names Information System for Hawaii to decimal degrees, checks every result against the decimal values GNIS publishes, parses free-form DMS strings with a regular expression, converts back to DMS, and measures how much precision whole seconds keep.
Quick answer
Split the degrees, minutes and seconds, combine them, and apply the sign from the hemisphere letter:
def gnis_dms(s):
"""'211825N' or '1575130W' -> decimal degrees."""
hemi, digits = s[-1], s[:-1]
deg_len = 3 if hemi in "EW" else 2
d, m, sec = int(digits[:deg_len]), int(digits[deg_len:deg_len + 2]), float(digits[deg_len + 2:])
value = d + m / 60 + sec / 3600
return -value if hemi in "SW" else value
row = G.iloc[0]
print(f"{row.feature_name}: {row.prim_lat_dms} {row.prim_long_dms} -> {gnis_dms(row.prim_lat_dms):.6f}, {gnis_dms(row.prim_long_dms):.6f}")
print(f"GNIS decimal values: {row.prim_lat_dec:.6f}, {row.prim_long_dec:.6f}")
Pearl and Hermes Atoll: 274942N 1754946W -> 27.828333, -175.829444
GNIS decimal values: 27.828203, -175.829529
The first feature, Pearl and Hermes Atoll in the north-west of the chain, converts to 27.828333°N, 175.829444°W. GNIS's own decimal values differ slightly, 27.828203 and -175.829529, because they were stored with more precision than the whole seconds of the DMS fields.
Convert and check a column, parse free text, convert back
1. Convert a whole column and check it
Apply the function to every feature and compare with the decimal columns GNIS publishes beside the DMS ones:
G["lat"] = G.prim_lat_dms.map(gnis_dms)
G["lon"] = G.prim_long_dms.map(gnis_dms)
dlat, dlon = (G.lat - G.prim_lat_dec).abs(), (G.lon - G.prim_long_dec).abs()
print(f"features: {len(G):,}; largest difference: latitude {dlat.max():.7f}°, longitude {dlon.max():.7f}°")
print(f"differences above half a second (0.000139°): {((dlat > 0.5 / 3600) | (dlon > 0.5 / 3600)).sum()}")
print(f"longitudes west of 180° (hemisphere W): {(G.prim_long_dms.str[-1] == 'W').sum():,}; east (E): {(G.prim_long_dms.str[-1] == 'E').sum()}")
features: 7,333; largest difference: latitude 0.0001389°, longitude 0.0001389°
differences above half a second (0.000139°): 1
longitudes west of 180° (hemisphere W): 7,333; east (E): 0
Across all 7,333 features the conversion agrees with GNIS's decimal columns to within 0.0001389°, half a second: the rounding of the DMS values to whole seconds. Only one feature, Lahomene Falls, goes over it, by a hair. Every longitude carries a W, so every one becomes negative.
2. Parse free-form strings
Text coordinates come with symbols, spaces, decimal seconds or decimal minutes. One regular expression covers the common forms:
import re
PAT = re.compile(r"""(?P<d>\d+(?:\.\d+)?)\s*[°d:]?\s*(?:(?P<m>\d+(?:\.\d+)?)\s*['′m:]?\s*)?(?:(?P<s>\d+(?:\.\d+)?)\s*["″s]?\s*)?(?P<h>[NSEW])""", re.I)
def parse(text):
out = []
for m in PAT.finditer(text):
v = float(m["d"]) + float(m["m"] or 0) / 60 + float(m["s"] or 0) / 3600
out.append(-v if m["h"].upper() in "SW" else v)
return out
for t in ["21°18'25.5\"N 157°51'30\"W", "21 18 25.5 N, 157 51 30 W", "21°18.425'N 157°51.5'W", "21.307083N 157.858333W"]:
print(f"{t!r:32} -> {[round(v, 6) for v in parse(t)]}")
'21°18\'25.5"N 157°51\'30"W' -> [21.307083, -157.858333]
'21 18 25.5 N, 157 51 30 W' -> [21.307083, -157.858333]
"21°18.425'N 157°51.5'W" -> [21.307083, -157.858333]
'21.307083N 157.858333W' -> [21.307083, -157.858333]
The same point, Honolulu's Iolani Palace area, written four ways, parses to the same 21.307083, -157.858333: with degree, minute and second symbols, with spaces, with decimal minutes as a GPS unit shows them, and as decimal degrees with hemisphere letters.
3. Back to degrees, minutes and seconds
For display, split the absolute value into parts and round the seconds; carry when rounding gives 60:
def to_dms(value, is_lat, decimals=1):
hemi = ("N" if value >= 0 else "S") if is_lat else ("E" if value >= 0 else "W")
v = abs(value)
d = int(v); m = int((v - d) * 60); s = round((v - d - m / 60) * 3600, decimals)
if s >= 60: s, m = s - 60, m + 1
if m >= 60: m, d = m - 60, d + 1
return f"{d}°{m:02d}'{s:0{3 + decimals}.{decimals}f}\"{hemi}"
peak = G[G.feature_name == "Mauna Kea"].iloc[0]
print(f"Mauna Kea: {to_dms(peak.prim_lat_dec, True)} {to_dms(peak.prim_long_dec, False)} (GNIS: {peak.prim_lat_dms} {peak.prim_long_dms})")
print(f"round trip of every feature, worst error: {max(abs(parse(to_dms(a, True, 2))[0] - a) for a in G.prim_lat_dec) * 3600:.3f} seconds")
Mauna Kea: 19°49'14.2"N 155°28'05.2"W (GNIS: 194914N 1552805W)
round trip of every feature, worst error: 0.005 seconds
Mauna Kea's decimal coordinates convert back to 19°49'14.2"N 155°28'05.2"W, matching GNIS's 194914N 1552805W to within the rounding of the seconds. Converting every feature to DMS with two decimals of seconds and parsing it again returns the original value to within 0.005 seconds, 15 cm.
How precise whole seconds are
The data used on this page
The USGS Geographic Names Information System domestic names file for Hawaii, DomesticNames_HI_Text.zip, a pipe-delimited table with each feature's primary coordinates in compact DMS (prim_lat_dms, prim_long_dms) and in decimal degrees.
import io
import zipfile
from pathlib import Path
import numpy as np
import pandas as pd
import requests
Path("data").mkdir(exist_ok=True)
p = Path("data/DomesticNames_HI_Text.zip")
if not p.exists():
r = requests.get("https://prd-tnm.s3.amazonaws.com/StagedProducts/GeographicNames/DomesticNames/DomesticNames_HI_Text.zip", timeout=300)
r.raise_for_status()
p.write_bytes(r.content)
with zipfile.ZipFile(p) as z:
G = pd.read_csv(io.BytesIO(z.read("Text/DomesticNames_HI.txt")), sep="|", dtype={"prim_lat_dms": str, "prim_long_dms": str}, encoding="utf-8-sig")
G = G[G.state_name == "Hawaii"].dropna(subset=["prim_lat_dms", "prim_long_dms"]).reset_index(drop=True)
print(f"{len(G):,} Hawaii features; example DMS values: {G.prim_lat_dms.iloc[0]}, {G.prim_long_dms.iloc[0]}")
print(f"feature classes: {G.feature_class.value_counts().head(5).to_dict()}")
7,333 Hawaii features; example DMS values: 274942N, 1754946W
feature classes: {'Civil': 1288, 'Summit': 1089, 'Cape': 789, 'Valley': 720, 'Populated Place': 563}
The Hawaii file has 7,333 features, from civil divisions and summits to capes, valleys and populated places. The coordinates come as compact strings such as 274942N and 1754946W: two or three digits of degrees, two of minutes, two of seconds and a hemisphere letter.
Example 1: how precise are whole seconds?
GNIS stores whole seconds. A second of latitude is about 31 m; a second of longitude shrinks with latitude. Compute the size of a one-second cell at Hawaii's latitude:
lat0 = G.prim_lat_dec.median()
ns_m = 1 / 3600 * 111_132.95
ew_m = 1 / 3600 * 111_412.84 * np.cos(np.radians(lat0))
print(f"at {lat0:.2f}°N: 1 second = {ns_m:.1f} m north-south, {ew_m:.1f} m east-west")
print(f"so whole seconds locate a point to within about {np.hypot(ns_m, ew_m) / 2:.0f} m; 0.1 second to about {np.hypot(ns_m, ew_m) / 20:.1f} m")
at 20.88°N: 1 second = 30.9 m north-south, 28.9 m east-west
so whole seconds locate a point to within about 21 m; 0.1 second to about 2.1 m
At Hawaii's median latitude, 20.88°N, one second is 30.9 m north-south and 28.9 m east-west. GNIS's whole seconds therefore pin a feature to within about 21 m; one decimal of a second would be about 2 m.
A sexagesimal notation from Babylon
Degrees, minutes and seconds are a sexagesimal notation inherited from Babylonian astronomy: a degree has 60 minutes and a minute 60 seconds, so decimal degrees are d + m/60 + s/3600. The notation carries no sign. Instead a hemisphere letter does: N and E positive, S and W negative, which is why dropping the letter is the most damaging mistake in a conversion. Compact formats such as GNIS's 1575130W also drop the separators, so the parser needs to know that longitudes have three degree digits and latitudes two.
Converting back needs care with rounding: 59.96 seconds rounded to one decimal becomes 60.0, which must carry into the minutes, and 60 minutes into the degrees, or the output shows impossible values like 21°59'60.0". Decimal minutes, the format of most GPS receivers and marine charts, sit between the two: d + m/60, with the minutes carrying the fraction.
Doubled signs, sixty seconds and primes
- Signs and letters together: some sources write
-157°51'30"W; decide whether the letter or the sign wins and check a known point. - Minutes or seconds of 60: invalid in a clean source, but they occur; reject or carry them.
- Locale: some countries write decimal commas,
21,307083; normalise before parsing. - Primes and quotes: documents may use ′ and ″ (U+2032, U+2033) instead of ' and "; the pattern above accepts both.
Internal links
- Fixing DMS coordinates that land in the wrong hemisphere — when the sign is lost
- How many decimal places do coordinates need? — precision in decimal form
- Latitude and longitude explained — what the numbers mean
FAQ
How do I convert DMS to decimal degrees in Python?
Decimal = degrees + minutes / 60 + seconds / 3600, made negative for S and W hemispheres.
How do I convert decimal degrees to DMS in Python?
Take the absolute value: degrees = int(v), minutes = int((v − degrees) × 60), seconds = the remainder × 3600; add the hemisphere letter and carry any rounded 60.
How do I parse a coordinate string like 21°18'25.5"N?
Use a regular expression that captures degrees, optional minutes, optional seconds and the hemisphere letter, as in step 2.
How accurate is one second of latitude?
About 31 metres; a second of longitude is 31 metres times the cosine of the latitude.