-- Note: Originally written on English Wikipedia at https://en.wikipedia.org/wiki/Module:Mapframe--[[---------------------------------------------------------------------------- ##### Localisation (L10n) settings ##### Replace values in quotes ("") with localised values----------------------------------------------------------------------------]]--localL10n={}-- Modue dependencieslocaltranscluder-- local copy of https://www.mediawiki.org/wiki/Module:Transcluder loaded lazily-- "strict" should not be used, at least until all other modules which require this module are not using globals.-- Template parameter names (unnumbered versions only)-- Specify each as either a single string, or a table of strings (aliases)-- Aliases are checked left-to-right, i.e. `{ "one", "two" }` is equivalent to using `{{{one| {{{two|}}} }}}` in a templateL10n.para={display="display",type="type",id={"id","ids"},from="from",raw="raw",title="title",description="description",strokeColor={"stroke-color","stroke-colour"},strokeWidth="stroke-width",strokeOpacity="stroke-opacity",fill="fill",fillOpacity="fill-opacity",coord="coord",marker="marker",markerColor={"marker-color","marker-colour"},markerSize="marker-size",radius={"radius","radius_m"},radiusKm="radius_km",radiusFt="radius_ft",radiusMi="radius_mi",edges="edges",text="text",icon="icon",zoom="zoom",frame="frame",plain="plain",frameWidth="frame-width",frameHeight="frame-height",frameCoordinates={"frame-coordinates","frame-coord"},frameLatitude={"frame-lat","frame-latitude"},frameLongitude={"frame-long","frame-longitude"},frameAlign="frame-align",switch="switch",overlay="overlay",overlayBorder="overlay-border",overlayHorizontalAlignment="overlay-horizontal-alignment",overlayVerticalAlignment="overlay-vertical-alignment",overlayHorizontalOffset="overlay-horizontal-offset",overlayVerticalOffset="overlay-vertical-offset"}-- Names of other templates this module can extract coordinates fromL10n.template={coord={-- The coord template, as well as templates with output that contains {{coord}}"Coord","Coord/sandbox","NRHP row","NRHP row/sandbox","WikidataCoord","WikidataCoord/sandbox","Wikidatacoord","Wikidata coord"}}-- Error messagesL10n.error={badDisplayPara="Invalid display parameter",noCoords="Coordinates must be specified on Wikidata or in |"..(type(L10n.para.coord)=='table'andL10n.para.coord[1]orL10n.para.coord).."=",wikidataCoords="Coordinates not found on Wikidata",noCircleCoords="Circle centre coordinates must be specified, or available via Wikidata",negativeRadius="Circle radius must be a positive number",noRadius="Circle radius must be specified",negativeEdges="Circle edges must be a positive number",noSwitchPara="Found only one switch value in |"..(type(L10n.para.switch)=='table'andL10n.para.switch[1]orL10n.para.switch).."=",oneSwitchLabel="Found only one label in |"..(type(L10n.para.switch)=='table'andL10n.para.switch[1]orL10n.para.switch).."=",noSwitchLists="At least one parameter must have a SWITCH: list",switchMismatches="All SWITCH: lists must have the same number of values",-- "%s" and "%d" tokens will be replaced with strings and numbers when usedoneSwitchValue="Found only one switch value in |%s=",fewerSwitchLabels="Found %d switch values but only %d labels in |"..(type(L10n.para.switch)=='table'andL10n.para.switch[1]orL10n.para.switch).."=",noNamedCoords="No named coordinates found in %s"}-- Other stringsL10n.str={-- valid values for display parameter, e.g. (|display=inline) or (|display=title) or (|display=inline,title) or (|display=title,inline)inline="inline",title="title",dsep=",",-- separator between inline and title (comma in the example above)-- valid values for type parameterline="line",-- geoline feature (e.g. a road)shape="shape",-- geoshape feature (e.g. a state or province)shapeInverse="shape-inverse",-- geomask feature (the inverse of a geoshape)data="data",-- geoJSON data page on Commonspoint="point",-- single point feature (coordinates)circle="circle",-- circular area around a pointnamed="named",-- all named coordinates in an article or section-- Keyword to indicate a switch list. Must NOT use the special characters ^$()%.[]*+-?switch="SWITCH",-- valid values for icon, frame, and plain parametersaffirmedWords=' '..table.concat({"add","added","affirm","affirmed","include","included","on","true","yes","y"},' ')..' ',declinedWords=' '..table.concat({"decline","declined","exclude","excluded","false","none","not","no","n","off","omit","omitted","remove","removed"},' ')..' '}-- Default values for parametersL10n.defaults={display=L10n.str.inline,text="Map",frameWidth="300",frameHeight="200",frameAlign="right",markerColor="5E74F3",markerSize=nil,strokeColor="#ff0000",strokeWidth=6,edges=32,-- number of edges used to approximate a circleoverlayBorder="1px solid white",overlayHorizontalAlignment="right",overlayHorizontalOffset="0",overlayVerticalAlignment="bottom",overlayVerticalOffset="0"}-- #### End of L10n settings ####--[[---------------------------------------------------------------------------- Utility methods----------------------------------------------------------------------------]]--localutil={}--[[Looks up a parameter value based on the id (a key from the L10n.para table) andoptionally a suffix, for parameters that can be suffixed (e.g. type2 is typewith suffix 2).@param {table} args key-value pairs of parameter names and their values@param {string} param_id id for parameter name (key from the L10n.para table)@param {string} [suffix] suffix for parameter name@returns {string|nil} parameter value if found, or nil if not found]]--functionutil.getParameterValue(args,param_id,suffix)suffix=suffixor''iftype(L10n.para[param_id])~='table'thenreturnargs[L10n.para[param_id]..suffix]endfor_i,paramAliasinipairs(L10n.para[param_id])doifargs[paramAlias..suffix]thenreturnargs[paramAlias..suffix]endendreturnnilend--[[Trim whitespace from args, and remove empty args. Also fix control characters.@param {table} argsTable@returns {table} trimmed args table]]--functionutil.trimArgs(argsTable)localcleanArgs={}forkey,valinpairs(argsTable)doiftype(key)=='string'andtype(val)=='string'thenval=val:match('^%s*(.-)%s*$')ifval~=''then-- control characters inside json need to be escaped, but stripping them is simpler-- See also T214984-- However, *don't* strip control characters from wikitext (text or description parameters) or you'll break strip markers-- Alternatively it might be better to only strip control char from raw parameter contentifutil.matchesParam('text',key)orutil.matchesParam('description',key,key:gsub('^%D+(%d+)$','%1'))thencleanArgs[key]=valelsecleanArgs[key]=val:gsub('%c',' ')endendelsecleanArgs[key]=valendendreturncleanArgsend--[[Check if a parameter name matches an unlocalized parameter key@param {string} key - the unlocalized parameter name to search through@param {string} name - the localized parameter name to check@param {string|nil} - an optional suffix to apply to the value(s) from the localization key@returns {boolean} true if the name matches the parameter, false otherwise]]--functionutil.matchesParam(key,name,suffix)localparam=L10n.para[key]suffix=suffixor''iftype(param)=='table'thenfor_,vinpairs(param)doif(v..suffix)==namethenreturntrueendendreturnfalseendreturn((param..suffix)==name)end--[[Check if a value is affirmed (one of the values in L10n.str.affirmedWords)@param {string} val Value to be checked@returns {boolean} true if affirmed, false otherwise]]--functionutil.isAffirmed(val)ifnot(val)thenreturnfalseendreturnstring.find(L10n.str.affirmedWords,' '..val..' ',1,true)andtrueorfalseend--[[Check if a value is declined (one of the values in L10n.str.declinedWords)@param {string} val Value to be checked@returns {boolean} true if declined, false otherwise]]--functionutil.isDeclined(val)ifnot(val)thenreturnfalseendreturnstring.find(L10n.str.declinedWords,' '..val..' ',1,true)andtrueorfalseend--[[Check if the name of a template matches the known coord templates or wrappers(in L10n.template.coord). The name is normalised when checked, so e.g. the names"Coord", "coord", and " Coord" all return true.@param {string} name@returns {boolean} true if it is a coord template or wrapper, false otherwise]]--functionutil.isCoordTemplateOrWrapper(name)name=mw.text.trim(name)localinputTitle=mw.title.new(name,'Template')ifnotinputTitlethenreturnfalseend-- Create (or reuse) mw.title objects for each known coord template/wrapper.-- Stored in L10n.template.title so that they don't need to be recreated-- each time this function is calledifnotL10n.template.titlesthenL10n.template.titles={}for_,vinpairs(L10n.template.coord)dotable.insert(L10n.template.titles,mw.title.new(v,'Template'))endendfor_,templateTitleinpairs(L10n.template.titles)doifmw.title.equals(inputTitle,templateTitle)thenreturntrueendendreturnfalseend--[[Recursively extract coord templates which have a name parameter.@param {string} wikitext@returns {table} table sequence of coord templates]]--functionutil.extractCoordTemplates(wikitext)localoutput={}localtemplates=mw.ustring.gmatch(wikitext,'{%b{}}')localsubtemplates={}fortemplateintemplatesdolocaltemplateName=mw.ustring.match(template,'{{([^}|]+)')localnameParam=mw.ustring.match(template,"|%s*name%s*=%s*[^}|]+")ifutil.isCoordTemplateOrWrapper(templateName)thenifnameParamthentable.insert(output,template)endelseifmw.ustring.find(mw.ustring.sub(template,2),"{{")thenlocalsubOutput=util.extractCoordTemplates(mw.ustring.sub(template,2))for_,tinpairs(subOutput)dotable.insert(output,t)endendend-- ensure coords are not using title displayfork,vinpairs(output)dooutput[k]=mw.ustring.gsub(v,"|%s*display%s*=[^|}]+","|display=inline")endreturnoutputend--[[Gets all named coordiates from a page or a section of a page.@param {string|nil} page Page name, or name#section, to get named coordinates from. If the name is omitted, i.e. #section or nil or empty string, then the current page will be used.@returns {table} sequence of {coord, name, description} tables where coord is the coordinates in a format suitable for #util.parseCoords, name is a string, and description is a string (coordinates in a format suitable for displaying to the reader). If for some reason the name can't be found, the description is nil and the name contains display-format coordinates.@throws {L10n.error.noNamedCoords} if no named coordinates are found.]]--functionutil.getNamedCoords(page)iftranscluder==nilthen-- load [[Module:Transcluder]] lazily so it is only transcluded on pages that-- actually use named coordinatestranscluder=require("Module:Transcluder")endlocalparts=mw.text.split(pageor"","#",true)localname=parts[1]==""andmw.title.getCurrentTitle().prefixedTextorparts[1]localsection=parts[2]localpageWikitext=transcluder.get(sectionandname.."#"..sectionorname)localcoordTemplates=util.extractCoordTemplates(pageWikitext)if#coordTemplates==0thenerror(string.format(L10n.error.noNamedCoords,pageorname),0)endlocalframe=mw.getCurrentFrame()localsep="________"localexpandedContent=frame:preprocess(table.concat(coordTemplates,sep))localexpandedTemplates=mw.text.split(expandedContent,sep)localnamedCoords={}for_,expandedTemplateinpairs(expandedTemplates)dolocalcoord=mw.ustring.match(expandedTemplate,"<span class=\"geo%-dec\".->(.-)</span>")ifcoordthenlocalname=(-- name specified by a wrapper template, e.g [[Article|Name]]mw.ustring.match(expandedTemplate,"<span class=\"mapframe%-coord%-name\">(.-)</span>")or-- name passed into coord templatemw.ustring.match(expandedTemplate,"<span class=\"fn org\">(.-)</span>")or-- default to the coordinates if the name can't be retrievedcoord)localdescription=name~=coordandcoordlocalcoord=mw.ustring.gsub(coord,"[° ]","_")table.insert(namedCoords,{coord=coord,name=name,description=description})endendif#namedCoords==0thenerror(string.format(L10n.error.noNamedCoords,pageorname),0)endreturnnamedCoordsend--[[Parse coordinate values from the params passed in a GeoHack url (such as//tools.wmflabs.org/geohack/geohack.php?pagename=Example¶ms=1_2_N_3_4_W_ or//tools.wmflabs.org/geohack/geohack.php?pagename=Example¶ms=1.23_S_4.56_E_ )or non-url string in the same format (such as `1_2_N_3_4_W_` or `1.23_S_4.56_E_`)@param {string} coords string containing coordinates@returns {number, number} latitude, longitude]]--functionutil.parseCoords(coords)localcoordsPattifmw.ustring.find(coords,"params=",1,true)then-- prevent false matches from page name, e.g. ?pagename=Lorem_S._IpsumcoordsPatt='params=([_%.%d]+[NS][_%.%d]+[EW])'else-- not actually a geohack url, just the same formatcoordsPatt='[_%.%d]+[NS][_%.%d]+[EW]'endlocalparts=mw.text.split((mw.ustring.match(coords,coordsPatt)or''),'_')locallat_d=tonumber(parts[1])assert(lat_d,"Unable to get latitude from input '"..coords.."'.")locallat_m=tonumber(parts[2])-- nil if coords are in decimal formatlocallat_s=lat_mandtonumber(parts[3])-- nil if coords are either in decimal format or degrees and minutes onlylocallat=lat_d+(lat_mor0)/60+(lat_sor0)/3600ifparts[#parts/2]=='S'thenlat=lat*-1endlocallong_d=tonumber(parts[1+#parts/2])assert(long_d,"Unable to get longitude from input '"..coords.."'.")locallong_m=tonumber(parts[2+#parts/2])-- nil if coords are in decimal formatlocallong_s=long_mandtonumber(parts[3+#parts/2])-- nil if coords are either in decimal format or degrees and minutes onlylocallong=long_d+(long_mor0)/60+(long_sor0)/3600ifparts[#parts]=='W'thenlong=long*-1endreturnlat,longend--[[Get coordinates from a Wikidata item@param {string} item_id Wikidata item id (Q number)@returns {number, number} latitude, longitude@throws {L10n.error.noCoords} if item_id is invalid or the item does not exist@throws {L10n.error.wikidataCoords} if the the item does not have a P625 statement (coordinates), or it is set to "no value"]]--functionutil.wikidataCoords(item_id)ifnot(item_idandmw.wikibase.isValidEntityId(item_id)andmw.wikibase.entityExists(item_id))thenerror(L10n.error.noCoords,0)endlocalcoordStatements=mw.wikibase.getBestStatements(item_id,'P625')ifnotcoordStatementsor#coordStatements==0thenerror(L10n.error.wikidataCoords,0)endlocalhasNoValue=(coordStatements[1].mainsnakand(coordStatements[1].mainsnak.snaktype=='novalue'orcoordStatements[1].mainsnak.snaktype=='somevalue'))ifhasNoValuethenerror(L10n.error.wikidataCoords,0)endlocalwdCoords=coordStatements[1]['mainsnak']['datavalue']['value']returntonumber(wdCoords['latitude']),tonumber(wdCoords['longitude'])end--[[Creates a polygon that approximates a circle@param {number} lat Latitude@param {number} long Longitude@param {number} radius Radius in metres@param {number} n Number of edges for the polygon@returns {table} sequence of {latitude, longitude} table sequences, where latitude and longitude are both numbers]]--functionutil.circleToPolygon(lat,long,radius,n)-- n is number of edges-- Based on https://github.com/gabzim/circle-to-polygon, ISC licencelocalfunctionoffset(cLat,cLon,distance,bearing)locallat1=math.rad(cLat)locallon1=math.rad(cLon)localdByR=distance/6378137-- distance divided by 6378137 (radius of the earth) wgs84locallat=math.asin(math.sin(lat1)*math.cos(dByR)+math.cos(lat1)*math.sin(dByR)*math.cos(bearing))locallon=lon1+math.atan2(math.sin(bearing)*math.sin(dByR)*math.cos(lat1),math.cos(dByR)-math.sin(lat1)*math.sin(lat))return{math.deg(lon),math.deg(lat)}endlocalcoordinates={};locali=0;whilei<ndotable.insert(coordinates,offset(lat,long,radius,(2*math.pi*i*-1)/n))i=i+1endtable.insert(coordinates,offset(lat,long,radius,0))returncoordinatesend--[[Get the number of key-value pairs in a table, which might not be a sequence.@param {table} t@returns {number} count of key-value pairs]]--functionutil.tableCount(t)localcount=0fork,vinpairs(t)docount=count+1endreturncountend--[[For a table where the values are all tables, returns either the util.tableCountof the subtables if they are all the same, or nil if they are not all the same.@param {table} t@returns {number|nil} count of key-value pairs of subtable, or nil if subtables have different counts]]--functionutil.subTablesCount(t)localcount=nilfork,vinpairs(t)doifcount==nilthencount=util.tableCount(v)elseifcount~=util.tableCount(v)thenreturnnilendendreturncountend--[[Splits a list into a table sequence. The items in the list may be separated bycommas, or by semicolons (if items may contain commas), or by "###" (if itemsmay contain semicolons).@param {string} listString@returns {table} sequence of list items]]--functionutil.tableFromList(listString)iftype(listString)~="string"orlistString==""thenreturnnilendlocalseparator=(mw.ustring.find(listString,"###",0,true)and"###")or(mw.ustring.find(listString,";",0,true)and";")or","localpattern="%s*"..separator.."%s*"returnmw.text.split(listString,pattern)end-- Boolean in outer scope indicating if Kartographer should be able to-- automatically calculate coordinates (see phab:T227402)localcoordsDerivedFromFeatures=false;--[[---------------------------------------------------------------------------- Make methods: These take in a table of arguments, and return either a string or a table to be used in the eventual output.----------------------------------------------------------------------------]]--localmake={}--[[Makes content to go inside the maplink or mapframe tag.@param {table} args@returns {string} tag content]]--functionmake.content(args)ifutil.getParameterValue(args,'raw')thencoordsDerivedFromFeatures=true-- Kartographer should be able to automatically calculate coords from raw geoJSONreturnutil.getParameterValue(args,'raw')endlocalcontent={}localargsExpanded={}fork,vinpairs(args)dolocalindex=string.match(k,'^[^0-9]+([0-9]*)$')ifindex~=nilthenlocalindexNumber=''ifindex~=''thenindexNumber=tonumber(index)elseindexNumber=1endifargsExpanded[indexNumber]==nilthenargsExpanded[indexNumber]={}endargsExpanded[indexNumber][string.gsub(k,index,'')]=vendendforcontentIndex,contentArgsinpairs(argsExpanded)dolocalargType=util.getParameterValue(contentArgs,"type")-- Kartographer automatically calculates coords if geolines/shapes are used (T227402)ifnotcoordsDerivedFromFeaturesthencoordsDerivedFromFeatures=(argType==L10n.str.lineorargType==L10n.str.shape)andtrueorfalseendifargType==L10n.str.namedthenlocalnamedCoords=util.getNamedCoords(util.getParameterValue(contentArgs,"from"))localtypeKey=type(L10n.para.type)=="table"andL10n.para.type[1]orL10n.para.typelocalcoordKey=type(L10n.para.coord)=="table"andL10n.para.coord[1]orL10n.para.coordlocaltitleKey=type(L10n.para.title)=="table"andL10n.para.title[1]orL10n.para.titlelocaldescKey=type(L10n.para.description)=="table"andL10n.para.description[1]orL10n.para.descriptionfor_,namedCoordinpairs(namedCoords)docontentArgs[typeKey]="point"contentArgs[coordKey]=namedCoord.coordcontentArgs[titleKey]=namedCoord.namecontentArgs[descKey]=namedCoord.descriptioncontent[#content+1]=make.contentJson(contentArgs)endelsecontent[#content+1]=make.contentJson(contentArgs)endend--Single item, no array neededif#content==1thenreturncontent[1]end--Multiple items get placed in a FeatureCollectionlocalcontentArray='[\n'..table.concat(content,',\n')..'\n]'returncontentArrayend--[[Make coordinates from the coord arg, or the id arg, or the current page'sWikidata item.@param {table} args@param {boolean} [plainOutput]@returns {Mixed} Either: {number, number} latitude, longitude if plainOutput is true; or {table} table sequence of longitude, then latitude (gives the required format for GeoJSON when encoded)]]--functionmake.coords(args,plainOutput)localcoords,lat,longlocalframe=mw.getCurrentFrame()ifutil.getParameterValue(args,'coord')thencoords=frame:preprocess(util.getParameterValue(args,'coord'))lat,long=util.parseCoords(coords)elselat,long=util.wikidataCoords(util.getParameterValue(args,'id')ormw.wikibase.getEntityIdForCurrentPage())endifplainOutputthenreturnlat,longendreturn{[0]=long,[1]=lat}end--[[Makes a table of coordinates that approximate a circle.@param {table} args@returns {table} sequence of {latitude, longitude} table sequences, where latitude and longitude are both numbers@throws {L10n.error.noCircleCoords} if centre coordinates are not specified@throws {L10n.error.noRadius} if radius is not specified@throws {L10n.error.negativeRadius} if radius is negative or zero@throws {L10n.error.negativeEdges} if edges is negative or zero]]--functionmake.circleCoords(args)locallat,long=make.coords(args,true)localradius=util.getParameterValue(args,'radius')ifnotradiusthenradius=util.getParameterValue(args,'radiusKm')andtonumber(util.getParameterValue(args,'radiusKm'))*1000ifnotradiusthenradius=util.getParameterValue(args,'radiusMi')andtonumber(util.getParameterValue(args,'radiusMi'))*1609.344ifnotradiusthenradius=util.getParameterValue(args,'radiusFt')andtonumber(util.getParameterValue(args,'radiusFt'))*0.3048endendendlocaledges=util.getParameterValue(args,'edges')orL10n.defaults.edgesifnotlatornotlongthenerror(L10n.error.noCircleCoords,0)elseifnotradiusthenerror(L10n.error.noRadius,0)elseiftonumber(radius)<=0thenerror(L10n.error.negativeRadius,0)elseiftonumber(edges)<=0thenerror(L10n.error.negativeEdges,0)endreturnutil.circleToPolygon(lat,long,radius,tonumber(edges))end--[[Makes JSON data for a feature@param contentArgs args for this feature. Keys must be the non-suffixed version of the parameter names, i.e. use type, stroke, fill,... rather than type3, stroke3, fill3,...@returns {string} JSON encoded data]]--functionmake.contentJson(contentArgs)localdata={}ifutil.getParameterValue(contentArgs,'type')==L10n.str.pointorutil.getParameterValue(contentArgs,'type')==L10n.str.circlethenlocalisCircle=util.getParameterValue(contentArgs,'type')==L10n.str.circledata.type="Feature"data.geometry={type=isCircleand"LineString"or"Point",coordinates=isCircleandmake.circleCoords(contentArgs)ormake.coords(contentArgs)}data.properties={title=util.getParameterValue(contentArgs,'title')ormw.getCurrentFrame():getParent():getTitle()}ifisCirclethen-- TODO: This is very similar to below, should be extracted into a functiondata.properties.stroke=util.getParameterValue(contentArgs,'strokeColor')orL10n.defaults.strokeColordata.properties["stroke-width"]=tonumber(util.getParameterValue(contentArgs,'strokeWidth'))orL10n.defaults.strokeWidthlocalstrokeOpacity=util.getParameterValue(contentArgs,'strokeOpacity')ifstrokeOpacitythendata.properties['stroke-opacity']=tonumber(strokeOpacity)endlocalfill=util.getParameterValue(contentArgs,'fill')iffillthendata.properties.fill=filllocalfillOpacity=util.getParameterValue(contentArgs,'fillOpacity')data.properties['fill-opacity']=fillOpacityandtonumber(fillOpacity)or0.6endelse-- is a pointlocalmarkerSymbol=util.getParameterValue(contentArgs,'marker')orL10n.defaults.marker-- allow blank to be explicitly specified, for overriding infoboxes or other templates with a default valueifmarkerSymbol~="blank"thendata.properties["marker-symbol"]=markerSymbolenddata.properties["marker-color"]=util.getParameterValue(contentArgs,'markerColor')orL10n.defaults.markerColordata.properties["marker-size"]=util.getParameterValue(contentArgs,'markerSize')orL10n.defaults.markerSizeendelsedata.type="ExternalData"ifutil.getParameterValue(contentArgs,'type')==L10n.str.dataorutil.getParameterValue(contentArgs,'from')thendata.service="page"elseifutil.getParameterValue(contentArgs,'type')==L10n.str.linethendata.service="geoline"elseifutil.getParameterValue(contentArgs,'type')==L10n.str.shapethendata.service="geoshape"elseifutil.getParameterValue(contentArgs,'type')==L10n.str.shapeInversethendata.service="geomask"endifutil.getParameterValue(contentArgs,'id')or(not(util.getParameterValue(contentArgs,'from'))andmw.wikibase.getEntityIdForCurrentPage())thendata.ids=util.getParameterValue(contentArgs,'id')ormw.wikibase.getEntityIdForCurrentPage()elsedata.title=util.getParameterValue(contentArgs,'from')enddata.properties={stroke=util.getParameterValue(contentArgs,'strokeColor')orL10n.defaults.strokeColor,["stroke-width"]=tonumber(util.getParameterValue(contentArgs,'strokeWidth'))orL10n.defaults.strokeWidth}localstrokeOpacity=util.getParameterValue(contentArgs,'strokeOpacity')ifstrokeOpacitythendata.properties['stroke-opacity']=tonumber(strokeOpacity)endlocalfill=util.getParameterValue(contentArgs,'fill')iffilland(data.service=="geoshape"ordata.service=="geomask")thendata.properties.fill=filllocalfillOpacity=util.getParameterValue(contentArgs,'fillOpacity')iffillOpacitythendata.properties['fill-opacity']=tonumber(fillOpacity)endendenddata.properties.title=util.getParameterValue(contentArgs,'title')ormw.title.getCurrentTitle().textifutil.getParameterValue(contentArgs,'description')thendata.properties.description=util.getParameterValue(contentArgs,'description')endreturnmw.text.jsonEncode(data)end--[[Makes attributes for the maplink or mapframe tag.@param {table} args@param {boolean} [isTitle] Tag is to be displayed in the title of page rather than inline@returns {table<string,string>} key-value pairs of attribute names and values]]--functionmake.tagAttribs(args,isTitle)localattribs={}ifutil.getParameterValue(args,'zoom')thenattribs.zoom=util.getParameterValue(args,'zoom')endifutil.isDeclined(util.getParameterValue(args,'icon'))thenattribs.class="no-icon"endifutil.getParameterValue(args,'type')==L10n.str.pointandnotcoordsDerivedFromFeaturesthenlocallat,long=make.coords(args,'plainOutput')attribs.latitude=tostring(lat)attribs.longitude=tostring(long)endifutil.isAffirmed(util.getParameterValue(args,'frame'))andnot(isTitle)thenattribs.width=util.getParameterValue(args,'frameWidth')orL10n.defaults.frameWidthattribs.height=util.getParameterValue(args,'frameHeight')orL10n.defaults.frameHeightifutil.getParameterValue(args,'frameCoordinates')thenlocalframeLat,frameLong=util.parseCoords(util.getParameterValue(args,'frameCoordinates'))attribs.latitude=frameLatattribs.longitude=frameLongelseifutil.getParameterValue(args,'frameLatitude')thenattribs.latitude=util.getParameterValue(args,'frameLatitude')endifutil.getParameterValue(args,'frameLongitude')thenattribs.longitude=util.getParameterValue(args,'frameLongitude')endendifnotattribs.latitudeandnotattribs.longitudeandnotcoordsDerivedFromFeaturesthenlocalsuccess,lat,long=pcall(util.wikidataCoords,util.getParameterValue(args,'id')ormw.wikibase.getEntityIdForCurrentPage())ifsuccessthenattribs.latitude=tostring(lat)attribs.longitude=tostring(long)endendifutil.getParameterValue(args,'frameAlign')thenattribs.align=util.getParameterValue(args,'frameAlign')endifutil.isAffirmed(util.getParameterValue(args,'plain'))thenattribs.frameless="1"elseattribs.text=util.getParameterValue(args,'text')orL10n.defaults.textendelseattribs.text=util.getParameterValue(args,'text')orL10n.defaults.textendreturnattribsend--[[Makes maplink wikitext that will be located in the top-right of the title of thepage (the same place where coords with |display=title are positioned).@param {table} args@param {string} tagContent Content for the maplink tag@returns {string}]]--functionmake.titleOutput(args,tagContent)localtitleTag=mw.text.tag('maplink',make.tagAttribs(args,true),tagContent)localspanAttribs={style="font-size: small;",id="mapframe-coordinates"}localindicatorContent=mw.text.tag('span',spanAttribs,titleTag)returnmw.getCurrentFrame():extensionTag{name="indicator",content=indicatorContent,args={name="zzz-mapframe"--zzz: show as last indicator}}end--[[Makes maplink or mapframe wikitext that will be located inline.@param {table} args@param {string} tagContent Content for the maplink tag@returns {string}]]--functionmake.inlineOutput(args,tagContent)localtagName='maplink'ifutil.getParameterValue(args,'frame')thentagName='mapframe'endreturnmw.text.tag(tagName,make.tagAttribs(args),tagContent)end--[[Makes the HTML required for the swicther to work, including the templatestylestag.@param {table} params table sequence of {map, label} tables @param {string} params{}.map Wikitext for mapframe map @param {string} params{}.label Label text for swicther option@param {table} options @param {string} options.alignment "left" or "center" or "right" @param {boolean} options.isThumbnail Display in a thumbnail @param {string} options.width Width of frame, e.g. "200" @param {string} [options.caption] Caption wikitext for thumnail@retruns {string} swicther HTML]]--functionmake.switcherHtml(params,options)options=optionsor{}localframe=mw.getCurrentFrame()localstyles=frame:extensionTag{name="templatestyles",args={src="Template:Maplink/styles-multi.css"}}localcontainer=mw.html.create("div"):addClass("switcher-container"):addClass("mapframe-multi-container")ifoptions.alignment=="left"oroptions.alignment=="right"thencontainer:addClass("float"..options.alignment)else-- alignment is "center"container:addClass("center")endfori=1,#paramsdocontainer:tag("div"):wikitext(params[i].map):tag("span"):addClass("switcher-label"):css("display","none"):wikitext(mw.text.trim(params[i].label))endifnotoptions.isThumbnailthenreturnstyles..tostring(container)endlocalclasslist=container:getAttr("class")classlist=mw.ustring.gsub(classlist,"%a*"..options.alignment,"")container:attr("class",classlist)localouterCountainer=mw.html.create("div"):addClass("mapframe-multi-outer-container"):addClass("mw-kartographer-container"):addClass("thumb")ifoptions.alignment=="left"oroptions.alignment=="right"thenouterCountainer:addClass("t"..options.alignment)else-- alignment is "center"outerCountainer:addClass("tnone"):addClass("center")endouterCountainer:tag("div"):addClass("thumbinner"):css("width",options.width.."px"):node(container):node(options.captionandmw.html.create("div"):addClass("thumbcaption"):wikitext(options.caption))returnstyles..tostring(outerCountainer)end--[[Makes the HTML required for an overlay map to worktag.@param {string} overlayMap wikitext for the overlay map@param {string} baseMap wikitext for the base map@param {table} options various styling/display options @param {string} options.align "left" or "center" or "right" @param {string|number} options.width Width of the base map, e.g. "300" @param {string|number} options.width Height of the base map, e.g. "200" @param {string} options.border Border style for the overlayed map, e.g. "1px solid white" @param {string} options.horizontalAlignment Horizontal alignment for overlay map, "left" or "right" @param {string|number} options.horizontalOffset Horizontal offset in pixels from the alignment edge, e.g "10" @param {string} options.verticalAlignment Vertical alignment for overlay map, "top" or "bottom" @param {string|number} options.verticalOffset Vertical offset in pixels from the alignment edge, e.g. is "10" @param {boolean} options.isThumbnail Display in a thumbnail @param {string} [options.caption] Caption wikitext for thumnail@retruns {string} HTML for basemap with overlay]]--functionmake.overlayHtml(overlayMap,baseMap,options)options=optionsor{}localcontainerFloatClass="float"..(options.alignor"none")ifoptions.align=="center"thencontainerFloatClass="center"endlocalcontainerStyle={position="relative",width=options.width.."px",height=options.height.."px",overflow="hidden"-- mobile/minerva tends to add scrollbars for a couple of pixels}ifoptions.align=="center"thencontainerStyle["margin-left"]="auto"containerStyle["margin-right"]="auto"endlocalcontainer=mw.html.create("div"):addClass("mapframe-withOverlay-container"):addClass(containerFloatClass):addClass("noresize"):css(containerStyle)localoverlayStyle={position="absolute",["z-index"]="1",border=options.borderor"1px solid white"}ifoptions.horizontalAlignment=="right"thenoverlayStyle.right=options.horizontalOffset.."px"elseoverlayStyle.left=options.horizontalOffset.."px"endifoptions.verticalAlignment=="bottom"thenoverlayStyle.bottom=options.verticalOffset.."px"elseoverlayStyle.top=options.verticalOffset.."px"endlocaloverlayDiv=mw.html.create("div"):css(overlayStyle):wikitext(overlayMap)container:node(overlayDiv):wikitext(baseMap)ifnotoptions.isThumbnailthenreturntostring(container)endlocalclasslist=container:getAttr("class")classlist=mw.ustring.gsub(classlist,"%a*"..options.align,"")container:attr("class",classlist)localouterCountainer=mw.html.create("div"):addClass("mapframe-withOverlay-outerContainer"):addClass("mw-kartographer-container"):addClass("thumb")ifoptions.align=="left"oroptions.align=="right"thenouterCountainer:addClass("t"..options.align)else-- alignment is "center"outerCountainer:addClass("tnone"):addClass("center")endouterCountainer:tag("div"):addClass("thumbinner"):css("width",options.width.."px"):node(container):node(options.captionandmw.html.create("div"):addClass("thumbcaption"):wikitext(options.caption))returntostring(outerCountainer)end--[[---------------------------------------------------------------------------- Package to be exported, i.e. methods which will available to templates and other modules.----------------------------------------------------------------------------]]--localp={}-- Entry point for templatesfunctionp.main(frame)localparent=frame.getParent(frame)-- Check for overlay optionlocaloverlay=util.getParameterValue(parent.args,'overlay')localhasOverlay=overlayandmw.text.trim(overlay)~=""-- Check for switch optionlocalswitch=util.getParameterValue(parent.args,'switch')localisMulti=switchandmw.text.trim(switch)~=""-- Create output by choosing method to suit optionslocaloutputifhasOverlaythenoutput=p.withOverlay(parent.args)elseifisMultithenoutput=p.multi(parent.args)elseoutput=p._main(parent.args)end-- Preprocess output before returning itreturnframe:preprocess(output)end-- Entry points for modulesfunctionp._main(_args)localargs=util.trimArgs(_args)localtagContent=make.content(args)localdisplay=mw.text.split(util.getParameterValue(args,'display')orL10n.defaults.display,'%s*'..L10n.str.dsep..'%s*')localdisplayInTitle=display[1]==L10n.str.titleordisplay[2]==L10n.str.titlelocaldisplayInline=display[1]==L10n.str.inlineordisplay[2]==L10n.str.inlinelocaloutputifdisplayInTitleanddisplayInlinethenoutput=make.titleOutput(args,tagContent)..make.inlineOutput(args,tagContent)elseifdisplayInTitlethenoutput=make.titleOutput(args,tagContent)elseifdisplayInlinethenoutput=make.inlineOutput(args,tagContent)elseerror(L10n.error.badDisplayPara)endreturnoutputendfunctionp.multi(_args)localargs=util.trimArgs(_args)ifnotargs[L10n.para.switch]thenerror(L10n.error.noSwitchPara,0)endlocalswitchParamValue=util.getParameterValue(args,'switch')localswitchLabels=util.tableFromList(switchParamValue)if#switchLabels==1thenerror(L10n.error.oneSwitchLabel,0)endlocalmapframeArgs={}localswitchParams={}forname,valinpairs(args)do-- Copy to mapframeArgs, if not the switch labels or a switch parameterifval~=switchParamValueandnotstring.match(val,"^"..L10n.str.switch..":")thenmapframeArgs[name]=valend-- Check if this is a param to switch. If so, store the name and switch-- values in switchParams table.localswitchList=string.match(val,"^"..L10n.str.switch..":(.+)")ifswitchList~=nilthenlocalvalues=util.tableFromList(switchList)if#values==1thenerror(string.format(L10n.error.oneSwitchValue,name),0)endswitchParams[name]=valuesendendifutil.tableCount(switchParams)==0thenerror(L10n.error.noSwitchLists,0)endlocalswitchCount=util.subTablesCount(switchParams)ifnotswitchCountthenerror(L10n.error.switchMismatches,0)elseifswitchCount>#switchLabelsthenerror(string.format(L10n.error.fewerSwitchLabels,switchCount,#switchLabels),0)end-- Ensure a plain frame will be used (thumbnail will be built by the-- make.switcherHtml function if required, so that switcher options are-- inside the thumnail)mapframeArgs.plain="yes"localswitcher={}fori=1,switchCountdolocallabel=switchLabels[i]forname,valuesinpairs(switchParams)domapframeArgs[name]=values[i]endtable.insert(switcher,{map=p._main(mapframeArgs),label="Show "..label})endreturnmake.switcherHtml(switcher,{alignment=args["frame-align"]or"right",isThumbnail=(args.frameandnotargs.plain)andtrueorfalse,width=args["frame-width"]orL10n.defaults.frameWidth,caption=args.text})endfunctionp.withOverlay(_args)-- Get and trim wikitext for overlay maplocaloverlayMap=_args.overlayiftype(overlayMap)=='string'thenoverlayMap=overlayMap:match('^%s*(.-)%s*$')endlocalisThumbnail=(util.getParameterValue(_args,"frame")andnotutil.getParameterValue(_args,"plain"))andtrueorfalse-- Get base map using the _main function, as a plain maplocalargs=util.trimArgs(_args)args.plain="yes"localbasemap=p._main(args)-- Extract overlay options from argslocaloverlayOptions={width=util.getParameterValue(args,"frameWidth")orL10n.defaults.frameWidth,height=util.getParameterValue(args,"frameHeight")orL10n.defaults.frameHeight,align=util.getParameterValue(args,"frameAlign")orL10n.defaults.frameAlign,border=util.getParameterValue(args,"overlayBorder")orL10n.defaults.overlayBorder,horizontalAlignment=util.getParameterValue(args,"overlayHorizontalAlignment")orL10n.defaults.overlayHorizontalAlignment,horizontalOffset=util.getParameterValue(args,"overlayHorizontalOffset")orL10n.defaults.overlayHorizontalOffset,verticalAlignment=util.getParameterValue(args,"overlayVerticalAlignment")orL10n.defaults.overlayVerticalAlignment,verticalOffset=util.getParameterValue(args,"overlayVerticalOffset")orL10n.defaults.overlayVerticalOffset,isThumbnail=isThumbnail,caption=util.getParameterValue(args,"text")orL10n.defaults.text}-- Make the HTML for the overlaying mapsreturnmake.overlayHtml(overlayMap,basemap,overlayOptions)endreturnp