PhotosLocation

From Wikipedia, the free encyclopedia

--[[

This module is intended to replace the functionality of {{Coord}} and related

templates.  It provides several methods, including



{{#invoke:Coordinates | coord }} : General function formatting and displaying

coordinate values.



{{#invoke:Coordinates | dec2dms }} : Simple function for converting decimal

degree values to DMS format.



{{#invoke:Coordinates | dms2dec }} : Simple function for converting DMS format

to decimal degree format.



{{#invoke:Coordinates | link }} : Export the link used to reach the tools



]]



require('strict')



local math_mod = require("Module:Math")

local mTemplateInvocation = require( 'Module:Template invocation' )

local mTableTools = require('Module:TableTools')

local coordinates = {}



local current_page = mw.title.getCurrentTitle()

local page_name = mw.uri.encode( current_page.prefixedText, 'WIKI' )

local coord_link = '//geohack.toolforge.org/geohack.php?pagename=' .. page_name .. '&params='

local templatestyles = 'Module:Coordinates/styles.css'



--[[ Helper function, replacement for {{coord/display/title}} ]]

local function displaytitle(s, notes)

	local l = "[[Geographic coordinate system|Coordinates]]: " .. s

	local co = '<span id="coordinates">' .. l .. notes .. '</span>';

	return mw.getCurrentFrame():extensionTag{

		name = 'indicator',

		args = { name = 'coordinates' },

		content = '<span style="font-size: small;">' .. co .. '</span>'

	};

end



--[[ Helper function, Replacement for {{coord/display/inline}} ]]

local function displayinline(s, notes)

	return s .. notes

end



--[[ Helper function, used in detecting DMS formatting ]]

local function dmsTest(first, second)

	if type(first) ~= 'string' or type(second) ~= 'string' then

		return nil

	end

	local s = (first .. second):upper()

	return s:find('^[NS][EW]$') or s:find('^[EW][NS]$')

end





--[[ Wrapper function to grab args, see Module:Arguments for this function's documentation. ]]

local function makeInvokeFunc(funcName)

	return function (frame)

		local args = require('Module:Arguments').getArgs(frame, {

			wrappers = 'Template:Coord'

		})

		return coordinatesfuncName](args, frame)

	end

end



--[[ Helper function, handle optional args. ]]

local function optionalArg(arg, supplement)

	return arg and arg .. supplement or ''

end



--[[

Formats any error messages generated for display

]]

local function errorPrinter(errors)

	local result = ""

	for i,v in ipairs(errors) do

		local errorHTML = '<strong class="error">Coordinates: ' .. v2 .. '</strong>'

		result = result .. errorHTML .. "<br />"

	end

	return result

end



--[[

Determine the required CSS class to display coordinates



Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself

default is the mode as specificied by the user when calling the {{coord}} template

mode is the display mode (dec or dms) that we will need to determine the css class for

]]

local function displayDefault(default, mode)

	if default == "" then

		default = "dec"

	end



	if default == mode then

		return "geo-default"

	else

		return "geo-nondefault"

	end

end



--[[

JSON doesn't handle .78 values, only 0.78

we do want to keep our precision though, so tonumber is bad.

]]

local function jsonSafeNumber(numberString)

	numberString = numberString:gsub('^(%.)', '0.', 1)

	return numberString:gsub('^(-%.)', '-0.', 1)

end



--[[

addHemisphereToDec



Rewrite the decimal format to also denote the hemisphere abbreviations

This is a typical Template:Coord presentation and rather uncommon outside of en.wp

]]

local function addHemisphereToDec( latitude, longitude )

	local lat = tonumber( latitude ) or 0

	local returnLat

	if lat < 0 then

		-- FIXME this breaks the pre-existing precision

		returnLat = tostring(latitude):sub(2) .. "°S"

	else

		returnLat = (latitude or 0) .. "°N"

	end



	local long = tonumber( longitude ) or 0

	local returnLong

	if long < 0 then

		-- FIXME does not handle unicode minus

		returnLong = tostring(longitude):sub(2) .. "°W"

	else

		returnLong = (longitude or 0) .. "°E"

	end

	return returnLat, returnLong

end



--[[

splitParam



Split the Geohack parameter string and convert it into an object.

]]

local function splitParam( param )

	local out = {}

	for pair in mw.text.gsplit( param, '_', true ) do

		local keyValue = mw.text.split( pair, ':', true )

		if #keyValue == 2 then

			outkeyValue1]] = keyValue2

		end

	end

	return out

end



--[[

geohackTypeToMarkerSymbol



Convert from Geohack's type to a Kartographer marker symbol

]]

local function geohackTypeToMarkerSymbol(type, population)

	-- /info/en/?search=Template:Coord#type:T

	-- https://www.mediawiki.org/wiki/Extension:GeoData

	-- https://www.mediawiki.org/wiki/Help:Extension:Kartographer/Icons

	local maplinkMarkerSymbol = 'circle'

	local markerSymbols = {

		country = 'city',

		adm1st = 'city', -- state, provence

		adm2nd = 'city', -- county

		adm3rd = 'city', -- municipality

		city = 'city', -- actually comes with size param. map to city/town/village

		airport = 'airport',

		edu = 'college', -- map to college or school

		forest = 'park',

		glacier = 'triangle-stroked',

		mountain = 'triangle-stroked',

		pass = 'cross',

		railwaystation = 'rail', -- rail, rail-above, rail-light, rail-metro, rail-underground

		river = 'water',

		waterbody = 'water',

		satellite = 'rocket',

		camera = 'camera',

		isle = 'circle-stroked',

		event = 'star-stroked',

		landmark = 'star'

	}

	if markerSymbolstype then

		maplinkMarkerSymbol = markerSymbolstype

	end

	

	-- https://meta.wikimedia.org/wiki/WikiMiniAtlas

	population = tonumber(population)

	if type == 'city' and population then

		if population < 100000 then -- town

			maplinkMarkerSymbol = 'town'

		elseif population < 10000 then -- village

			maplinkMarkerSymbol = 'village'

		end

	end

	return maplinkMarkerSymbol

end



--[[

geohackTypeToScale



Convert from Geohack's types to Geohack's scale levels

]]

local function geohackTypeToScale(type, population)

	local typeScale = {

		adm1st = 1000000,

		adm2nd = 300000,

		adm3rd = 100000,

		airport = 30000,

		city = 100000,

		country = 10000000,

		edu = 10000,

		event = 50000,

		forest = 50000,

		glacier = 50000,

		isle = 100000,

		landmark = 10000,

		mountain = 100000,

		pass = 10000,

		railwaystation = 10000,

		river = 100000,

		satellite = 10000000,

		waterbody = 100000,

		camera = 10000

	}

	local scale

	if typeScaletype then

		scale = typeScaletype

	end

	population = tonumber(population)

	if type == 'city' and population and population > 0 then

		-- assume city is a circle with density of 1000/square kilometer

		-- compute diameter

		scale = 356.82 * math.sqrt(population)

		-- don't zoom in too far

		if scale < 30000 then

			scale = 30000

		end

	end

	return scale

end



local log2 = 0.693147181



--[[

geohackTypeToScale



Convert from Geohack's scale levels to OSM style zoom levels as used by <maplink>

]]

local function geohackScaleToMapZoom(scale)

	scale = tonumber(scale)

	if not scale or scale <= 0 then return end

	-- Empirically derived from geohack behavior

	return 29.214-math.log(scale)/log2

end



local function geohackDimToMapZoom(dim, units)

	dim = tonumber(dim)

	if not dim or dim <= 0 then return end

	if units and string.lower(units) == 'km' then

		dim = dim*1000

	end

	-- Empirically derived from geohack behavior

	return 25.892-math.log(dim)/log2

end



local function labelForQID(qid)

	local entity = mw.wikibase and qid and (mw.wikibase.getEntityObject(qid) or mw.wikibase.getEntityObject())

	if entity then

		return entity:getLabel()

	end

	return nil

end



--[[

coordLinkRenderer



Render a traditional coord-style geohacklink based on the provided information

]]

local function coordLinkRenderer(args, coordinateSpec)

	local uriComponents = coordinateSpec"param"

	if uriComponents == "" then

		-- RETURN error, should never be empty or nil

		return "ERROR param was empty"

	end

	if args"name" then

		uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec"name"])

	end



	local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">'

			.. '<span class="latitude">' .. coordinateSpec"dms-lat" .. '</span> '

			.. '<span class="longitude">' ..coordinateSpec"dms-long" .. '</span>'

			.. '</span>'



	local geodeclat, geodeclong = addHemisphereToDec( coordinateSpec'dec-lat'], coordinateSpec'dec-long'])

	local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">'

			.. geodeclat .. ' '

			.. geodeclong

			.. '</span>'



	local geonumhtml = '<span class="geo">'

			.. coordinateSpec"dec-lat" .. '; '

			.. coordinateSpec"dec-long"

			.. '</span>'



	local inner = '<span class="' .. displayDefault(coordinateSpec"default"], "dms" ) .. '">' .. geodmshtml .. '</span>'

				.. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'

				.. '<span class="' .. displayDefault(coordinateSpec"default"], "dec" ) .. '">';



	if not args"name" then

		inner = inner .. geodechtml

				.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span></span>'

	else

		inner = inner .. '<span class="vcard">' .. geodechtml

				.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span>'

				.. '<span style="display:none">&#xfeff; (<span class="fn org">'

				.. args"name" .. '</span>)</span></span></span>'

	end

	return '[' .. coord_link .. uriComponents .. ' ' .. inner .. ']'

end



--[[

kartographerRenderer



Render a Kartographer <maplink> based on the provided information

]]

local function kartographerRenderer(args, coordinateSpec)

	-- we should bring the useful geohack params to template level

	-- where they can easily be used in TemplateData etc.

	-- then render those to geohack links

	local params = splitParam( coordinateSpec.param )

	local type, hasPopulationInType = string.gsub( string.lower( params.type or '' ), '%(.+$', '' )

	

	-- parse the population number

	local population

	if hasPopulationInType == 1 then

		population = string.match( params.type , '[%d,]+')

		population = string.gsub(population, ',', '')

	end



	local dim, units = string.match(params.dim or '', '^(%d+)(%a*)$')

	local scale = tonumber(params.scale) or geohackTypeToScale(type, population)

	local zoom = geohackScaleToMapZoom(scale) or geohackDimToMapZoom(dim, units) or 12



	-- compensate for Mercator projection

	local lat = tonumber(coordinateSpec'dec-lat']) or 45

	local coslat = math.cos(math.rad(lat))

    -- don't overcompensate near poles

    if coslat < 1./8. then

    	coslat = 1./8.

    end

	zoom = math.floor(zoom + math.log(coslat)/log2)

	zoom = zoom < 3 and 3 or zoom > 18 and 18 or zoom

	

	local maplinkArgs = {

		'latitude' = coordinateSpec'dec-lat'],

		'longitude' = coordinateSpec'dec-long'],

		'zoom' = zoom,

		'text' = coordinateSpec'dms-lat' .. ' ' .. coordinateSpec'dms-long'],

	}



	if coordinateSpec'default' == 'dec' then

		local geodeclat, geodeclong = addHemisphereToDec( coordinateSpec'dec-lat'], coordinateSpec'dec-long'])

		maplinkArgs'text' = geodeclat .. ' ' .. geodeclong

	end



	-- if possible, retrieve title from qid ?

	local coordinateTitle = args'name' or labelForQID(args'qid']) or mw.title.getCurrentTitle().text

	local maplinkMarkerSymbol = geohackTypeToMarkerSymbol(type, population);



	local maplinkContent = [[ {

		"type": "Feature",

		"geometry": {

			"type": "Point",

			"coordinates": [

				]] .. jsonSafeNumber( coordinateSpec'dec-long' ) .. [[,

				]] .. jsonSafeNumber( coordinateSpec'dec-lat' ) .. [[

			]

		},

		"properties": {

			"title": "]] .. mw.text.encode( coordinateTitle ) .. [[",

			"marker-symbol": "]] .. maplinkMarkerSymbol .. [[",

			"marker-color": "#3366cc"

		}

    } ]];

    

    -- Add popup with feature title and primary image ?

    local entityId = mw.wikibase and args.qid;

    if entityId then

	    maplinkContent = maplinkContent .. [[, {

			"type": "ExternalData",

			"service": "geoline",

			"ids": "]] .. entityId .. [[",

			"properties": {

				"stroke": "#FF9999"

			}

	    }, {

			"type": "ExternalData",

			"service": "geoshape",

			"ids": "]] .. entityId .. [[",

			"properties": {

				"fill": "#FF0000",

				"fill-opacity": 0.1,

				"stroke": "#FF9999"

			}

	    } ]]

	    -- use autozoom if we only had a qid

	    if not args'qid_is_guessed' then

	    	maplinkArgs'zoom' = nil

   			maplinkArgs'latitude' = nil

			maplinkArgs'longitude' = nil

	    end

    end



	local result = mw.getCurrentFrame():extensionTag{

    	name = 'maplink',

    	content = '[' .. maplinkContent .. ']',

    	args = maplinkArgs

	}

	-- append microformat

	local microformat = '<span class="h-geo geo" style="display:none;">'

		.. '<span class="p-latitude latitude">' .. coordinateSpec"dec-lat" .. '</span>'

		.. '<span class="p-longitude longitude">' .. coordinateSpec"dec-long" .. '</span>'

		.. '</span>'

	

	-- append hidden geolink for backwardscompatibility with scraping services

	local uriComponents = coordinateSpec"param"

	if args"name" then

		uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec"name"])

	end

	local geohacklink = '<span class="coord-geohack" style="display:none">&nbsp;['.. coord_link .. uriComponents .. ' (external maps)]</span>'

	-- What to do to help non-JS clients ?

	return result .. microformat .. geohacklink;

