Module:Coordinates

From Wikidata
Jump to navigation Jump to search

Documentation for this module may be created at Module:Coordinates/doc

local math_mod = require( "Module:Math" )
local p = {}
local lang = mw.getCurrentFrame():preprocess('{{int:lang}}')
local geohackurl = 'http://tools.wmflabs.org/geohack/geohack.php?language=' .. lang

local i18ntable = require('Module:Coordinates/i18n')
local langSwitch = require('Module:Fallback')._langSwitch
local function i18n(msg)
	return langSwitch(i18ntable[msg], lang)
end

local globedata = {
	--[[ notes:
		radius in kilometers (especially imprecise for non spheric bodies)
	]]--
	ariel = {radius = 580, defaultdisplay = 'dec east'},
	callisto = {radius = 2410, defaultdisplay = 'dec east'},
	ceres = {radius = 470, defaultdisplay = 'dec east'},
	deimos = {radius = 7, defaultdisplay = 'dec east'},
	dione = {radius = 560, defaultdisplay = 'dec east'},
	enceladus = {radius = 255, defaultdisplay = 'dec east'},
	ganymede = {radius = 1631, defaultdisplay = 'dec east'},
	earth = {radius = 6371, defaultdisplay = 'dms'},
	europa = {radius = 1561, defaultdisplay = 'dec east'},
	hyperion = {radius = 140, defaultdisplay = 'dec east'},
	iapetus = {radius = 725, defaultdisplay = 'dec east'},
	['io'] = {radius = 1322, defaultdisplay = 'dec east'},
	jupiter = {radius = 68911, defaultdisplay = 'dec east'},
	mars = {radius = 3389.5, defaultdisplay = 'dec east'},
	mercury = {radius = 2439.7, defaultdisplay = 'dec east'},
	mimas = {radius = 197, defaultdisplay = 'dec east'},
	miranda = {radius = 335, defaultdisplay = 'dec east'},
	moon = {radius = 1736, defaultdisplay = 'dec east'},
	neptune = {radius = 24553, defaultdisplay = 'dec east'},
	oberon = {radius = 761, defaultdisplay = 'dec east'},
	phoebe = {radius = 110, defaultdisplay = 'dec east'},
	phobos = {radius = 11, defaultdisplay = 'dec east'},
	rhea = {radius = 765, defaultdisplay = 'dec east'},
	saturn = {radius = 58232, defaultdisplay = 'dec east'},
	titan = {radius = 2575.5, defaultdisplay = 'dec west'},
	tethys = {radius = 530, defaultdisplay = 'dec east'},
	titania = {radius = 394, defaultdisplay = 'dec east'},
	triton = {radius = 1353, defaultdisplay = 'dec west'},
	umbriel = {radius = 584, defaultdisplay = 'dec east'},
	uranus = {radius = 25266, defaultdisplay = 'dec east'},
	venus = {radius = 6051.8, defaultdisplay = 'dec east'},
	vesta = {radius = 260, defaultdisplay = 'dec east'}
}

local errorstring = '' -- error messages and categories are conctenated here
local errocat = 'invalid coordinates' -- name of the cat used for errors

local function makecat(cat, sortkey)
	return '[[Category:' .. cat .. '|' .. (sortkey or '*') .. ']]'
end
----------------------------------------
--Error handling
	--[[ Notes:
	when errors occure a new error message is concatenated to errorstring
	an error message contains an error category with a sortkey
	For major errors, it can also display an error message (the error message will the usually be returned and the function terminated)
	More minor errors do only add a category, so that readers are not bothered with error texts
	sortkeys:
		* A: invalid latitude, longitude or direction
		* B: invalid globe
		* C: something wrong with other parameters
		* D: more than one primary coord
	]]--

local function makeerror(args) -- add an error message string
	local errormessage = ''
	if args.message then
		errormessage = '<strong class="error">Coordinates: ' .. args.message .. '</strong>'
	end
	local errorcat = makecat('errorcat', args.sortkey)
	errorstring = errormessage .. errorcat -- reinitializes the string to avoid absurdly long messages
	return nil
end

local function showerrors()
	return errorstring
end

