<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AOrdnance_Survey_coordinates</id>
	<title>Module:Ordnance Survey coordinates - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://the-democratika.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AOrdnance_Survey_coordinates"/>
	<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Ordnance_Survey_coordinates&amp;action=history"/>
	<updated>2026-04-05T02:15:13Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://the-democratika.com/wiki/index.php?title=Module:Ordnance_Survey_coordinates&amp;diff=6765&amp;oldid=prev</id>
		<title>&gt;Hike395: factor data out into subpage, use loadData</title>
		<link rel="alternate" type="text/html" href="https://the-democratika.com/wiki/index.php?title=Module:Ordnance_Survey_coordinates&amp;diff=6765&amp;oldid=prev"/>
		<updated>2024-11-04T14:29:39Z</updated>

		<summary type="html">&lt;p&gt;factor data out into subpage, use loadData&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;--[[ Ordnance Survey / Lat Long functions in Lua &lt;br /&gt;
&lt;br /&gt;
****** ORDNANCE SURVEY TO LAT LONG FUNCTIONS ******&lt;br /&gt;
&lt;br /&gt;
Ported to Lua from PHP by Wikipedia User Hike395, 18 Aug 2019&lt;br /&gt;
&lt;br /&gt;
found by RWH at http://www.megalithia.com/search/llfuncshighlight.php&lt;br /&gt;
&lt;br /&gt;
With thanks to Andy, G4JNT for inspiration in GEOG, and to the OSGB for their white paper on coordinate transformation&lt;br /&gt;
describing the iterative method used&lt;br /&gt;
thanks to the Ordnance survey of Ireland for details of the true and false origins of the Irish grid&lt;br /&gt;
&lt;br /&gt;
You may use and redistribute this code under the terms of the GPL see http://www.gnu.org/copyleft/gpl.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Written by Richard&lt;br /&gt;
www.megalithia.com&lt;br /&gt;
v0.something 27/2/2000&lt;br /&gt;
v 1.01 28 June 2004&lt;br /&gt;
v 1.02 6 Aug 2004 line 89 add &amp;quot;0&amp;quot; to chars in ngr=stripcharsnotinbag Thx Andy&lt;br /&gt;
v 1.03 9 Mar 2005 Jan (Klingon) added conversion to WGS84 map datum and removed limitation of digits of the grid ref&lt;br /&gt;
v 1.04 10 Aug 2005 Richard correct error trapping (only manifest on malformed ngrs&lt;br /&gt;
&lt;br /&gt;
This code is predicated on the assumption that your are ONLY feeding it Irish or UK Grid references.&lt;br /&gt;
It uses the single char prefix of Irish grid refs to tell the difference, UK grid refs have a two letter prefix.&lt;br /&gt;
We would like an even number of digits for the rest of the grid ref.&lt;br /&gt;
Anything in the NGR other than 0-9, A-Z, a-z is eliminated.&lt;br /&gt;
WARNING this assumes there are no decimal points in your NGR components.&lt;br /&gt;
&lt;br /&gt;
The transformation from OSGB36/Ireland 1965 to WGS84 is more precise than 5 m.&lt;br /&gt;
&lt;br /&gt;
The function is case insensitive&lt;br /&gt;
&lt;br /&gt;
Lat/Long to Ordnance Survey conversion is at bottom of file, see further authorship there&lt;br /&gt;
&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
local oscoord = {}&lt;br /&gt;
local getArgs = require(&amp;#039;Module:Arguments&amp;#039;).getArgs&lt;br /&gt;
local yesno = require(&amp;#039;Module:Yesno&amp;#039;)&lt;br /&gt;
local namespace = mw.title.getCurrentTitle().namespace&lt;br /&gt;
&lt;br /&gt;
local pow = math.pow&lt;br /&gt;
local sqrt = math.sqrt&lt;br /&gt;
local abs = math.abs&lt;br /&gt;
local floor = math.floor&lt;br /&gt;
local ceil = math.ceil&lt;br /&gt;
local sin = math.sin&lt;br /&gt;
local cos = math.cos&lt;br /&gt;
local tan = math.tan&lt;br /&gt;
local atan = math.atan&lt;br /&gt;
local deg2rad = math.rad&lt;br /&gt;
local rad2deg = math.deg&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
--[[ DATUM SHIFT USING HELMERT TRANSFORMATION&lt;br /&gt;
 *&lt;br /&gt;
 * height above the ellipsoid is ignored&lt;br /&gt;
 * latitude and longitude must be given in decimal degrees&lt;br /&gt;
 *&lt;br /&gt;
 * no transformation if abs(latitude)&amp;gt;89 or abs(longitude)&amp;gt;89&lt;br /&gt;
 * (lifting this restriction requires some more lines of code)&lt;br /&gt;
 *&lt;br /&gt;
 * Written in 2009 by user Telford at de.wikipedia&lt;br /&gt;
 * Based on math described in &amp;quot;A Guide to Coordinate Systems in Great Britain&amp;quot;&lt;br /&gt;
 * by the UK Ordnance Survey&lt;br /&gt;
 * URL: https://www.ordnancesurvey.co.uk/documents/resources/guide-coordinate-systems-great-britain.pdf&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
-- Datum parameters&lt;br /&gt;
&lt;br /&gt;
local data = mw.loadData(&amp;#039;Module:Ordnance Survey coordinates/data&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
local OSGBglobe = data.OSGBglobe&lt;br /&gt;
local IEglobe = data.IEglobe&lt;br /&gt;
local WGSglobe = data.WGSglobe&lt;br /&gt;
local WGS2OSGBparam = data.WGS2OSGBparam&lt;br /&gt;
local OSGB2WGSparam = data.OSGB2WGSparam&lt;br /&gt;
local IE2WGSparam = data.IE2WGSparam&lt;br /&gt;
&lt;br /&gt;
local function HelmertDatumShift ( latitude , longitude, param )&lt;br /&gt;
	-- latitude and longitude are in degrees&lt;br /&gt;
    -- return if latitude or longitude out of range &lt;br /&gt;
&lt;br /&gt;
    if abs(latitude) &amp;gt; 89. or abs(longitude) &amp;gt; 89. then&lt;br /&gt;
    	return {lat=latitude, lon=longitude}&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
	param = param or WGS2OSGBparam&lt;br /&gt;
&lt;br /&gt;
    -- parameters for ellipsoids&lt;br /&gt;
    -- Annex A.1, Ordnance Survey document --&lt;br /&gt;
&lt;br /&gt;
    local a1 = param.semimajor_src&lt;br /&gt;
    local b1 = param.semiminor_src&lt;br /&gt;
    local e1 = param.ecc_src&lt;br /&gt;
&lt;br /&gt;
    local a2 = param.semimajor_dst&lt;br /&gt;
    local b2 = param.semiminor_dst&lt;br /&gt;
    local e2 = param.ecc_dst&lt;br /&gt;
&lt;br /&gt;
    -- convert latitude and longitude to cartesian coordinates&lt;br /&gt;
    -- math in Annex B.1, Ordnance Survey document&lt;br /&gt;
    local phi = deg2rad ( latitude )&lt;br /&gt;
    local lambda = deg2rad ( longitude )&lt;br /&gt;
    local cosphi = cos ( phi )&lt;br /&gt;
    local sinphi = sin ( phi )&lt;br /&gt;
    local coslambda = cos ( lambda )&lt;br /&gt;
    local sinlambda = sin ( lambda )&lt;br /&gt;
&lt;br /&gt;
    local ny = a1 / sqrt ( 1. - e1*sinphi*sinphi )&lt;br /&gt;
&lt;br /&gt;
    local x1 = ny * cosphi * coslambda&lt;br /&gt;
    local y1 = ny * cosphi * sinlambda&lt;br /&gt;
    local z1 = ny * ( 1. - e1 ) * sinphi&lt;br /&gt;
    -- the helmert transformation proper&lt;br /&gt;
    -- Equation 3, Section 6.2, Ordnance Survey document&lt;br /&gt;
&lt;br /&gt;
    local x2 = x1 + param.tx + ( param.s0 * x1 - param.rz * y1 + param.ry * z1 )&lt;br /&gt;
    local y2 = y1 + param.ty + ( param.rz * x1 + param.s0 * y1 - param.rx * z1 )&lt;br /&gt;
    local z2 = z1 + param.tz + ( -param.ry *x1 + param.rx * y1 + param.s0 * z1 )&lt;br /&gt;
    -- convert cartesian coordinates to latitude and longitude&lt;br /&gt;
    -- math in Annex B.2, of Ordnance Survey document &lt;br /&gt;
&lt;br /&gt;
    lambda = atan ( y2 / x2 )&lt;br /&gt;
&lt;br /&gt;
    local p2 = sqrt ( x2*x2 + y2*y2 )&lt;br /&gt;
    phi = atan ( z2 / p2*(1.-e2) )&lt;br /&gt;
&lt;br /&gt;
    local n0 = 101&lt;br /&gt;
&lt;br /&gt;
	local phi_alt&lt;br /&gt;
    repeat&lt;br /&gt;
	    phi_alt = phi&lt;br /&gt;
	    ny = a2 / sqrt ( 1. - e2 * sin(phi) * sin(phi) )&lt;br /&gt;
	    phi = atan ( (z2 + e2 * ny * sin(phi)) / p2 )&lt;br /&gt;
	    n0 = n0 - 1&lt;br /&gt;
    until abs ( phi - phi_alt ) &amp;lt;= 0.000000000001 or n0 &amp;lt;= 0&lt;br /&gt;
&lt;br /&gt;
	return {lat=rad2deg(phi),lon=rad2deg(lambda)}&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--  LAT LONG TO OS GRID LIBRARY RESUMES HERE&lt;br /&gt;
&lt;br /&gt;
local function northeast(lett,num,shift)&lt;br /&gt;
   -- split into northings and eastings&lt;br /&gt;
   local le=mw.ustring.len(num)&lt;br /&gt;
   if le%2 == 1 then&lt;br /&gt;
   	 return {err=&amp;quot;Malformed numerical part of NGR&amp;quot;}&lt;br /&gt;
   end&lt;br /&gt;
   local pr=le/2&lt;br /&gt;
   local n = mw.ustring.sub(num,pr+1)&lt;br /&gt;
   local e = mw.ustring.sub(num,1,pr)&lt;br /&gt;
   -- Hack to move to center of square: append a 5 to northings and eastings&lt;br /&gt;
   shift = yesno(shift)&lt;br /&gt;
   if shift then&lt;br /&gt;
     n = n..&amp;quot;5&amp;quot;&lt;br /&gt;
     e = e..&amp;quot;5&amp;quot;&lt;br /&gt;
     pr = pr+1&lt;br /&gt;
   end&lt;br /&gt;
   -- end hack&lt;br /&gt;
   n = n == &amp;#039;&amp;#039; and 0 or n&lt;br /&gt;
   e = e == &amp;#039;&amp;#039; and 0 or e&lt;br /&gt;
   pr = pow(10.0,(5.0-pr))&lt;br /&gt;
   local T1 = mw.ustring.byte(mw.ustring.sub(lett,1,1))-65&lt;br /&gt;
   if T1&amp;gt;8 then&lt;br /&gt;
     T1 = T1-1&lt;br /&gt;
   end&lt;br /&gt;
   local T2 = nil&lt;br /&gt;
   if mw.ustring.len(lett)&amp;gt;1 then&lt;br /&gt;
     T2 = mw.ustring.byte(mw.ustring.sub(lett,2))-65&lt;br /&gt;
     if T2&amp;gt;8 then&lt;br /&gt;
       T2 = T2-1&lt;br /&gt;
     end&lt;br /&gt;
   end&lt;br /&gt;
   return {n=n,e=e,pr=pr,T1=T1,T2=T2}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function EN2LL(e,n,datum)&lt;br /&gt;
   local a = datum.semimajor*datum.scale&lt;br /&gt;
   local b = datum.semiminor*datum.scale&lt;br /&gt;
   local lat0rad = deg2rad(datum.lat0)&lt;br /&gt;
   local n1 = datum.n1&lt;br /&gt;
   local n12 = n1*n1&lt;br /&gt;
   local n13 = n12*n1&lt;br /&gt;
   local k=(n-datum.n0)/a+lat0rad&lt;br /&gt;
   local nextcounter=0&lt;br /&gt;
   local j3, j4, j5, j6, m&lt;br /&gt;
   repeat&lt;br /&gt;
     nextcounter=nextcounter+1&lt;br /&gt;
     local k3=k-lat0rad&lt;br /&gt;
     local k4=k+lat0rad&lt;br /&gt;
     j3=(1.0+n1+1.25*n12+1.25*n13)*k3&lt;br /&gt;
     j4=(3.0*n1+3.0*n12+2.625*n13)*sin(k3)*cos(k4)&lt;br /&gt;
     j5=(1.875*n12+1.875*n13)*sin(2.0*k3)*cos(2.0*k4)&lt;br /&gt;
     j6=35.0/24.0*n13*sin(3.0*k3)*cos(3.0*k4)&lt;br /&gt;
     m=b*(j3-j4+j5-j6)&lt;br /&gt;
     k=k+(n-datum.n0-m)/a&lt;br /&gt;
   until abs(n-datum.n0-m)&amp;lt;=0.000000001 or nextcounter&amp;gt;=100&lt;br /&gt;
   local x = 1.0-datum.ecc*pow(sin(k),2.0)&lt;br /&gt;
   local v=a/sqrt(x)&lt;br /&gt;
   local r=v*(1.0-datum.ecc)/x&lt;br /&gt;
   local h2=v/r-1.0&lt;br /&gt;
   local y1=e-datum.e0&lt;br /&gt;
   local tank = tan(k)&lt;br /&gt;
   local tank2 = tank*tank&lt;br /&gt;
   local tank4 = tank2*tank2&lt;br /&gt;
   local tank6 = tank2*tank4&lt;br /&gt;
   local cosk = cos(k)&lt;br /&gt;
   local yv = y1/v -- dimensionless quantity in series expansion&lt;br /&gt;
   local yv2 = yv*yv&lt;br /&gt;
   local yv3 = yv*yv2&lt;br /&gt;
   local yv5 = yv3*yv2&lt;br /&gt;
   local yv7 = yv5*yv2&lt;br /&gt;
   j3=tank/(2.0*r)&lt;br /&gt;
   j4=tank/(24.0*r)*(5.0+3.0*tank2+h2-9.0*tank2*h2)&lt;br /&gt;
   j5=tank/(720.0*r)*(61.0+90.0*tank2+45.0*tank4)&lt;br /&gt;
   local k9=k-y1*(yv*j3+yv3*j4-yv5*j5)&lt;br /&gt;
   j6=1.0/(cosk)&lt;br /&gt;
   local j7=1.0/(cosk*6.0)*(v/r+2.0*tank2)&lt;br /&gt;
   local j8=1.0/(cosk*120.0)*(5.0+28.0*tank2+24.0*tank4)&lt;br /&gt;
   local j9=1.0/(cosk*5040.0)&lt;br /&gt;
   j9=j9*(61.0+662.0*tank2+1320.0*tank4+720.0*tank6)&lt;br /&gt;
   local long=datum.lon0+rad2deg(yv*j6-yv3*j7+yv5*j8-yv7*j9)&lt;br /&gt;
   local lat=rad2deg(k9)&lt;br /&gt;
   return {lat=lat,lon=long}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function GBEN2LL(e,n)&lt;br /&gt;
   local latlong = EN2LL(e,n,OSGBglobe)&lt;br /&gt;
   local helmert = HelmertDatumShift ( latlong.lat, latlong.lon, OSGB2WGSparam)&lt;br /&gt;
   return {region=&amp;quot;GB&amp;quot;,lat=helmert.lat,long=helmert.lon}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function GB2LL(lett,num)&lt;br /&gt;
   -- British OS to Lat+Long&lt;br /&gt;
   -- first caclulate e,n&lt;br /&gt;
   -- computing e and n exactly, to get SW corner of box&lt;br /&gt;
   local ne = northeast(lett,num)&lt;br /&gt;
   if ne.err then&lt;br /&gt;
   	 return {region=&amp;quot;GB&amp;quot;,err=ne.err}&lt;br /&gt;
   end&lt;br /&gt;
   -- use British definition of e and n&lt;br /&gt;
   local e=500000.0*(ne.T1%5)+100000.0*(ne.T2%5)-1000000.0+ne.e*ne.pr&lt;br /&gt;
   local n=1900000.0-500000.0*floor(ne.T1/5)-100000.0*floor(ne.T2/5)+ne.n*ne.pr&lt;br /&gt;
   local result = GBEN2LL(e,n)&lt;br /&gt;
   result.prec = 0.8165*ne.pr&lt;br /&gt;
   return result&lt;br /&gt;
end&lt;br /&gt;
   &lt;br /&gt;
local function IrishEN2LL(e,n)&lt;br /&gt;
   local latlong = EN2LL(e,n,IEglobe)&lt;br /&gt;
   local helmert = HelmertDatumShift ( latlong.lat, latlong.lon, IE2WGSparam)&lt;br /&gt;
   return {region=&amp;quot;IE&amp;quot;,lat=helmert.lat,long=helmert.lon}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function Irish2LL(lett,num)&lt;br /&gt;
   -- Irish OS to Lat+Long&lt;br /&gt;
   -- first caclulate e,n&lt;br /&gt;
   -- computing e and n exactly, to get SW corner of box&lt;br /&gt;
   local ne = northeast(lett,num)&lt;br /&gt;
   if ne.err then&lt;br /&gt;
   	 return {region=&amp;quot;IE&amp;quot;, err=ne.err}&lt;br /&gt;
   end&lt;br /&gt;
   -- use Irish definition of northing and easting&lt;br /&gt;
   local e=100000.0*(ne.T1%5.0)+ne.e*ne.pr&lt;br /&gt;
   local n=ne.n*ne.pr+100000.0*(4.0-floor(ne.T1/5.0))&lt;br /&gt;
   local result = IrishEN2LL(e,n)&lt;br /&gt;
   result.prec = 0.8165*ne.pr  -- useful @ Commons&lt;br /&gt;
   return result&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function empty(s)&lt;br /&gt;
  return not s or s == &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function NGR2LL(ngr)&lt;br /&gt;
  local result = {}&lt;br /&gt;
  local _ = 0&lt;br /&gt;
  ngr, _ = mw.ustring.gsub(mw.ustring.upper(ngr),&amp;quot;[%s%p]&amp;quot;,&amp;quot;&amp;quot;)&lt;br /&gt;
  local first, last, lett, num = mw.ustring.find(ngr,&amp;quot;^([A-Z]+)(%d+)$&amp;quot;)&lt;br /&gt;
  if not first or empty(lett) or empty(num) or mw.ustring.len(lett) &amp;gt; 2 then&lt;br /&gt;
  	return {err=&amp;quot;Malformed NGR&amp;quot;}&lt;br /&gt;
  end&lt;br /&gt;
  if mw.ustring.len(lett) == 1 then&lt;br /&gt;
    return Irish2LL(lett,num)&lt;br /&gt;
  end&lt;br /&gt;
  return GB2LL(lett, num)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function split(s,sep)&lt;br /&gt;
-- split a string s into chunks, separated by sep&lt;br /&gt;
  sep = sep or &amp;quot;%s&amp;quot;&lt;br /&gt;
  local t = {}&lt;br /&gt;
  for chunk in mw.ustring.gmatch(s,&amp;quot;([^&amp;quot;..sep..&amp;quot;]+)&amp;quot;) do&lt;br /&gt;
    table.insert(t,chunk)&lt;br /&gt;
  end&lt;br /&gt;
  return t&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function trim(s)&lt;br /&gt;
  local _ = 0&lt;br /&gt;
  s, _ = mw.ustring.gsub(s,&amp;quot;^%s+&amp;quot;,&amp;quot;&amp;quot;)&lt;br /&gt;
  s, _ = mw.ustring.gsub(s,&amp;quot;%s+$&amp;quot;,&amp;quot;&amp;quot;)&lt;br /&gt;
  return s&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function alldigits(s)&lt;br /&gt;
  return not mw.ustring.find(s,&amp;quot;%D&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function warning(errmsg)&lt;br /&gt;
  local preview = require(&amp;#039;Module:If preview&amp;#039;)&lt;br /&gt;
  local msg = errmsg or &amp;#039;Empty OS grid ref&amp;#039;&lt;br /&gt;
&lt;br /&gt;
  local html = preview._warning({ msg })&lt;br /&gt;
&lt;br /&gt;
  if namespace == 0 and errmsg then&lt;br /&gt;
  	html = html..&amp;#039;[[Category:Pages with malformed OS coordinates]]&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  return html&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- generate URL of geohack map&lt;br /&gt;
local function geohack(main_args, other_args, LL)&lt;br /&gt;
  -- create geohack link. Example:&lt;br /&gt;
  -- https://geohack.toolforge.org/geohack.php?pagename=Mount_Whitney&amp;amp;params=36.578580925_N_118.29199495_W_type:mountain_region:US-CA_scale:100000_source:NGS&lt;br /&gt;
  local url = &amp;#039;https://geohack.toolforge.org/geohack.php?&amp;#039;&lt;br /&gt;
  local input = main_args[1]&lt;br /&gt;
  local namearg = main_args[&amp;quot;name&amp;quot;]&lt;br /&gt;
  local current_page = mw.title.getCurrentTitle()&lt;br /&gt;
  local pagename = mw.uri.encode( current_page.prefixedText, &amp;#039;WIKI&amp;#039; )&lt;br /&gt;
  if not empty(pagename) then&lt;br /&gt;
  	url = url..&amp;#039;pagename=&amp;#039;..pagename..&amp;#039;&amp;amp;&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  url = url..&amp;#039;params=&amp;#039;..mw.ustring.format(&amp;#039;%.6f&amp;#039;,LL.lat)..&amp;#039;_N_&amp;#039;&lt;br /&gt;
  if LL.long &amp;lt; 0 then&lt;br /&gt;
  	url = url..mw.ustring.format(&amp;#039;%.6f&amp;#039;,-LL.long)..&amp;#039;_W&amp;#039;&lt;br /&gt;
  else&lt;br /&gt;
  	url = url..mw.ustring.format(&amp;#039;%.6f&amp;#039;,LL.long)..&amp;#039;_E&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  for _, a in ipairs(other_args) do&lt;br /&gt;
  	url = url..&amp;#039;_&amp;#039;..a&lt;br /&gt;
  end&lt;br /&gt;
  if not mw.ustring.find(input,&amp;quot;region&amp;quot;) and LL.region then&lt;br /&gt;
    url = url..&amp;#039;_region:&amp;#039;..LL.region&lt;br /&gt;
  end&lt;br /&gt;
  if not mw.ustring.find(input,&amp;quot;scale&amp;quot;) and&lt;br /&gt;
     not mw.ustring.find(input,&amp;quot;type&amp;quot;) and&lt;br /&gt;
     not mw.ustring.find(input,&amp;quot;dim&amp;quot;) and LL.prec then&lt;br /&gt;
     	url = url..&amp;#039;_dim:&amp;#039;..floor(50*LL.prec+0.5)..&amp;#039;m&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  if not empty(namearg) then&lt;br /&gt;
    url = url .. &amp;quot;&amp;amp;title=&amp;quot; .. mw.uri.encode(namearg)&lt;br /&gt;
  end&lt;br /&gt;
  return url&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- function to generate direct link to OS map&lt;br /&gt;
local function directLink(main_args, other_args, LL)&lt;br /&gt;
  -- create link to Bing server for OS maps. Example:&lt;br /&gt;
  -- https://www.bing.com/maps/?mkt=en-gb&amp;amp;v=2&amp;amp;cp=56.796026%7E-5.01307&amp;amp;lvl=16.0&amp;amp;sp=Point.56.796029_-5.004711_Ben+Nevis&amp;amp;sty=s&amp;amp;style=s&lt;br /&gt;
  local current_page = mw.title.getCurrentTitle()&lt;br /&gt;
  local namearg = mw.uri.encode( main_args[&amp;quot;name&amp;quot;] or current_page.prefixedText, &amp;#039;QUERY&amp;#039; )&lt;br /&gt;
  local args = {}&lt;br /&gt;
  for _, a in ipairs(other_args) do&lt;br /&gt;
    local splitOut = mw.text.split(a, &amp;#039;:&amp;#039;, true)&lt;br /&gt;
    args[splitOut[1]] = splitOut[2]&lt;br /&gt;
  end&lt;br /&gt;
  if not args.scale and not args.type and not args.dim then&lt;br /&gt;
    args.dim = LL.prec and tostring(floor(50*LL.prec+0.5))..&amp;#039;m&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  args.viewport_cm = 10&lt;br /&gt;
  local zoom = require(&amp;#039;Module:Infobox dim&amp;#039;)._zoom&lt;br /&gt;
  local lvl = zoom(args) or 12&lt;br /&gt;
  local url = mw.ustring.format(&amp;#039;https://www.bing.com/maps/?mkt=en-gb&amp;amp;v=2&amp;amp;cp=%.6f~%.6f&amp;amp;lvl=%d&amp;amp;sp=Point.%.6f_%.6f&amp;#039;,LL.lat,LL.long,lvl,LL.lat,LL.long)&lt;br /&gt;
  if not empty(namearg) then&lt;br /&gt;
    url = url..&amp;#039;_&amp;#039;..namearg&lt;br /&gt;
  end&lt;br /&gt;
  url = url..&amp;#039;&amp;amp;sty=s&amp;amp;style=s&amp;#039;&lt;br /&gt;
  return url&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord._main(main_args)&lt;br /&gt;
  local input = main_args[1]&lt;br /&gt;
  if empty(input) then&lt;br /&gt;
  	return warning(nil)&lt;br /&gt;
  end&lt;br /&gt;
  local linktitle = main_args[2]&lt;br /&gt;
  local rawurl = yesno(main_args[&amp;quot;rawurl&amp;quot;])&lt;br /&gt;
  local direct = yesno(main_args[&amp;quot;direct&amp;quot;])&lt;br /&gt;
  local args = split(input,&amp;#039;_&amp;#039;)&lt;br /&gt;
  local LL&lt;br /&gt;
  local restargs = 1&lt;br /&gt;
  if #args &amp;gt;= 2 and alldigits(args[2]) then&lt;br /&gt;
    if mw.ustring.sub(args[1],1,1) == &amp;#039;i&amp;#039; then&lt;br /&gt;
      local firstArg = mw.ustring.sub(args[1],2)&lt;br /&gt;
      if alldigits(firstArg) then&lt;br /&gt;
        LL = IrishEN2LL(firstArg,args[2])&lt;br /&gt;
	    restargs = 3&lt;br /&gt;
	    if empty(linktitle) then&lt;br /&gt;
          linktitle=args[1]..&amp;#039;_&amp;#039;..args[2]&lt;br /&gt;
	    end&lt;br /&gt;
      end&lt;br /&gt;
    elseif alldigits(args[1]) then&lt;br /&gt;
      LL = GBEN2LL(args[1],args[2])&lt;br /&gt;
      restargs = 3&lt;br /&gt;
      if empty(linktitle) then&lt;br /&gt;
        linktitle=args[1]..&amp;#039;_&amp;#039;..args[2]&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
  else&lt;br /&gt;
    LL = NGR2LL(args[1])&lt;br /&gt;
    restargs = 2&lt;br /&gt;
    if empty(linktitle) then&lt;br /&gt;
      linktitle=args[1]&lt;br /&gt;
    end&lt;br /&gt;
  end&lt;br /&gt;
  linktitle = trim(linktitle)&lt;br /&gt;
  if not empty(LL.err) then&lt;br /&gt;
    return linktitle ..warning(LL.err)&lt;br /&gt;
  end&lt;br /&gt;
  LL.lat = LL.lat or 0&lt;br /&gt;
  LL.long = LL.long or 0&lt;br /&gt;
  LL.lat = ceil(LL.lat*1000000)/1000000&lt;br /&gt;
  LL.long = ceil(LL.long*1000000)/1000000&lt;br /&gt;
  local other_args = {}&lt;br /&gt;
  for i = restargs, #args do&lt;br /&gt;
    table.insert(other_args, args[i])&lt;br /&gt;
  end&lt;br /&gt;
  local url&lt;br /&gt;
  if not direct then&lt;br /&gt;
    url = geohack(main_args, other_args, LL)&lt;br /&gt;
  else&lt;br /&gt;
    url = directLink(main_args, other_args, LL)&lt;br /&gt;
  end&lt;br /&gt;
  if not rawurl then&lt;br /&gt;
	url = &amp;#039;[&amp;#039;..url..&amp;#039; &amp;#039;..linktitle..&amp;#039;]&amp;#039;&lt;br /&gt;
  end&lt;br /&gt;
  return url&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord._oscoord(args)&lt;br /&gt;
	local output = &amp;#039;&amp;lt;span class=&amp;quot;plainlinks nourlexpansion&amp;quot; style=&amp;quot;white-space: nowrap&amp;quot;&amp;gt;&amp;#039; .. oscoord._main(args) .. &amp;#039;&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	if namespace == 0 then&lt;br /&gt;
		output = output .. &amp;#039;[[Category:Articles with OS grid coordinates]]&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	return output&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord.main(frame)&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
	return oscoord._main(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord.oscoord(frame)&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
	return oscoord._oscoord(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
 ******  LAT/LONG CONVERSION TO OS GRID REF FUNCTIONS *****&lt;br /&gt;
 *  Uses the WGS-84 ellipsoid and only works for GB grid ref&lt;br /&gt;
 *&lt;br /&gt;
 *  See also:&lt;br /&gt;
 *  http://www.posc.org/Epicentre.2_2/DataModel/ExamplesofUsage/eu_cs34h.html&lt;br /&gt;
 *  http://kanadier.gps-info.de/d-utm-gitter.htm&lt;br /&gt;
 *  http://www.gpsy.com/gpsinfo/geotoutm/&lt;br /&gt;
 *  http://www.colorado.edu/geography/gcraft/notes/gps/gps_f.html&lt;br /&gt;
 *  http://search.cpan.org/src/GRAHAMC/Geo-Coordinates-UTM-0.05/&lt;br /&gt;
 *  UK Ordnance Survey grid (OSBG36): http://www.gps.gov.uk/guidecontents.asp&lt;br /&gt;
 *  Swiss CH1903: http://www.gps.gov.uk/guidecontents.asp&lt;br /&gt;
 *&lt;br /&gt;
 *  ----------------------------------------------------------------------&lt;br /&gt;
 *&lt;br /&gt;
 *  Copyright 2005, Egil Kvaleberg &amp;lt;egil@kvaleberg.no&amp;gt;&lt;br /&gt;
 *&lt;br /&gt;
 *  This program is free software; you can redistribute it and/or modify&lt;br /&gt;
 *  it under the terms of the GNU General Public License as published by&lt;br /&gt;
 *  the Free Software Foundation; either version 2 of the License, or&lt;br /&gt;
 *  (at your option) any later version.&lt;br /&gt;
 *&lt;br /&gt;
 *  This program is distributed in the hope that it will be useful,&lt;br /&gt;
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of&lt;br /&gt;
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the&lt;br /&gt;
 *  GNU General Public License for more details.&lt;br /&gt;
 *&lt;br /&gt;
 *  You should have received a copy of the GNU General Public License&lt;br /&gt;
 *  along with this program; if not, write to the Free Software&lt;br /&gt;
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA&lt;br /&gt;
 &lt;br /&gt;
 Converted to Lua by User:Hike395 on 2023-12-15&lt;br /&gt;
]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
local function find_M ( lat_rad )&lt;br /&gt;
	local e = OSGBglobe.ecc&lt;br /&gt;
	local e2 = e*e&lt;br /&gt;
	local e3 = e*e*e&lt;br /&gt;
&lt;br /&gt;
	return OSGBglobe.semimajor * ( ( 1 - e/4 - 3 * e2/64&lt;br /&gt;
			      - 5 * e3/256&lt;br /&gt;
			    ) * lat_rad&lt;br /&gt;
			  - ( 3 * e/8 + 3 * e2/32&lt;br /&gt;
			      + 45 * e3/1024&lt;br /&gt;
			    ) * sin(2 * lat_rad)&lt;br /&gt;
			  + ( 15 * e2/256 +&lt;br /&gt;
			      45 * e3/1024&lt;br /&gt;
			    ) * sin(4 * lat_rad)&lt;br /&gt;
			  - ( 35 * e3/3072&lt;br /&gt;
			    ) * sin(6 * lat_rad) )&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
*  Convert latitude, longitude in decimal degrees to&lt;br /&gt;
*  Transverse Mercator Easting and Northing based on a GB origin&lt;br /&gt;
*&lt;br /&gt;
*  return nil if problems&lt;br /&gt;
]]&lt;br /&gt;
local function LatLonOrigin2TM( latitude, longitude )&lt;br /&gt;
	if longitude &amp;lt; -180 or longitude &amp;gt; 180 or latitude &amp;lt; -80 or latitude &amp;gt; 84 then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local longitude2 = longitude - floor((longitude + 180)/360) * 360&lt;br /&gt;
&lt;br /&gt;
	local lat_rad = deg2rad( latitude )&lt;br /&gt;
&lt;br /&gt;
	local e = OSGBglobe.ecc&lt;br /&gt;
	local e_prime_sq = e / (1-e)&lt;br /&gt;
&lt;br /&gt;
	local v = OSGBglobe.semimajor / sqrt(1 - e * sin(lat_rad)*sin(lat_rad))&lt;br /&gt;
	local tank = tan(lat_rad)&lt;br /&gt;
	local T = tank*tank&lt;br /&gt;
	local T2 = T*T&lt;br /&gt;
	local C = e_prime_sq * pow( cos(lat_rad), 2)&lt;br /&gt;
	local A = deg2rad( longitude2 -OSGBglobe.lon0 ) * cos(lat_rad)&lt;br /&gt;
	local A2 = A*A&lt;br /&gt;
	local A3 = A2*A&lt;br /&gt;
	local A4 = A2*A2&lt;br /&gt;
	local A5 = A3*A2&lt;br /&gt;
	local A6 = A3*A3&lt;br /&gt;
	local M = find_M( lat_rad )&lt;br /&gt;
	local M0 = 0.0&lt;br /&gt;
	if latitude_origin ~= 0 then&lt;br /&gt;
		M0 = find_M(deg2rad( OSGBglobe.lat0 ))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local northing = OSGBglobe.n0 + OSGBglobe.scale *&lt;br /&gt;
			    ( (M - M0) + v*tan(lat_rad) * &lt;br /&gt;
			      ( A2/2 &lt;br /&gt;
				+ (5 - T + 9*C + 4*C*C) * A4/24&lt;br /&gt;
				+ (61 - 58*T + T2&lt;br /&gt;
				+ 600*C - 330*e_prime_sq) * A6/720 ))&lt;br /&gt;
&lt;br /&gt;
	local easting = OSGBglobe.e0 + OSGBglobe.scale * v *&lt;br /&gt;
			   ( A &lt;br /&gt;
			     + (1-T+C)*A3/6&lt;br /&gt;
			     + (5 - 18*T + T2 + 72*C &lt;br /&gt;
				- 58 * e_prime_sq)*A5/120 )&lt;br /&gt;
&lt;br /&gt;
	return {northing=northing,easting=easting}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--[[&lt;br /&gt;
*  Convert latitude, longitude in decimal degrees to&lt;br /&gt;
*  OSBG36 Easting and Northing&lt;br /&gt;
]]&lt;br /&gt;
local function LatLon2OSGB36( latlon, prec )&lt;br /&gt;
	local tm = LatLonOrigin2TM(latlon.lat, latlon.lon)&lt;br /&gt;
	if not tm then return &amp;#039;&amp;#039; end&lt;br /&gt;
	if not tonumber(prec) then prec = 5 end&lt;br /&gt;
	prec = floor(prec+0.5)&lt;br /&gt;
	if prec &amp;gt; 5 then prec = 5 end&lt;br /&gt;
	if prec &amp;lt; 1 then prec = 1 end&lt;br /&gt;
&lt;br /&gt;
	-- fix by Roger W Haworth&lt;br /&gt;
	local grid_x = floor( tm.easting / 100000 )&lt;br /&gt;
	local grid_y = floor( tm.northing / 100000 )&lt;br /&gt;
	if grid_x &amp;lt; 0 or grid_x &amp;gt; 6 or grid_y &amp;lt; 0 or grid_y &amp;gt; 12 then return &amp;#039;&amp;#039; end&lt;br /&gt;
	--               0000000001111111111222222&lt;br /&gt;
	--               1234567890123456789012345&lt;br /&gt;
	local letters = &amp;quot;ABCDEFGHJKLMNOPQRSTUVWXYZ&amp;quot;&lt;br /&gt;
	&lt;br /&gt;
	local indx1 = 18-floor(grid_y/5)*5+floor(grid_x/5)&lt;br /&gt;
	local indx2 = 21-(grid_y%5)*5+grid_x%5&lt;br /&gt;
	local c1 = mw.ustring.sub(letters,indx1,indx1)&lt;br /&gt;
	local c2 = mw.ustring.sub(letters,indx2,indx2)&lt;br /&gt;
&lt;br /&gt;
	local easting = tm.easting%100000&lt;br /&gt;
	local northing = tm.northing%100000&lt;br /&gt;
	local grid = pow(10.0,5.0-prec)&lt;br /&gt;
	easting = floor(easting/grid)&lt;br /&gt;
	northing = floor(northing/grid)&lt;br /&gt;
	local fmt = &amp;#039;%0&amp;#039;..prec..&amp;#039;d&amp;#039;&lt;br /&gt;
	local e = mw.ustring.format(fmt, easting)&lt;br /&gt;
	local n = mw.ustring.format(fmt, northing)&lt;br /&gt;
&lt;br /&gt;
	return c1..c2..e..n&lt;br /&gt;
&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord._WGS2OSGB(lat,lon,prec)&lt;br /&gt;
	return LatLon2OSGB36(HelmertDatumShift(lat,lon,WGS2OSGBparam),prec)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord.WGS2OSGB(frame)&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
	return args[1] and args[2] and oscoord._WGS2OSGB(args[1],args[2],args[3]) or &amp;#039;&amp;#039;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function oscoord.LL2OS(frame)&lt;br /&gt;
	local args = getArgs(frame)&lt;br /&gt;
	if not args[1] or not args[2] then return &amp;#039;&amp;#039; end&lt;br /&gt;
	local gridRef = oscoord._WGS2OSGB(args[1],args[2],args.prec)&lt;br /&gt;
	if not gridRef or #gridRef == 0 then return &amp;#039;&amp;#039; end&lt;br /&gt;
	if args[3] then&lt;br /&gt;
		gridRef = gridRef..&amp;#039;_&amp;#039;..args[3]&lt;br /&gt;
	end&lt;br /&gt;
	return oscoord._oscoord({gridRef,args.linktitle,name=args.name})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return oscoord&lt;/div&gt;</summary>
		<author><name>&gt;Hike395</name></author>
	</entry>
</feed>