end



--[[

specPrinter



Output formatter.  Takes the structure generated by either parseDec

or parseDMS and formats it for inclusion on Wikipedia.

]]

local function specPrinter(args, originalArgs, coordinateSpec)

	local params = splitParam( coordinateSpec.param )

	local globe = string.lower( args.globe or params.globe or '' )

	local result = mw.html.create( 'span')

	result:attr('class', 'plainlinks nourlexpansion')

	result:attr('data-coord-values', string.gsub( mTemplateInvocation.invocation('coord', originalArgs), "{{(.-)}}", "%1"))



	if args'renderer' == 'kartographer' or (

		not args'renderer' and (globe == '' or globe == 'earth'))

	then

	    result = result:node( kartographerRenderer(args, coordinateSpec) )

	else

		result = result:node( coordLinkRenderer(args, coordinateSpec) )

	end

	return tostring(result)

end



--[[ Helper function, convert decimal to degrees ]]

local function convert_dec2dms_d(coordinate)

	local d = math_mod._round( coordinate, 0 ) .. "°"

	return d .. ""

end



--[[ Helper function, convert decimal to degrees and minutes ]]

local function convert_dec2dms_dm(coordinate)

	coordinate = math_mod._round( coordinate * 60, 0 );

	local m = coordinate % 60;

	coordinate = math.floor( (coordinate - m) / 60 );

	local d = coordinate % 360 .."°"



	return d .. string.format( "%02d′", m )

end



--[[ Helper function, convert decimal to degrees, minutes, and seconds ]]

local function convert_dec2dms_dms(coordinate)

	coordinate = math_mod._round( coordinate * 60 * 60, 0 );

	local s = coordinate % 60

	coordinate = math.floor( (coordinate - s) / 60 );

	local m = coordinate % 60

	coordinate = math.floor( (coordinate - m) / 60 );

	local d = coordinate % 360 .."°"



	return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s )

end



--[[

Helper function, convert decimal latitude or longitude to

degrees, minutes, and seconds format based on the specified precision.

]]

local function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)

	local coord = tonumber(coordinate)

	local postfix

	if coord >= 0 then

		postfix = firstPostfix

	else

		postfix = secondPostfix

	end



	precision = precision:lower();

	if precision == "dms" then

		return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;

	elseif precision == "dm" then

		return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;

	elseif precision == "d" then

		return convert_dec2dms_d( math.abs( coord ) ) .. postfix;

	end

end



--[[

Convert DMS format into a N or E decimal coordinate

]]

local function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)

	local degrees = tonumber(degrees_str)

	local minutes = tonumber(minutes_str) or 0

	local seconds = tonumber(seconds_str) or 0



	local factor = 1

	if direction == "S" or direction == "W" then

		factor = -1

	end



	local precision = 0

	if seconds_str then

		precision = 5 + math.max( math_mod._precision(seconds_str), 0 );

	elseif minutes_str and minutes_str ~= '' then

		precision = 3 + math.max( math_mod._precision(minutes_str), 0 );

	else

		precision = math.max( math_mod._precision(degrees_str), 0 );

	end



	local decimal = factor * (degrees+(minutes+seconds/60)/60)

	return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based.

end



--[[

Checks input values to for out of range errors.

]]

