Jump to content

Module:Location map

Permanently protected module
From Wikipedia, the free encyclopedia

require('strict')

localp={}

localgetArgs=require('Module:Arguments').getArgs

localfunctionround(n,decimals)
localpow=10^(decimalsor0)
returnmath.floor(n*pow+0.5)/pow
end

functionp.getMapParams(map,frame)
ifnotmapthen
error('The name of the location map definition to use must be specified',2)
end
localmoduletitle=mw.title.new('Module:Location map/data/'..map)
ifnotmoduletitlethen
error(string.format('%q is not a valid name for a location map definition',map),2)
elseifmoduletitle.existsthen
localmapData=mw.loadData('Module:Location map/data/'..map)
returnfunction(name,params)
ifname==nilthen
return'Module:Location map/data/'..map
elseifmapData[name]==nilthen
return''
elseifparamsthen
returnmw.message.newRawMessage(tostring(mapData[name]),unpack(params)):plain()
else
returnmapData[name]
end
end
else
error('Unable to find the specified location map definition: "Module:Location map/data/'..map..' "does not exist',2)
end
end

functionp.data(frame,args,map)
ifnotargsthen
args=getArgs(frame,{frameOnly=true})
end
ifnotmapthen
map=p.getMapParams(args[1],frame)
end
localparams={}
fork,vinipairs(args)do
ifk>2then
params[k-2]=v
end
end
returnmap(args[2],#params~=0andparams)
end

localhemisphereMultipliers={
longitude={W=-1,w=-1,E=1,e=1},
latitude={S=-1,s=-1,N=1,n=1}
}

localfunctiondecdeg(degrees,minutes,seconds,hemisphere,decimal,direction)
ifdecimalthen
ifdegreesthen
error('Decimal and DMS degrees cannot both be provided for '..direction,2)
elseifminutesthen
error('Minutes can only be provided with DMS degrees for '..direction,2)
elseifsecondsthen
error('Seconds can only be provided with DMS degrees for '..direction,2)
elseifhemispherethen
error('A hemisphere can only be provided with DMS degrees for '..direction,2)
end
localretval=tonumber(decimal)
ifretvalthen
returnretval
end
error('The value "'..decimal..' "provided for '..direction..' is not valid',2)
elseifsecondsandnotminutesthen
error('Seconds were provided for '..direction..' without minutes also being provided',2)
elseifnotdegreesthen
ifminutesthen
error('Minutes were provided for '..direction..' without degrees also being provided',2)
elseifhemispherethen
error('A hemisphere was provided for '..direction..' without degrees also being provided',2)
end
returnnil
end
decimal=tonumber(degrees)
ifnotdecimalthen
error('The degree value "'..degrees..' "provided for '..direction..' is not valid',2)
elseifminutesandnottonumber(minutes)then
error('The minute value "'..minutes..' "provided for '..direction..' is not valid',2)
elseifsecondsandnottonumber(seconds)then
error('The second value "'..seconds..' "provided for '..direction..' is not valid',2)
end
decimal=decimal+(minutesor0)/60+(secondsor0)/3600
ifhemispherethen
localmultiplier=hemisphereMultipliers[direction][hemisphere]
ifnotmultiplierthen
error('The hemisphere "'..hemisphere..' "provided for '..direction..' is not valid',2)
end
decimal=decimal*multiplier
end
returndecimal
end

-- Finds a parameter in a transclusion of {{Coord}}.
localfunctioncoord2text(para,coord)-- this should be changed for languages which do not use Arabic numerals or the degree sign
locallat,long=mw.ustring.match(coord,'<span class= "p%-latitude latitude" >([^<]+)</span><span class= "p%-longitude longitude" >([^<]+)</span>')
iflatthen
returntonumber(para=='longitude'andlongorlat)
end
localresult=mw.text.split(mw.ustring.match(coord,'%-?[%.%d]+°[NS] %-?[%.%d]+°[EW]')or'','[ °]')
ifpara=='longitude'thenresult={result[3],result[4]}end
ifnottonumber(result[1])ornotresult[2]then
mw.log('Malformed coordinates value')
mw.logObject(para,'para')
mw.logObject(coord,'coord')
returnerror('Malformed coordinates value',2)
end
returntonumber(result[1])*hemisphereMultipliers[para][result[2]]
end

-- effectively make removeBlanks false for caption and maplink, and true for everything else
-- if useWikidata is present but blank, convert it to false instead of nil
-- p.top, p.bottom, and their callers need to use this
functionp.valueFunc(key,value)
ifvaluethen
value=mw.text.trim(value)
end
ifvalue~=''orkey=='caption'orkey=='maplink'then
returnvalue
elseifkey=='useWikidata'then
returnfalse
end
end

localfunctiongetContainerImage(args,map)
ifargs.AlternativeMapthen
returnargs.AlternativeMap
elseifargs.reliefthen
localdigits=mw.ustring.match(args.relief,'^[1-9][0-9]?$')or'1'-- image1 to image99
ifmap('image'..digits)~=''then
returnmap('image'..digits)
end
end
returnmap('image')
end

functionp.top(frame,args,map)
ifnotargsthen
args=getArgs(frame,{frameOnly=true,valueFunc=p.valueFunc})
end
ifnotmapthen
map=p.getMapParams(args[1],frame)
end
localwidth
localdefault_as_number=tonumber(mw.ustring.match(tostring(args.default_width),"%d*"))
ifnotargs.widththen
width=round((default_as_numberor240)*(tonumber(map('defaultscale'))or1))
elseifmw.ustring.sub(args.width,-2)=='px'then
width=mw.ustring.sub(args.width,1,-3)
else
width=args.width
end
localwidth_as_number=tonumber(mw.ustring.match(tostring(width),"%d*"))or0;
ifwidth_as_number==0then
-- check to see if width is junk. If it is, then use default calculation
width=round((default_as_numberor240)*(tonumber(map('defaultscale'))or1))
width_as_number=tonumber(mw.ustring.match(tostring(width),"%d*"))or0;
end
ifargs.max_width~=""andargs.max_width~=nilthen
-- check to see if width bigger than max_width
localmax_as_number=tonumber(mw.ustring.match(args.max_width,"%d*"))or0;
ifwidth_as_number>max_as_numberandmax_as_number>0then
width=args.max_width;
end
end
localretval=frame:extensionTag{name='templatestyles',args={src='Module:Location map/styles.css'}}
ifargs.float=='center'then
retval=retval..'<div class= "center" >'
end
ifargs.captionandargs.caption~=''andargs.border~='infobox'then
retval=retval..'<div class= "locmap noviewer noresize thumb '
ifargs.float==' "left" 'orargs.float=='left'then
retval=retval..'tleft'
elseifargs.float==' "center" 'orargs.float=='center'orargs.float==' "none" 'orargs.float=='none'then
retval=retval..'tnone'
else
retval=retval..'tright'
end
retval=retval..' "><div class=" thumbinner "style=" width:'..(width+2)..'px'
ifargs.border=='none'then
retval=retval..';border:none'
elseifargs.borderthen
retval=retval..';border-color:'..args.border
end
retval=retval..' "><div style=" position:relative;width:'..width..'px'..(args.border~='none'and';border:1px solid lightgray ">'or' ">')
else
retval=retval..'<div class= "locmap" style= "width:'..width..'px;'
ifargs.float==' "left" 'orargs.float=='left'then
retval=retval..'float:left;clear:left'
elseifargs.float==' "center" 'orargs.float=='center'then
retval=retval..'float:none;clear:both;margin-left:auto;margin-right:auto'
elseifargs.float==' "none" 'orargs.float=='none'then
retval=retval..'float:none;clear:none'
else
retval=retval..'float:right;clear:right'
end
retval=retval..' "><div style=" width:'..width..'px;padding:0 "><div style=" position:relative;width:'..width..'px ">'
end
localimage=getContainerImage(args,map)
localcurrentTitle=mw.title.getCurrentTitle()
retval=string.format(
'%s[[File:%s|%spx|%s%s|class=notpageimage]]',
retval,
image,
width,
args.altor((args.labelorcurrentTitle.text)..' is located in '..map('name')),
args.maplinkand('|link='..args.maplink)or''
)
ifargs.captionandargs.caption~=''then
if(currentTitle.namespace==0)andmw.ustring.find(args.caption,'##')then
retval=retval..'[[Category:Pages using location map with a double number sign in the caption]]'
end
end
ifargs.overlay_imagethen
returnretval..'<div style= "position:absolute;top:0;left:0" >[[File:'..args.overlay_image..'|'..width..'px|class=notpageimage]]</div>'
else
returnretval
end
end

functionp.bottom(frame,args,map)
ifnotargsthen
args=getArgs(frame,{frameOnly=true,valueFunc=p.valueFunc})
end
ifnotmapthen
map=p.getMapParams(args[1],frame)
end
localretval='</div>'
localcurrentTitle=mw.title.getCurrentTitle()
ifnotargs.captionorargs.border=='infobox'then
ifargs.borderthen
retval=retval..'<div style= "padding-top:0.2em" >'
else
retval=retval..'<div style= "font-size:91%;padding-top:3px" >'
end
retval=retval
..(args.captionor(args.labelorcurrentTitle.text)..' ('..map('name')..')')
..'</div>'
elseifargs.caption~=''then
-- This is not the pipe trick. We're creating a link with no text on purpose, so that CSS can give us a nice image
retval=retval..'<div class= "thumbcaption" ><div class= "magnify" >[[:File:'..getContainerImage(args,map)..'|class=notpageimage| ]]</div>'..args.caption..'</div>'
end

ifargs.switcherLabelthen
retval=retval..'<span class= "switcher-label" style= "display:none" >'..args.switcherLabel..'</span>'
elseifargs.autoSwitcherLabelthen
retval=retval..'<span class= "switcher-label" style= "display:none" >Show map of '..map('name')..'</span>'
end

retval=retval..'</div></div>'
ifargs.caption_undefinedthen
mw.log('Removed parameter caption_undefined used.')
localparent=frame:getParent()
ifparentthen
mw.log('Parent is '..parent:getTitle())
end
mw.logObject(args,'args')
ifcurrentTitle.namespace==0then
retval=retval..'[[Category:Location maps with removed parameters|caption_undefined]]'
end
end
ifmap('skew')~=''ormap('lat_skew')~=''ormap('crosses180')~=''ormap('type')~=''then
mw.log('Removed parameter used in map definition '..map())
ifcurrentTitle.namespace==0then
localkey=(map('skew')~=''and'skew'or'')..
(map('lat_skew')~=''and'lat_skew'or'')..
(map('crosses180')~=''and'crosses180'or'')..
(map('type')~=''and'type'or'')
retval=retval..'[[Category:Location maps with removed parameters|'..key..' ]]'
end
end
ifstring.find(map('name'),'|',1,true)then
mw.log('Pipe used in name of map definition '..map())
ifcurrentTitle.namespace==0then
retval=retval..'[[Category:Location maps with a name containing a pipe]]'
end
end
ifargs.float=='center'then
retval=retval..'</div>'
end
returnretval
end

localfunctionmarkOuterDiv(x,y,imageDiv,labelDiv,label_size)
returnmw.html.create('div')
:addClass('od')
:addClass('notheme')-- T236137
:cssText('top:'..round(y,3)..'%;left:'..round(x,3)..'%;font-size:'..label_size..'%')
:node(imageDiv)
:node(labelDiv)
end

localfunctionmarkImageDiv(mark,marksize,label,link,alt,title)
localbuilder=mw.html.create('div')
:addClass('id')
:cssText('left:-'..round(marksize/2)..'px;top:-'..round(marksize/2)..'px')
:attr('title',title)
ifmarksize~=0then
builder:wikitext(string.format(
'[[File:%s|%dx%dpx|%s|link=%s%s|class=notpageimage]]',
mark,
marksize,
marksize,
label,
link,
altand('|alt='..alt)or''
))
end
returnbuilder
end

localfunctionmarkLabelDiv(label,label_size,label_width,position,background,x,marksize)
iftonumber(label_size)==0then
returnmw.html.create('div'):addClass('l0'):wikitext(label)
end
localbuilder=mw.html.create('div')
:cssText('width:'..label_width..'em')
localdistance=round(marksize/2+1)
ifposition=='top'then-- specified top
builder:addClass('pv'):cssText('bottom:'..distance..'px;left:'..(-label_width/2)..'em')
elseifposition=='bottom'then-- specified bottom
builder:addClass('pv'):cssText('top:'..distance..'px;left:'..(-label_width/2)..'em')
elseifposition=='left'or(tonumber(x)>70andposition~='right')then-- specified left or autodetected to left
builder:addClass('pl'):cssText('right:'..distance..'px')
else-- specified right or autodetected to right
builder:addClass('pr'):cssText('left:'..distance..'px')
end
builder=builder:tag('div')
:wikitext(label)
ifbackgroundthen
builder:cssText('background-color:'..background)
end
returnbuilder:done()
end

localfunctiongetX(longitude,left,right)
localwidth=(right-left)%360
ifwidth==0then
width=360
end
localdistanceFromLeft=(longitude-left)%360
-- the distance needed past the map to the right equals distanceFromLeft - width. the distance needed past the map to the left equals 360 - distanceFromLeft. to minimize page stretching, go whichever way is shorter
ifdistanceFromLeft-width/2>=180then
distanceFromLeft=distanceFromLeft-360
end
return100*distanceFromLeft/width
end

localfunctiongetY(latitude,top,bottom)
return100*(top-latitude)/(top-bottom)
end

functionp.mark(frame,args,map)
ifnotargsthen
args=getArgs(frame,{wrappers='Template:Location map~'})
end
localmapnames={}
ifnotmapthen
ifargs[1]then
map={}
formapnameinmw.text.gsplit(args[1],'#',true)do
map[#map+1]=p.getMapParams(mw.ustring.gsub(mapname,'^%s*(.-)%s*$','%1'),frame)
mapnames[#mapnames+1]=mapname
end
if#map==1thenmap=map[1]end
else
map=p.getMapParams('World',frame)
args[1]='World'
end
end
iftype(map)=='table'then
localoutputs={}
localoldargs=args[1]
fork,vinipairs(map)do
args[1]=mapnames[k]
outputs[k]=tostring(p.mark(frame,args,v))
end
args[1]=oldargs
returntable.concat(outputs,'#PlaceList#')..'#PlaceList#'
end
localx,y,longitude,latitude
longitude=decdeg(args.lon_deg,args.lon_min,args.lon_sec,args.lon_dir,args.long,'longitude')
latitude=decdeg(args.lat_deg,args.lat_min,args.lat_sec,args.lat_dir,args.lat,'latitude')
ifargs.excludefromthen
-- If this mark is to be excluded from certain maps entirely (useful in the context of multiple maps)
forexclusionmapinmw.text.gsplit(args.excludefrom,'#',true)do
-- Check if this map is excluded. If so, return an empty string.
ifargs[1]==exclusionmapthen
return''
end
end

end
localbuilder=mw.html.create()
localcurrentTitle=mw.title.getCurrentTitle()
ifargs.coordinatesthen
-- Temporarily removed to facilitate infobox conversion. See [[Wikipedia:Coordinates in infoboxes]]

-- if longitude or latitude then
-- error('Coordinates from [[Module:Coordinates]] and individual coordinates cannot both be provided')
-- end
longitude=coord2text('longitude',args.coordinates)
latitude=coord2text('latitude',args.coordinates)
elseifnotlongitudeandnotlatitudeandargs.useWikidatathen
-- If they didn't provide either coordinate, try Wikidata. If they provided one but not the other, don't.
localentity=mw.wikibase.getEntity()
ifentityandentity.claimsandentity.claims.P625andentity.claims.P625[1].mainsnak.snaktype=='value'then
localvalue=entity.claims.P625[1].mainsnak.datavalue.value
longitude,latitude=value.longitude,value.latitude
end
ifargs.linkand(currentTitle.namespace==0)then
builder:wikitext('[[Category:Location maps with linked markers with coordinates from Wikidata]]')
end
end
ifnotlongitudethen
error('No value was provided for longitude')
elseifnotlatitudethen
error('No value was provided for latitude')
end
ifcurrentTitle.namespace>0then
if(notargs.lon_deg)~=(notargs.lat_deg)then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Degrees]]')
elseif(notargs.lon_min)~=(notargs.lat_min)then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Minutes]]')
elseif(notargs.lon_sec)~=(notargs.lat_sec)then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Seconds]]')
elseif(notargs.lon_dir)~=(notargs.lat_dir)then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Hemisphere]]')
elseif(notargs.long)~=(notargs.lat)then
builder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Decimal]]')
end
end
if((tonumber(args.lat_deg)or0)<0)and((tonumber(args.lat_min)or0)~=0or(tonumber(args.lat_sec)or0)~=0or(args.lat_dirandargs.lat_dir~=''))then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if((tonumber(args.lon_deg)or0)<0)and((tonumber(args.lon_min)or0)~=0or(tonumber(args.lon_sec)or0)~=0or(args.lon_dirandargs.lon_dir~=''))then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if(((tonumber(args.lat_min)or0)<0)or((tonumber(args.lat_sec)or0)<0))then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
if(((tonumber(args.lon_min)or0)<0)or((tonumber(args.lon_sec)or0)<0))then
builder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')
end
ifargs.skeworargs.lon_shiftorargs.markhighthen
mw.log('Removed parameter used in invocation.')
localparent=frame:getParent()
ifparentthen
mw.log('Parent is '..parent:getTitle())
end
mw.logObject(args,'args')
ifcurrentTitle.namespace==0then
localkey=(args.skewand'skew'or'')..
(args.lon_shiftand'lon_shift'or'')..
(args.markhighand'markhigh'or'')
builder:wikitext('[[Category:Location maps with removed parameters|'..key..' ]]')
end
end
ifmap('x')~=''then
x=tonumber(mw.ext.ParserFunctions.expr(map('x',{latitude,longitude})))
else
x=tonumber(getX(longitude,map('left'),map('right')))
end
ifmap('y')~=''then
y=tonumber(mw.ext.ParserFunctions.expr(map('y',{latitude,longitude})))
else
y=tonumber(getY(latitude,map('top'),map('bottom')))
end
if(x<0orx>100ory<0ory>100)andnotargs.outsidethen
mw.log('Mark placed outside map boundaries without outside flag set. x = '..x..', y = '..y)
localparent=frame:getParent()
ifparentthen
mw.log('Parent is '..parent:getTitle())
end
mw.logObject(args,'args')
ifcurrentTitle.namespace==0then
localkey=currentTitle.prefixedText
builder:wikitext('[[Category:Location maps with marks outside map and outside parameter not set|'..key..' ]]')
end
end
localmark=args.markormap('mark')
ifmark==''then
mark='Red pog.svg'
end
localmarksize=tonumber(args.marksize)ortonumber(map('marksize'))or8
localimageDiv=markImageDiv(mark,marksize,args.labelormw.title.getCurrentTitle().text,args.linkor'',args.alt,args[2])
locallabel_size=args.label_sizeor91
locallabelDiv
ifargs.labelandargs.position~='none'then
labelDiv=markLabelDiv(args.label,label_size,args.label_widthor6,args.position,args.background,x,marksize)
end
returnbuilder:node(markOuterDiv(x,y,imageDiv,labelDiv,label_size))
end

