Geographic coordinates are commonly represented using Decimal Degrees (DD).
For example:
Latitude: 40.748817
Longitude: -73.985428
This format works well for APIs, databases, calculations, and web mapping libraries.
However, coordinates are also frequently displayed using Degrees, Minutes, and Seconds (DMS):
40°44'55.74"N
73°59'07.54"W
In this article, we'll build a reusable JavaScript implementation for converting Decimal Degrees to DMS.
Along the way, we'll also cover:
- How DD-to-DMS conversion works
- Latitude and longitude directions
- Floating-point rounding
- Coordinate validation
- The 60-second rounding edge case
- Returning structured coordinate data
- Formatting latitude and longitude pairs
- Testing the implementation
Decimal Degrees vs DMS
Before writing any JavaScript, it helps to understand the two coordinate formats.
Decimal Degrees
Decimal Degrees represent a coordinate using a single decimal number.
40.748817
-73.985428
For latitude:
Positive = North
Negative = South
For longitude:
Positive = East
Negative = West
So this coordinate:
40.748817, -73.985428
means:
Latitude: 40.748817° North
Longitude: 73.985428° West
Decimal Degrees are especially common in:
- JavaScript mapping libraries
- GeoJSON
- GPS APIs
- Geocoding APIs
- Spatial databases
- Web mapping applications
Degrees, Minutes, and Seconds
DMS breaks a coordinate into three parts:
Degrees
Minutes
Seconds
For example:
40°44'55.74"N
The relationships are:
1 degree = 60 minutes
1 minute = 60 seconds
Because of that, converting DD to DMS mainly involves extracting the integer part and repeatedly multiplying the fractional part by 60.
The Conversion Formula
Suppose we want to convert:
40.748817
First, take the absolute value:
const value = Math.abs(40.748817);
The integer part becomes the degrees:
const degrees = Math.floor(value);
Result:
40
Now subtract the degree portion and multiply the remainder by 60:
const minutesFloat = (value - degrees) * 60;
This gives:
44.92902
The integer portion becomes minutes:
const minutes = Math.floor(minutesFloat);
Result:
44
Finally, convert the remaining fractional minutes into seconds:
const seconds = (minutesFloat - minutes) * 60;
Result:
55.7412
So:
40.748817
becomes approximately:
40°44'55.74"
The final step is determining whether the direction should be:
N
S
E
W
A Basic JavaScript Function
Let's start with the simplest version.
function decimalToDMS(decimal) {
const absolute = Math.abs(decimal);
const degrees = Math.floor(absolute);
const minutesFloat = (absolute - degrees) * 60;
const minutes = Math.floor(minutesFloat);
const seconds = (minutesFloat - minutes) * 60;
return {
degrees,
minutes,
seconds
};
}
Usage:
console.log(decimalToDMS(40.748817));
The result may look something like this:
{
degrees: 40,
minutes: 44,
seconds: 55.7411999999
}
The calculation is correct, but the seconds value exposes a common JavaScript issue:
floating-point precision.
Rounding the Seconds
For coordinate display, we usually don't need a long floating-point value.
Two decimal places are generally much easier to read:
const seconds = Number(
((minutesFloat - minutes) * 60).toFixed(2)
);
Now our function becomes:
function decimalToDMS(decimal) {
const absolute = Math.abs(decimal);
const degrees = Math.floor(absolute);
const minutesFloat = (absolute - degrees) * 60;
const minutes = Math.floor(minutesFloat);
const seconds = Number(
((minutesFloat - minutes) * 60).toFixed(2)
);
return {
degrees,
minutes,
seconds
};
}
Calling:
decimalToDMS(40.748817);
now produces:
{
degrees: 40,
minutes: 44,
seconds: 55.74
}
That's much better for display purposes.
Determining N, S, E, and W
A coordinate's sign does not directly tell us the final direction unless we also know whether the value represents latitude or longitude.
For latitude:
Positive = N
Negative = S
For longitude:
Positive = E
Negative = W
We can create a helper function:
function getDirection(decimal, type) {
if (type === "lat") {
return decimal >= 0 ? "N" : "S";
}
if (type === "lng") {
return decimal >= 0 ? "E" : "W";
}
throw new Error("Coordinate type must be 'lat' or 'lng'");
}
Examples:
console.log(getDirection(40.748817, "lat"));
// N
console.log(getDirection(-73.985428, "lng"));
// W
Building a Complete DD-to-DMS Function
Now we can combine the conversion and direction logic.
function decimalToDMS(decimal, type) {
if (type !== "lat" && type !== "lng") {
throw new Error("Coordinate type must be 'lat' or 'lng'");
}
const absolute = Math.abs(decimal);
const degrees = Math.floor(absolute);
const minutesFloat = (absolute - degrees) * 60;
const minutes = Math.floor(minutesFloat);
const seconds = Number(
((minutesFloat - minutes) * 60).toFixed(2)
);
const direction =
type === "lat"
? decimal >= 0
? "N"
: "S"
: decimal >= 0
? "E"
: "W";
return {
degrees,
minutes,
seconds,
direction
};
}
Usage:
console.log(decimalToDMS(40.748817, "lat"));
Output:
{
degrees: 40,
minutes: 44,
seconds: 55.74,
direction: "N"
}
Longitude:
console.log(decimalToDMS(-73.985428, "lng"));
Output:
{
degrees: 73,
minutes: 59,
seconds: 7.54,
direction: "W"
}
Formatting the Result
Structured data is useful internally, but users usually expect a formatted coordinate.
For example:
40°44'55.74"N
Let's add a formatter:
function formatDMS({
degrees,
minutes,
seconds,
direction
}) {
return `${degrees}°${minutes}'${seconds}"${direction}`;
}
Usage:
const latitude = decimalToDMS(
40.748817,
"lat"
);
console.log(formatDMS(latitude));
Output:
40°44'55.74"N
For longitude:
const longitude = decimalToDMS(
-73.985428,
"lng"
);
console.log(formatDMS(longitude));
Output:
73°59'7.54"W
This works, but we can improve the formatting slightly.
Padding Minutes and Seconds
For geographic coordinate displays, this:
73°59'07.54"W
usually looks cleaner than:
73°59'7.54"W
We can pad the values:
function formatDMS({
degrees,
minutes,
seconds,
direction
}) {
const formattedMinutes = String(minutes)
.padStart(2, "0");
const formattedSeconds = seconds
.toFixed(2)
.padStart(5, "0");
return `${degrees}°${formattedMinutes}'${formattedSeconds}"${direction}`;
}
Now:
formatDMS(
decimalToDMS(-73.985428, "lng")
);
returns:
73°59'07.54"W
Handling the 60-Second Edge Case
There is an important rounding edge case that is easy to miss.
Imagine the calculated seconds are:
59.9999
After rounding to two decimal places:
Number((59.9999).toFixed(2));
we get:
60
But this isn't valid DMS formatting:
10°20'60"
Instead, the extra 60 seconds should carry into the minutes:
10°21'00"
The same principle applies when minutes reach 60.
A more reliable function therefore needs carry handling.
function decimalToDMS(decimal, type, precision = 2) {
if (!Number.isFinite(decimal)) {
throw new TypeError(
"Coordinate must be a finite number"
);
}
if (type !== "lat" && type !== "lng") {
throw new Error(
"Coordinate type must be 'lat' or 'lng'"
);
}
const absolute = Math.abs(decimal);
let degrees = Math.floor(absolute);
const minutesFloat =
(absolute - degrees) * 60;
let minutes = Math.floor(minutesFloat);
let seconds = Number(
((minutesFloat - minutes) * 60)
.toFixed(precision)
);
if (seconds >= 60) {
seconds = 0;
minutes += 1;
}
if (minutes >= 60) {
minutes = 0;
degrees += 1;
}
const direction =
type === "lat"
? decimal >= 0
? "N"
: "S"
: decimal >= 0
? "E"
: "W";
return {
degrees,
minutes,
seconds,
direction
};
}
This is much safer than relying only on toFixed().
Validating Latitude and Longitude
A conversion function should also reject impossible geographic coordinates.
Latitude must be between:
-90 and 90
Longitude must be between:
-180 and 180
We can add range checks:
if (
type === "lat" &&
(decimal < -90 || decimal > 90)
) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
Longitude validation:
if (
type === "lng" &&
(decimal < -180 || decimal > 180)
) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
Without this validation, something like:
decimalToDMS(250, "lng");
could be converted mathematically even though it is not a valid longitude.
Production-Ready Implementation
Putting everything together gives us a more complete implementation:
function decimalToDMS(
decimal,
type,
precision = 2
) {
if (!Number.isFinite(decimal)) {
throw new TypeError(
"Coordinate must be a finite number"
);
}
if (type !== "lat" && type !== "lng") {
throw new Error(
"Coordinate type must be 'lat' or 'lng'"
);
}
if (
type === "lat" &&
(decimal < -90 || decimal > 90)
) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
if (
type === "lng" &&
(decimal < -180 || decimal > 180)
) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
const absolute = Math.abs(decimal);
let degrees = Math.floor(absolute);
const minutesFloat =
(absolute - degrees) * 60;
let minutes = Math.floor(minutesFloat);
let seconds = Number(
((minutesFloat - minutes) * 60)
.toFixed(precision)
);
if (seconds >= 60) {
seconds = 0;
minutes += 1;
}
if (minutes >= 60) {
minutes = 0;
degrees += 1;
}
const direction =
type === "lat"
? decimal >= 0
? "N"
: "S"
: decimal >= 0
? "E"
: "W";
return {
degrees,
minutes,
seconds,
direction
};
}
And the formatter:
function formatDMS(
{
degrees,
minutes,
seconds,
direction
},
precision = 2
) {
const formattedMinutes =
String(minutes).padStart(2, "0");
const formattedSeconds =
seconds
.toFixed(precision)
.padStart(precision + 3, "0");
return (
`${degrees}°` +
`${formattedMinutes}'` +
`${formattedSeconds}"` +
direction
);
}
Example:
const lat = decimalToDMS(
40.748817,
"lat"
);
const lng = decimalToDMS(
-73.985428,
"lng"
);
console.log(formatDMS(lat));
console.log(formatDMS(lng));
Output:
40°44'55.74"N
73°59'07.54"W
Converting a Latitude and Longitude Pair
Most applications work with both values together.
We can wrap the converter with another helper:
function coordinatesToDMS(
latitude,
longitude
) {
return {
latitude: decimalToDMS(
latitude,
"lat"
),
longitude: decimalToDMS(
longitude,
"lng"
)
};
}
Usage:
const coordinates = coordinatesToDMS(
40.748817,
-73.985428
);
console.log(coordinates);
Output:
{
latitude: {
degrees: 40,
minutes: 44,
seconds: 55.74,
direction: "N"
},
longitude: {
degrees: 73,
minutes: 59,
seconds: 7.54,
direction: "W"
}
}
We can then format the pair:
const latitudeDMS =
formatDMS(coordinates.latitude);
const longitudeDMS =
formatDMS(coordinates.longitude);
console.log(
`${latitudeDMS}, ${longitudeDMS}`
);
Output:
40°44'55.74"N, 73°59'07.54"W
Why Return an Object Instead of a String?
It might seem easier to make decimalToDMS() directly return:
40°44'55.74"N
For very small projects, that approach is fine.
However, returning structured data gives your application more flexibility.
For example:
{
degrees: 40,
minutes: 44,
seconds: 55.74,
direction: "N"
}
The UI can then choose how to display the same value.
Compact:
40°44'55.74"N
Spaced:
40° 44′ 55.74″ N
Detailed:
Degrees: 40
Minutes: 44
Seconds: 55.74
Direction: North
Keeping conversion logic separate from presentation logic usually makes the code easier to reuse and test.
Testing the Conversion
Coordinate conversion code is small, but there are several useful boundary conditions to test.
Start with common values:
console.log(
decimalToDMS(40.748817, "lat")
);
console.log(
decimalToDMS(-73.985428, "lng")
);
Then test zero:
console.log(
decimalToDMS(0, "lat")
);
console.log(
decimalToDMS(0, "lng")
);
Latitude limits:
console.log(
decimalToDMS(90, "lat")
);
console.log(
decimalToDMS(-90, "lat")
);
Longitude limits:
console.log(
decimalToDMS(180, "lng")
);
console.log(
decimalToDMS(-180, "lng")
);
Invalid values should throw errors:
decimalToDMS(91, "lat");
// RangeError
decimalToDMS(-181, "lng");
// RangeError
decimalToDMS(NaN, "lat");
// TypeError
Testing With Vitest or Jest
This function is also easy to unit-test.
For example:
expect(
decimalToDMS(
40.748817,
"lat"
)
).toEqual({
degrees: 40,
minutes: 44,
seconds: 55.74,
direction: "N"
});
Longitude:
expect(
decimalToDMS(
-73.985428,
"lng"
)
).toEqual({
degrees: 73,
minutes: 59,
seconds: 7.54,
direction: "W"
});
You can also verify that invalid coordinates throw:
expect(() => {
decimalToDMS(91, "lat");
}).toThrow(RangeError);
And:
expect(() => {
decimalToDMS(181, "lng");
}).toThrow(RangeError);
What About Negative Zero?
JavaScript has both:
0
and:
-0
You can verify this using:
Object.is(-0, 0);
// false
For most geographic applications, treating zero latitude as North and zero longitude as East is acceptable for display purposes.
The function above does this because:
0 >= 0
returns:
true
If your application needs to preserve the distinction between 0 and -0, you can detect it using:
Object.is(decimal, -0)
For most map and GPS interfaces, however, this level of distinction is unnecessary.
Where DD-to-DMS Conversion Is Useful
This kind of coordinate conversion appears in many applications:
- GIS tools
- GPS applications
- Mapping websites
- Hiking apps
- Photo geotagging
- Survey interfaces
- Navigation tools
- Location sharing
- Marine navigation
- Aviation software
- Geographic visualization
APIs often return coordinates like this:
{
"latitude": 40.748817,
"longitude": -73.985428
}
But users may prefer to see:
40°44'55.74"N, 73°59'07.54"W
So the converter often sits between the data layer and the presentation layer.
Using the Converter With Browser Geolocation
The converter can also be combined with the browser Geolocation API.
navigator.geolocation.getCurrentPosition(
({ coords }) => {
const latitude = decimalToDMS(
coords.latitude,
"lat"
);
const longitude = decimalToDMS(
coords.longitude,
"lng"
);
console.log(
formatDMS(latitude)
);
console.log(
formatDMS(longitude)
);
}
);
This lets you take coordinates returned by the browser and immediately display them in DMS format.
Example: Building a Simple Coordinate Formatter
Here's a small reusable helper:
function formatCoordinatePair(
latitude,
longitude
) {
const lat = decimalToDMS(
latitude,
"lat"
);
const lng = decimalToDMS(
longitude,
"lng"
);
return (
`${formatDMS(lat)}, ` +
`${formatDMS(lng)}`
);
}
Usage:
console.log(
formatCoordinatePair(
40.748817,
-73.985428
)
);
Output:
40°44'55.74"N, 73°59'07.54"W
This kind of helper works well in map popups, coordinate panels, location-sharing tools, and GPS interfaces.
Try the Conversion With Real Coordinates
When building geographic utilities, testing only a few hard-coded numbers isn't always enough.
It can be useful to select real locations on a map, copy their Decimal Degree coordinates, and compare the result produced by your JavaScript implementation.
I've been building CoordMap, a browser-based coordinate and mapping tool for working with latitude, longitude, locations, elevation, and map-based geographic data.
You can use it to find a real coordinate pair and then test the converter from this article.
For example, after getting coordinates such as:
40.748817, -73.985428
you can run:
console.log(
formatCoordinatePair(
40.748817,
-73.985428
)
);
and compare the formatted result.
This is also a convenient way to test your implementation with locations from different hemispheres:
North + West
North + East
South + West
South + East
That helps confirm that your N, S, E, and W direction logic behaves correctly.
Complete Copy-Paste Version
If you just want the full implementation, here it is:
function decimalToDMS(
decimal,
type,
precision = 2
) {
if (!Number.isFinite(decimal)) {
throw new TypeError(
"Coordinate must be a finite number"
);
}
if (type !== "lat" && type !== "lng") {
throw new Error(
"Coordinate type must be 'lat' or 'lng'"
);
}
if (
type === "lat" &&
(decimal < -90 || decimal > 90)
) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
if (
type === "lng" &&
(decimal < -180 || decimal > 180)
) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
const absolute = Math.abs(decimal);
let degrees = Math.floor(absolute);
const minutesFloat =
(absolute - degrees) * 60;
let minutes = Math.floor(minutesFloat);
let seconds = Number(
((minutesFloat - minutes) * 60)
.toFixed(precision)
);
if (seconds >= 60) {
seconds = 0;
minutes += 1;
}
if (minutes >= 60) {
minutes = 0;
degrees += 1;
}
const direction =
type === "lat"
? decimal >= 0
? "N"
: "S"
: decimal >= 0
? "E"
: "W";
return {
degrees,
minutes,
seconds,
direction
};
}
function formatDMS(
{
degrees,
minutes,
seconds,
direction
},
precision = 2
) {
const formattedMinutes =
String(minutes).padStart(2, "0");
const formattedSeconds =
seconds
.toFixed(precision)
.padStart(precision + 3, "0");
return (
`${degrees}°` +
`${formattedMinutes}'` +
`${formattedSeconds}"` +
direction
);
}
function formatCoordinatePair(
latitude,
longitude
) {
const lat = decimalToDMS(
latitude,
"lat"
);
const lng = decimalToDMS(
longitude,
"lng"
);
return (
`${formatDMS(lat)}, ` +
`${formatDMS(lng)}`
);
}
console.log(
formatCoordinatePair(
40.748817,
-73.985428
)
);
Output:
40°44'55.74"N, 73°59'07.54"W
Final Thoughts
The basic Decimal Degrees to DMS formula is simple:
Decimal Degrees
↓
Degrees
↓
Minutes
↓
Seconds
But a reliable implementation should also handle:
- Latitude and longitude direction
- Valid geographic ranges
- Floating-point precision
- Seconds rounding to 60
- Minute carry-over
- Invalid inputs
- Reusable structured output
- Separate formatting logic
That turns a few lines of coordinate math into something much more suitable for real GIS, GPS, and mapping applications.
If you're building a map-related project, keeping coordinate conversion as a small standalone utility also makes it easier to reuse across different parts of the application.
Happy mapping. 🗺️
Top comments (0)