require('strict')localp={}localgetArgs=require('Module:Arguments').getArgslocalfunctionround(n,decimals)localpow=10^(decimalsor0)returnmath.floor(n*pow+0.5)/powendfunctionp.getMapParams(map,frame)ifnotmapthenerror('The name of the location map definition to use must be specified',2)endlocalmoduletitle=mw.title.new('Module:Location map/data/'..map)ifnotmoduletitlethenerror(string.format('%q is not a valid name for a location map definition',map),2)elseifmoduletitle.existsthenlocalmapData=mw.loadData('Module:Location map/data/'..map)returnfunction(name,params)ifname==nilthenreturn'Module:Location map/data/'..mapelseifmapData[name]==nilthenreturn''elseifparamsthenreturnmw.message.newRawMessage(tostring(mapData[name]),unpack(params)):plain()elsereturnmapData[name]endendelseerror('Unable to find the specified location map definition: "Module:Location map/data/'..map..'" does not exist',2)endendfunctionp.data(frame,args,map)ifnotargsthenargs=getArgs(frame,{frameOnly=true})endifnotmapthenmap=p.getMapParams(args[1],frame)endlocalparams={}fork,vinipairs(args)doifk>2thenparams[k-2]=vendendreturnmap(args[2],#params~=0andparams)endlocalhemisphereMultipliers={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)ifdecimalthenifdegreesthenerror('Decimal and DMS degrees cannot both be provided for '..direction,2)elseifminutesthenerror('Minutes can only be provided with DMS degrees for '..direction,2)elseifsecondsthenerror('Seconds can only be provided with DMS degrees for '..direction,2)elseifhemispherethenerror('A hemisphere can only be provided with DMS degrees for '..direction,2)endlocalretval=tonumber(decimal)ifretvalthenreturnretvalenderror('The value "'..decimal..'" provided for '..direction..' is not valid',2)elseifsecondsandnotminutesthenerror('Seconds were provided for '..direction..' without minutes also being provided',2)elseifnotdegreesthenifminutesthenerror('Minutes were provided for '..direction..' without degrees also being provided',2)elseifhemispherethenerror('A hemisphere was provided for '..direction..' without degrees also being provided',2)endreturnnilenddecimal=tonumber(degrees)ifnotdecimalthenerror('The degree value "'..degrees..'" provided for '..direction..' is not valid',2)elseifminutesandnottonumber(minutes)thenerror('The minute value "'..minutes..'" provided for '..direction..' is not valid',2)elseifsecondsandnottonumber(seconds)thenerror('The second value "'..seconds..'" provided for '..direction..' is not valid',2)enddecimal=decimal+(minutesor0)/60+(secondsor0)/3600ifhemispherethenlocalmultiplier=hemisphereMultipliers[direction][hemisphere]ifnotmultiplierthenerror('The hemisphere "'..hemisphere..'" provided for '..direction..' is not valid',2)enddecimal=decimal*multiplierendreturndecimalend-- 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 signlocallat,long=mw.ustring.match(coord,'<span class="p%-latitude latitude">([^<]+)</span><span class="p%-longitude longitude">([^<]+)</span>')iflatthenreturntonumber(para=='longitude'andlongorlat)endlocalresult=mw.text.split(mw.ustring.match(coord,'%-?[%.%d]+°[NS] %-?[%.%d]+°[EW]')or'','[ °]')ifpara=='longitude'thenresult={result[3],result[4]}endifnottonumber(result[1])ornotresult[2]thenmw.log('Malformed coordinates value')mw.logObject(para,'para')mw.logObject(coord,'coord')returnerror('Malformed coordinates value',2)endreturntonumber(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 thisfunctionp.valueFunc(key,value)ifvaluethenvalue=mw.text.trim(value)endifvalue~=''orkey=='caption'orkey=='maplink'thenreturnvalueelseifkey=='useWikidata'thenreturnfalseendendlocalfunctiongetContainerImage(args,map)ifargs.AlternativeMapthenreturnargs.AlternativeMapelseifargs.reliefthenlocaldigits=mw.ustring.match(args.relief,'^[1-9][0-9]?$')or'1'-- image1 to image99ifmap('image'..digits)~=''thenreturnmap('image'..digits)endendreturnmap('image')endfunctionp.top(frame,args,map)ifnotargsthenargs=getArgs(frame,{frameOnly=true,valueFunc=p.valueFunc})endifnotmapthenmap=p.getMapParams(args[1],frame)endlocalwidthlocaldefault_as_number=tonumber(mw.ustring.match(tostring(args.default_width),"%d*"))ifnotargs.widththenwidth=round((default_as_numberor240)*(tonumber(map('defaultscale'))or1))elseifmw.ustring.sub(args.width,-2)=='px'thenwidth=mw.ustring.sub(args.width,1,-3)elsewidth=args.widthendlocalwidth_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 calculationwidth=round((default_as_numberor240)*(tonumber(map('defaultscale'))or1))width_as_number=tonumber(mw.ustring.match(tostring(width),"%d*"))or0;endifargs.max_width~=""andargs.max_width~=nilthen-- check to see if width bigger than max_widthlocalmax_as_number=tonumber(mw.ustring.match(args.max_width,"%d*"))or0;ifwidth_as_number>max_as_numberandmax_as_number>0thenwidth=args.max_width;endendlocalretval=frame:extensionTag{name='templatestyles',args={src='Module:Location map/styles.css'}}ifargs.float=='center'thenretval=retval..'<div class="center">'endifargs.captionandargs.caption~=''andargs.border~='infobox'thenretval=retval..'<div class="locmap noresize thumb 'ifargs.float=='"left"'orargs.float=='left'thenretval=retval..'tleft'elseifargs.float=='"center"'orargs.float=='center'orargs.float=='"none"'orargs.float=='none'thenretval=retval..'tnone'elseretval=retval..'tright'endretval=retval..'"><div class="thumbinner" style="width:'..(width+2)..'px'ifargs.border=='none'thenretval=retval..';border:none'elseifargs.borderthenretval=retval..';border-color:'..args.borderendretval=retval..'"><div style="position:relative;width:'..width..'px'..(args.border~='none'and';border:1px solid lightgray">'or'">')elseretval=retval..'<div class="locmap" style="width:'..width..'px;'ifargs.float=='"left"'orargs.float=='left'thenretval=retval..'float:left;clear:left'elseifargs.float=='"center"'orargs.float=='center'thenretval=retval..'float:none;clear:both;margin-left:auto;margin-right:auto'elseifargs.float=='"none"'orargs.float=='none'thenretval=retval..'float:none;clear:none'elseretval=retval..'float:right;clear:right'endretval=retval..'"><div style="width:'..width..'px;padding:0"><div style="position:relative;width:'..width..'px">'endlocalimage=getContainerImage(args,map)localcurrentTitle=mw.title.getCurrentTitle()retval=string.format('%s[[File:%s|%spx|%s%s|class=notpageimage noviewer]]',retval,image,width,args.altor((args.labelorcurrentTitle.text)..' is located in '..map('name')),args.maplinkand('|link='..args.maplink)or'')ifargs.captionandargs.caption~=''thenif(currentTitle.namespace==0)andmw.ustring.find(args.caption,'##')thenretval=retval..'[[Category:Pages using location map with a double number sign in the caption]]'endendifargs.overlay_imagethenreturnretval..'<div style="position:absolute;top:0;left:0">[[File:'..args.overlay_image..'|'..width..'px|class=notpageimage noviewer]]</div>'elsereturnretvalendendfunctionp.bottom(frame,args,map)ifnotargsthenargs=getArgs(frame,{frameOnly=true,valueFunc=p.valueFunc})endifnotmapthenmap=p.getMapParams(args[1],frame)endlocalretval='</div>'localcurrentTitle=mw.title.getCurrentTitle()ifnotargs.captionorargs.border=='infobox'thenifargs.borderthenretval=retval..'<div style="padding-top:0.2em">'elseretval=retval..'<div style="font-size:91%;padding-top:3px">'endretval=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 imageretval=retval..'<div class="thumbcaption"><div class="magnify">[[:File:'..getContainerImage(args,map)..'|class=notpageimage| ]]</div>'..args.caption..'</div>'endifargs.switcherLabelthenretval=retval..'<span class="switcher-label" style="display:none">'..args.switcherLabel..'</span>'elseifargs.autoSwitcherLabelthenretval=retval..'<span class="switcher-label" style="display:none">Show map of '..map('name')..'</span>'endretval=retval..'</div></div>'ifargs.caption_undefinedthenmw.log('Removed parameter caption_undefined used.')localparent=frame:getParent()ifparentthenmw.log('Parent is '..parent:getTitle())endmw.logObject(args,'args')ifcurrentTitle.namespace==0thenretval=retval..'[[Category:Location maps with removed parameters|caption_undefined]]'endendifmap('skew')~=''ormap('lat_skew')~=''ormap('crosses180')~=''ormap('type')~=''thenmw.log('Removed parameter used in map definition '..map())ifcurrentTitle.namespace==0thenlocalkey=(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..' ]]'endendifstring.find(map('name'),'|',1,true)thenmw.log('Pipe used in name of map definition '..map())ifcurrentTitle.namespace==0thenretval=retval..'[[Category:Location maps with a name containing a pipe]]'endendifargs.float=='center'thenretval=retval..'</div>'endreturnretvalendlocalfunctionmarkOuterDiv(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)endlocalfunctionmarkImageDiv(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~=0thenbuilder:wikitext(string.format('[[File:%s|%dx%dpx|%s|link=%s%s|class=notpageimage noviewer]]',mark,marksize,marksize,label,link,altand('|alt='..alt)or''))endreturnbuilderendlocalfunctionmarkLabelDiv(label,label_size,label_width,position,background,x,marksize)iftonumber(label_size)==0thenreturnmw.html.create('div'):addClass('l0'):wikitext(label)endlocalbuilder=mw.html.create('div'):cssText('width:'..label_width..'em')localdistance=round(marksize/2+1)ifposition=='top'then-- specified topbuilder:addClass('pv'):cssText('bottom:'..distance..'px;left:'..(-label_width/2)..'em')elseifposition=='bottom'then-- specified bottombuilder:addClass('pv'):cssText('top:'..distance..'px;left:'..(-label_width/2)..'em')elseifposition=='left'or(tonumber(x)>70andposition~='right')then-- specified left or autodetected to leftbuilder:addClass('pl'):cssText('right:'..distance..'px')else-- specified right or autodetected to rightbuilder:addClass('pr'):cssText('left:'..distance..'px')endbuilder=builder:tag('div'):wikitext(label)ifbackgroundthenbuilder:cssText('background-color:'..background)endreturnbuilder:done()endlocalfunctiongetX(longitude,left,right)localwidth=(right-left)%360ifwidth==0thenwidth=360endlocaldistanceFromLeft=(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 shorterifdistanceFromLeft-width/2>=180thendistanceFromLeft=distanceFromLeft-360endreturn100*distanceFromLeft/widthendlocalfunctiongetY(latitude,top,bottom)return100*(top-latitude)/(top-bottom)endfunctionp.mark(frame,args,map)ifnotargsthenargs=getArgs(frame,{wrappers='Template:Location map~'})endlocalmapnames={}ifnotmapthenifargs[1]thenmap={}formapnameinmw.text.gsplit(args[1],'#',true)domap[#map+1]=p.getMapParams(mw.ustring.gsub(mapname,'^%s*(.-)%s*$','%1'),frame)mapnames[#mapnames+1]=mapnameendif#map==1thenmap=map[1]endelsemap=p.getMapParams('World',frame)args[1]='World'endendiftype(map)=='table'thenlocaloutputs={}localoldargs=args[1]fork,vinipairs(map)doargs[1]=mapnames[k]outputs[k]=tostring(p.mark(frame,args,v))endargs[1]=oldargsreturntable.concat(outputs,'#PlaceList#')..'#PlaceList#'endlocalx,y,longitude,latitudelongitude=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]==exclusionmapthenreturn''endendendlocalbuilder=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')--endlongitude=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'thenlocalvalue=entity.claims.P625[1].mainsnak.datavalue.valuelongitude,latitude=value.longitude,value.latitudeendifargs.linkand(currentTitle.namespace==0)thenbuilder:wikitext('[[Category:Location maps with linked markers with coordinates from Wikidata]]')endendifnotlongitudethenerror('No value was provided for longitude')elseifnotlatitudethenerror('No value was provided for latitude')endifcurrentTitle.namespace>0thenif(notargs.lon_deg)~=(notargs.lat_deg)thenbuilder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Degrees]]')elseif(notargs.lon_min)~=(notargs.lat_min)thenbuilder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Minutes]]')elseif(notargs.lon_sec)~=(notargs.lat_sec)thenbuilder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Seconds]]')elseif(notargs.lon_dir)~=(notargs.lat_dir)thenbuilder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Hemisphere]]')elseif(notargs.long)~=(notargs.lat)thenbuilder:wikitext('[[Category:Location maps with different longitude and latitude precisions|Decimal]]')endendif((tonumber(args.lat_deg)or0)<0)and((tonumber(args.lat_min)or0)~=0or(tonumber(args.lat_sec)or0)~=0or(args.lat_dirandargs.lat_dir~=''))thenbuilder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')endif((tonumber(args.lon_deg)or0)<0)and((tonumber(args.lon_min)or0)~=0or(tonumber(args.lon_sec)or0)~=0or(args.lon_dirandargs.lon_dir~=''))thenbuilder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')endif(((tonumber(args.lat_min)or0)<0)or((tonumber(args.lat_sec)or0)<0))thenbuilder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')endif(((tonumber(args.lon_min)or0)<0)or((tonumber(args.lon_sec)or0)<0))thenbuilder:wikitext('[[Category:Location maps with negative degrees and minutes or seconds]]')endifargs.skeworargs.lon_shiftorargs.markhighthenmw.log('Removed parameter used in invocation.')localparent=frame:getParent()ifparentthenmw.log('Parent is '..parent:getTitle())endmw.logObject(args,'args')ifcurrentTitle.namespace==0thenlocalkey=(args.skewand'skew'or'')..(args.lon_shiftand'lon_shift'or'')..(args.markhighand'markhigh'or'')builder:wikitext('[[Category:Location maps with removed parameters|'..key..' ]]')endendifmap('x')~=''thenx=tonumber(mw.ext.ParserFunctions.expr(map('x',{latitude,longitude})))elsex=tonumber(getX(longitude,map('left'),map('right')))endifmap('y')~=''theny=tonumber(mw.ext.ParserFunctions.expr(map('y',{latitude,longitude})))elsey=tonumber(getY(latitude,map('top'),map('bottom')))endif(x<0orx>100ory<0ory>100)andnotargs.outsidethenmw.log('Mark placed outside map boundaries without outside flag set. x = '..x..', y = '..y)localparent=frame:getParent()ifparentthenmw.log('Parent is '..parent:getTitle())endmw.logObject(args,'args')ifcurrentTitle.namespace==0thenlocalkey=currentTitle.prefixedTextbuilder:wikitext('[[Category:Location maps with marks outside map and outside parameter not set|'..key..' ]]')endendlocalmark=args.markormap('mark')ifmark==''thenmark='Red pog.svg'endlocalmarksize=tonumber(args.marksize)ortonumber(map('marksize'))or8localimageDiv=markImageDiv(mark,marksize,args.labelormw.title.getCurrentTitle().text,args.linkor'',args.alt,args[2])locallabel_size=args.label_sizeor91locallabelDivifargs.labelandargs.position~='none'thenlabelDiv=markLabelDiv(args.label,label_size,args.label_widthor6,args.position,args.background,x,marksize)endreturnbuilder:node(markOuterDiv(x,y,imageDiv,labelDiv,label_size))endlocalfunctionswitcherSeparate(s)ifs==nilthenreturn{}endlocalretval={}foriinstring.gmatch(s..'#','([^#]*)#')doi=mw.text.trim(i)retval[#retval+1]=(i~=''andi)endreturnretvalendfunctionp.main(frame,args,map)localcaption_list={}ifnotargsthenargs=getArgs(frame,{wrappers='Template:Location map',valueFunc=p.valueFunc})endifargs.useWikidata==nilthenargs.useWikidata=trueendifnotmapthenifargs[1]thenmap={}formapnameinstring.gmatch(args[1],'[^#]+')domap[#map+1]=p.getMapParams(mw.ustring.gsub(mapname,'^%s*(.-)%s*$','%1'),frame)endifargs['caption']thenifargs['caption']==""thenwhile#caption_list<#mapdocaption_list[#caption_list+1]=args['caption']endelseforcaptioninmw.text.gsplit(args['caption'],'##',true)docaption_list[#caption_list+1]=captionendendendif#map==1thenmap=map[1]endelsemap=p.getMapParams('World',frame)endendiftype(map)=='table'thenlocalaltmaps=switcherSeparate(args.AlternativeMap)if#altmaps>#mapthenerror(string.format('%d AlternativeMaps were provided, but only %d maps were provided',#altmaps,#map))endlocaloverlays=switcherSeparate(args.overlay_image)if#overlays>#mapthenerror(string.format('%d overlay_images were provided, but only %d maps were provided',#overlays,#map))endif#caption_list>#mapthenerror(string.format('%d captions were provided, but only %d maps were provided',#caption_list,#map))endlocaloutputs={}args.autoSwitcherLabel=truefork,vinipairs(map)doargs.AlternativeMap=altmaps[k]args.overlay_image=overlays[k]args.caption=caption_list[k]outputs[k]=p.main(frame,args,v)endreturn'<div class="switcher-container">'..table.concat(outputs)..'</div>'elsereturnp.top(frame,args,map)..tostring(p.mark(frame,args,map))..p.bottom(frame,args,map)endendreturnp