# Programing: Decimalize Latitude Longitude

, , …,

Here's a programing exercise, with solution's in emacs lisp, python, ruby.

## Problem

Write a function “latitude-longitude-decimalize”.

It should take a string like this: `"37°26′36.42″N 06°15′14.28″W"`. The return value should be a pair of numbers, like this: `[37.44345 -6.25396]`.

Feel free to use { Perl, Python, Ruby, PHP, Emacs Lisp, Java, …}

## Solutions

### Emacs Lisp

solution by Xah Lee.

```(defun latitude-longitude-decimalize-xah (lat-lon-str)
"Convert latitude longitude string LAT-LON-STR in minutes second format to decimal.

For example: 「\"37°26′36.42″N 06°15′14.28″W\"」
becomes 「[37.44345 -6.253966666666667]」"
(interactive)
(let (ξtmpPair (tt2 lat-lon-str) ξlatStr ξlatNum ξlonStr ξlonNum
ξdeg ξmin ξsec ξsign (ξc (/ 1.0 60.0))
)

(setq tt2 (replace-regexp-in-string "''" "″" tt2 t t) )
(setq tt2 (replace-regexp-in-string "\"" "″" tt2 t t) )
(setq tt2 (replace-regexp-in-string "'" "′" tt2 t t) )

(setq ξtmpPair (split-string tt2 " +"))
(when (not (equal (length ξtmpPair) 2)) (user-error "Error: input can contain only one space"))

(setq ξlatStr (elt ξtmpPair 0))
(setq ξlonStr (elt ξtmpPair 1))
(if (string-match "\\`\\([0-9]+\\)°\\([0-9]+\\)′\\([.0-9]+\\)″\\(.?\\)\\'" ξlatStr )
(progn
(setq ξdeg  (string-to-number (match-string 1 ξlatStr) ))
(setq ξmin  (string-to-number (match-string 2 ξlatStr) ))
(setq ξsec  (string-to-number (match-string 3 ξlatStr) ))
(setq ξsign (match-string 4 ξlatStr))
(setq ξlatNum (+ ξdeg (* (+ ξmin (* ξsec ξc)) ξc)))
(cond
((string= (downcase ξsign) "n") nil)
((string= ξsign "") nil)
((string= (downcase ξsign) "s") (setq ξlatNum (* -1 ξlatNum) ))
(t (user-error "Your input is malformed. Your latitude ends with a char that's not N or S"))
)
)
(progn (user-error "Your latitude is malformed") )
)

(if (string-match "\\`\\([0-9]+\\)°\\([0-9]+\\)′\\([.0-9]+\\)″\\(.?\\)\\'" ξlonStr )
(progn
(setq ξdeg  (string-to-number (match-string 1 ξlonStr) ))
(setq ξmin  (string-to-number (match-string 2 ξlonStr) ))
(setq ξsec  (string-to-number (match-string 3 ξlonStr) ))
(setq ξsign (match-string 4 ξlonStr))
(setq ξlonNum (+ ξdeg (* (+ ξmin (* ξsec ξc)) ξc)))
(cond
((string= (downcase ξsign) "e") nil)
((string= ξsign "") nil)
((string= (downcase ξsign) "w") (setq ξlonNum (* -1 ξlonNum) ))
(t (user-error "Your input is malformed. Your longitude ends with a char that's not E or W"))
)
)
(progn (user-error "Your longitude is malformed") )
)
(vector ξlatNum ξlonNum) ) )

(message "%S" (latitude-longitude-decimalize-xah "37°26′36.42″N 06°15′14.28″W"))
;; "[37.44345 -6.253966666666667]"```

```;; -*- coding: utf-8 -*-
;; by Jon Snader 2012-04-24 http://irreal.org/blog/?p=795