--HTML builder for a geohack link
local function buildHTML(decLat, decLong, dmsLat, dmsLong, globe, displayformat, displayinline, displaytitle, objectname, extraparams)
	local root, text, url, noprint
	extraparams = extraparams or ''
	-- geohack url and parameters
	local decimalcoords = p.displaydec(decLat, decLong, displayformat)
	local geohacklatitude, geohacklongitude

	-- format latitude and longitude for the URL
	if tonumber(decLat) < 0 then
		geohacklatitude = tostring(-tonumber(decLat)) .. '_S'
	else
		geohacklatitude = decLat .. '_N'
	end
	if tonumber(decLong) < 0 then
		geohacklongitude = tostring(-tonumber(decLong)) .. '_W'
	else
		geohacklongitude = decLong .. '_E'
	end
	-- prepares the 'paramss=' parameter
	local geohackparams = geohacklatitude .. '_' .. geohacklongitude .. '_' ..extraparams
	-- concatenate parameteres for geohack
	local url = geohackurl ..
		"&pagename=" .. mw.uri.encode(mw.title.getCurrentTitle().prefixedText, "WIKI") ..
		"&params=" .. geohackparams ..
		(objectname and ("&title=" .. mw.uri.encode(objectname)) or "")

	root = mw.html.create('')
	root
		:tag("span")
			:addClass("plainlinks nourlexpansion")
			:wikitext("[" .. url)
	
	if string.sub(displayformat,1,3) == "dms" then
		root
			:tag("span")
				:addClass(string.sub(displayformat,1,3) == "dec" and "geo-nondefault" or "geo-default")
					:tag("span")
					:addClass("geo-dms")
					:attr("title", i18n('tooltip'))
					:tag("span")
						:addClass("latitude")
						:wikitext(p.displaydmsdimension(dmsLat, displayformat))
						:done()
					:wikitext(" ")
					:tag("span")
						:addClass("longitude")
						:wikitext(p.displaydmsdimension(dmsLong, displayformat))
						:done()
					:done()
				:done()
	--[[ unavailable on Wikidata
			:tag("span")
				:addClass("geo-multi-punct")
				:wikitext("&#xfeff; / &#xfeff;")
				:done()
	]]--
	else
		root
			:tag("span")
				:addClass(string.sub(displayformat,1,3) == 'dec' and "geo-default" or "geo-nondefault")
				:wikitext(objectname and "<span class=\"vcard\">" or "")
				:tag("span")
					:addClass("geo-dec")
					:attr("title", i18n('tooltip'))
					:wikitext(decimalcoords)
					:done()
				:wikitext(objectname and ("<span style=\"display:none\"> (<span class=\"fn org\">" ..
						objectname .. "</span>)</span></span>") or "")
				:done()
			
	end
	
		root:wikitext("]")
			:done()
	-- formatta il risultato a seconda di args["display"] (nil, "inline", "title", "inline,title")
	text = tostring(root)

	noprint = displayinline and "class=\"noprint\" " or ""
	htmlTitle = "<span style=\"font-size: small;\"><span " .. noprint .. "id=\"coordinates\">"

	return (displayinline and text or "") ..
			(displaytitle and (htmlTitle .. text .. "</span></span>") or "")
end


-- dms specific funcions
function p.displaydmsdimension(valuetable, format) -- formate en latitude ou une longitude dms
	local str = ''
	local direction = valuetable.direction
	local degrees, minutes, seconds = '', '', ''
	local dimension

	if format == 'dms long' then
		direction = i18n(direction .. 'long')
	else
		direction = i18n(direction)
	end
	degrees = valuetable.degrees .. i18n('degrees')
	
	if valuetable.minutes then
		minutes = valuetable.minutes .. i18n('minutes')
		if (valuetable.minutes < 10) then
			minutes = '0' .. minutes
		end
	end
	if valuetable.seconds then
		seconds = valuetable.seconds
		if (valuetable.seconds < 10) then
			seconds = '0' .. seconds
		end
		if valuetable.precision == 'dmsand2' and math.floor(valuetable.seconds) == valuetable.seconds then --add trailing 0 to show the precision
			seconds = seconds .. '.00'
		elseif valuetable.precision == 'dmsand2' and valuetable.seconds * 10 == math.floor(10 * valuetable.seconds) then
			seconds = seconds .. '0'
		end
		seconds = seconds .. i18n('seconds')
	end
	return degrees .. minutes .. seconds .. direction
end

