User:The Anome/lat-long.py
Appearance
# Lat/long functions in Python # Converts OSGB and Irish grid references to WGS84 latitude/longitude # and formats the resulting lat/long data as a Wikipedia {{coord}} tag # # Mostly auto-translated from a original PHP source code by # (various) original authors as listed below, with some manual cleanup. # # Python conversion by The Anome, November 2007 # # You may use and redistribute this code under the terms of the GPL # see http://www.gnu.org/copyleft/gpl.html ### ORIGINAL SOURCE COMMENTS BELOW # Lat Long functions in PHP # Note: from http://www.megalithia.com/search/llfuncshighlight.php # # With thanks to Andy, G4JNT for inspiration in GEOG, and to the OSGB # for their white paper on coordinate transformation describing the # iterative method used thanks to the Ordnance survey of Ireland for # details of the true and false origins of the Irish grid # # You may use and redistribute this code under the terms of the GPL # see http://www.gnu.org/copyleft/gpl.html # # # Written by Richard # www.megalithia.com # v0.something 27/2/2000 # v 1.01 28 June 2004 # v 1.02 6 Aug 2004 line 89 add "0" to chars in $ngr = stripcharsnotinbag Thx Andy # v 1.03 9 Mar 2005 Jan (Klingon) added conversion to WGS84 map datum and removed limitation of digits of the grid ref # v 1.04 10 Aug 2005 Richard correct error trapping (only manifest on malformed ngrs # # This code is predicated on the assumption that your are ONLY feeding # it Irish or UK Grid references. It uses the single char prefix of # Irish grid refs to tell the difference, UK grid refs have a two # letter prefix. We would like an even number of digits for the rest # of the grid ref. Anything in the NGR other than 0-9, A-Z, a-z is # eliminated. WARNING this assumes there are no decimal points in # your NGR components. (before v 1.03) you must have at least 4 # numerical digits eg TM1234 and no more than eight eg TM12345678 # otherwise you and your data are viewed with suspicion. NEW (Jan): # at least the letter prefix of the Grid ref, no upper limit # # The transformation from OSGB36/Ireland 1965 to WGS84 is more precise than 5 m. # # The function NGR2LL(ngr) is case insensitive: its return value is # (country, lat, long) or an exception is raised for any errors # import string, math, re, sys # Get the following maths fns sin = math.sin cos = math.cos tan = math.tan atan = math.atan atan2 = math.atan2 floor = math.floor sqrt = math.sqrt PI = math.pi def modified_alpha_offset(ch): n = ord(ch) - ord("A") if n > 8: # Letter 'I' is not used return n-1 else: return n def NGR2LL(text): # Must have at least one letter and one digit, with optional spaces matches = re.findall(r"^([A-Z]+) *([0-9]+) *([0-9]*)$", string.upper(text)) if not matches: raise "Malformed NGR" letters, left, right = matches[0] # If digits are split, # check for balanced digit groups if len(right) != 0 and len(left) != len(right): raise "Unbalanced digit groups in NGR" digits = left + right if len(digits) % 2: raise "Odd number of digits in NGR" halflen = len(digits)/2 multiplier = pow(10.0, (5.0-halflen)) n = int(digits[halflen:])*multiplier e = int(digits[:halflen])*multiplier if len(letters) == 1: return IE_NGR2LL(letters, e, n) if len(letters) == 2: return GB_NGR2LL(letters, e, n) raise "Wrong number of letters in NGR" def IE_NGR2LL(lett, e, n): # USE IRISH values # now for the heavy stuff T1 = modified_alpha_offset(lett[0]) e = 100000.0*(T1%5)+e n = n+100000.0*(4.0-floor(T1/5.0)) # deg2rad dr = 180.0/PI # True Origin is 8 deg W phi0ir = -8.0 # True Origin is 53.5 deg N lambda0ir = 53.5 # scale factor @ central meridian F0ir = 1.000035 # True origin in 200 km E of false origin E0ir = 200000.0 #True origin is 250km N of false origin N0ir = 250000.0 # semi-major axis (in line to equator) 1.000035 is yer scale @ central meridian air = 6377340.189*F0ir #semi-minor axis (in line to poles) bir = 6356034.447*F0ir # flatness = a1-b1/(a1 + b1) n1ir = 0.001673220384152058651484728058385228837777 # first eccentricity squared = 2*f-f^2 where f = (a1-b1)/a1 e2ir = 0.006670540293336110419293763349975612794125 # radius of earth #re = 6371.29 k = (n-N0ir)/air+lambda0ir/dr nextcounter = 0 while 1: nextcounter = nextcounter+1 k3 = k-lambda0ir/dr k4 = k+lambda0ir/dr j3 = (1.0+n1ir+1.25*pow(n1ir, 2.0)+1.25*pow(n1ir, 3.0))*k3 j4 = (3.0*n1ir+3.0*pow(n1ir, 2.0)+2.625*pow(n1ir, 3.0))*sin(k3)*cos(k4) j5 = (1.875*pow(n1ir, 2.0)+1.875*pow(n1ir, 3.0))*sin(2.0*k3)*cos(2.0*k4) j6 = 35.0/24.0*pow(n1ir, 3.0)*sin(3.0*k3)*cos(3.0*k4) m = bir*(j3-j4+j5-j6) k = k+(n-N0ir-m)/air if not ((abs(n-N0ir-m)>0.000000000001) and (nextcounter<10000)): break v = air/sqrt(1.0-e2ir*pow(sin(k), 2.0)) r = v*(1.0-e2ir)/(1.0-e2ir*pow(sin(k), 2.0)) h2 = v/r-1.0 y1 = e-E0ir j3 = tan(k)/(2.0*r*v) j4 = tan(k)/(24.0*r*pow(v, 3.0))*(5.0+3.0*pow(tan(k), 2.0)+h2-9.0*pow(tan(k), 2.0)*h2) j5 = tan(k)/(720.0*r*pow(v, 5.0))*(61.0+90.0*pow(tan(k), 2.0)+45.0*pow(tan(k), 4.0)) k9 = k-y1*y1*j3+pow(y1, 4.0)*j4-pow(y1, 6.0)*j5 j6 = 1.0/(cos(k)*v) j7 = 1.0/(cos(k)*6.0*pow(v, 3.0))*(v/r+2.0*pow(tan(k), 2.0)) j8 = 1.0/(cos(k)*120.0*pow(v, 5.0))*(5.0+28.0*pow(tan(k), 2.0)+24.0*pow(tan(k), 4.0)) j9 = 1.0/(cos(k)*5040.0*pow(v, 7.0)) j9 = j9*(61.0+662.0*pow(tan(k), 2.0)+1320.0*pow(tan(k), 4.0)+720.0*pow(tan(k), 6.0)) long = phi0ir+dr*(y1*j6-y1*y1*y1*j7+pow(y1, 5.0)*j8-pow(y1, 7.0)*j9) lat = k9*dr # v1.04 this bracket moved to just before elsif # } # convert long/lat to Cartesian coordinates v = 6377340.189/sqrt(1.0-e2ir*pow(sin(k), 2.0)) cartxa = v*cos(k9)*cos(long/dr) cartya = v*cos(k9)*sin(long/dr) cartza = (1.0-e2ir)*v*sin(k9) # Helmert-Transformation from Ireland 1965 to WGS84 map date rotx = 1.042/3600.0*PI/180.0 roty = 0.214/3600.0*PI/180.0 rotz = 0.631/3600.0*PI/180.0 scale = 8.15/1000000.0 cartxb = 482.53+(1.0+scale)*cartxa+rotz*cartya-roty*cartza cartyb = -130.596-rotz*cartxa+(1.0+scale)*cartya+rotx*cartza cartzb = 564.557+roty*cartxa-rotx*cartya+(1.0+scale)*cartza # convert Cartesian to long/lat awgs84 = 6378137.0 bwgs84 = 6356752.3141 e2wgs84 = 0.00669438003551279089034150031998869922791 lambdaradwgs84 = atan(cartyb/cartxb) long = lambdaradwgs84*180.0/PI pxy = sqrt(pow(cartxb, 2.0)+pow(cartyb, 2.0)) phiradwgs84 = atan(cartzb/pxy/(1.0-e2wgs84)) nextcounter = 0 while 1: nextcounter = nextcounter+1 v = awgs84/sqrt(1.0-e2wgs84*pow(sin(phiradwgs84), 2.0)) phinewwgs84 = atan((cartzb+e2wgs84*v*sin(phiradwgs84))/pxy) # Now testing _before_ the assignment below... if abs(phinewwgs84-phiradwgs84)<0.000000000001: break if nextcounter > 20: raise "loop not converging" phiradwgs84 = phinewwgs84 lat = phiradwgs84*180.0/PI return ("IE", lat, long) def GB_NGR2LL(lett, e, n): # Use British values # now for the heavy stuff T1 = modified_alpha_offset(lett[0]) T2 = modified_alpha_offset(lett[1]) e = 500000.0*(T1%5)+100000.0*(T2%5)-1000000.0+e n = 1900000.0-500000.0*floor(T1/5.0)-100000.0*floor(T2/5.0)+n # deg2rad dr = 180.0/PI # True Origin is 2 deg W phi0uk = -2.0 # True Origin is 49 deg N lambda0uk = 49.0 # scale factor @ central meridian F0uk = 0.9996012717 # True origin in 400 km E of false origin E0uk = 400000.0 #True origin is 100 km S of false origin N0uk = -100000.0 # semi-major axis (in line to equator) 0.996012717 is yer scale @ central meridian auk = 6377563.396*F0uk #semi-minor axis (in line to poles) buk = 6356256.91*F0uk # flatness = a1-b1/(a1+b1) n1uk = 0.00167322025032508731869331280635710896296 # first eccentricity squared = 2*f-f^2where f = (a1-b1)/a1 e2uk = 0.006670539761597529073698869358812557054558 # radius of earth #re = 6371.29 k = (n-N0uk)/auk+lambda0uk/dr nextcounter = 0 while 1: nextcounter = nextcounter+1 k3 = k-lambda0uk/dr k4 = k+lambda0uk/dr j3 = (1.0+n1uk+1.25*pow(n1uk, 2.0)+1.25*pow(n1uk, 3.0))*k3 j4 = (3.0*n1uk+3.0*pow(n1uk, 2.0)+2.625*pow(n1uk, 3.0))*sin(k3)*cos(k4) j5 = (1.875*pow(n1uk, 2.0)+1.875*pow(n1uk, 3.0))*sin(2.0*k3)*cos(2.0*k4) j6 = 35.0/24.0*pow(n1uk, 3.0)*sin(3.0*k3)*cos(3.0*k4) m = buk*(j3-j4+j5-j6) k = k+(n-N0uk-m)/auk if not ((abs(n-N0uk-m)>0.000000000001) and (nextcounter<10000)): break v = auk/sqrt(1.0-e2uk*pow(sin(k), 2.0)) r = v*(1.0-e2uk)/(1.0-e2uk*pow(sin(k), 2.0)) h2 = v/r-1.0 y1 = e-E0uk j3 = tan(k)/(2.0*r*v) j4 = tan(k)/(24.0*r*pow(v, 3.0))*(5.0+3.0*pow(tan(k), 2.0)+h2-9.0*pow(tan(k), 2.0)*h2) j5 = tan(k)/(720.0*r*pow(v, 5.0))*(61.0+90.0*pow(tan(k), 2.0)+45.0*pow(tan(k), 4.0)) k9 = k-y1*y1*j3+pow(y1, 4.0)*j4-pow(y1, 6.0)*j5 j6 = 1.0/(cos(k)*v) j7 = 1.0/(cos(k)*6.0*pow(v, 3.0))*(v/r+2.0*pow(tan(k), 2.0)) j8 = 1.0/(cos(k)*120.0*pow(v, 5.0))*(5.0+28.0*pow(tan(k), 2.0)+24.0*pow(tan(k), 4.0)) j9 = 1.0/(cos(k)*5040.0*pow(v, 7.0)) j9 = j9*(61.0+662.0*pow(tan(k), 2.0)+1320.0*pow(tan(k), 4.0)+720.0*pow(tan(k), 6.0)) long = phi0uk+dr*(y1*j6-y1*y1*y1*j7+pow(y1, 5.0)*j8-pow(y1, 7.0)*j9) lat = k9*dr # v1.04 this bracket moved to just before elsif # } # convert long/lat to Cartesian coordinates v = 6377563.396/sqrt(1.0-e2uk*pow(sin(k), 2.0)) cartxa = v*cos(k9)*cos(long/dr) cartya = v*cos(k9)*sin(long/dr) cartza = (1.0-e2uk)*v*sin(k9) # Helmert-Transformation from OSGB36 to WGS84 map date rotx = -0.1502/3600.0*PI/180.0 roty = -0.2470/3600.0*PI/180.0 rotz = -0.8421/3600.0*PI/180.0 scale = -20.4894/1000000.0 cartxb = 446.448+(1.0+scale)*cartxa+rotz*cartya-roty*cartza cartyb = -125.157-rotz*cartxa+(1.0+scale)*cartya+rotx*cartza cartzb = 542.06+roty*cartxa-rotx*cartya+(1.0+scale)*cartza # convert Cartesian to long/lat awgs84 = 6378137.0 bwgs84 = 6356752.3141 e2wgs84 = 0.00669438003551279089034150031998869922791 lambdaradwgs84 = atan(cartyb/cartxb) long = lambdaradwgs84*180.0/PI pxy = sqrt(pow(cartxb, 2.0)+pow(cartyb, 2.0)) phiradwgs84 = atan(cartzb/pxy/(1.0-e2wgs84)) nextcounter = 0 while 1: nextcounter = nextcounter+1 v = awgs84/sqrt(1.0-e2wgs84*pow(sin(phiradwgs84), 2.0)) phinewwgs84 = atan((cartzb+e2wgs84*v*sin(phiradwgs84))/pxy) # Now testing _before_ the assignment below... if abs(phinewwgs84-phiradwgs84)<0.000000000001: break if nextcounter > 20: raise "loop not converging" phiradwgs84 = phinewwgs84 lat = phiradwgs84*180.0/PI # v 1.04 mod return ("GB", lat, long) def dirselect(x, options): if x < 0: return options[1] else: return options[0] osref = string.join(sys.argv[1:]) country, lat, long = NGR2LL(osref) print ( "{{coor title d|%1.5lf|%s|%1.5lf|%s|region:%s_source:enwiki-osgb36(%s)}}" % (abs(lat), dirselect(lat, "NS"), abs(long), dirselect(long, "EW"), country, osref))