local function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )

	local errors = {};

	lat_d = tonumber( lat_d ) or 0;

	lat_m = tonumber( lat_m ) or 0;

	lat_s = tonumber( lat_s ) or 0;

	long_d = tonumber( long_d ) or 0;

	long_m = tonumber( long_m ) or 0;

	long_s = tonumber( long_s ) or 0;



	if strong then

		if lat_d < 0 then

			table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"})

		end

		if long_d < 0 then

			table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"})

		end

		--[[

		#coordinates is inconsistent about whether this is an error.  If globe: is

		specified, it won't error on this condition, but otherwise it will.



		For not simply disable this check.



		if long_d > 180 then

			table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})

		end

		]]

	end



	if lat_d > 90 then

		table.insert(errors, {source, "latitude degrees > 90"})

	end

	if lat_d < -90 then

		table.insert(errors, {source, "latitude degrees < -90"})

	end

	if lat_m >= 60 then

		table.insert(errors, {source, "latitude minutes >= 60"})

	end

	if lat_m < 0 then

		table.insert(errors, {source, "latitude minutes < 0"})

	end

	if lat_s >= 60 then

		table.insert(errors, {source, "latitude seconds >= 60"})

	end

	if lat_s < 0 then

		table.insert(errors, {source, "latitude seconds < 0"})

	end

	if long_d >= 360 then

		table.insert(errors, {source, "longitude degrees >= 360"})

	end

	if long_d <= -360 then

		table.insert(errors, {source, "longitude degrees <= -360"})

	end

	if long_m >= 60 then

		table.insert(errors, {source, "longitude minutes >= 60"})

	end

	if long_m < 0 then

		table.insert(errors, {source, "longitude minutes < 0"})

	end

	if long_s >= 60 then

		table.insert(errors, {source, "longitude seconds >= 60"})

	end

	if long_s < 0 then

		table.insert(errors, {source, "longitude seconds < 0"})

	end



	return errors;

end



--[[

parseDec



Transforms decimal format latitude and longitude into the

structure to be used in displaying coordinates

]]

local function parseDec( lat, long, format )

	local coordinateSpec = {}

	local errors = {}



	if not long then

		return nil, {{"parseDec", "Missing longitude"}}

	elseif not tonumber(long) then

		return nil, {{"parseDec", "Longitude could not be parsed as a number: " .. long}}

	end



	errors = validate( lat, nil, nil, long, nil, nil, 'parseDec', false );

	coordinateSpec"dec-lat"  = lat;

	coordinateSpec"dec-long" = long;



	local mode = coordinates.determineMode( lat, long );

	coordinateSpec"dms-lat"  = convert_dec2dms( lat, "N", "S", mode)  -- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}

	coordinateSpec"dms-long" = convert_dec2dms( long, "E", "W", mode)  -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}



	if format then

		coordinateSpec.default = format

	else

		coordinateSpec.default = "dec"

	end



	return coordinateSpec, errors

end



--[[

parseDMS



Transforms degrees, minutes, seconds format latitude and longitude

into the a structure to be used in displaying coordinates

]]

local function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )

	local coordinateSpec, errors, backward = {}, {}



	lat_f = lat_f:upper();

	long_f = long_f:upper();



	-- Check if specified backward

	if lat_f == 'E' or lat_f == 'W' then

		lat_d, long_d, lat_m, long_m, lat_s, long_s, lat_f, long_f, backward = long_d, lat_d, long_m, lat_m, long_s, lat_s, long_f, lat_f, true;

	end



	errors = validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, 'parseDMS', true );

	if not long_d then

		return nil, {{"parseDMS", "Missing longitude" }}

	elseif not tonumber(long_d) then

		return nil, {{"parseDMS", "Longitude could not be parsed as a number:" .. long_d }}

	end



	if not lat_m and not lat_s and not long_m and not long_s and #errors == 0 then

		if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then

			if lat_f:upper() == 'S' then

				lat_d = '-' .. lat_d;

			end

			if long_f:upper() == 'W' then

				long_d = '-' .. long_d;

			end



			return parseDec( lat_d, long_d, format );

		end

	end



	coordinateSpec"dms-lat"  = lat_d.."°"..optionalArg(lat_m,"′") .. optionalArg(lat_s,"″") .. lat_f

	coordinateSpec"dms-long" = long_d.."°"..optionalArg(long_m,"′") .. optionalArg(long_s,"″") .. long_f

	coordinateSpec"dec-lat"  = convert_dms2dec(lat_f, lat_d, lat_m, lat_s) -- {{coord/dms2dec|{{{4}}}|{{{1}}}|0{{{2}}}|0{{{3}}}}}

	coordinateSpec"dec-long" = convert_dms2dec(long_f, long_d, long_m, long_s) -- {{coord/dms2dec|{{{8}}}|{{{5}}}|0{{{6}}}|0{{{7}}}}}



	if format then

		coordinateSpec.default = format

	else

		coordinateSpec.default = "dms"

	end



	return coordinateSpec, errors, backward

end



--[[

Check the input arguments for coord to determine the kind of data being provided

and then make the necessary processing.

]]