local function validdms(coordtable)
	local direction = coordtable.direction
	local degrees = coordtable.degrees or 0
	local minutes = coordtable.minutes or 0
	local seconds = coordtable.seconds or 0
	local dimension = coordtable.dimension
	if not dimension then
		if direction == 'N' or direction == 'S' then
			dimension = 'latitude'
		elseif direction == 'E' or direction == 'W' then
			dimension = 'longitude'
		else
			makeerror({message = 'invalid direction should be "N", "S", "E" or "W"', sortkey = 'A'})
			return false
		end
end

	if type(degrees) ~= 'number' or type(minutes) ~= 'number' or type(seconds) ~= 'number' then
		makeerror({message = 'invalid format', sortkey = 'A'})
		return false
	end
	
	if dimension == 'latitude' and direction ~= 'N' and direction ~= 'S' then
		makeerror({message = 'could not find latitude direction (should be N or S)', sortkey = 'A'})
		return false
	end
	if dimension == 'longitude' and direction ~= 'W' and direction ~= 'E' then
		makeerror({message = 'could not find longitude direction (should be W or E) ', sortkey = 'A'})
		return false
	end
	
	if dimension == 'latitude' and degrees > 90 then
		makeerror({message = 'latitude > 90', sortkey = 'A'})
		return false
	end
	
	if dimension == 'longitude' and degrees > 360 then
		makeerror({message = 'longitude > 360', sortkey = 'A'})
		return false
	end
	
	if degrees < 0 or minutes < 0 or seconds < 0 then
		makeerror({message = 'dms coordinates should be positive', sortkey = 'A'})
		return false
	end
	
	if minutes > 60 or seconds > 60 then
		makeerror({message = 'minutes or seconds > 60', sortkey = 'A'})
		return false
	end	
	if (math.floor(degrees) ~= degrees and minutes ~= 0) or (math.floor(minutes) ~= minutes and seconds ~= 0) then
		makeerror({message = 'degrees and minutes should be integers', sortkey = 'A'})
		return false
	end
	return true
end

local function builddmsdimension(degrees, minutes, seconds, direction, precision)
	-- no error checking, done in function validdms
	local dimensionobject = {}

	
	-- direction and dimension (= latitude or longitude)
	dimensionobject.direction = direction
	if direction == 'N' or direction == 'S' then
		dimensionobject.dimension = 'latitude'
	else
		dimensionobject.dimension = 'longitude'
	end
	
	-- degrees, minutes, seconds
	dimensionobject.degrees = tonumber(degrees)
	dimensionobject.minutes = tonumber(minutes)
	dimensionobject.seconds = tonumber(seconds)
	if degrees and not dimensionobject.degrees then dimensionobject.degrees = 'error' end
	if minutes and not dimensionobject.minutes then dimensionobject.minutes = 'error' end
	if seconds and not dimensionobject.seconds then dimensionobject.seconds = 'error' end
	
	dimensionobject.precision = precision -- so that the display function knows that some 33 seconds mean 33 secodns and other 33.0
	return dimensionobject
end

