モジュール:Coordinates

2014年4月20日 (日) 10:36時点におけるWikiSysop (トーク | 投稿記録)による版 (1版)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

このモジュールについての説明文ページを モジュール:Coordinates/doc に作成できます

--[[

This module is intended to (eventually) replace some or all functionality of {{location}} and related
templates. At the moment it is collection of methods related to geolocation. 

*function coordinates.LocationTemplateCore(frame)
**function coordinates.GeoHack_link(frame)
***function coordinates.lat_lon(frame)
****function coordinates._deg2dms(deg,lang)
***function coordinates.externalLink(frame)
****function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
**function coordinates._getHeading(attributes)
**function coordinates.externalLinksSection(frame)
***function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
*function coordinates.getHeading(frame)  
*function coordinates.deg2dms(frame)

]]

coordinates = {};

-- =======================================
-- === Dependencies ======================
-- =======================================
local i18n     = require('Module:I18n/coordinates')    -- get localized translations of site names
local Fallback = require('Module:Fallback')            -- get fallback functions
local yesno    = require('Module:Yesno')

-- =======================================
-- === Hardwired parameters ==============
-- =======================================

-- Angles associated with each abriviation of compass point names. See [[:en:Points of the compass]]
local compass_points = {
  N    = 0,
  NBE  = 11.25,
  NNE  = 22.5,
  NEBN = 33.75,
  NE   = 45,
  NEBE = 56.25,
  ENE  = 67.5,
  EBN  = 78.75,
  E    = 90,
  EBS  = 101.25,
  ESE  = 112.5,
  SEBE = 123.75,
  SE   = 135,
  SEBS = 146.25,
  SSE  = 157.5,
  SBE  = 168.75,
  S    = 180,
  SBW  = 191.25,
  SSW  = 202.5,
  SWBS = 213.75,
  SW   = 225,
  SWBW = 236.25,
  WSW  = 247.5,
  WBS  = 258.75,
  W    = 270,
  WBN  = 281.25,
  WNW  = 292.5,
  NWBW = 303.75,
  NW   = 315,
  NWBN = 326.25,
  NNW  = 337.5,
  NBW  = 348.75,
}

-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr will be replaced with latitude, longitude, language code and GeoHack attribution parameters strings.
local SiteURL = {
	GeoHack        = 'http://tools.wmflabs.org/geohack/geohack.php?pagename={{FULLPAGENAMEE}}&params=$lat_N_$lon_E_$attr&language=$lang',
	GoogleEarth    = '{{fullurl:tools:~para/GeoCommons/earth.php|latdegdec=$lat&londegdec=$lon&scale=10000&commons=1}}',
	Proximityrama  = '{{fullurl:tools:~para/GeoCommons/proximityrama|latlon=$lat,$lon}}',
	OpenStreetMap  = '{{fullurl:tools:~kolossos/openlayers/commons-on-osm.php|zoom=16&lat=$lat&lon=$lon}}',
	GoogleMaps = { 
		Mars  = 'http://www.google.com/mars/#lat=$lat&lon=$lon&zoom=8',
		Moon  = 'http://www.google.com/moon/#lat=$lat&lon=$lon&zoom=8',
		Earth = 'http://maps.google.com/maps?ll=$lat,$lon&spn=0.01,0.01&t=k&q=http://toolserver.org/~para/GeoCommons/GeoCommons-simple.kml&hl=$lang'
	}
}

-- Categories
local CoorCat = {
	File          = '[[Category:Media with locations]]',
	Gallery       = '[[Category:Galleries with coordinates]]',
	Category      = '[[Category:Categories with coordinates]]',
	globe         = '[[Category:Media with %s locations]]',
	default       = '[[Category:Media with default locations]]',
	erroneous     = '[[Category:Media with erroneous locations]]<span style="color:red;font-weight:bold">Error: Invalid parameters!</span>\n'
}

-- =======================================
-- === Functions =========================
-- =======================================

-- parse attribute variable returning desired field
function coordinates.parseAttribute(frame)
  return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or ''
end

-- Parse attribute variable returning heading field. If heading is a string than try to convert it to an angle
function coordinates.getHeading(frame)  
	local attributes
	if frame.args[1] then
		attributes = frame.args[1]
	elseif frame.args.attributes then
		attributes = frame.args.attributes
	else
		return ''
	end
	local hNum = coordinates._getHeading(attributes)
	if hNum == nil then
		return ''
	end
	return tostring(hNum)