local function formatTest(args, originalArgs)

	local result, errors

	local backward, primary = false, false



	local function getParam(args, lim)

		local ret = {}

		for i = 1, lim do

			reti = argsi or ''

		end

		return table.concat(ret, '_')

	end



	if not args1 then

		-- no lat logic

		return errorPrinter( {{"formatTest", "Missing latitude"}} )

	elseif not tonumber(args1]) then

		-- bad lat logic

		return errorPrinter( {{"formatTest", "Unable to parse latitude as a number:" .. args1]}} )

	elseif not args4 and not args5 and not args6 then

		-- dec logic

		result, errors = parseDec(args1], args2], args.format)

		if not result then

			return errorPrinter(errors);

		end

		-- formatting for geohack: geohack expects D_N_D_E notation or D;D notation

		-- wikiminiatlas doesn't support D;D notation

		-- #coordinates parserfunction doesn't support negative decimals with NSWE

		result.param = table.concat({

			math.abs(tonumber(args1])),

			((tonumber(args1]) or 0) < 0) and 'S' or 'N',

			math.abs(tonumber(args2])),

			((tonumber(args2]) or 0) < 0) and 'W' or 'E',

			args3 or ''}, '_')

	elseif dmsTest(args4], args8]) then

		-- dms logic

		result, errors, backward = parseDMS(args1], args2], args3], args4],

			args5], args6], args7], args8], args.format)

		if args10 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 9)

	elseif dmsTest(args3], args6]) then

		-- dm logic

		result, errors, backward = parseDMS(args1], args2], nil, args3],

			args4], args5], nil, args6], args'format'])

		if args8 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 7)

	elseif dmsTest(args2], args4]) then

		-- d logic

		result, errors, backward = parseDMS(args1], nil, nil, args2],

			args3], nil, nil, args4], args.format)

		if args6 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 5)

	else

		-- Error

		return errorPrinter({{"formatTest", "Unknown argument format"}}) .. '[[Category:Pages with malformed coordinate tags]]'

	end

	result.name = args.name



	local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'}

	for _, v in ipairs(extra_param) do

		if argsv then

			table.insert(errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' })

		end

	end



	local ret = specPrinter(args, originalArgs, result)

	if #errors > 0 then

		ret = ret .. ' ' .. errorPrinter(errors) .. '[[Category:Pages with malformed coordinate tags]]'

	end

	return ret, backward

end



--[[

Generate Wikidata tracking categories.

]]

local function makeWikidataCategories(qid)

	local ret

	local qid = qid or mw.wikibase.getEntityIdForCurrentPage()

	if mw.wikibase and current_page.namespace == 0 then

		if qid and mw.wikibase.entityExists(qid) and mw.wikibase.getBestStatements(qid, "P625") and mw.wikibase.getBestStatements(qid, "P625")[1 then

			local snaktype = mw.wikibase.getBestStatements(qid, "P625")[1].mainsnak.snaktype

			if snaktype == 'value' then

				-- coordinates exist both here and on Wikidata, and can be compared.

				ret = 'Coordinates on Wikidata'

			elseif snaktype == 'somevalue' then

				ret = 'Coordinates on Wikidata set to unknown value'

			elseif snaktype == 'novalue' then

				ret = 'Coordinates on Wikidata set to no value'

			end

		else

			-- We have to either import the coordinates to Wikidata or remove them here.

			ret = 'Coordinates not on Wikidata'

		end

	end

	if ret then

		return string.format('[[Category:%s]]', ret)

	else

		return ''

	end

end



--[[

link



Simple function to export the coordinates link for other uses.



Usage:

	{{#invoke:Coordinates | link }}



]]

function coordinates.link(frame)

	return coord_link;

end



--[[

dec2dms



Wrapper to allow templates to call dec2dms directly.



Usage:

	{{#invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix |

		negative_suffix | precision }}



decimal_coordinate is converted to DMS format.  If positive, the positive_suffix

is appended (typical N or E), if negative, the negative suffix is appended.  The

specified precision is one of 'D', 'DM', or 'DMS' to specify the level of detail

to use.

]]

coordinates.dec2dms = makeInvokeFunc('_dec2dms')

function coordinates._dec2dms(args)

	local coordinate = args1

	local firstPostfix = args2 or ''

	local secondPostfix = args3 or ''

	local precision = args4 or ''



	return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)

end



--[[

Helper function to determine whether to use D, DM, or DMS

format depending on the precision of the decimal input.

]]

function coordinates.determineMode( value1, value2 )

	local precision = math.max( math_mod._precision( value1 ), math_mod._precision( value2 ) );

	if precision <= 0 then

		return 'd'

	elseif precision <= 2 then

		return 'dm';

	else

		return 'dms';

	end

end



--[[

dms2dec



Wrapper to allow templates to call dms2dec directly.



Usage:

	{{#invoke:Coordinates | dms2dec | direction_flag | degrees |

		minutes | seconds }}



Converts DMS values specified as degrees, minutes, seconds too decimal format.

direction_flag is one of N, S, E, W, and determines whether the output is

positive (i.e. N and E) or negative (i.e. S and W).

]]

coordinates.dms2dec = makeInvokeFunc('_dms2dec')

function coordinates._dms2dec(args)

	local direction = args1

	local degrees = args2

	local minutes = args3

	local seconds = args4



	return convert_dms2dec(direction, degrees, minutes, seconds)

end



--[[

coord



Main entry point for Lua function to replace {{coord}}



Usage:

	{{#invoke:Coordinates | coord }}

	{{#invoke:Coordinates | coord | lat | long }}

	{{#invoke:Coordinates | coord | lat | lat_flag | long | long_flag }}

	...



	Refer to {{coord}} documentation page for many additional parameters and

	configuration options.



Note: This function provides the visual display elements of {{coord}}.  In

order to load coordinates into the database, the {{#coordinates:}} parser

function must also be called, this is done automatically in the Lua

version of {{coord}}.

]]

coordinates.coord = makeInvokeFunc('_coord')

function coordinates._coord(args)

	local originalArgs = mTableTools.shallowClone( args )

	local Notes = args.notes or ''

	local Display = args.display and args.display:lower() or 'inline'



	local function isInline(s)

		-- Finds whether coordinates are displayed inline.

		return s:find('inline') ~= nil or s == 'i' or s == 'it' or s == 'ti'

	end

	local function isInTitle(s)

		-- Finds whether coordinates are displayed in the title.

		return s:find('title') ~= nil or s == 't' or s == 'it' or s == 'ti'

	end

	

	-- lookup qid item when not provided

	-- use name as backup option when non-current page

	if mw.wikibase and not args'qid' then

		if isInTitle(Display) then

			args'qid' = mw.wikibase.getEntityIdForCurrentPage()

		elseif not isInTitle(Display) and args'name' then

			args'qid' = mw.wikibase.getEntityIdForTitle( args'name'  )

		end

		args'qid_is_guessed' = true

	end



	-- fill in coordinates using qid when not present

	if not tonumber(args1]) and not args2 and mw.wikibase and args.qid then

		-- If no unnamed args, then try to backfill with qid

		args3 = args1]; args1 = nil

		local entity = mw.wikibase.getEntityObject(args.qid)

		if entity

			and entity.claims

			and entity.claims.P625

			and entity.claims.P6251].mainsnak.snaktype == 'value'

		then

			local precision = entity.claims.P6251].mainsnak.datavalue.value.precision

			args1 = entity.claims.P6251].mainsnak.datavalue.value.latitude

			args2 = entity.claims.P6251].mainsnak.datavalue.value.longitude

			if precision then

				precision = -math_mod._round(math.log(precision)/math.log(10),0)

				args1 = math_mod._round(args1],precision)

				args2 = math_mod._round(args2],precision)

			end

			-- rest of the code expects strings

			args1 = tostring(args1])

			args2 = tostring(args2])

		end

	end



	local function coord_wrapper(in_args)

		-- Calls the parser function {{#coordinates:}}.

		return mw.getCurrentFrame():callParserFunction('#coordinates', in_args) or ''

	end



	-- check input and generate the coordinate content

	local contents, backward = formatTest(args, originalArgs)

	local text = ''

	-- wrap contents as needed for display

	if isInline(Display) then

		text = text .. displayinline(contents, Notes)

	end

	if isInTitle(Display) then

		text = text

			.. displaytitle(contents, Notes)

			.. makeWikidataCategories(args.qid)

	end

	

	if not args.nosave then

		-- Unless nosave, mark in Extension:GeoData with {{#coordinates}}

		local page_title, count = mw.title.getCurrentTitle(), 1

		if backward then

			local tmp = {}

			while not string.find((argscount-1 or ''), '[EW]') do tmpcount = (argscount or ''); count = count+1 end

			tmp.count = count; count = 2*(count-1)

			while count >= tmp.count do table.insert(tmp, 1, (argscount or '')); count = count-1 end

			for i, v in ipairs(tmp) do argsi = v end

		else

			while count <= 9 do argscount = (argscount or ''); count = count+1 end

		end

		-- doc, testcases and talkpages should not be 'primary'

		if isInTitle(Display) and not page_title.isTalkPage and page_title.subpageText ~= 'doc' and page_title.subpageText ~= 'testcases' then args10 = 'primary' end

		args.notes, args.format, args.display = nil

		text = text .. coord_wrapper(args)

	end

	

	-- load templatestyles

	text = mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = { src = templatestyles} } .. text

	

	return text

end



--[[

coord2text



Extracts a single value from a transclusion of {{Coord}}.

IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.



Usage:



    {{#invoke:Coordinates | coord2text | {{Coord}} | parameter }}



Valid values for the second parameter are: lat (signed integer), long (signed integer), type, scale, dim, region, globe, source



]]

function coordinates.coord2text(frame)

	if frame.args1 == '' or frame.args2 == '' or not frame.args2 then return nil end

	frame.args2 = mw.text.trim(frame.args2])

	if frame.args2 == 'lat' or frame.args2 == 'long' then

		local lat, long = mw.ustring.match(frame.args1],'<span class="p%-latitude latitude">([^<]+)</span><span class="p%-longitude longitude">([^<]+)</span>')

		if lat then

			return tonumber(frame.args2 == 'long' and long or lat)

		end

		local result, negative = mw.text.split((mw.ustring.match(frame.args1],'[%.%d]+°[NS] [%.%d]+°[EW]') or ''), ' ')

		if frame.args2 == 'lat' then

			result, negative = result1], 'S'

		else

			result, negative = result2], 'W'

		end

		result = mw.text.split(result, '°')

		if result2 == negative then result1 = '-'..result1 end

		return result1

	else

		return mw.ustring.match(frame.args1], 'params=.-_'..frame.args2..':(.-)[ _]')

	end

end



--[[

coordinsert



Injects some text into the Geohack link of a transclusion of {{Coord}} (if that text isn't already in the transclusion). Outputs the modified transclusion of {{Coord}}.

IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.



Usage:



    {{#invoke:Coordinates | coordinsert | {{Coord}} | parameter:value | parameter:value | … }}



Do not make Geohack unhappy by inserting something which isn't mentioned in the {{Coord}} documentation.



]]

function coordinates.coordinsert(frame)

	for i, v in ipairs(frame.args) do

		if i ~= 1 then

			if not mw.ustring.find(frame.args1], (mw.ustring.match(frame.argsi], '^(.-:)') or '')) then

				frame.args1 = mw.ustring.gsub(frame.args1], '(params=.-)_? ', '%1_'..frame.argsi..' ')

			end

		end

	end

	if frame.args.name then

		if not mw.ustring.find(frame.args1], '<span class="vcard">') then

			local namestr = frame.args.name

			frame.args1 = mw.ustring.gsub(frame.args1],

				'(<span class="geo%-default">)(<span[^<>]*>[^<>]*</span><span[^<>]*>[^<>]*<span[^<>]*>[^<>]*</span></span>)(</span>)',

				'%1<span class="vcard">%2<span style="display:none">&#xfeff; (<span class="fn org">' .. namestr .. '</span>)</span></span>%3')

			frame.args1 = mw.ustring.gsub(frame.args1], '(&params=[^&"<>%[%] ]*) ', '%1&title=' .. mw.uri.encode(namestr) .. ' ')

		end

	end

	return frame.args1

end



return coordinates
From Wikipedia, the free encyclopedia