local function parsedmsstring(str) -- prend une séquence et donne des noms aux paramètres
	-- output table: {latitude=, longitude = , direction =  }
	if not str then
		return nil
	end
	if not tonumber(str) and not string.find(str, '/') then
		makeerror({message ='invalid coordinate format', sortkey= 'A'})
		return nil
	end
	args = mw.text.split(str, '/', true)
	if #args > 4 then
		makeerror({message = "too many parameters for coordinates", sortkey= 'A' })
	end	
	local direction = mw.text.trim(args[#args])
	table.remove(args)
	local degrees, minutes, seconds = args[1], args[2], args[3]
	local dimensionobject = builddmsdimension(degrees, minutes, seconds, direction)
	if validdms(dimensionobject) then
		return dimensionobject
	else
		return nil
	end
end

--- decimal specific functions
function p.displaydec(latitude, longitude, format)
	if format == 'dec west' then
		local latsymbol = i18n('N')
		longitude = - longitude
		if latitude < 0 then latsymbol = i18n('S') end
		if longitude < 0 then
			longitude = 360 + longitude
		end
	return latitude .. i18n('degrees') .. latsymbol .. ', ' .. longitude .. i18n.degrees .. i18n('W')
		
	elseif format == 'dec east' then
		local latsymbol = i18n('N')
		if latitude < 0 then latsymbol = i18n('S') end
		if longitude < 0 then
			longitude = 360 + longitude
		end
		return latitude .. i18n('degrees') .. latsymbol .. ', ' .. longitude .. i18n('degrees') .. i18n('E')
		
	else
		return latitude .. ', ' .. longitude
	end
end


local function parsedec(dec, coordtype) -- coordtype = latitude or longitude
	dec = mw.text.trim(dec)
	if coordtype ~= 'latitude' and coordtype ~= 'longitude' then
		makeerror({'invalid coord type', sortkey = "A"})
		return nil
	end
	if not dec then
		return nil
	end
	local numdec = tonumber(dec) -- numeric value, kept separated as it looses significant zeros
	if not numdec then -- tries the decimal + direction format
		direction = mw.ustring.sub(dec, mw.ustring.len(dec), mw.ustring.len(dec))
		dec = mw.ustring.sub(dec, 1, mw.ustring.len(dec)-2) -- removes the /N at the end
		if not dec or not tonumber(dec) then
			return nil
		end
		if direction == 'N' or direction == 'E' then
			return dec
		elseif direction == 'W' or direction == 'S' then
			return '-' .. dec
		else
			makeerror({message = 'could not find longitude direction (should be W or E) ', sortkey = 'A'})
			return nil
		end
	end

	if coordtype == 'latitude' and math.abs(numdec) > 90 then
		makeerror({message = 'latitude > 90' , sortkey = 'A'})
		return nil
	end
	if coordtype == 'longitude' and math.abs(numdec) > 360 then
		makeerror({message = 'longitude > 360' , sortkey = 'A'})
		return nil
	end
	return dec
end

-- dms/dec conversion functions
local function convertprecision(precision) -- converts a decimal precision like "2" into "dm"
	if precision >= 5 then
		return 'dmsand2'
	elseif precision >= 3 then
		return 'dms'
	elseif precision >=1 then
		return 'dm'
	else
		return 'd'
	end
end

local function determinedmsprec(decs) -- returns the most precision for a dec2dms conversion, depending on the most precise value in the decs table
	local precision = 0
	for d, val in ipairs(decs) do
		precision = math.max(precision, math_mod._precision(val))
	end
	return convertprecision(precision)
end

local function dec2dms_d(dec)
	local degrees = math_mod._round( dec, 0 )
	return degrees
end

local function dec2dms_dm(dec)
	dec = math_mod._round( dec * 60, 0 )
	local minutes = dec % 60
	dec = math.floor( (dec - minutes) / 60 )
	local degrees = dec % 360
	return degrees, minutes
end

local function dec2dms_dms(dec)
	dec = math_mod._round( dec * 60 * 60, 0 )
	local seconds = dec % 60
	dec = math.floor( (dec - seconds) / 60 )
	local minutes = dec % 60
	dec = math.floor( (dec - minutes) / 60 )
	local degrees = dec % 360
	return degrees, minutes, seconds
end

local function dec2dms_dmsand2(dec)
	dec = dec * 60 * 60
	local seconds = math_mod._round(dec % 60, 2)
	if 10 * (seconds / 10) == 10 * seconds % 10 then -- add a 0 at the end
		seconds = tonumber(tostring(seconds) .. '0')
	end
	dec = math.floor( (dec - seconds) / 60 )
	local minutes = dec % 60
	dec = math.floor( (dec - minutes) / 60 )
	local degrees = dec % 360
	return degrees, minutes, seconds
end

function p._dec2dms(dec, coordtype, precision) -- type: latitude or longitude
	if not precision then
		precision = determinedmsprec({latitude, longitude})
	end
	local degrees, minutes, seconds
	
	-- precision
	if precision ~= 'd' and precision ~= 'dm' and precision ~= 'dms' and precision ~= 'dmsand2' then
		return makeerror({sortkey = 'C'})
	end
	local dec = tonumber(dec)
	-- direction
	local direction
	if coordtype == 'latitude' then
		if dec < 0 then
			direction = 'S'
		else
			direction = 'N'
		end
	elseif coordtype == 'longitude' then
		if dec < 0 then
			direction = 'W'
		else
			direction = 'E'
		end
	end
	
	-- conversion
	dec = math.abs(dec) -- dec coordinates are always positive
	if precision == 'dmsand2' then
		degrees, minutes, seconds = dec2dms_dmsand2(dec)
	elseif precision == 'dms' then
		degrees, minutes, seconds = dec2dms_dms(dec)
	elseif precision == 'dm' then
		degrees, minutes = dec2dms_dm(dec)
	else
		degrees = dec2dms_d(dec)
	end
	return builddmsdimension(degrees, minutes, seconds, direction, precision)
end

function p.dec2dms(frame) -- legacy function somewhat cumbersome syntax
	args = frame.args
	local dec = args[1]
	if not tonumber(dec) then
		makeerror({message='invalid coordinate format', sortkey = 'A'})
		return showerrors()
	end
	local precision = string.lower(args[4] or '')
	local displayformat, coordtype
	
	if args[2] == 'N' or args[2] == 'Nord' then
		coordtype = 'latitude'
	else
		coordtype = 'longitude'
	end
	if args[2] == 'Nord' or args[2] == 'Est' or args[3] == 'Ouest' or args[3] == 'Sud' then
		displayformat = 'dms long'
	end
	local coordobject = p._dec2dms(dec, coordtype, precision)
	if coordobject then
		return p.displaydmsdimension(coordobject, displayformat, precision) .. showerrors()
	else
		return showerrors()
	end
end


function p._dms2dec(dmsobject) -- transforme une table degré minute secondes en nombre décimal
	local direction, degrees, minutes, seconds = dmsobject.direction, dmsobject.degrees, dmsobject.minutes, dmsobject.seconds
	local factor = 0
	local precision = 0
	if not minutes then minutes = 0 end
	if not seconds then seconds = 0 end
	
	if direction == "N" or direction == "E" then
		factor = 1
	elseif direction == "W" or direction == "S" then
		factor = -1
	elseif not direction then
		makeerror({message = 'no cardinal direction found in coordinates', sortkey = 'A'})
		return nil
	else
		makeerror({message = 'invalid direction', sortkey = 'A'})
		return nil
	end
	
	if dmsobject.seconds then -- vérifie la précision des données initiales
		precision = 5 + math.max( math_mod._precision(tostring(seconds), 0 ) ) -- passage par des strings assez tarabiscoté ?
	elseif dmsobject.minutes then
		precision = 3 + math.max( math_mod._precision(tostring(minutes), 0 ) )
	else
		precision = math.max( math_mod._precision(tostring(degrees), 0 ) )
	end
	
	local decimal = factor * (degrees+(minutes+seconds/60)/60)
	return math_mod._round(decimal, precision)
end

function p.dms2dec(frame) -- legacy function, somewhat bizarre syntax
	local args = frame.args
	if tonumber(args[1]) then
		return args[1] -- coordonnées déjà en décimal
	elseif not args[2] then
		local dmsobject = parsedmsstring(args[1])
		if dmsobject then
			return p._dms2dec(dmsobject) -- coordonnées sous la fore 23/22/N
		else
			return showerrors()
		end
	else
		return p._dms2dec({direction = args[1], degrees = args[2], minutes = args[3], seconds = args[4]})
	end
end

function p._distance(a, b, globe) -- calcule la [[distance orthodromique]] en kilomètres entre deux points du globe

	globe = string.lower(globe or 'earth')
	
	-- check arguments and converts degreees to radians
	local latA, latB, longA, longB = a.latitude, b.latitude, a.longitude, b.longitude
	if (not latA) or (not latB) or (not longA) or (not longB) then return
		error('coordinates missing, can\'t compute distance')
	end
	if type(latA) ~= 'number' or type(latB) ~= 'number' or type(longA) ~= 'number' or type(longB) ~= 'number' then
		error('coordinates are not numeric, can\'t compute distance')
	end
		if not globe or not globedata[globe] then
		return error('globe: ' .. globe .. 'is not supported')
	end
	
	-- calcul de la distance angulaire en radians
	local convratio = math.pi / 180 -- convertit en radians
	latA, latB, longA, longB = convratio * latA, convratio * latB, convratio * longA, convratio * longB
	local cosangle = math.sin(latA) * math.sin(latB) + math.cos(latA) * math.cos(latB) * math.cos(longB - longA)
	if cosangle >= 1 then -- may be above one because of rounding errors
		return 0
	end
	local angle = math.acos(cosangle)
	-- calcul de la distance en km
	local radius = globedata[globe].radius
	return radius * angle
end

-- main function for displaying coordinates
function p._coord(args)
	-- latitude and longitude
	local latitude, longitude = args.latitude, args.longitude, args.precision
	if not latitude and not longitude then
		return nil -- ne rien ajouter ici pour que l'appel à cette fonction retourne bien nil en l'absence de données
	end
	if (latitude and not longitude) or (longitude and not latitude) then
		makeerror({message = 'latitude or longitude missing', sortkey = 'A'})
		return showerrors()
	end

	--- globe
	local globe = string.lower(args.globe or '') -- string: see the globedata table for accepted values
	local extraparams = string.lower(args.extraparams or '') -- string (legacy, corresponds to geohackparams)
	if globe == '' then -- cherche le globe dans l'extraparams destinée à geohack
		local globe2 = string.match(extraparams, 'globe\:%a+')
		if globe2 then globe = string.sub(globe2, 7) end
		if globe == '' then
			globe = 'earth'
		end
	end	
	
	local precision = args.precision
	if not precision or precision == '' then
		precision = determinedmsprec({latitude, longitude})
	end
	local dmslatitude, dmslongitude = p._dec2dms(latitude, 'latitude', precision), p._dec2dms(longitude, 'longitude', precision)	
	local trackingstring = '' -- tracking cats except error cats (already in errorstring)


	-- other parameters
	local displayformat = args.format -- string: one of: 'dms', 'dms long', 'dec', 'dec east' and 'dec west'
	if not displayformat or displayformat == '' then
		displayformat = globedata[globe].defaultdisplay
	end

	local displayplace = string.lower(args.display or 'inline') --string: one of 'inline', 'title' or 'inline,title'

	local objectname = args.name -- string: name of the title displayed in geohack

	local notes = (' ' and args.notes) or '' -- string: notes to de displayed after coordinates
	
	-- displayinline/displaytitle
	local displayinline = string.find(displayplace, 'inline')
	local displaytitle = string.find(displayplace, 'title')
	if not displayinline and not displaytitle then
		displayinline = true
		if displayplace ~= '' then
			makeerror({sortkey = 'C'}) --error if display not empty, but not not a major error, continue
		end
	end
-- geodata
	-- not useful in Wikidata

-- Build final output
	extraparams = extraparams .. '_globe:' .. globe -- pas de problème si le globe est en double

	local mainstring = ''
	if args.formatitle then
		if displaytitle then
			mainstring = mainstring .. buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, args.formatitle, false, true, objectname,extraparams )
		end
		if displayinline then
			mainstring = mainstring .. buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, true, false, objectname,extraparams )
		end
	else
		mainstring = buildHTML(latitude, longitude, dmslatitude, dmslongitude, globe, displayformat, displayinline, displaytitle, objectname,extraparams )
	end