end
-- Helper core function for getHeading. 
function coordinates._getHeading(attributes)
	if attributes == nil then
		return nil
	end
	local hStr = string.match(mw.text.decode(attributes), 'heading:([^_]*)')
	if hStr == nil then
		return nil
	end
	local hNum = tonumber( hStr )
	if hNum == nil then
		hStr = string.upper (hStr)
		hNum = compass_points[hStr]  
	end
	if hNum ~= nil then
		hNum = hNum%360
	end
	return hNum
end

-- Convert degrees to degrees/minutes/seconds notation comonly used when displaying coordinates
function coordinates.deg2dms(frame)
	local deg = tonumber(frame.args[1])
	local lang
	if frame.args.lang and mw.language.isSupportedLanguage(frame.args.lang) then 
		lang = frame.args.lang
	else -- get user's chosen language 
		lang = frame:preprocess( "{{int:lang}}" )
	end
	if deg==nil then
		return frame.args[1];
	else
		return coordinates._deg2dms(deg,lang)
	end
end
-- Helper core function for deg2dms. 
function coordinates._deg2dms(deg,lang)
	local dNum, mNum, sNum, dStr, mStr, sStr
	local Lang = mw.language.new(lang)
	deg  = math.floor(360000*(deg%360)+0.49)     -- convert float to an integer. This step HAS to be identical for all conversions to avoid incorrect results due to different rounding
	dNum = math.floor(deg/360000) % 360          -- degree number (integer in 0-360 range)
	mNum = math.floor(deg/6000  ) %  60          -- minute number (integer in 0-60 range)
	sNum =           (deg%6000  ) / 100          -- seconds number (float with 2 decimal digits in 0-60 range)
	dStr = Lang:formatNum(dNum)                  -- degree string 
	mStr = Lang:formatNum(mNum)                  -- minute string 
	sStr = Lang:formatNum(sNum)                  -- second string 
	if mNum<10 then
		mStr = '0' ..mStr                        -- pad with zero if a single digit
	end
	if sNum<10 then
		sStr = '0' ..sStr                        -- pad with zero if less than ten
	end
	str = string.format('%s°&nbsp;%s′&nbsp;%s″', dStr, mStr, sStr);
	return str
end

-- format coordinate location string 
function coordinates.lat_lon(frame)
	local lat = tonumber(frame.args.lat)
	local lon = tonumber(frame.args.lon)
	if lon then -- get longitude t0 be in -180 to 180 range
		lon=lon%360
		if lon>180 then
			lon = lon-360
		end
	end
	local lang
	if frame.args.lang and mw.language.isSupportedLanguage(frame.args.lang) then 
		lang = frame.args.lang
	else -- get user's chosen language 
		lang = frame:preprocess( "{{int:lang}}" )
	end
	if lat==nil or lon==nil then
		return 'latitude, longitude'
	else
		local nsew = Fallback._langSwitch(i18n.NSEW, lang) -- find set of localized translation of N, S, W and E in the desired language 
		local SN, EW, latStr, lonStr
		if lat<0 then SN = nsew.S else SN = nsew.N end         -- choose S or N depending on latitude  degree sign
		if lon<0 then EW = nsew.W else EW = nsew.E end         -- choose W or E depending on longitude degree sign
		latStr = coordinates._deg2dms(math.abs(lat), lang)     -- Convert latitude  degrees to degrees/minutes/seconds
		lonStr = coordinates._deg2dms(math.abs(lon), lang)     -- Convert longitude degrees to degrees/minutes/seconds
		return string.format('%s&nbsp;%s, %s&nbsp;%s', latStr, SN, lonStr, EW)
		--return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW)
	end
end

-- Create URL for different sites based on globe (planet), latitude, longitude, language code and GeoHack attribution parameters
function coordinates.externalLink(frame)
	args = frame.args
	if args.lang and mw.language.isSupportedLanguage(args.lang) then 
		lang = args.lang
	else -- get user's chosen language 
		lang = frame:preprocess( "{{int:lang}}" )
	end
	return coordinates._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, lang, args.attributes or '')
