local GlobeCoordinate = {} --Internal functions --[[ Check if a value is a number in the given range @param mixed value @param number min @param number max @return boolean ]]-- local function validateNumberInRange( value, min, max ) return type( value ) == 'number' and value >= min and value <= max end --[[ Validate a GlobeCoordinate defintion @param table definition data @return boolean ]]-- local 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 --[[ Try to find the relevant precision for a latitude or longitude as float @param float float @return number the precision ]]-- local 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 --[[ Try to find the relevant precision for a GlobeCoordinate definition @param table GlobeCoordinate definition @return number the precision ]]-- local function guessPrecision( definition ) return math.max( detectPrecisionForFloat( definition.latitude ), detectPrecisionForFloat( definition.longitude ) ) 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 ]]-- local 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 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 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