--[[

This module is intended to replace the functionality of {{Coord}} and related

templates.  It provides several methods, including



{{#invoke:Coordinates | coord }} : General function formatting and displaying

coordinate values.



{{#invoke:Coordinates | dec2dms }} : Simple function for converting decimal

degree values to DMS format.



{{#invoke:Coordinates | dms2dec }} : Simple function for converting DMS format

to decimal degree format.



{{#invoke:Coordinates | link }} : Export the link used to reach the tools



]]



require('strict')



local math_mod = require("Module:Math")

local mTemplateInvocation = require( 'Module:Template invocation' )

local mTableTools = require('Module:TableTools')

local coordinates = {}



local current_page = mw.title.getCurrentTitle()

local page_name = mw.uri.encode( current_page.prefixedText, 'WIKI' )

local coord_link = '//geohack.toolforge.org/geohack.php?pagename=' .. page_name .. '&params='

local templatestyles = 'Module:Coordinates/styles.css'



--[[ Helper function, replacement for {{coord/display/title}} ]]

local function displaytitle(s, notes)

	local l = "[[Geographic coordinate system|Coordinates]]: " .. s

	local co = '<span id="coordinates">' .. l .. notes .. '</span>';

	return mw.getCurrentFrame():extensionTag{

		name = 'indicator',

		args = { name = 'coordinates' },

		content = '<span style="font-size: small;">' .. co .. '</span>'

	};

end



--[[ Helper function, Replacement for {{coord/display/inline}} ]]

local function displayinline(s, notes)

	return s .. notes

end



--[[ Helper function, used in detecting DMS formatting ]]

local function dmsTest(first, second)

	if type(first) ~= 'string' or type(second) ~= 'string' then

		return nil

	end

	local s = (first .. second):upper()

	return s:find('^[NS][EW]$') or s:find('^[EW][NS]$')

end





--[[ Wrapper function to grab args, see Module:Arguments for this function's documentation. ]]

local function makeInvokeFunc(funcName)

	return function (frame)

		local args = require('Module:Arguments').getArgs(frame, {

			wrappers = 'Template:Coord'

		})

		return coordinatesfuncName](args, frame)

	end

end



--[[ Helper function, handle optional args. ]]

local function optionalArg(arg, supplement)

	return arg and arg .. supplement or ''

end



--[[

Formats any error messages generated for display

]]

local function errorPrinter(errors)

	local result = ""

	for i,v in ipairs(errors) do

		local errorHTML = '<strong class="error">Coordinates: ' .. v2 .. '</strong>'

		result = result .. errorHTML .. "<br />"

	end

	return result

end



--[[

Determine the required CSS class to display coordinates



Usually geo-nondefault is hidden by CSS, unless a user has overridden this for himself

default is the mode as specificied by the user when calling the {{coord}} template

mode is the display mode (dec or dms) that we will need to determine the css class for

]]

local function displayDefault(default, mode)

	if default == "" then

		default = "dec"

	end



	if default == mode then

		return "geo-default"

	else

		return "geo-nondefault"

	end

end



--[[

JSON doesn't handle .78 values, only 0.78

we do want to keep our precision though, so tonumber is bad.

]]

local function jsonSafeNumber(numberString)

	numberString = numberString:gsub('^(%.)', '0.', 1)

	return numberString:gsub('^(-%.)', '-0.', 1)

end



--[[

addHemisphereToDec



Rewrite the decimal format to also denote the hemisphere abbreviations

This is a typical Template:Coord presentation and rather uncommon outside of en.wp

]]

local function addHemisphereToDec( latitude, longitude )

	local lat = tonumber( latitude ) or 0

	local returnLat

	if lat < 0 then

		-- FIXME this breaks the pre-existing precision

		returnLat = tostring(latitude):sub(2) .. "°S"

	else

		returnLat = (latitude or 0) .. "°N"

	end



	local long = tonumber( longitude ) or 0

	local returnLong

	if long < 0 then

		-- FIXME does not handle unicode minus

		returnLong = tostring(longitude):sub(2) .. "°W"

	else

		returnLong = (longitude or 0) .. "°E"

	end

	return returnLat, returnLong

end



--[[

splitParam



Split the Geohack parameter string and convert it into an object.

]]

local function splitParam( param )

	local out = {}

	for pair in mw.text.gsplit( param, '_', true ) do

		local keyValue = mw.text.split( pair, ':', true )

		if #keyValue == 2 then

			outkeyValue1]] = keyValue2

		end

	end

	return out

end



--[[

geohackTypeToMarkerSymbol



Convert from Geohack's type to a Kartographer marker symbol

]]

local function geohackTypeToMarkerSymbol(type, population)

	-- /info/en/?search=Template:Coord#type:T

	-- https://www.mediawiki.org/wiki/Extension:GeoData

	-- https://www.mediawiki.org/wiki/Help:Extension:Kartographer/Icons

	local maplinkMarkerSymbol = 'circle'

	local markerSymbols = {

		country = 'city',

		adm1st = 'city', -- state, provence

		adm2nd = 'city', -- county

		adm3rd = 'city', -- municipality

		city = 'city', -- actually comes with size param. map to city/town/village

		airport = 'airport',

		edu = 'college', -- map to college or school

		forest = 'park',

		glacier = 'triangle-stroked',

		mountain = 'triangle-stroked',

		pass = 'cross',

		railwaystation = 'rail', -- rail, rail-above, rail-light, rail-metro, rail-underground

		river = 'water',

		waterbody = 'water',

		satellite = 'rocket',

		camera = 'camera',

		isle = 'circle-stroked',

		event = 'star-stroked',

		landmark = 'star'

	}

	if markerSymbolstype then

		maplinkMarkerSymbol = markerSymbolstype

	end

	

	-- https://meta.wikimedia.org/wiki/WikiMiniAtlas

	population = tonumber(population)

	if type == 'city' and population then

		if population < 100000 then -- town

			maplinkMarkerSymbol = 'town'

		elseif population < 10000 then -- village

			maplinkMarkerSymbol = 'village'

		end

	end

	return maplinkMarkerSymbol

end



--[[

geohackTypeToScale



Convert from Geohack's types to Geohack's scale levels

]]

local function geohackTypeToScale(type, population)

	local typeScale = {

		adm1st = 1000000,

		adm2nd = 300000,

		adm3rd = 100000,

		airport = 30000,

		city = 100000,

		country = 10000000,

		edu = 10000,

		event = 50000,

		forest = 50000,

		glacier = 50000,

		isle = 100000,

		landmark = 10000,

		mountain = 100000,

		pass = 10000,

		railwaystation = 10000,

		river = 100000,

		satellite = 10000000,

		waterbody = 100000,

		camera = 10000

	}

	local scale

	if typeScaletype then

		scale = typeScaletype

	end

	population = tonumber(population)

	if type == 'city' and population and population > 0 then

		-- assume city is a circle with density of 1000/square kilometer

		-- compute diameter

		scale = 356.82 * math.sqrt(population)

		-- don't zoom in too far

		if scale < 30000 then

			scale = 30000

		end

	end

	return scale

end



local log2 = 0.693147181



--[[

geohackTypeToScale



Convert from Geohack's scale levels to OSM style zoom levels as used by <maplink>

]]

local function geohackScaleToMapZoom(scale)

	scale = tonumber(scale)

	if not scale or scale <= 0 then return end

	-- Empirically derived from geohack behavior

	return 29.214-math.log(scale)/log2

end



local function geohackDimToMapZoom(dim, units)

	dim = tonumber(dim)

	if not dim or dim <= 0 then return end

	if units and string.lower(units) == 'km' then

		dim = dim*1000

	end

	-- Empirically derived from geohack behavior

	return 25.892-math.log(dim)/log2

end



local function labelForQID(qid)

	local entity = mw.wikibase and qid and (mw.wikibase.getEntityObject(qid) or mw.wikibase.getEntityObject())

	if entity then

		return entity:getLabel()

	end

	return nil

end



--[[

coordLinkRenderer



Render a traditional coord-style geohacklink based on the provided information

]]

local function coordLinkRenderer(args, coordinateSpec)

	local uriComponents = coordinateSpec"param"

	if uriComponents == "" then

		-- RETURN error, should never be empty or nil

		return "ERROR param was empty"

	end

	if args"name" then

		uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec"name"])

	end



	local geodmshtml = '<span class="geo-dms" title="Maps, aerial photos, and other data for this location">'

			.. '<span class="latitude">' .. coordinateSpec"dms-lat" .. '</span> '

			.. '<span class="longitude">' ..coordinateSpec"dms-long" .. '</span>'

			.. '</span>'



	local geodeclat, geodeclong = addHemisphereToDec( coordinateSpec'dec-lat'], coordinateSpec'dec-long'])

	local geodechtml = '<span class="geo-dec" title="Maps, aerial photos, and other data for this location">'

			.. geodeclat .. ' '

			.. geodeclong

			.. '</span>'



	local geonumhtml = '<span class="geo">'

			.. coordinateSpec"dec-lat" .. '; '

			.. coordinateSpec"dec-long"

			.. '</span>'



	local inner = '<span class="' .. displayDefault(coordinateSpec"default"], "dms" ) .. '">' .. geodmshtml .. '</span>'

				.. '<span class="geo-multi-punct">&#xfeff; / &#xfeff;</span>'

				.. '<span class="' .. displayDefault(coordinateSpec"default"], "dec" ) .. '">';



	if not args"name" then

		inner = inner .. geodechtml

				.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span></span>'

	else

		inner = inner .. '<span class="vcard">' .. geodechtml

				.. '<span style="display:none">&#xfeff; / ' .. geonumhtml .. '</span>'

				.. '<span style="display:none">&#xfeff; (<span class="fn org">'

				.. args"name" .. '</span>)</span></span></span>'

	end

	return '[' .. coord_link .. uriComponents .. ' ' .. inner .. ']'

end



--[[

kartographerRenderer



Render a Kartographer <maplink> based on the provided information

]]

local function kartographerRenderer(args, coordinateSpec)

	-- we should bring the useful geohack params to template level

	-- where they can easily be used in TemplateData etc.

	-- then render those to geohack links

	local params = splitParam( coordinateSpec.param )

	local type, hasPopulationInType = string.gsub( string.lower( params.type or '' ), '%(.+$', '' )

	

	-- parse the population number

	local population

	if hasPopulationInType == 1 then

		population = string.match( params.type , '[%d,]+')

		population = string.gsub(population, ',', '')

	end



	local dim, units = string.match(params.dim or '', '^(%d+)(%a*)$')

	local scale = tonumber(params.scale) or geohackTypeToScale(type, population)

	local zoom = geohackScaleToMapZoom(scale) or geohackDimToMapZoom(dim, units) or 12



	-- compensate for Mercator projection

	local lat = tonumber(coordinateSpec'dec-lat']) or 45

	local coslat = math.cos(math.rad(lat))

    -- don't overcompensate near poles

    if coslat < 1./8. then

    	coslat = 1./8.

    end

	zoom = math.floor(zoom + math.log(coslat)/log2)

	zoom = zoom < 3 and 3 or zoom > 18 and 18 or zoom

	

	local maplinkArgs = {

		'latitude' = coordinateSpec'dec-lat'],

		'longitude' = coordinateSpec'dec-long'],

		'zoom' = zoom,

		'text' = coordinateSpec'dms-lat' .. ' ' .. coordinateSpec'dms-long'],

	}



	if coordinateSpec'default' == 'dec' then

		local geodeclat, geodeclong = addHemisphereToDec( coordinateSpec'dec-lat'], coordinateSpec'dec-long'])

		maplinkArgs'text' = geodeclat .. ' ' .. geodeclong

	end



	-- if possible, retrieve title from qid ?

	local coordinateTitle = args'name' or labelForQID(args'qid']) or mw.title.getCurrentTitle().text

	local maplinkMarkerSymbol = geohackTypeToMarkerSymbol(type, population);



	local maplinkContent = [[ {

		"type": "Feature",

		"geometry": {

			"type": "Point",

			"coordinates": [

				]] .. jsonSafeNumber( coordinateSpec'dec-long' ) .. [[,

				]] .. jsonSafeNumber( coordinateSpec'dec-lat' ) .. [[

			]

		},

		"properties": {

			"title": "]] .. mw.text.encode( coordinateTitle ) .. [[",

			"marker-symbol": "]] .. maplinkMarkerSymbol .. [[",

			"marker-color": "#3366cc"

		}

    } ]];

    

    -- Add popup with feature title and primary image ?

    local entityId = mw.wikibase and args.qid;

    if entityId then

	    maplinkContent = maplinkContent .. [[, {

			"type": "ExternalData",

			"service": "geoline",

			"ids": "]] .. entityId .. [[",

			"properties": {

				"stroke": "#FF9999"

			}

	    }, {

			"type": "ExternalData",

			"service": "geoshape",

			"ids": "]] .. entityId .. [[",

			"properties": {

				"fill": "#FF0000",

				"fill-opacity": 0.1,

				"stroke": "#FF9999"

			}

	    } ]]

	    -- use autozoom if we only had a qid

	    if not args'qid_is_guessed' then

	    	maplinkArgs'zoom' = nil

   			maplinkArgs'latitude' = nil

			maplinkArgs'longitude' = nil

	    end

    end



	local result = mw.getCurrentFrame():extensionTag{

    	name = 'maplink',

    	content = '[' .. maplinkContent .. ']',

    	args = maplinkArgs

	}

	-- append microformat

	local microformat = '<span class="h-geo geo" style="display:none;">'

		.. '<span class="p-latitude latitude">' .. coordinateSpec"dec-lat" .. '</span>'

		.. '<span class="p-longitude longitude">' .. coordinateSpec"dec-long" .. '</span>'

		.. '</span>'

	

	-- append hidden geolink for backwardscompatibility with scraping services

	local uriComponents = coordinateSpec"param"

	if args"name" then

		uriComponents = uriComponents .. "&title=" .. mw.uri.encode(coordinateSpec"name"])

	end

	local geohacklink = '<span class="coord-geohack" style="display:none">&nbsp;['.. coord_link .. uriComponents .. ' (external maps)]</span>'

	-- What to do to help non-JS clients ?

	return result .. microformat .. geohacklink;