end
-- Helper core function for externalLink 
function coordinates._externalLink(site, globe, latStr, lonStr, lang, attributes)
	local str
	if site == 'GoogleMaps' then
		str = SiteURL.GoogleMaps[globe]
	else
		str = SiteURL[site];
		attributes = string.format('globe:%s_%s', globe, attributes)
		str = mw.ustring.gsub( str, '$attr', attributes)
	end
	str = mw.ustring.gsub( str, '$lat', latStr)
	str = mw.ustring.gsub( str, '$lon', lonStr)
	str = mw.ustring.gsub( str, '$lang', lang)
	return str
end

-- adjust attributes depending on the template that calls it
function coordinates.alterAttributes(attributes, mode)
	-- indicate which template called it
	if mode=='camera' then                                   -- Used by {{Location}} and {{Location dec}}
		if string.find(attributes, 'type:camera')==nil then
			attributes = 'type:camera_' .. attributes
		end
	elseif mode=='object'or mode =='globe' then                           -- Used by {{Object location}}
		if string.find(attributes, 'class:object')==nil then
			attributes = 'class:object_' .. attributes
		end
	elseif mode=='inline' then                               -- Used by {{Inline coordinates}} (actually that template does not set any attributes at the moment)
	elseif mode=='user' then                                 -- Used by {{User location}}
		attributes = 'type:user_location'
	elseif mode=='institution' then                          --Used by {{Institution/coordinates}} (categories only)	
		attributes = 'type:institution'
	end
	return attributes
end
	
-- Create link to GeoHack tool which displays latitude and longitude coordinates in DMS format
function coordinates.GeoHack_link(frame)
	-- create link and coordintate string
	local latlon = coordinates.lat_lon(frame)
	if latlon=='lattiude, longitude' then
		return latlon
	else
		frame.args.site = 'GeoHack'
		local url = frame:preprocess(coordinates.externalLink(frame)) -- use preprocess to get page name
		return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion">
	end
end

