「モジュール:Coordinates」の版間の差分
ナビゲーションに移動
検索に移動
support of SDC
細 (1版) |
bsd>Jarekt (support of SDC) |
||
1行目: | 1行目: | ||
--[[ | --[[ | ||
__ __ _ _ ____ _ _ _ | |||
| \/ | ___ __| |_ _| | ___ _ / ___|___ ___ _ __ __| (_)_ __ __ _| |_ ___ ___ | |||
| |\/| |/ _ \ / _` | | | | |/ _ (_) | / _ \ / _ \| '__/ _` | | '_ \ / _` | __/ _ \/ __| | |||
| | | | (_) | (_| | |_| | | __/_| |__| (_) | (_) | | | (_| | | | | | (_| | || __/\__ \ | |||
|_| |_|\___/ \__,_|\__,_|_|\___(_)\____\___/ \___/|_| \__,_|_|_| |_|\__,_|\__\___||___/ | |||
This module is intended to | This module is intended to provide functionality of {{location}} and related | ||
templates. | templates. It was developed on Wikimedia Commons, so if you find this code on | ||
other sites, check there for updates and discussions. | |||
*function | Please do not modify this code without applying the changes first at Module:Coordinates/sandbox and testing | ||
**function | at Module:Coordinates/sandbox/testcases and Module talk:Coordinates/sandbox/testcases. | ||
***function | |||
****function | Authors and maintainers: | ||
***function | * User:Jarekt | ||
****function | * User:Ebraminio | ||
**function | |||
**function | Functions: | ||
***function | *function p.LocationTemplateCore(frame) | ||
*function | **function p.GeoHack_link(frame) | ||
*function | ***function p.lat_lon(frame) | ||
****function p._deg2dms(deg,lang) | |||
***function p.externalLink(frame) | |||
****function p._externalLink(site, globe, latStr, lonStr, lang, attributes) | |||
**function p._getHeading(attributes) | |||
**function p.externalLinksSection(frame) | |||
***function p._externalLink(site, globe, latStr, lonStr, lang, attributes) | |||
*function p.getHeading(frame) | |||
*function p.deg2dms(frame) | |||
]] | ]] | ||
-- ======================================= | -- ======================================= | ||
-- === Dependencies ====================== | -- === Dependencies ====================== | ||
-- ======================================= | -- ======================================= | ||
require('Module:No globals') -- used for debugging purposes as it detects cases of unintended global variables | |||
local | local i18n = require('Module:I18n/coordinates') -- get localized translations of site names | ||
local yesno | local yesno = require('Module:Yesno') | ||
-- ======================================= | -- ======================================= | ||
31行目: | 44行目: | ||
-- ======================================= | -- ======================================= | ||
-- Angles associated with each | -- Angles associated with each abbreviation of compass point names. See [[:en:Points of the compass]] | ||
local compass_points = { | local compass_points = { | ||
N = 0, | N = 0, | ||
67行目: | 80行目: | ||
} | } | ||
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr will be replaced with latitude, longitude, language code | -- files to use for different headings | ||
local heading_icon = { | |||
[ 1] = 'File:Compass-icon bb N.svg', | |||
[ 2] = 'File:Compass-icon bb NbE.svg', | |||
[ 3] = 'File:Compass-icon bb NNE.svg', | |||
[ 4] = 'File:Compass-icon bb NEbN.svg', | |||
[ 5] = 'File:Compass-icon bb NE.svg', | |||
[ 6] = 'File:Compass-icon bb NEbE.svg', | |||
[ 7] = 'File:Compass-icon bb ENE.svg', | |||
[ 8] = 'File:Compass-icon bb EbN.svg', | |||
[ 9] = 'File:Compass-icon bb E.svg', | |||
[10] = 'File:Compass-icon bb EbS.svg', | |||
[11] = 'File:Compass-icon bb ESE.svg', | |||
[12] = 'File:Compass-icon bb SEbE.svg', | |||
[13] = 'File:Compass-icon bb SE.svg', | |||
[14] = 'File:Compass-icon bb SEbS.svg', | |||
[15] = 'File:Compass-icon bb SSE.svg', | |||
[16] = 'File:Compass-icon bb SbE.svg', | |||
[17] = 'File:Compass-icon bb S.svg', | |||
[18] = 'File:Compass-icon bb SbW.svg', | |||
[19] = 'File:Compass-icon bb SSW.svg', | |||
[20] = 'File:Compass-icon bb SWbS.svg', | |||
[21] = 'File:Compass-icon bb SW.svg', | |||
[22] = 'File:Compass-icon bb SWbW.svg', | |||
[23] = 'File:Compass-icon bb WSW.svg', | |||
[24] = 'File:Compass-icon bb WbS.svg', | |||
[25] = 'File:Compass-icon bb W.svg', | |||
[26] = 'File:Compass-icon bb WbN.svg', | |||
[27] = 'File:Compass-icon bb WNW.svg', | |||
[28] = 'File:Compass-icon bb NWbW.svg', | |||
[29] = 'File:Compass-icon bb NW.svg', | |||
[30] = 'File:Compass-icon bb NWbN.svg', | |||
[31] = 'File:Compass-icon bb NNW.svg', | |||
[32] = 'File:Compass-icon bb NbW.svg' | |||
} | |||
-- URL definitions for different sites. Strings: $lat, $lon, $lang, $attr, $page will be | |||
-- replaced with latitude, longitude, language code, GeoHack attribution parameters and full-page-name strings. | |||
local SiteURL = { | local SiteURL = { | ||
GeoHack = ' | GeoHack = '//tools.wmflabs.org/geohack/geohack.php?pagename=$page¶ms=$lat_N_$lon_E_$attr&language=$lang', | ||
GoogleEarth = ' | GoogleEarth = '//tools.wmflabs.org/geocommons/earth.kml?latdegdec=$lat&londegdec=$lon&scale=10000&commons=1', | ||
Proximityrama = ' | Proximityrama = '//tools.wmflabs.org/geocommons/proximityrama?latlon=$lat,$lon', | ||
WikimediaMap = '//maps.wikimedia.org/#16/$lat/$lon', | |||
OpenStreetMap1 = '//tools.wmflabs.org/wiwosm/osm-on-ol/commons-on-osm.php?zoom=16&lat=$lat&lon=$lon', | |||
OpenStreetMap2 = '//tools.wmflabs.org/osm4wiki/cgi-bin/wiki/wiki-osm.pl?project=Commons&article=$page&l=$level', | |||
GoogleMaps = { | GoogleMaps = { | ||
Mars = ' | Mars = '//www.google.com/mars/#lat=$lat&lon=$lon&zoom=8', | ||
Moon = ' | Moon = '//www.google.com/moon/#lat=$lat&lon=$lon&zoom=8', | ||
Earth = ' | Earth = '//tools.wmflabs.org/wp-world/googlmaps-proxy.php?page=http://tools.wmflabs.org/kmlexport/%3Fproject%3DCommons%26article%3D$page&l=$level&output=classic' | ||
} | } | ||
} | } | ||
85行目: | 137行目: | ||
Gallery = '[[Category:Galleries with coordinates]]', | Gallery = '[[Category:Galleries with coordinates]]', | ||
Category = '[[Category:Categories with coordinates]]', | Category = '[[Category:Categories with coordinates]]', | ||
wikidata0 = '[[Category:Pages with coordinates from Wikidata]]', | |||
wikidata1 = '[[Category:Pages with local coordinates and matching Wikidata coordinates]]', | |||
wikidata2 = '[[Category:Pages with local coordinates and similar Wikidata coordinates]]', | |||
wikidata3 = '[[Category:Pages with local coordinates and mismatching Wikidata coordinates]]', | |||
wikidata4 = '[[Category:Pages with local coordinates and missing Wikidata coordinates]]', | |||
wikidata5 = '[[Category:Pages with locations and Wikidata ID to wrong type of entry]]', | |||
sdc0 = '[[Category:Pages with coordinates from SDC]]', | |||
sdc1 = '[[Category:Pages with local coordinates and matching SDC coordinates]]', | |||
sdc2 = '[[Category:Pages with local coordinates and similar SDC coordinates]]', | |||
sdc3 = '[[Category:Pages with local coordinates and mismatching SDC coordinates]]', | |||
sdc4 = '[[Category:Pages with local coordinates and missing SDC coordinates]]', | |||
globe = '[[Category:Media with %s locations]]', | globe = '[[Category:Media with %s locations]]', | ||
default = '[[Category:Media with default 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' | attribute = '[[Category:Media with erroneous geolocation attributes]]', | ||
erroneous = '[[Category:Media with erroneous locations]]<span style="color:red;font-weight:bold">Error: Invalid parameters!</span>\n', | |||
dms = '[[Category:Media with coordinates in DMS format]]' | |||
} | } | ||
-- ======================================= | -- ======================================= | ||
-- === Functions | -- === Local Functions =================== | ||
-- ======================================= | -- ======================================= | ||
-- | local function normalize_input_args(input_args, output_args) | ||
function | for name, value in pairs( input_args ) do | ||
if value ~= '' then -- nuke empty strings | |||
if type(name)=='string' then | |||
name = string.lower(name) | |||
end | |||
output_args[name] = string.gsub(value, "^%s*(.-)%s*$", "%1") -- trim whitespaces from the beggining and the end of the string | |||
end | |||
end | |||
return output_args | |||
end | |||
local function getArgs(frame) | |||
local args = {} | |||
args = normalize_input_args(frame:getParent().args, args) | |||
args = normalize_input_args(frame.args, args) | |||
if not (args.lang and mw.language.isSupportedLanguage(args.lang)) then | |||
args.lang = frame:callParserFunction("int","lang") -- get user's chosen language | |||
end | |||
return args | |||
end | |||
local NoLatLonString = 'latitude, longitude' | |||
local function langSwitch(list,lang) | |||
local langList = mw.language.getFallbacksFor(lang) | |||
table.insert(langList,1,lang) | |||
for i,language in ipairs(langList) do | |||
if list[language] then | |||
return list[language] | |||
end | |||
end | |||
end | |||
local function add_maplink(lat, lon, marker, text) | |||
local tstr = '' | |||
if text then | |||
tstr = string.format('text="%s" ', text) | |||
end | |||
return string.format('<maplink %szoom="13" latitude="%f" longitude="%f" class="no-icon">{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"%s", "marker-size": "large", "marker-color": "0050d0" }'.. | |||
'}</maplink>', tstr, lat, lon, lon, lat, marker) | |||
end | |||
local function add_maplink2(lat1, lon1, lat2, lon2) | |||
return string.format('<maplink zoom="13" latitude="%f" longitude="%f" class="no-icon">[{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"c", "marker-size": "large", "marker-color": "0050d0", "title": "Location on Wikimedia Commons" }'.. | |||
'},{'.. | |||
' "type": "Feature",'.. | |||
' "geometry": { "type":"Point", "coordinates":[%f, %f] },'.. | |||
' "properties": { "marker-symbol":"w", "marker-size": "large", "marker-color": "228b22", "title": "Location on Wikidata" }'.. | |||
'}]</maplink>', lat2, lon2, lon1, lat1, lon2, lat2) | |||
end | |||
local function info_box(text) | |||
return string.format('<table class="messagebox plainlinks layouttemplate" style="border-collapse:collapse; border-width:2px; border-style:solid; width:100%%; clear: both; '.. | |||
'border-color:#f28500; background:#ffe;direction:ltr; border-left-width: 8px; ">'.. | |||
'<tr>'.. | |||
'<td class="mbox-image" style="padding-left:.9em;">'.. | |||
' [[File:Commons-emblem-issue.svg|class=noviewer|45px]]</td>'.. | |||
'<td class="mbox-text" style="">%s</td>'.. | |||
'</tr></table>', text) | |||
end | |||
local function distance(lat1, lon1, lat2, lon2) | |||
-- calculate distance | |||
local dLat = math.rad(lat1-lat2) | |||
local dLon = math.rad(lon1-lon2) | |||
local d = math.pow(math.sin(dLat/2),2) + math.pow(math.sin(dLon/2),2) * math.cos(math.rad(lat1)) * math.cos(math.rad(lat2)) | |||
d = 2 * math.atan2(math.sqrt(d), math.sqrt(1-d)) -- angular distance in radians | |||
d = 6371000 * d -- radians to meters conversion | |||
d = math.floor(d+0.5) -- round it to even meters | |||
return d | |||
end | end | ||
-- | local function mergeWithWikidata(qID, lat1, lon1) | ||
-- we are given wikidata q-code so look up the coordinates | |||
local | local dist_str='' | ||
if | local entity | ||
-- Wikiata coordinates | |||
elseif | if qID==nil then | ||
entity = mw.wikibase.getEntity() | |||
elseif type(qID)=='string' and qID:match( '^[Qq]%d+$' ) then | |||
entity = mw.wikibase.getEntity(qID) | |||
else | else | ||
return '' | entity = qID | ||
end | |||
if not entity then | |||
return lat1, lon1, nil, '', dist_str | |||
end | |||
qID = entity.id | |||
local v, lat2, lon2, precision | |||
if entity then | |||
local P625 = entity:getBestStatements( 'P625' ) -- coordinate location | |||
local P159 = entity:getBestStatements( 'P159' ) -- headquarters location | |||
if P625[1] and P625[1].mainsnak.datavalue.value.latitude then | |||
v = P625[1].mainsnak.datavalue.value | |||
elseif P159[1] and P159[1].qualifiers and P159[1].qualifiers.P625 then | |||
v = P159[1].qualifiers.P625[1].datavalue.value | |||
end | |||
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then | |||
lat2 = v.latitude | |||
lon2 = v.longitude | |||
precision = v.precision or 1e-4 | |||
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round | |||
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree | |||
end | |||
end | |||
-- compare coordinates | |||
local cat = '' | |||
if not lat1 or not lon1 then -- wikidata coordinates only | |||
lat1 = lat2 | |||
lon1 = lon2 | |||
cat = CoorCat.wikidata0 | |||
elseif lat1 and lon1 and not lat2 and not lon2 then | |||
cat = string.format('The above coordinates are missing from linked Wikidata item [[d:%s|%s]]. Click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'.. | |||
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span> to copy it", | |||
qID, qID, qID, lat1, lon1) | |||
cat = CoorCat.wikidata4 .. info_box(cat) | |||
elseif lat1 and lon1 and lat2 and lon2 then | |||
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance | |||
local frame = mw.getCurrentFrame() | |||
local info = frame:preprocess(add_maplink2(lat1, lon1, lat2, lon2)) -- fancy link to OSM | |||
info = string.format("There is a discrepancy of %i meters between the above coordinates and the ones stored at linked Wikidata item [[d:%s|%s]] (%s, precision: %i m). ".. | |||
'Please reconcile them. To copy Commons coordinates to Wikidata, click <span class=\"plainlinks\" title=\"Click to copy to wikidata\">'.. | |||
"[https://tools.wmflabs.org/quickstatements/index_old.html#v1=%s%%09P625%%09@%09.5f/%09.5f%%09S143%%09Q565 here]</span>", | |||
d, qID, qID, info, precision, qID, lat1, lon1) | |||
if d<20 or d<precision then -- will consider location within 20 meters or precisi0on distance as the same | |||
cat = CoorCat.wikidata1 | |||
dist_str = string.format(' (discrepancy of %i meters between the above coordinates and the ones stored on Wikidata)', d) -- will be displayed when hovering a mouse above wikidata icon | |||
elseif d>1000 and d>5*precision then -- locations 1 km off and 5 precision distances away are likely wrong | |||
cat = CoorCat.wikidata3 .. info_box(info) | |||
else | |||
cat = CoorCat.wikidata2 .. info_box(info) | |||
end | |||
end | |||
-- verify proper P31 (instance of). List is based on https://www.wikidata.org/wiki/Property_talk:P625 | |||
local QCodes = { | |||
Q5 = 1, -- human | |||
Q11879590 = 1, -- female given name | |||
Q202444 = 1, -- given name | |||
Q12308941 = 1, -- male given name | |||
Q4167836 = 1, -- Wikimedia category | |||
Q4167410 = 1, -- Wikimedia disambiguation page | |||
Q783794 = 2, -- company | |||
Q4830453 = 2, -- business enterprise | |||
} | |||
local s = entity:getBestStatements( 'P31' ) | |||
if s[1] and s[1].mainsnak.datavalue.value['id'] then | |||
local instanceOf = s[1].mainsnak.datavalue.value['id'] | |||
if QCodes[instanceOf] then | |||
cat = '' -- wipe out categories | |||
if QCodes[instanceOf]==1 then -- add problem category | |||
cat = CoorCat.wikidata5 | |||
end | |||
end | |||
end | |||
return lat1, lon1, qID, cat, dist_str | |||
end | |||
local function mergeWithSDC(lat1, lon1, heading1) | |||
-- we are given SDC m-code so look up the coordinates | |||
local entity = nil | |||
-- Wikiata coordinates | |||
entity = mw.wikibase.getEntity() | |||
if not entity and lat1 and lon1 then | |||
return lat1, lon1, heading1, nil, CoorCat.sdc4 | |||
elseif not entity then | |||
return lat1, lon1, heading1, nil, '' | |||
end | |||
local v, lat2, lon2, heading2, precision | |||
if entity then | |||
local P1259 = entity:getBestStatements( 'P1259' ) -- coordinates of the point of view | |||
if P1259[1] and P1259[1].mainsnak.datavalue.value.latitude then | |||
if P1259[1].qualifiers and P1259[1].qualifiers.P7787 then | |||
v = P1259[1].qualifiers.P7787[1].datavalue.value | |||
if v.unit == "http://www.wikidata.org/entity/Q28390" then -- in degrees | |||
heading2 = v.amount | |||
elseif v.unit == "http://www.wikidata.org/entity/Q33680" then -- in radians | |||
heading2 = v.amount*57.2957795131 | |||
end | |||
end | |||
v = P1259[1].mainsnak.datavalue.value -- get coordinates | |||
end | |||
if v and v.globe == 'http://www.wikidata.org/entity/Q2' then | |||
lat2 = v.latitude | |||
lon2 = v.longitude | |||
precision = v.precision or 1e-4 | |||
precision = math.floor(precision*111000) -- convert precision from degrees to meters and round | |||
precision = math.max(math.min(precision,111000),5) -- bound precision to a number between 5 meters and 1 degree | |||
end | |||
end | |||
-- compare coordinates | |||
local cat = '' | |||
if not lat1 or not lon1 then -- SDC coordinates only | |||
lat1 = lat2 | |||
lon1 = lon2 | |||
heading1 = heading2 | |||
cat = CoorCat.sdc0 | |||
elseif lat1 and lon1 and not lat2 and not lon2 then | |||
cat = CoorCat.sdc4 | |||
elseif lat1 and lon1 and lat2 and lon2 then | |||
local d = distance(lat1, lon1, lat2, lon2) -- calculate distance | |||
if d<20 or d<precision then -- will consider location within 20 meters or precision distance as the same | |||
cat = CoorCat.sdc1 | |||
else | |||
cat = CoorCat.sdc2 | |||
end | |||
end | |||
return lat1, lon1, heading1, entity.id, cat | |||
end | |||
local function dms2deg_ ( d, m, s, h ) | |||
d,m,s = tonumber(d), tonumber(m), tonumber(s) | |||
if not (d and m and s and h) then | |||
return nil | |||
end | end | ||
local | local LUT = {N=1, S=-1, E=1, W=-1} -- look up table | ||
if | h = LUT[mw.ustring.upper( h )] | ||
return | if not h then | ||
return nil | |||
end | end | ||
return | return h * (d + m/60.0 + s/3600.0) | ||
end | |||
local function dms2deg ( dms ) | |||
local ltab = mw.text.split(dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", ""), "/") | |||
local degre = dms2deg_ (ltab[1], ltab[2], ltab[3], ltab[4]) | |||
--return dms .. '->' .. dms:gsub("[°'′″\",%s]+" , "/" ):gsub("^%/", "") .. '->' .. (degre or 'nil') | |||
return degre or dms | |||
end | |||
-- ======================================= | |||
-- === External Functions ================ | |||
-- ======================================= | |||
local p = {} | |||
-- parse attribute variable returning desired field (used for debugging) | |||
function p.parseAttribute(frame) | |||
return string.match(mw.text.decode(frame.args[1]), mw.text.decode(frame.args[2]) .. ':' .. '([^_]*)') or '' | |||
end | end | ||
-- Helper core function for getHeading. | -- Helper core function for getHeading. | ||
function | function p._getHeading(attributes) | ||
if attributes == nil then | if attributes == nil then | ||
return nil | return nil | ||
135行目: | 434行目: | ||
end | end | ||
-- | --[[============================================================================ | ||
function | Parse attribute variable returning heading field. If heading is a string than | ||
local | try to convert it to an angle | ||
==============================================================================]] | |||
function p.getHeading(frame) | |||
else | local attributes | ||
if frame.args[1] then | |||
attributes = frame.args[1] | |||
elseif frame.args.attributes then | |||
attributes = frame.args.attributes | |||
else | |||
return '' | |||
end | end | ||
if | local hNum = p._getHeading(attributes) | ||
return | if hNum == nil then | ||
return '' | |||
end | end | ||
return tostring(hNum) | |||
end | end | ||
-- Helper core function for deg2dms. | |||
function | |||
local dNum, mNum, sNum, dStr, mStr, sStr | --[[============================================================================ | ||
Helper core function for deg2dms. deg2dms can be called by templates, while | |||
_deg2dms should be called from Lua. | |||
Inputs: | |||
* degree - positive coordinate in degrees | |||
* degPrec - coordinate precision in degrees will result in different angle format | |||
* lang - language to used when formatting the number | |||
==============================================================================]] | |||
function p._deg2dms(degree, degPrec, lang) | |||
local dNum, mNum, sNum, dStr, mStr, sStr, formatStr, secPrec, c, k, d, zero | |||
local Lang = mw.language.new(lang) | local Lang = mw.language.new(lang) | ||
dNum = math.floor( | -- adjust number display based on precision | ||
mNum = math.floor( | secPrec = degPrec*3600.0 -- coordinate precision in seconds | ||
sNum = | if secPrec<0.05 then -- degPrec<1.3889e-05 | ||
dStr = Lang:formatNum(dNum) | formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.SS″ format | ||
mStr = Lang:formatNum(mNum) | c = 360000 | ||
sStr = Lang:formatNum(sNum) | elseif secPrec<0.5 then -- 1.3889e-05<degPrec<1.3889e-04 | ||
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS.S″ format | |||
c = 36000 | |||
elseif degPrec*60.0<0.5 then -- 1.3889e-04<degPrec<0.0083 | |||
formatStr = '%s° %s′ %s″' -- use DD° MM′ SS″ format | |||
c = 3600 | |||
elseif degPrec<0.5 then -- 0.0083<degPrec<0.5 | |||
formatStr = '%s° %s′' -- use DD° MM′ format | |||
c = 60 | |||
else -- if degPrec>0.5 then | |||
formatStr = '%s°' -- use DD° format | |||
c = 1 | |||
end | |||
-- create degree, minute and seconds numbers and string | |||
d = c/60 | |||
k = math.floor(c*(degree%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(k/c) % 360 -- degree number (integer in 0-360 range) | |||
mNum = math.floor(k/d) % 60 -- minute number (integer in 0-60 range) | |||
sNum = 3600*(k%d) / c -- seconds number (float in 0-60 range with 0, 1 or 2 decimal digits) | |||
dStr = Lang:formatNum(dNum) -- degree string | |||
mStr = Lang:formatNum(mNum) -- minute string | |||
sStr = Lang:formatNum(sNum) -- second string | |||
zero = Lang:formatNum(0) -- zero string in local language | |||
if mNum<10 then | if mNum<10 then | ||
mStr = | mStr = zero .. mStr -- pad with zero if a single digit | ||
end | end | ||
if sNum<10 then | if sNum<10 then | ||
sStr = | sStr = zero .. sStr -- pad with zero if less than ten | ||
end | |||
return string.format(formatStr, dStr, mStr, sStr); | |||
end | |||
--[[============================================================================ | |||
Convert degrees to degrees/minutes/seconds notation commonly used when displaying | |||
coordinates. | |||
Inputs: | |||
1) latitude or longitude angle in degrees | |||
2) georeference precision in degrees | |||
3) language used in formatting of the number | |||
==============================================================================]] | |||
function p.deg2dms(frame) | |||
local args = getArgs(frame) | |||
local degree = tonumber(args[1]) | |||
local degPrec = tonumber(args[2]) or 0-- precision in degrees | |||
if degree==nil then | |||
return args[1]; | |||
else | |||
return p._deg2dms(degree, degPrec, args.lang) | |||
end | end | ||
end | end | ||
-- | function p.dms2deg(frame) | ||
function | return dms2deg(frame.args[1]) | ||
end | |||
if lon then -- get longitude | --[[============================================================================ | ||
Format coordinate location string, by creating and joining DMS strings for | |||
latitude and longitude. Also convert precision from meters to degrees. | |||
INPUTS: | |||
* lat = latitude in degrees | |||
* lon = longitude in degrees | |||
* lang = language code | |||
* prec = geolocation precision in meters | |||
==============================================================================]] | |||
function p._lat_lon(lat, lon, prec, lang) | |||
lat = tonumber(lat) | |||
lon = tonumber(lon) | |||
prec = math.abs(tonumber(prec) or 0) | |||
if lon then -- get longitude to be in -180 to 180 range | |||
lon=lon%360 | lon=lon%360 | ||
if lon>180 then | if lon>180 then | ||
lon = lon-360 | lon = lon-360 | ||
end | end | ||
end | end | ||
if lat==nil or lon==nil then | if lat==nil or lon==nil then | ||
return | return NoLatLonString | ||
else | else | ||
local nsew = | local nsew = langSwitch(i18n.NSEW, lang) -- find set of localized translation of N, S, W and E in the desired language | ||
local SN, EW, latStr, lonStr | local SN, EW, latStr, lonStr, lon2m, lat2m, phi | ||
if lat<0 then SN = nsew.S else SN = nsew.N end | 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 | if lon<0 then EW = nsew.W else EW = nsew.E end -- choose W or E depending on longitude degree sign | ||
latStr = | lat2m=1 | ||
lonStr = | lon2m=1 | ||
if prec>0 then -- if user specified the precision of the geo location... | |||
phi = math.abs(lat)*math.pi/180 -- latitude in radiants | |||
lon2m = 6378137*math.cos(phi)*math.pi/180 -- see https://en.wikipedia.org/wiki/Longitude | |||
lat2m = 111000 -- average latitude degree size in meters | |||
end | |||
latStr = p._deg2dms(math.abs(lat), prec/lat2m, lang) -- Convert latitude degrees to degrees/minutes/seconds | |||
lonStr = p._deg2dms(math.abs(lon), prec/lon2m, lang) -- Convert longitude degrees to degrees/minutes/seconds | |||
return string.format('%s %s, %s %s', latStr, SN, lonStr, EW) | return string.format('%s %s, %s %s', latStr, SN, lonStr, EW) | ||
--return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW) | --return string.format('<span class="latitude">%s %s</span>, <span class="longitude">%s %s</span>', latStr, SN, lonStr, EW) | ||
201行目: | 570行目: | ||
end | end | ||
function p.lat_lon(frame) | |||
function | local args = getArgs(frame) | ||
args = | return p._lat_lon(args.lat, args.lon, args.prec, args.lang) | ||
return | |||
end | end | ||
-- Helper core function for externalLink | |||
function | --[[============================================================================ | ||
local | Helper core function for externalLink. Create URL for different sites: | ||
INPUTS: | |||
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama, | |||
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon) | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* latStr = latitude string or number | |||
* lonStr = longitude string or number | |||
* lang = language code | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p._externalLink(site, globe, latStr, lonStr, lang, attributes, level) | |||
local URLstr = SiteURL[site]; | |||
level = level or 1 | |||
local pageName = mw.uri.encode( mw.title.getCurrentTitle().prefixedText, 'WIKI' ) | |||
pageName = mw.ustring.gsub( pageName, '%%', '%%%%') | |||
if site == 'GoogleMaps' then | if site == 'GoogleMaps' then | ||
URLstr = SiteURL.GoogleMaps[globe] | |||
elseif site == 'GeoHack' then | |||
attributes = string.format('globe:%s_%s', globe, attributes) | attributes = string.format('globe:%s_%s', globe, attributes) | ||
URLstr = mw.ustring.gsub( URLstr, '$attr', attributes) | |||
end | end | ||
URLstr = mw.ustring.gsub( URLstr, '$lat' , latStr) | |||
URLstr = mw.ustring.gsub( URLstr, '$lon' , lonStr) | |||
URLstr = mw.ustring.gsub( URLstr, '$lang' , lang) | |||
return | URLstr = mw.ustring.gsub( URLstr, '$level', level) | ||
URLstr = mw.ustring.gsub( URLstr, '$page' , pageName) | |||
URLstr = mw.ustring.gsub( URLstr, '+', '') | |||
URLstr = mw.ustring.gsub( URLstr, ' ', '_') | |||
return URLstr | |||
end | |||
--[[============================================================================ | |||
Create URL for different sites. | |||
INPUTS: | |||
* site = Possible sites: GeoHack, GoogleEarth, Proximityrama, | |||
OpenStreetMap, GoogleMaps (for Earth, Mars and Moon) | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* lat = latitude string or number | |||
* lon = longitude string or number | |||
* lang = language code | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p.externalLink(frame) | |||
local args = getArgs(frame) | |||
return p._externalLink(args.site or 'GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '') | |||
end | end | ||
-- | --[[============================================================================ | ||
function | Adjust GeoHack attributes depending on the template that calls it | ||
INPUTS: | |||
* attributes = attributes to be passed to GeoHack | |||
* mode = set by each calling template | |||
==============================================================================]] | |||
function p.alterAttributes(attributes, mode) | |||
-- indicate which template called it | -- indicate which template called it | ||
if mode=='camera' then -- Used by {{Location}} and {{Location dec}} | if mode=='camera' then -- Used by {{Location}} and {{Location dec}} | ||
234行目: | 638行目: | ||
attributes = 'type:camera_' .. attributes | attributes = 'type:camera_' .. attributes | ||
end | end | ||
elseif mode=='object'or mode =='globe' then | elseif mode=='object'or mode =='globe' then -- Used by {{Object location}} | ||
if mode=='object' and string.find(attributes, 'type:')==nil then | |||
attributes = 'type:object_' .. attributes | |||
end | |||
if string.find(attributes, 'class:object')==nil then | if string.find(attributes, 'class:object')==nil then | ||
attributes = 'class:object_' .. attributes | attributes = 'class:object_' .. attributes | ||
247行目: | 654行目: | ||
end | end | ||
-- Create link to GeoHack tool which displays latitude and longitude coordinates in DMS format | --[[============================================================================ | ||
function | Create link to GeoHack tool which displays latitude and longitude coordinates | ||
in DMS format | |||
INPUTS: | |||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, | |||
Ganymede are also supported but are unused as of 2013. | |||
* lat = latitude in degrees | |||
* lon = longitude in degrees | |||
* lang = language code | |||
* prec = geolocation precision in meters | |||
* attributes = attributes to be passed to GeoHack | |||
==============================================================================]] | |||
function p._GeoHack_link(args) | |||
-- create link and coordintate string | -- create link and coordintate string | ||
local latlon = | local latlon = p._lat_lon(args.lat, args.lon, args.prec, args.lang) | ||
if latlon== | if latlon==NoLatLonString then | ||
return latlon | return latlon | ||
else | else | ||
local url = p._externalLink('GeoHack', args.globe or 'Earth', args.lat, args.lon, args.lang, args.attributes or '') | |||
return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion"> | return string.format('<span class="plainlinksneverexpand">[%s %s]</span>', url, latlon) --<span class="plainlinks nourlexpansion"> | ||
end | end | ||
end | end | ||
function | function p.GeoHack_link(frame) | ||
return p._GeoHack_link(getArgs(frame)) | |||
end | |||
--[[============================================================================ | |||
Create full external links section of {{Location}} or {{Object location}} | |||
if not args. | templates, based on: | ||
args.namespace = | * 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 | |||
* lang = language code | |||
* namespace = namespace name: File, Category, (Gallery) | |||
==============================================================================]] | |||
function p._externalLinksSection(args) | |||
local lang = args.lang | |||
if not args.namespace then | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
end | end | ||
local str | local str, link1, link2, link3, link4 | ||
if args.globe=='Earth' then -- Earth locations will have | if args.globe=='Earth' and args.namespace~="Category" then -- Earth locations for files will have 2 links | ||
link1 = p._externalLink('OpenStreetMap1', 'Earth', args.lat, args.lon, lang, '') | |||
link2 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s] - [%s %s]', | |||
link1, langSwitch(i18n.OpenStreetMaps, lang), | |||
link2, langSwitch(i18n.GoogleEarth, lang)) | |||
elseif args.globe=='Earth' and args.namespace=="Category" then -- Earth locations for categories will have 4 links | |||
link1 = p._externalLink('OpenStreetMap2', 'Earth', args.lat, args.lon, lang, '', args.catRecurse) | |||
--link2 = p._externalLink('GoogleMaps' , 'Earth', args.lat, args.lon, lang, '', args.catRecurse) | |||
link3 = p._externalLink('GoogleEarth' , 'Earth', args.lat, args.lon, lang, '') | |||
link4 = p._externalLink('Proximityrama' , 'Earth', args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s] - [%s %s] - [%s %s]', | str = string.format('[%s %s] - [%s %s] - [%s %s]', | ||
link1, langSwitch(i18n.OpenStreetMaps, lang), | |||
--link2, langSwitch(i18n.GoogleMaps, lang), | |||
link3, langSwitch(i18n.GoogleEarth, lang), | |||
link4, langSwitch(i18n.Proximityrama, lang)) | |||
elseif args.globe=='Mars' or args.globe=='Moon' then | elseif args.globe=='Mars' or args.globe=='Moon' then | ||
link1 = p._externalLink('GoogleMaps', args.globe, args.lat, args.lon, lang, '') | |||
str = string.format('[%s %s]', link1, langSwitch(i18n.GoogleMaps, lang)) | |||
end | end | ||
return str | return str | ||
end | end | ||
function p.externalLinksSection(frame) | |||
return p._externalLinksSection(getArgs(frame)) | |||
end | |||
--[[============================================================================ | |||
Core section of template:Location, template:Object location and template:Globe location. | 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 | This method requires several arguments to be passed to it or it's parent method/template: | ||
* globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013. | * globe = Possible options: Earth, Mars or Moon. Venus, Mercury, Titan, Ganymede are also supported but are unused as of 2013. | ||
* mode = Possible options: | * mode = Possible options: | ||
308行目: | 742行目: | ||
* attributes = attributes | * attributes = attributes | ||
* lang = language code | * lang = language code | ||
* namespace = namespace | * namespace = namespace: File, Category, Gallery | ||
]] | * prec = geolocation precision in meters | ||
function | ==============================================================================]] | ||
function p._LocationTemplateCore(args) | |||
-- prepare arguments | -- prepare arguments | ||
if not (args.namespace) then -- if namespace not provided than look it up | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
if not (args. | |||
args.namespace = | |||
end | end | ||
if args.namespace=='' then -- if empty than it is a gallery | if args.namespace=='' then -- if empty than it is a gallery | ||
330行目: | 758行目: | ||
Status = 'secondary' | Status = 'secondary' | ||
end | end | ||
args.globe = args.globe or 'Earth' | |||
local attributes0 = args.attributes | |||
args.attributes = p.alterAttributes(args.attributes or '', args.mode) | |||
-- | -- Convert coordinates from string to numbers | ||
local lat = tonumber(args.lat) | local lat = tonumber(args.lat) | ||
local lon = tonumber(args.lon) | local lon = tonumber(args.lon) | ||
if lon then -- get longitude | local heading = p._getHeading(attributes0) -- get heading arrow section | ||
if lon then -- get longitude to be in -180 to 180 range | |||
lon=lon%360 | lon=lon%360 | ||
if lon>180 then | if lon>180 then | ||
342行目: | 772行目: | ||
end | end | ||
end | end | ||
local Categories, geoMicroFormat, coorTag = '', '', '' | |||
-- If wikidata link provided than compare coordinates | |||
local Categories, geoMicroFormat, coorTag, wikidata_link = '', '', '', '' | |||
if (args.mode=='object') and (args.namespace~='File') then | |||
local dist_str, qID | |||
-- look up the coordinates on Wikidata | |||
lat, lon, qID, Categories, dist_str = mergeWithWikidata(args.wikidata, lat, lon) | |||
if qID then | |||
wikidata_link = string.format("\n[[File:Wikidata-logo.svg|20px|Edit coordinates on Wikidata%s|link=wikidata:%s]]", dist_str, qID); | |||
args.wikidata = args.wikidata or qID | |||
end | |||
elseif (args.mode=='camera') and (args.namespace=='File') then | |||
local dist_str, mID | |||
-- look up lat/lon on SDC | |||
lat, lon, heading, mID, Categories = mergeWithSDC(lat, lon, heading) | |||
if mID then | |||
wikidata_link = "\n[[File:Commons structured data logo.svg|16px|Edit coordinates on Structured Data on Commons|link=]]"; | |||
end | |||
end | |||
args.lat = string.format('%010.6f', lat or 0) | |||
args.lon = string.format('%011.6f', lon or 0) | |||
local frame = mw.getCurrentFrame() | |||
-- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages | -- Categories, {{#coordinates}} and geoMicroFormat will be only added to File, Category and Gallery pages | ||
348行目: | 800行目: | ||
if lat and lon then -- if lat and lon are numbers... | 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 | 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 | Categories = Categories .. CoorCat.default | ||
end | |||
if attributes0 and string.find(attributes0, '=') then | |||
Categories = Categories .. CoorCat.attribute | |||
end | end | ||
if | if (math.abs(lon)>180) or (math.abs(lat)>90) then -- check for errors ({{#coordinates:}} also checks for errors ) | ||
Categories = Categories .. CoorCat.erroneous | Categories = Categories .. CoorCat.erroneous | ||
end | end | ||
363行目: | 818行目: | ||
-- add <span class="geo"> Geo (microformat) code: it is included for machine readability | -- 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) | geoMicroFormat = string.format('<span class="geo" style="display:none">%10.6f; %11.6f</span>',lat, lon) | ||
-- https://www.mediawiki.org/wiki/Extension:GeoData | -- add {{#coordinates}} tag, see https://www.mediawiki.org/wiki/Extension:GeoData | ||
if args.namespace == 'File' and Status | if args.namespace == 'File' and Status == 'primary' and args.mode=='camera' then | ||
coorTag = frame:callParserFunction( '#coordinates', { 'primary', lat, lon, args.attributes } ) | |||
coorTag = | elseif args.namespace == 'File' and args.mode=='object' then | ||
coorTag = frame:callParserFunction( '#coordinates', { lat, lon, args.attributes } ) | |||
end | end | ||
else -- if lat and lon are not numbers then add error category | else -- if lat and lon are not numbers then add error category | ||
Categories = Categories .. CoorCat.erroneous | Categories = Categories .. CoorCat.erroneous | ||
end | end | ||
end | end | ||
-- Call helper functions to render different parts of the template | -- Call helper functions to render different parts of the template | ||
local | local coor, info_link, inner_table, OSM = '','','','','','' | ||
coor = p._GeoHack_link(args) -- the p and link to GeoHack | |||
heading = | if heading then | ||
local k = math.fmod(math.floor(0.5+math.fmod(heading+360,360)/11.25),32)+1 | |||
local fname = heading_icon[k] | |||
coor = string.format('%s <span title="%s°">[[%s|25px|link=|alt=Heading=%s°]]</span>', coor, heading, fname, heading) | |||
end | end | ||
if args.globe=='Earth' then | |||
local icon = 'marker' | |||
if args.mode=='camera' then | |||
inner_table = string.format('<td style="border:none;">%s</td><td style="border:none;"> | icon = 'camera' | ||
end | |||
OSM = frame:preprocess(add_maplink(args.lat, args.lon, icon, '[[File:Openstreetmap logo.svg|20px|link=|Kartographer map based on OpenStreetMap.]]')) -- fancy link to OSM | |||
end | |||
local external_link = p._externalLinksSection(args) -- external link section | |||
if external_link and args.namespace == 'File' then | |||
external_link = langSwitch(i18n.LocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{location}} template | |||
elseif external_link then | |||
external_link = langSwitch(i18n.ObjectLocationTemplateLinkLabel, args.lang) .. ' ' .. external_link -- header of the link section for {{Object location}} template | |||
end | |||
info_link = string.format('[[File:OOjs UI icon help.svg|18x18px|alt=info|link=%s]]', langSwitch(i18n.COM_GEO, args.lang) ) | |||
inner_table = string.format('<td style="border:none;">%s %s</td><td style="border:none;">%s</td><td style="border:none;">%s%s%s</td>', | |||
coor, OSM, external_link or '', wikidata_link, info_link, geoMicroFormat) | |||
-- combine strings into a table | -- combine strings into a table | ||
396行目: | 862行目: | ||
local field_name = 'Location' | local field_name = 'Location' | ||
if args.mode=='camera' then | if args.mode=='camera' then | ||
field_name = | field_name = langSwitch(i18n.CameraLocation, args.lang) | ||
elseif args.mode=='object' then | elseif args.mode=='object' then | ||
field_name = | field_name = langSwitch(i18n.ObjectLocation, args.lang) | ||
elseif args.mode=='globe' then | elseif args.mode=='globe' then | ||
field_list = | local field_list = langSwitch(i18n.GlobeLocation, args.lang) | ||
if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized | if args.globe and i18n.GlobeLocation['en'][args.globe] then -- verify globe is provided and is recognized | ||
field_name = field_list[args.globe] | field_name = field_list[args.globe] | ||
end | end | ||
end | end | ||
local style = | --Create HTML text | ||
templateText = string.format('<table %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', style, field_name, inner_table) | local dir, text_align | ||
if mw.language.new( args.lang ):isRTL() then | |||
dir = 'rtl' | |||
text_align = 'right' | |||
else | |||
dir = 'ltr' | |||
text_align = 'left' | |||
end | |||
local style = string.format('class="toccolours mw-content-%s layouttemplate commons-file-information-table" cellpadding="2" style="width: 100%%; direction:%s;" lang="%s"', | |||
args.lang, dir, text_align, args.lang) | |||
templateText = string.format('<table lang="%s" %s><tr><th class="type fileinfo-paramfield">%s</th>%s</tr></table>', args.lang, style, field_name, inner_table) | |||
end | |||
return templateText, Categories, coorTag | |||
end | |||
function p.LocationTemplateCore(frame) | |||
local args = getArgs(frame) | |||
args.namespace = mw.title.getCurrentTitle().nsText | |||
if not args.lat and not args.lon then -- if no lat and lon but numbered arguments present | |||
if args[4] then -- DMS with pipes format, ex. "34|5|32.36|N|116|9|24|55|W" | |||
args.lat = dms2deg_ ( args[1], args[2], args[3], args[4] ) | |||
args.lon = dms2deg_ ( args[5], args[6], args[7], args[8] ) | |||
args.attributes = args.attributes or args[9] | |||
elseif args[2] and not (type(args[2])=='string' and args[2]:find(":")) then -- decimal format or DMS with one pipe, ex. "34° 05′ 32.36″ N| 116° 09′ 24.55″ W" | |||
args.lat = args[1] | |||
args.lon = args[2] | |||
args.attributes = args.attributes or args[3] | |||
elseif args[1] then -- detect a single argument in the form "34° 05′ 32.36″ N, 116° 09′ 24.55″ W" or similar | |||
local v = mw.text.split(args[1]:gsub("([NnSs])", "%1/" ), "/") -- split into lat and lon using splitting point after any letter | |||
args.lat, args.lon = v[1], v[2] | |||
args.attributes = args.attributes or args[2] | |||
end | |||
end | |||
local cat = '' | |||
if args.lat and args.lon then | |||
local lat = tonumber(args.lat) | |||
local lon = tonumber(args.lon) | |||
if not lat or not lon then | |||
args.lat = dms2deg(args.lat or '') | |||
args.lon = dms2deg(args.lon or '') | |||
if (args.namespace == 'File' or args.namespace == 'Category') then | |||
cat = CoorCat.dms | |||
end | |||
end | |||
end | end | ||
return | local templateText, Categories, coorTag = p._LocationTemplateCore(args) | ||
return templateText .. Categories .. cat .. coorTag | |||
end | end | ||
return | return p |