(require 'cl)

(defun latitude-longitude-decimalize-jcs (lat-lon-str)
"Decimalize latitude longitude
\"37°26′36.42″N 06°15′14.28″W\" ⇒ (37.44345 -6.253966666666667)"
(let (result)
(dolist (ll (split-string lat-lon-str " +"))
(destructuring-bind (deg min sec dir)
(split-string ll "[°′″]")
(if (string= "" dir) (error "malformed lat-lon-str %s" ll))
(let ((factor (if (member dir '("S" "W")) -1 1)))
(push (* factor (+ (string-to-number deg)
(/ (string-to-number min) 60.0)
(/ (string-to-number sec) 3600.0))) result))))
(reverse result)))

(message "%S" (latitude-longitude-decimalize-jcs "37°26′36.42″N 06°15′14.28″W"))
;; (37.44345 -6.253966666666667)```
```;; -*- coding: utf-8 -*-

;; by Jorge A. Alfaro Murillo 2012-04-26 https://plus.google.com/+JorgeAAlfaroMurillo/posts http://pastebin.com/16d67W71

;; Answer to Programing Problem: Decimalize Latitude Longitude.
;; Found in http://xahlee.blogspot.com/2012/04/programing-problem decimalize-latitude.html

;; it is very elisp in that it uses a regexp for splitting the
;; strings, and the fact that characters are also numbers in elisp:
;; N=78<S=83 and E=69<W=87

(defun latitude-longitude-decimalize-jaam (lat-lon-str)
"Decimalize latitude longitude. Converts a string of the form \"37°26′36.42″N 06°15′14.28″W\" to a list with two numbers of the form (37.44345 -6.253966666666667)."
(let* ((lat-strings (split-string (car (split-string lat-lon-str)) "[^0-9A-Z.]+"))
(lon-strings (split-string (cadr (split-string lat-lon-str)) "[^0-9A-Z.]+"))
(lat-sign (- 1 (* 2(/ (string-to-char (nth 3 lat-strings)) 83))))
(lon-sign (- 1 (* 2(/ (string-to-char (nth 3 lon-strings)) 87))))
(lat (* lat-sign
(+ (string-to-number (nth 0 lat-strings))
(/ (string-to-number (nth 1 lat-strings)) 60.0)
(/ (string-to-number (nth 2 lat-strings)) (* 60.0 60.0)))))
(lon (* lon-sign
(+ (string-to-number (nth 0 lon-strings))
(/ (string-to-number (nth 1 lon-strings)) 60.0)
(/ (string-to-number (nth 2 lon-strings)) (* 60.0 60.0))))))
(list lat lon)))

(message "%s" (latitude-longitude-decimalize-jaam "37°26′36.42″N 06°15′14.28″W"))
;; "(37.44345 -6.253966666666667)"```

Mickey's solution ( http://www.masteringemacs.org/articles/2012/04/25/fun-emacs-calc/ ) This solution didn't account North/West sign.

```;; -*- coding: utf-8 -*-
;; by Mickey 2012-04-25 http://www.masteringemacs.org/articles/2012/04/25/fun-emacs-calc/

(defun latitude-longitude-decimalize-mickey (lat-lon-str)
(let ((hms (split-string lat-lon-str "[°′″NW ]" t)))
(flet ((to-deg ()
(string-to-number
(calc-eval (format "deg(%s@ %s' %s\")"
(pop hms) (pop hms) (pop hms))))))
(list (to-deg) (to-deg)))))

(message "%s" (latitude-longitude-decimalize-mickey "37°26′36.42″N 06°15′14.28″W"))
;; "(37.44345 6.25396666667)"```

emacs calc solution by Aaron Hawley. This solution does not qualify, but interesting anyway.

`C-x * * [37@26'36.6",_6@15'14.28"] m d V M c d`

## Python

Kurt Schwehr's solution in python, at http://schwehr.org/blog/archives/2011-11.html .

```# -*- coding: utf-8 -*-
# 2011-11 by Kurt Schwehr http://schwehr.org/blog/archives/2011-11.html
# 2014-01-21 trivial mod to accept Unicode input by Xah Lee

# Wish I'd been up for writing an elisp version!  This is from 2011-11.  My blog is offline at the moment.  -kurt

# Here is my answers to emacs lisp exercise: latitude-longitude-decimalize. The first is dumb. It hard codes the characters, which I think source-highlight mangled pretty badly. The code wasn't very pretty to start with. The second solution is much more robust. It allows and character(s) to be the separator(s).

def latitude_longitude_decimalize_kurt1(lat_lon_str):
lat_str, lon_str = lat_lon_str.split()
lat = int(lat_str.split(u'°')[0])
lat += int(lat_str.split(u'°')[1].split(u'′')[0]) / 60.
lat += float(lat_str.split(u'′')[1][:-4]) / 3600.
if 'S' in lat_str:
lat = -lat

lon = int(lon_str.split(u'°')[0])
lon += int(lon_str.split(u'°')[1].split(u'′')[0]) / 60.
lon += float(lon_str.split(u'′')[1][:-4]) / 3600.
if 'W' in lon_str:
lon = -lon

return lat,lon

print latitude_longitude_decimalize_kurt1( u"37°26′36.42″N 06°15′14.28″W")
# (37.44333333333333, -6.253888888888889)```

Kurt's second solution, using regex. (note: it didn't take account of North/South/East/West)

```# -*- coding: utf-8 -*-
# by Kurt Schwehr 2011-11 http://schwehr.org/blog/archives/2011-11.html

# this solution is much more robust. It allows and character(s) to be the separator(s).

import re

rawstr = r"""(?P<lat_deg>\d{1,2})\D+
(?P<lat_min>\d{1,2})\D+
(?P<lat_sec>\d{1,2}(\.\d+))\D+
(?P<lat_hemi>[NS])
\s+
(?P<lon_deg>\d{1,3})\D+
(?P<lon_min>\d{1,2})\D+
(?P<lon_sec>\d{1,2}(\.\d+))\D+
(?P<lon_hemi>[EW])
"""

compile_obj = re.compile(rawstr,  re.VERBOSE)

def decimalize_latitude_longitude_kurt2(lat_lon_str):
g = compile_obj.search(lat_lon_str).groupdict() # m is match
lat = int(g['lat_deg']) + int(g['lat_min'])/60. + float(g['lat_sec']) / 3600.
if g['lat_hemi'] == 'S':
lat = -lat
lon = int(g['lon_deg']) + int(g['lon_min'])/60. + float(g['lon_sec']) / 3600.
if g['lon_hemi'] == 'S':
lon = -lon
return {'y':lat, 'x':lon}

print decimalize_latitude_longitude_kurt2(u"37°26′36.42″N 06°15′14.28″W")
# {'y': 37.44345, 'x': 6.253966666666667}```

## Python 3

```# -*- coding: utf-8 -*-
# Python 3

# by Ian Kelly. 2011-11-29 http://groups.google.com/group/comp.lang.python/msg/ce03aab1d5864c01
# trivially modified by Xah Lee for unicode input

import re

def decimalize_latitude_longitude_Ian_Kelly(lat_lon_str):
regex = r"""(\d+)°(\d+)′([\d+.]+)″([NS])\s*(\d+)°(\d+)′([\d+.]+)″([EW])"""
match = re.match(regex, lat_lon_str)
if not match:
raise ValueError("Invalid input string: {0:r}".format(lat_lon_str))
def decimalize(degrees, minutes, seconds, direction):
decimal = int(degrees) + int(minutes) / 60 + float(seconds) / 3600
if direction in 'SW':
decimal = -decimal
return decimal
latitude = decimalize(*match.groups()[:4])
longitude = decimalize(*match.groups()[4:8])
return latitude, longitude

print(decimalize_latitude_longitude_Ian_Kelly("37°26′36.42″N 06°15′14.28″W"))
# (37.44345, -6.253966666666667)```

## Ruby

port of Kurt's python code to ruby. by Jean-Sébastien Ney. https://gist.github.com/49658505d40e11cb1769

```# -*- coding: utf-8 -*-
# Ruby

# by Jean-Sébastien Ney https://plus.google.com/114233876886322311595/posts https://gist.github.com/49658505d40e11cb1769

# Decimalize latitude longitude
# "37°26′36.42″N 06°15′14.28″W" ⇒ (37.44345 -6.253966666666667)

\$regex = %r{
(?<lat_deg>\d{1,2})\D+
(?<lat_min>\d{1,2})\D+
(?<lat_sec>\d{1,2}(\.\d+))\D+
(?<lat_hemi>[NS])
\s+
(?<lng_deg>\d{1,3})\D+
(?<lng_min>\d{1,2})\D+
(?<lng_sec>\d{1,2}(\.\d+))\D+
(?<lng_hemi>[EW])
}x

def decimalize_latitude_longitude_jsn(lat_lon_str)
if g = \$regex.match(lat_lon_str)
lat = g['lat_deg'].to_i + g['lat_min'].to_f/60 + g['lat_sec'].to_f/3600
lat = -lat if g['lat_hemi'] == 'S'
lng = g['lng_deg'].to_i + g['lng_min'].to_f/60 + g['lng_sec'].to_f/3600
lng = -lng if g['lng_hemi'] == 'W'
{y:lat, x:lng}
end
end

p decimalize_latitude_longitude_jsn "37°26′36.42″N 06°15′14.28″W"
# {:y=>37.44345, :x=>-6.253966666666667}```

## Test Data

```Las Vegas
36°10′30″N 115°08′11″W
36.175  -115.136389

NYC
40°39′51″N 73°56′19″W
40.664167 -73.938611

Shanghai
31°12′0″N 121°30′0″E
31.2 121.5

London
51°30′26″N 0°7′39″W
51.507222 -0.1275

Tokyo
35°41′22.22″N 139°41′30.12″E
35.689506 139.6917

SF
37°46′45.48″N 122°25′9.12″W
37.7793 -122.4192```