localfunctionswitcherSeparate(s)
ifs==nilthenreturn{}end
localretval={}
foriinstring.gmatch(s..'#','([^#]*)#')do
i=mw.text.trim(i)
retval[#retval+1]=(i~=''andi)
end
returnretval
end

functionp.main(frame,args,map)
localcaption_list={}
ifnotargsthen
args=getArgs(frame,{wrappers='Template:Location map',valueFunc=p.valueFunc})
end
ifargs.useWikidata==nilthen
args.useWikidata=true
end
ifnotmapthen
ifargs[1]then
map={}
formapnameinstring.gmatch(args[1],'[^#]+')do
map[#map+1]=p.getMapParams(mw.ustring.gsub(mapname,'^%s*(.-)%s*$','%1'),frame)
end
ifargs['caption']then
ifargs['caption']==""then
while#caption_list<#mapdo
caption_list[#caption_list+1]=args['caption']
end
else
forcaptioninmw.text.gsplit(args['caption'],'##',true)do
caption_list[#caption_list+1]=caption
end
end
end
if#map==1thenmap=map[1]end
else
map=p.getMapParams('World',frame)
end
end
iftype(map)=='table'then
localaltmaps=switcherSeparate(args.AlternativeMap)
if#altmaps>#mapthen
error(string.format('%d AlternativeMaps were provided, but only %d maps were provided',#altmaps,#map))
end
localoverlays=switcherSeparate(args.overlay_image)
if#overlays>#mapthen
error(string.format('%d overlay_images were provided, but only %d maps were provided',#overlays,#map))
end
if#caption_list>#mapthen
error(string.format('%d captions were provided, but only %d maps were provided',#caption_list,#map))
end
localoutputs={}
args.autoSwitcherLabel=true
fork,vinipairs(map)do
args.AlternativeMap=altmaps[k]
args.overlay_image=overlays[k]
args.caption=caption_list[k]
outputs[k]=p.main(frame,args,v)
end
return'<div class= "switcher-container" >'..table.concat(outputs)..'</div>'
else
returnp.top(frame,args,map)..tostring(p.mark(frame,args,map))..p.bottom(frame,args,map)
end
end

returnp