end



--[[

specPrinter



Output formatter.  Takes the structure generated by either parseDec

or parseDMS and formats it for inclusion on Wikipedia.

]]

local function specPrinter(args, originalArgs, coordinateSpec)

	local params = splitParam( coordinateSpec.param )

	local globe = string.lower( args.globe or params.globe or '' )

	local result = mw.html.create( 'span')

	result:attr('class', 'plainlinks nourlexpansion')

	result:attr('data-coord-values', string.gsub( mTemplateInvocation.invocation('coord', originalArgs), "{{(.-)}}", "%1"))



	if args'renderer' == 'kartographer' or (

		not args'renderer' and (globe == '' or globe == 'earth'))

	then

	    result = result:node( kartographerRenderer(args, coordinateSpec) )

	else

		result = result:node( coordLinkRenderer(args, coordinateSpec) )

	end

	return tostring(result)

end



--[[ Helper function, convert decimal to degrees ]]

local function convert_dec2dms_d(coordinate)

	local d = math_mod._round( coordinate, 0 ) .. "°"

	return d .. ""

end



--[[ Helper function, convert decimal to degrees and minutes ]]

local function convert_dec2dms_dm(coordinate)

	coordinate = math_mod._round( coordinate * 60, 0 );

	local m = coordinate % 60;

	coordinate = math.floor( (coordinate - m) / 60 );

	local d = coordinate % 360 .."°"



	return d .. string.format( "%02d′", m )

end



--[[ Helper function, convert decimal to degrees, minutes, and seconds ]]

local function convert_dec2dms_dms(coordinate)

	coordinate = math_mod._round( coordinate * 60 * 60, 0 );

	local s = coordinate % 60

	coordinate = math.floor( (coordinate - s) / 60 );

	local m = coordinate % 60

	coordinate = math.floor( (coordinate - m) / 60 );

	local d = coordinate % 360 .."°"



	return d .. string.format( "%02d′", m ) .. string.format( "%02d″", s )

end



--[[

Helper function, convert decimal latitude or longitude to

degrees, minutes, and seconds format based on the specified precision.

]]

local function convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)

	local coord = tonumber(coordinate)

	local postfix

	if coord >= 0 then

		postfix = firstPostfix

	else

		postfix = secondPostfix

	end



	precision = precision:lower();

	if precision == "dms" then

		return convert_dec2dms_dms( math.abs( coord ) ) .. postfix;

	elseif precision == "dm" then

		return convert_dec2dms_dm( math.abs( coord ) ) .. postfix;

	elseif precision == "d" then

		return convert_dec2dms_d( math.abs( coord ) ) .. postfix;

	end

end



--[[

Convert DMS format into a N or E decimal coordinate

]]

local function convert_dms2dec(direction, degrees_str, minutes_str, seconds_str)

	local degrees = tonumber(degrees_str)

	local minutes = tonumber(minutes_str) or 0

	local seconds = tonumber(seconds_str) or 0



	local factor = 1

	if direction == "S" or direction == "W" then

		factor = -1

	end



	local precision = 0

	if seconds_str then

		precision = 5 + math.max( math_mod._precision(seconds_str), 0 );

	elseif minutes_str and minutes_str ~= '' then

		precision = 3 + math.max( math_mod._precision(minutes_str), 0 );

	else

		precision = math.max( math_mod._precision(degrees_str), 0 );

	end



	local decimal = factor * (degrees+(minutes+seconds/60)/60)

	return string.format( "%." .. precision .. "f", decimal ) -- not tonumber since this whole thing is string based.

end



--[[

Checks input values to for out of range errors.

]]