-- Return result
	return mainstring .. notes .. trackingstring .. showerrors()
end

function p.coord(frame) -- parrses the strange parameters of Template:Coord before sending them to p.coord
	local args = frame:getParent().args
	local numericargs = {}
	for i, j in ipairs(args) do
		args[i] = mw.text.trim(j)
		if type(i) == 'number' and args[i] ~= '' then
			table.insert(numericargs, args[i])
		end
	end
	
	if #numericargs %2 == 1 then -- if the number of args is odd, the last one provides formatting parameters
		args.extraparams = numericargs[#numericargs]
		if #numericargs == 1 and tonumber(numericargs[1]) then
			makeerror({message = 'latitude or longitude missing', sortkey = 'A'})
			return showerrors()
		end
		table.remove(numericargs)
	end
	if #numericargs == 1 then
		makeerror({message = 'missing data for coords', sortkey = 'A'})
		return showerrors()
	end
	
	local rawlatitude, rawlongitude -- will concatenate the parse the latitude and longitude arguments from the messy {{tl|Coords}}
	for i, j in ipairs(numericargs) do
		if i <= (#numericargs / 2) then -- first half: latitude
			if not rawlatitude then
				rawlatitude = j
			else
				rawlatitude = rawlatitude .. '/' .. j
			end
		else
			if not rawlongitude then -- second half = longitude
				rawlongitude = j
			else
				rawlongitude = rawlongitude .. '/' .. j
			end
		end
	end
	local latitude = parsedec(rawlatitude, 'latitude')
	local longitude = parsedec(rawlongitude, 'longitude')
	if not latitude or longitude then
		local dmslatitude, dmslongitude = parsedmsstring(rawlatitude), parsedmsstring(rawlongitude)
		latitude, longitude = p._dms2dec(dmslatitude), p._dms2dec(dmslongitude)
		if (not latitude) or (not longitude) then return
			showerrors()
		end
	end
	args.latitude = latitude
	args.longitude = longitude
	return p._coord(args)
end

return p