function coordinates.externalLinksSection(frame)
	args = frame.args
	if args.lang and mw.language.isSupportedLanguage(args.lang) then 
		lang = args.lang
	else -- get user's chosen language 
		lang = frame:preprocess( "{{int:lang}}" )
	end
	if not args.namespaceNum then
		args.namespace = frame:preprocess( "{{NAMESPACE}}" )
	end
	
	local str
	if args.globe=='Earth' then -- Earth locations will have 3 or 4 links
		str = string.format('[%s %s] - [%s %s] - [%s %s]', 
			coordinates._externalLink('OpenStreetMap', 'Earth', args.lat, args.lon, lang, ''),  
			Fallback._langSwitch(i18n.OpenStreetMaps, lang),
			coordinates._externalLink('GoogleMaps'   , 'Earth', args.lat, args.lon, lang, ''),  
			Fallback._langSwitch(i18n.GoogleMaps, lang),
			coordinates._externalLink('GoogleEarth'  , 'Earth', args.lat, args.lon, lang, ''),  
			Fallback._langSwitch(i18n.GoogleEarth, lang))
		if args.namespace=="Category" then
			str = string.format('%s - [%s %s]', str,
				coordinates._externalLink('Proximityrama', 'Earth', args.lat, args.lon, lang, ''),  
				Fallback._langSwitch(i18n.Proximityrama, lang))
		end 
	elseif args.globe=='Mars' or args.globe=='Moon' then
		str = string.format('[%s %s]', 
			coordinates._externalLink('GoogleMaps', args.globe, args.lat, args.lon, lang, ''),  
			Fallback._langSwitch(i18n.GoogleMaps, lang))
	end
	
	--return frame:preprocess(str) -- use preprocess to expand {{#fullurl}}
	return str
end

--[[

Core section of template:Location, template:Object location and template:Globe location.
This method requires several arguments to be passed to it or it's parent metchod/template:
 * globe      = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013.
 * mode       = Possible options: 
  - camera - call from {{location}}
  - object - call from {{Object location}}
  - globe  - call from {{Globe location}}
 * lat        = latitude in degrees
 * lon        = longitude in degrees
 * attributes = attributes
 * lang       = language code
 * namespace  = namespace name: File, Category, (Gallery)
]]
function coordinates.LocationTemplateCore(frame)
	-- prepare arguments
	args = frame.args
	if not args or not args.lat then -- if no arguments provided than use parent arguments
		args = mw.getCurrentFrame():getParent().args
	end
	if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then 
		args.lang = frame:preprocess( "{{int:lang}}" ) -- get user's chosen language 
	end
	if not (args.namespace) then -- if not provided than look up
		args.namespace = frame:preprocess( "{{NAMESPACE}}" )
	end
	if args.namespace=='' then -- if empty than it is a gallery
		args.namespace = 'Gallery'
	end
	local bare   = yesno(args.bare,false)
	local Status = 'primary' -- used by {{#coordinates:}}
	if yesno(args.secondary,false) then
		Status = 'secondary'
	end
    args.attributes = coordinates.alterAttributes(args.attributes or '', args.mode)
	frame.args = args
	
	-- check for errors and add Geo (microformat) code for machine readability.
	local lat = tonumber(args.lat)
	local lon = tonumber(args.lon)
	if lon then -- get longitude t0 be in -180 to 180 range
		lon=lon%360
		if lon>180 then
			lon = lon-360
		end
	end
	local Categories, geoMicroFormat, coorTag = '', '', ''

	-- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages
	if (args.namespace == 'File' or args.namespace == 'Category' or args.namespace == 'Gallery') then
		if lat and lon then -- if lat and lon are numbers...
			if lat==0 and lon==0 then -- lat=0 and lon=0 is a common issue when copying from flickr and other sources
				Categories = CoorCat.default
			end
			if args.noError==0 or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors )
				Categories = Categories .. CoorCat.erroneous
			end
			local cat = CoorCat[args.namespace]
			if cat then -- add category based on namespace
				Categories = Categories .. cat
			end
			-- if not earth than add a category for each globe
			if args.mode and args.globe and args.mode=='globe' and args.globe~='Earth' then
				Categories = Categories .. string.format(CoorCat[args.mode], args.globe)
			end
			-- add  <span class="geo"> Geo (microformat) code: it is included for machine readability
			geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon)
			-- https://www.mediawiki.org/wiki/Extension:GeoData
			if args.namespace == 'File' and Status ~= 'secondary' then -- TODO enable for secondary cases without throwing errors
				--coorTag = string.format('{{#coordinates:%f|%f|%s|%s}}', frame.args.lat, frame.args.lon, args.attributes, Status)
				coorTag = string.format('{{#coordinates:%10.6f|%11.6f|%s}}', lat, lon, args.attributes)
			end
		else -- if lat and lon are not numbers then add error category
			Categories = Categories .. CoorCat.erroneous
		end

	end

	-- Call helper functions to render different parts of the template
	local str1, str2, str3, str4, inner_table, heading
	str1 = coordinates.GeoHack_link(frame)  									-- the coordinates and link to GeoHack
	heading = coordinates._getHeading(frame.args.attributes)					-- get heading arrow section
	if heading then
		--str1 =  string.format('%s&nbsp;&nbsp;<span style="{{Transform-rotate|%f}}">[[File:North Pointer.svg|20px|link=|alt=]]</span>', str1, 360-heading)
		local fname = string.format('{{Compass rose file|%f|style=heading}}', heading)
		str1 =  string.format('%s&nbsp;&nbsp;[[%s|25px|link=|alt=]]', str1, fname, heading)
	end
	str2 = Fallback._langSwitch(i18n.LocationTemplateLinkLabel, args.lang) 	-- header of the link section
	str3 = coordinates.externalLinksSection(frame) or ''					-- external link section
	str4 = '[[File:Circle-information.svg|18x18px|alt=info|link=Commons:Geocoding]]'
	inner_table = string.format('<td style="border:none;">%s</td><td style="border:none;">%s %s</td><td style="border:none;">%s%s</td>', str1, str2, str3, str4, geoMicroFormat)
	
	-- combine strings into a table
	local templateText
	if bare then
		templateText  = string.format('<table style="width:100%%"><tr>%s</tr></table>', inner_table)
	else
		-- choose name of the field
		local field_name = 'Location'
		if args.mode=='camera' then 
			field_name = Fallback._langSwitch(i18n.CameraLocation, args.lang)
		elseif args.mode=='object' then 
			field_name = Fallback._langSwitch(i18n.ObjectLocation, args.lang)
		elseif args.mode=='globe' then
			field_list = Fallback._langSwitch(i18n.GlobeLocation, args.lang)
			if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized
				field_name = field_list[args.globe]
			end
		end
		local style = frame:preprocess(string.format('{{Infobar-Layout|lang=%s}}',lang))
		templateText  = string.format('<table %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', style, field_name, inner_table)
	end
	return frame:preprocess(templateText .. Categories .. coorTag)
end

return coordinates