local function validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, source, strong )

	local errors = {};

	lat_d = tonumber( lat_d ) or 0;

	lat_m = tonumber( lat_m ) or 0;

	lat_s = tonumber( lat_s ) or 0;

	long_d = tonumber( long_d ) or 0;

	long_m = tonumber( long_m ) or 0;

	long_s = tonumber( long_s ) or 0;



	if strong then

		if lat_d < 0 then

			table.insert(errors, {source, "latitude degrees < 0 with hemisphere flag"})

		end

		if long_d < 0 then

			table.insert(errors, {source, "longitude degrees < 0 with hemisphere flag"})

		end

		--[[

		#coordinates is inconsistent about whether this is an error.  If globe: is

		specified, it won't error on this condition, but otherwise it will.



		For not simply disable this check.



		if long_d > 180 then

			table.insert(errors, {source, "longitude degrees > 180 with hemisphere flag"})

		end

		]]

	end



	if lat_d > 90 then

		table.insert(errors, {source, "latitude degrees > 90"})

	end

	if lat_d < -90 then

		table.insert(errors, {source, "latitude degrees < -90"})

	end

	if lat_m >= 60 then

		table.insert(errors, {source, "latitude minutes >= 60"})

	end

	if lat_m < 0 then

		table.insert(errors, {source, "latitude minutes < 0"})

	end

	if lat_s >= 60 then

		table.insert(errors, {source, "latitude seconds >= 60"})

	end

	if lat_s < 0 then

		table.insert(errors, {source, "latitude seconds < 0"})

	end

	if long_d >= 360 then

		table.insert(errors, {source, "longitude degrees >= 360"})

	end

	if long_d <= -360 then

		table.insert(errors, {source, "longitude degrees <= -360"})

	end

	if long_m >= 60 then

		table.insert(errors, {source, "longitude minutes >= 60"})

	end

	if long_m < 0 then

		table.insert(errors, {source, "longitude minutes < 0"})

	end

	if long_s >= 60 then

		table.insert(errors, {source, "longitude seconds >= 60"})

	end

	if long_s < 0 then

		table.insert(errors, {source, "longitude seconds < 0"})

	end



	return errors;

end



--[[

parseDec



Transforms decimal format latitude and longitude into the

structure to be used in displaying coordinates

]]

local function parseDec( lat, long, format )

	local coordinateSpec = {}

	local errors = {}



	if not long then

		return nil, {{"parseDec", "Missing longitude"}}

	elseif not tonumber(long) then

		return nil, {{"parseDec", "Longitude could not be parsed as a number: " .. long}}

	end



	errors = validate( lat, nil, nil, long, nil, nil, 'parseDec', false );

	coordinateSpec"dec-lat"  = lat;

	coordinateSpec"dec-long" = long;



	local mode = coordinates.determineMode( lat, long );

	coordinateSpec"dms-lat"  = convert_dec2dms( lat, "N", "S", mode)  -- {{coord/dec2dms|{{{1}}}|N|S|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}

	coordinateSpec"dms-long" = convert_dec2dms( long, "E", "W", mode)  -- {{coord/dec2dms|{{{2}}}|E|W|{{coord/prec dec|{{{1}}}|{{{2}}}}}}}



	if format then

		coordinateSpec.default = format

	else

		coordinateSpec.default = "dec"

	end



	return coordinateSpec, errors

end



--[[

parseDMS



Transforms degrees, minutes, seconds format latitude and longitude

into the a structure to be used in displaying coordinates

]]

local function parseDMS( lat_d, lat_m, lat_s, lat_f, long_d, long_m, long_s, long_f, format )

	local coordinateSpec, errors, backward = {}, {}



	lat_f = lat_f:upper();

	long_f = long_f:upper();



	-- Check if specified backward

	if lat_f == 'E' or lat_f == 'W' then

		lat_d, long_d, lat_m, long_m, lat_s, long_s, lat_f, long_f, backward = long_d, lat_d, long_m, lat_m, long_s, lat_s, long_f, lat_f, true;

	end



	errors = validate( lat_d, lat_m, lat_s, long_d, long_m, long_s, 'parseDMS', true );

	if not long_d then

		return nil, {{"parseDMS", "Missing longitude" }}

	elseif not tonumber(long_d) then

		return nil, {{"parseDMS", "Longitude could not be parsed as a number:" .. long_d }}

	end



	if not lat_m and not lat_s and not long_m and not long_s and #errors == 0 then

		if math_mod._precision( lat_d ) > 0 or math_mod._precision( long_d ) > 0 then

			if lat_f:upper() == 'S' then

				lat_d = '-' .. lat_d;

			end

			if long_f:upper() == 'W' then

				long_d = '-' .. long_d;

			end



			return parseDec( lat_d, long_d, format );

		end

	end



	coordinateSpec"dms-lat"  = lat_d.."°"..optionalArg(lat_m,"′") .. optionalArg(lat_s,"″") .. lat_f

	coordinateSpec"dms-long" = long_d.."°"..optionalArg(long_m,"′") .. optionalArg(long_s,"″") .. long_f

	coordinateSpec"dec-lat"  = convert_dms2dec(lat_f, lat_d, lat_m, lat_s) -- {{coord/dms2dec|{{{4}}}|{{{1}}}|0{{{2}}}|0{{{3}}}}}

	coordinateSpec"dec-long" = convert_dms2dec(long_f, long_d, long_m, long_s) -- {{coord/dms2dec|{{{8}}}|{{{5}}}|0{{{6}}}|0{{{7}}}}}



	if format then

		coordinateSpec.default = format

	else

		coordinateSpec.default = "dms"

	end



	return coordinateSpec, errors, backward

end



--[[

Check the input arguments for coord to determine the kind of data being provided

and then make the necessary processing.

]]

local function formatTest(args, originalArgs)

	local result, errors

	local backward, primary = false, false



	local function getParam(args, lim)

		local ret = {}

		for i = 1, lim do

			reti = argsi or ''

		end

		return table.concat(ret, '_')

	end



	if not args1 then

		-- no lat logic

		return errorPrinter( {{"formatTest", "Missing latitude"}} )

	elseif not tonumber(args1]) then

		-- bad lat logic

		return errorPrinter( {{"formatTest", "Unable to parse latitude as a number:" .. args1]}} )

	elseif not args4 and not args5 and not args6 then

		-- dec logic

		result, errors = parseDec(args1], args2], args.format)

		if not result then

			return errorPrinter(errors);

		end

		-- formatting for geohack: geohack expects D_N_D_E notation or D;D notation

		-- wikiminiatlas doesn't support D;D notation

		-- #coordinates parserfunction doesn't support negative decimals with NSWE

		result.param = table.concat({

			math.abs(tonumber(args1])),

			((tonumber(args1]) or 0) < 0) and 'S' or 'N',

			math.abs(tonumber(args2])),

			((tonumber(args2]) or 0) < 0) and 'W' or 'E',

			args3 or ''}, '_')

	elseif dmsTest(args4], args8]) then

		-- dms logic

		result, errors, backward = parseDMS(args1], args2], args3], args4],

			args5], args6], args7], args8], args.format)

		if args10 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 9)

	elseif dmsTest(args3], args6]) then

		-- dm logic

		result, errors, backward = parseDMS(args1], args2], nil, args3],

			args4], args5], nil, args6], args'format'])

		if args8 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 7)

	elseif dmsTest(args2], args4]) then

		-- d logic

		result, errors, backward = parseDMS(args1], nil, nil, args2],

			args3], nil, nil, args4], args.format)

		if args6 then

			table.insert(errors, {'formatTest', 'Extra unexpected parameters'})

		end

		if not result then

			return errorPrinter(errors)

		end

		result.param = getParam(args, 5)

	else

		-- Error

		return errorPrinter({{"formatTest", "Unknown argument format"}}) .. '[[Category:Pages with malformed coordinate tags]]'

	end

	result.name = args.name



	local extra_param = {'dim', 'globe', 'scale', 'region', 'source', 'type'}

	for _, v in ipairs(extra_param) do

		if argsv then

			table.insert(errors, {'formatTest', 'Parameter: "' .. v .. '=" should be "' .. v .. ':"' })

		end

	end



	local ret = specPrinter(args, originalArgs, result)

	if #errors > 0 then

		ret = ret .. ' ' .. errorPrinter(errors) .. '[[Category:Pages with malformed coordinate tags]]'

	end

	return ret, backward

end



--[[

Generate Wikidata tracking categories.

]]

