Programing: Decimalize Latitude Longitude
Here is 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 XLATLONSTR 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 (xtmpPair (xtt2 Lat-lon-str) xlatStr xlatNum xlonStr xlonNum xdeg xmin xsec xsign (xc (/ 1.0 60.0))) (setq xtt2 (replace-regexp-in-string "''" "″" xtt2 t t)) (setq xtt2 (replace-regexp-in-string "\"" "″" xtt2 t t)) (setq xtt2 (replace-regexp-in-string "'" "′" xtt2 t t)) (setq xtmpPair (split-string xtt2 " +")) (when (not (equal (length xtmpPair) 2)) (user-error "Error: input can contain only one space")) (setq xlatStr (elt xtmpPair 0)) (setq xlonStr (elt xtmpPair 1)) (if (string-match "\\`\\([0-9]+\\)°\\([0-9]+\\)′\\([.0-9]+\\)″\\(.?\\)\\'" xlatStr) (progn (setq xdeg (string-to-number (match-string 1 xlatStr))) (setq xmin (string-to-number (match-string 2 xlatStr))) (setq xsec (string-to-number (match-string 3 xlatStr))) (setq xsign (match-string 4 xlatStr)) (setq xlatNum (+ xdeg (* (+ xmin (* xsec xc)) xc))) (cond ((string-equal (downcase xsign) "n") nil) ((string-equal xsign "") nil) ((string-equal (downcase xsign) "s") (setq xlatNum (* -1 xlatNum))) (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]+\\)″\\(.?\\)\\'" xlonStr) (progn (setq xdeg (string-to-number (match-string 1 xlonStr))) (setq xmin (string-to-number (match-string 2 xlonStr))) (setq xsec (string-to-number (match-string 3 xlonStr))) (setq xsign (match-string 4 xlonStr)) (setq xlonNum (+ xdeg (* (+ xmin (* xsec xc)) xc))) (cond ((string-equal (downcase xsign) "e") nil) ((string-equal xsign "") nil) ((string-equal (downcase xsign) "w") (setq xlonNum (* -1 xlonNum))) (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 xlatNum xlonNum))) (message "%S" (latitude-longitude-decimalize-xah "37°26′36.42″N 06°15′14.28″W")) ;; "[37.44345 -6.253966666666667]"
solution by Jon Snader. http://irreal.org/blog/?p=795
;; -*- 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-equal "" 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)
[Jorge A. Alfaro Murillo https://plus.google.com/108846296933670938573/posts]
http://pastebin.com/16d67W71
;; -*- 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 https://plus.google.com/102101513984015207006/posts]'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 # https://plus.google.com/u/0/113859563190964307534/posts/aqmFrjcY7WH # 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
By Ian Kelly. . http://groups.google.com/group/comp.lang.python/msg/ce03aab1d5864c01
# 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://plus.google.com/114233876886322311595/posts].
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
- Programing Problem: Normalize a Vector of Any Dimension
- In-place Algorithm, Reverse List in JavaScript, Python, Perl, Lisp, Wolfram Lang
- Programing Problem: Construct a Tree Given Its Edges
- Programing Exercise, Validate Matching Brackets
- One Language to Rule Them All? Or, What Language to Use for Find Replace?
- Programing Challenge: Replace String Pairs