local GlobeCoordinate = {}
--Internal functions
--[[
Validate a GlobeCoordinate defintion
@param table definition data
@return boolean
]]--
function validate( definition )
--Validate constantes
if definition.globe ~= GlobeCoordinate.GLOBE.EARTH then
return false
end
--Validate precision
if not validateNumberInRange( definition.precision, 0, 1 ) then
return false
end
--Validate latitude and longitude
if not validateNumberInRange( definition.latitude, -180, 360 ) or not validateNumberInRange( definition.longitude, -180, 360 ) then
return false
end
return true
end
--[[
Check if a value is a number in the given range
@param mixed value
@param number min
@param number max
@return boolean
]]--
function validateNumberInRange( value, min, max )
return type( value ) == 'number' and value >= min and value <= max
end
--[[
Try to find the relevant precision for a GlobeCoordinate definition
@param table GlobeCoordinate definition
@return number the precision
]]--
function guessPrecision( definition )
return math.max( detectPrecisionForFloat( definition.latitude ), detectPrecisionForFloat( definition.longitude ) )
end
--[[
Try to find the relevant precision for a latitude or longitude as float
@param float float
@return number the precision
]]--
function detectPrecisionForFloat( float )
local parts = mw.text.split( tostring( float ), '.' )
if parts[2] then
return math.pow( 10, -1 * #parts[2] )
else
return 1
end
end
--[[
Format a float coordinate as DMS according to the precision
@param float float
@param precision float
@param positive string the tag if the coordinate is positive, like 'N'
@param positive string the tag if the coordinate is negative, like 'S'
@return string the coordinate in DMS format
]]--
function formatDMS( float, precision, positive, negative )
local isNegative = float < 0
float = math.abs( float )
local d = math.floor( float )
local dms = d .. '°'
if precision <= 1/60 then
float = (float - d) * 60
local m = math.floor( float )
dms = dms .. ' ' .. m .. '′'
if precision <= 1/3600 then
float = (float - m) * 60
local s
if float%2 ~= 0.5 then
s = math.floor( float + 0.5 )
else
s = float - 0.5
end
dms = dms .. ' ' .. s .. '″'
--TODO: precision higher than second
end
end
if isNegative then
return dms .. ' ' .. negative
else
return dms .. ' ' .. positive
end
end
--Public interface
--[[
Build a new GlobeCoordinate
@param table definition definition of the coodinate
@return GlobeCoordinate|nil
]]--
function GlobeCoordinate.new( definition )
--Default values
if definition.precision == nil then
definition.precision = guessPrecision( definition )
end
if definition.globe == nil then
definition.globe = GlobeCoordinate.GLOBE.EARTH
end
if not validate( definition ) then
return nil
end
local coord = {
latitude = definition.latitude,
longitude = definition.longitude,
globe = definition.globe or GlobeCoordinate.GLOBE.EARTH,
precision = definition.precision or 0
}
setmetatable( coord, {
__index = GlobeCoordinate,
__tostring = function( self ) return self:toString() end
} )
return coord
end
--[[
Build a new GlobeCoordinate from a Wikidata GlobeCoordinate value
@param table wikidataValue the coordinate as represented by Wikidata
@return GlobeCoordinate|nil
]]--
function GlobeCoordinate.newFromWikidataValue( wikidataValue )
if wikidataValue.globe == 'http://www.wikidata.org/entity/Q2' then
wikidataValue.globe = GlobeCoordinate.GLOBE.EARTH
else
return nil
end
return GlobeCoordinate.new( wikidataValue )
end
--[[
Return a GlobeCoordinate as a string
@param mw.language|string|nil language to use. By default the content language.
@return string
@todo i18n
]]--
function GlobeCoordinate:toString( language )
return formatDMS( self.latitude, self.precision, 'N', 'S' ) .. ' ' .. formatDMS( self.longitude, self.precision, 'E', 'W' )
end
--[[
Return a GlobeCoordinate in HTMl (with a <GlobeCoordinate> node)
@param mw.language|string|nil language to use. By default the content language.
@param table|nil attributes table of attributes to add to the <GlobeCoordinate> node.
@return string
]]--
function GlobeCoordinate:toHtml( language, attributes )
return mw.text.tag(
'span', {
["class"] = "geo"
},
mw.text.tag( 'span', {
["class"] = "latitude",
["title"] = self.latitude,
},
formatDMS( self.latitude, self.precision, 'N', 'S' )
) ..
' ' ..
mw.text.tag( 'span', {
["class"] = "longitude",
["title"] = self.longitude,
},
formatDMS( self.longitude, self.precision, 'E', 'W' )
)
)
end
--[[
Supported globes
]]--
GlobeCoordinate.GLOBE = {
EARTH = 'Earth'
}
return GlobeCoordinate