local function makeWikidataCategories(qid)

	local ret

	local qid = qid or mw.wikibase.getEntityIdForCurrentPage()

	if mw.wikibase and current_page.namespace == 0 then

		if qid and mw.wikibase.entityExists(qid) and mw.wikibase.getBestStatements(qid, "P625") and mw.wikibase.getBestStatements(qid, "P625")[1 then

			local snaktype = mw.wikibase.getBestStatements(qid, "P625")[1].mainsnak.snaktype

			if snaktype == 'value' then

				-- coordinates exist both here and on Wikidata, and can be compared.

				ret = 'Coordinates on Wikidata'

			elseif snaktype == 'somevalue' then

				ret = 'Coordinates on Wikidata set to unknown value'

			elseif snaktype == 'novalue' then

				ret = 'Coordinates on Wikidata set to no value'

			end

		else

			-- We have to either import the coordinates to Wikidata or remove them here.

			ret = 'Coordinates not on Wikidata'

		end

	end

	if ret then

		return string.format('[[Category:%s]]', ret)

	else

		return ''

	end

end



--[[

link



Simple function to export the coordinates link for other uses.



Usage:

	{{#invoke:Coordinates | link }}



]]

function coordinates.link(frame)

	return coord_link;

end



--[[

dec2dms



Wrapper to allow templates to call dec2dms directly.



Usage:

	{{#invoke:Coordinates | dec2dms | decimal_coordinate | positive_suffix |

		negative_suffix | precision }}



decimal_coordinate is converted to DMS format.  If positive, the positive_suffix

is appended (typical N or E), if negative, the negative suffix is appended.  The

specified precision is one of 'D', 'DM', or 'DMS' to specify the level of detail

to use.

]]

coordinates.dec2dms = makeInvokeFunc('_dec2dms')

function coordinates._dec2dms(args)

	local coordinate = args1

	local firstPostfix = args2 or ''

	local secondPostfix = args3 or ''

	local precision = args4 or ''



	return convert_dec2dms(coordinate, firstPostfix, secondPostfix, precision)

end



--[[

Helper function to determine whether to use D, DM, or DMS

format depending on the precision of the decimal input.

]]

function coordinates.determineMode( value1, value2 )

	local precision = math.max( math_mod._precision( value1 ), math_mod._precision( value2 ) );

	if precision <= 0 then

		return 'd'

	elseif precision <= 2 then

		return 'dm';

	else

		return 'dms';

	end

end



--[[

dms2dec



Wrapper to allow templates to call dms2dec directly.



Usage:

	{{#invoke:Coordinates | dms2dec | direction_flag | degrees |

		minutes | seconds }}



Converts DMS values specified as degrees, minutes, seconds too decimal format.

direction_flag is one of N, S, E, W, and determines whether the output is

positive (i.e. N and E) or negative (i.e. S and W).

]]

coordinates.dms2dec = makeInvokeFunc('_dms2dec')

function coordinates._dms2dec(args)

	local direction = args1

	local degrees = args2

	local minutes = args3

	local seconds = args4



	return convert_dms2dec(direction, degrees, minutes, seconds)

end



--[[

coord



Main entry point for Lua function to replace {{coord}}



Usage:

	{{#invoke:Coordinates | coord }}

	{{#invoke:Coordinates | coord | lat | long }}

	{{#invoke:Coordinates | coord | lat | lat_flag | long | long_flag }}

	...



	Refer to {{coord}} documentation page for many additional parameters and

	configuration options.



Note: This function provides the visual display elements of {{coord}}.  In

order to load coordinates into the database, the {{#coordinates:}} parser

function must also be called, this is done automatically in the Lua

version of {{coord}}.

]]

coordinates.coord = makeInvokeFunc('_coord')

function coordinates._coord(args)

	local originalArgs = mTableTools.shallowClone( args )

	local Notes = args.notes or ''

	local Display = args.display and args.display:lower() or 'inline'



	local function isInline(s)

		-- Finds whether coordinates are displayed inline.

		return s:find('inline') ~= nil or s == 'i' or s == 'it' or s == 'ti'

	end

	local function isInTitle(s)

		-- Finds whether coordinates are displayed in the title.

		return s:find('title') ~= nil or s == 't' or s == 'it' or s == 'ti'

	end

	

	-- lookup qid item when not provided

	-- use name as backup option when non-current page

	if mw.wikibase and not args'qid' then

		if isInTitle(Display) then

			args'qid' = mw.wikibase.getEntityIdForCurrentPage()

		elseif not isInTitle(Display) and args'name' then

			args'qid' = mw.wikibase.getEntityIdForTitle( args'name'  )

		end

		args'qid_is_guessed' = true

	end



	-- fill in coordinates using qid when not present

	if not tonumber(args1]) and not args2 and mw.wikibase and args.qid then

		-- If no unnamed args, then try to backfill with qid

		args3 = args1]; args1 = nil

		local entity = mw.wikibase.getEntityObject(args.qid)

		if entity

			and entity.claims

			and entity.claims.P625

			and entity.claims.P6251].mainsnak.snaktype == 'value'

		then

			local precision = entity.claims.P6251].mainsnak.datavalue.value.precision

			args1 = entity.claims.P6251].mainsnak.datavalue.value.latitude

			args2 = entity.claims.P6251].mainsnak.datavalue.value.longitude

			if precision then

				precision = -math_mod._round(math.log(precision)/math.log(10),0)

				args1 = math_mod._round(args1],precision)

				args2 = math_mod._round(args2],precision)

			end

			-- rest of the code expects strings

			args1 = tostring(args1])

			args2 = tostring(args2])

		end

	end



	local function coord_wrapper(in_args)

		-- Calls the parser function {{#coordinates:}}.

		return mw.getCurrentFrame():callParserFunction('#coordinates', in_args) or ''

	end



	-- check input and generate the coordinate content

	local contents, backward = formatTest(args, originalArgs)

	local text = ''

	-- wrap contents as needed for display

	if isInline(Display) then

		text = text .. displayinline(contents, Notes)

	end

	if isInTitle(Display) then

		text = text

			.. displaytitle(contents, Notes)

			.. makeWikidataCategories(args.qid)

	end

	

	if not args.nosave then

		-- Unless nosave, mark in Extension:GeoData with {{#coordinates}}

		local page_title, count = mw.title.getCurrentTitle(), 1

		if backward then

			local tmp = {}

			while not string.find((argscount-1 or ''), '[EW]') do tmpcount = (argscount or ''); count = count+1 end

			tmp.count = count; count = 2*(count-1)

			while count >= tmp.count do table.insert(tmp, 1, (argscount or '')); count = count-1 end

			for i, v in ipairs(tmp) do argsi = v end

		else

			while count <= 9 do argscount = (argscount or ''); count = count+1 end

		end

		-- doc, testcases and talkpages should not be 'primary'

		if isInTitle(Display) and not page_title.isTalkPage and page_title.subpageText ~= 'doc' and page_title.subpageText ~= 'testcases' then args10 = 'primary' end

		args.notes, args.format, args.display = nil

		text = text .. coord_wrapper(args)

	end

	

	-- load templatestyles

	text = mw.getCurrentFrame():extensionTag{ name = 'templatestyles', args = { src = templatestyles} } .. text

	

	return text

end



--[[

coord2text



Extracts a single value from a transclusion of {{Coord}}.

IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.



Usage:



    {{#invoke:Coordinates | coord2text | {{Coord}} | parameter }}



Valid values for the second parameter are: lat (signed integer), long (signed integer), type, scale, dim, region, globe, source



]]

function coordinates.coord2text(frame)

	if frame.args1 == '' or frame.args2 == '' or not frame.args2 then return nil end

	frame.args2 = mw.text.trim(frame.args2])

	if frame.args2 == 'lat' or frame.args2 == 'long' then

		local lat, long = mw.ustring.match(frame.args1],'<span class="p%-latitude latitude">([^<]+)</span><span class="p%-longitude longitude">([^<]+)</span>')

		if lat then

			return tonumber(frame.args2 == 'long' and long or lat)

		end

		local result, negative = mw.text.split((mw.ustring.match(frame.args1],'[%.%d]+°[NS] [%.%d]+°[EW]') or ''), ' ')

		if frame.args2 == 'lat' then

			result, negative = result1], 'S'

		else

			result, negative = result2], 'W'

		end

		result = mw.text.split(result, '°')

		if result2 == negative then result1 = '-'..result1 end

		return result1

	else

		return mw.ustring.match(frame.args1], 'params=.-_'..frame.args2..':(.-)[ _]')

	end

end



--[[

coordinsert



Injects some text into the Geohack link of a transclusion of {{Coord}} (if that text isn't already in the transclusion). Outputs the modified transclusion of {{Coord}}.

IF THE GEOHACK LINK SYNTAX CHANGES THIS FUNCTION MUST BE MODIFIED.



Usage:



    {{#invoke:Coordinates | coordinsert | {{Coord}} | parameter:value | parameter:value | … }}



Do not make Geohack unhappy by inserting something which isn't mentioned in the {{Coord}} documentation.



]]

function coordinates.coordinsert(frame)

	for i, v in ipairs(frame.args) do

		if i ~= 1 then

			if not mw.ustring.find(frame.args1], (mw.ustring.match(frame.argsi], '^(.-:)') or '')) then

				frame.args1 = mw.ustring.gsub(frame.args1], '(params=.-)_? ', '%1_'..frame.argsi..' ')

			end

		end

	end

	if frame.args.name then

		if not mw.ustring.find(frame.args1], '<span class="vcard">') then

			local namestr = frame.args.name

			frame.args1 = mw.ustring.gsub(frame.args1],

				'(<span class="geo%-default">)(<span[^<>]*>[^<>]*</span><span[^<>]*>[^<>]*<span[^<>]*>[^<>]*</span></span>)(</span>)',

				'%1<span class="vcard">%2<span style="display:none">&#xfeff; (<span class="fn org">' .. namestr .. '</span>)</span></span>%3')

			frame.args1 = mw.ustring.gsub(frame.args1], '(&params=[^&"<>%[%] ]*) ', '%1&title=' .. mw.uri.encode(namestr) .. ' ')

		end

	end

	return frame.args1

end



return coordinates

Videos

Youtube | Vimeo | Bing

Websites

Google | Yahoo | Bing

Encyclopedia

Google | Yahoo | Bing

Facebook