Jump to content
Wikipedia The Free Encyclopedia

Module:Mapframe/sandbox

From Wikipedia, the free encyclopedia
This is the module sandbox page for Module:Mapframe (diff).
See also the companion subpage for test cases.
Module documentation[view] [edit] [history] [purge]
This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected.
Warning This Lua module is used on approximately 1,200,000 pages, or roughly 2% of all pages .
To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them.
This module depends on the following other modules:

On English Wikipedia, this module is called by {{Maplink}}, see that template's documentation for usage instructions.

Usage

[edit ]
Standard usage
Just use {{Maplink }}, which passes its parameters to this module's main function as default.
If a page has a rendering time by Lua of between 5 seconds and 10 seconds using {{Maplink }} the use of the direct module call by syntax like: {{#tag:mapframe|[raw GeoJSON]|frameless=[1 for frame]|align=[left/right/center]|text=[caption]|width=[in px]|height=[in px]|latitude=[decimal degrees]|longitude=[decimal degrees]|zoom=[zoom factor]}} saves Lua over-head. An example of this substitution is at https://en.wikipedia.org/w/index.php?diff=970846012. Such code minimises the chances of hitting the ten second Lua timeout if the back-end servers are busy.
From another module
  1. Import this module, e.g. local mf = require('Module:Mapframe')
  2. Pass a table of parameter names/values to the _main function. See {{Maplink }} documentation for parameter names and descriptions. E.g. local mapframe = mf._main(parameters)
  3. Preprocess _main's output before returning it, e.g. return frame:preprocess(mapframe)

Set up on another wiki

[edit ]
  1. Create template and module:
    • Import this module and its template to that wiki (or copy the code over, giving attribution in the edit summary). Optionally, give them a name that makes sense in that wiki's language
    • On Wikidata, add them to the items Module:Mapframe (Q52554979) and Template:Maplink (Q27882107)
  2. Localise the module
    • Edit the top bits of the module, between the comments -- ##### Localisation (L10n) settings ##### and -- #### End of L10n settings ####, replacing values between " " symbols with local values (when necessary)
  3. Add documentation
    • to the template (e.g. by translating Template:Maplink/doc, adjusting as necessary per any localisations made in the previous step)
    • to the module (please transfer/translate these instructions so that wikimedians who read your wiki but not the English Wikipedia can also set up the module and template on another wiki).
The above documentation is transcluded from Module:Mapframe/doc. (edit | history)
Editors can experiment in this module's sandbox (edit | diff) and testcases (edit) pages.
Add categories to the /doc subpage. Subpages of this module.
 -- 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 dependencies
 localtranscluder-- 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 template
 L10n.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 from
 L10n.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 messages
 L10n.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 used
 oneSwitchValue="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 strings
 L10n.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 parameter
 line="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 Commons
 point="point",-- single point feature (coordinates)
 circle="circle",-- circular area around a point
 named="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 parameters
 affirmedWords=' '..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 parameters
 L10n.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 circle
 overlayBorder="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) and
 optionally a suffix, for parameters that can be suffixed (e.g. type2 is type
 with 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'then
 returnargs[L10n.para[param_id]..suffix]
 end
 for_i,paramAliasinipairs(L10n.para[param_id])do
 ifargs[paramAlias..suffix]then
 returnargs[paramAlias..suffix]
 end
 end
 returnnil
 end

 --[[
 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)do
 iftype(key)=='string'andtype(val)=='string'then
 val=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 content
 ifutil.matchesParam('text',key)orutil.matchesParam('description',key,key:gsub('^%D+(%d+)$','%1'))then
 cleanArgs[key]=val
 else
 cleanArgs[key]=val:gsub('%c',' ')
 end
 end
 else
 cleanArgs[key]=val
 end
 end
 returncleanArgs
 end

 --[[
 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'then
 for_,vinpairs(param)do
 if(v..suffix)==namethenreturntrueend
 end
 returnfalse
 end
 return((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)thenreturnfalseend
 returnstring.find(L10n.str.affirmedWords,' '..val..' ',1,true)andtrueorfalse
 end

 --[[
 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)thenreturnfalseend
 returnstring.find(L10n.str.declinedWords,' '..val..' ',1,true)andtrueorfalse
 end

 --[[
 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')
 ifnotinputTitlethen
 returnfalse
 end

 -- 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 called
 ifnotL10n.template.titlesthen
 L10n.template.titles={}
 for_,vinpairs(L10n.template.coord)do
 table.insert(L10n.template.titles,mw.title.new(v,'Template'))
 end
 end

 for_,templateTitleinpairs(L10n.template.titles)do
 ifmw.title.equals(inputTitle,templateTitle)then
 returntrue
 end
 end

 returnfalse
 end

 --[[
 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={}
 fortemplateintemplatesdo
 localtemplateName=mw.ustring.match(template,'{{([^}|]+)')
 localnameParam=mw.ustring.match(template,"|%s*name%s*=%s*[^}|]+")
 ifutil.isCoordTemplateOrWrapper(templateName)then
 ifnameParamthentable.insert(output,template)end
 elseifmw.ustring.find(mw.ustring.sub(template,2),"{{")then
 localsubOutput=util.extractCoordTemplates(mw.ustring.sub(template,2))
 for_,tinpairs(subOutput)do
 table.insert(output,t)
 end
 end
 end
 -- ensure coords are not using title display
 fork,vinpairs(output)do
 output[k]=mw.ustring.gsub(v,"|%s*display%s*=[^|}]+","|display=inline")
 end
 returnoutput
 end

 --[[
 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 coordinates
 transcluder=require("Module:Transcluder")
 end
 localparts=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)end
 localframe=mw.getCurrentFrame()
 localsep="________"
 localexpandedContent=frame:preprocess(table.concat(coordTemplates,sep))
 localexpandedTemplates=mw.text.split(expandedContent,sep)
 localnamedCoords={}
 for_,expandedTemplateinpairs(expandedTemplates)do
 localcoord=mw.ustring.match(expandedTemplate,"<span class=\"geo%-dec\".->(.-)</span>")
 ifcoordthen
 localname=(
 -- 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 template
 mw.ustring.match(expandedTemplate,"<span class=\"fn org\">(.-)</span>")or
 -- default to the coordinates if the name can't be retrieved
 coord
 )
 localdescription=name~=coordandcoord
 localcoord=mw.ustring.gsub(coord,"[° ]","_")
 table.insert(namedCoords,{coord=coord,name=name,description=description})
 end
 end
 if#namedCoords==0thenerror(string.format(L10n.error.noNamedCoords,pageorname),0)end
 returnnamedCoords
 end

 --[[
 Parse coordinate values from the params passed in a GeoHack url (such as
 //tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=1_2_N_3_4_W_ or
 //tools.wmflabs.org/geohack/geohack.php?pagename=Example&params=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)
 localcoordsPatt
 ifmw.ustring.find(coords,"params=",1,true)then
 -- prevent false matches from page name, e.g. ?pagename=Lorem_S._Ipsum
 coordsPatt='params=([_%.%d]+[NS][_%.%d]+[EW])'
 else
 -- not actually a geohack url, just the same format
 coordsPatt='[_%.%d]+[NS][_%.%d]+[EW]'
 end
 localparts=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 format
 locallat_s=lat_mandtonumber(parts[3])-- nil if coords are either in decimal format or degrees and minutes only
 locallat=lat_d+(lat_mor0)/60+(lat_sor0)/3600
 ifparts[#parts/2]=='S'then
 lat=lat*-1
 end

 locallong_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 format
 locallong_s=long_mandtonumber(parts[3+#parts/2])-- nil if coords are either in decimal format or degrees and minutes only
 locallong=long_d+(long_mor0)/60+(long_sor0)/3600
 ifparts[#parts]=='W'then
 long=long*-1
 end

 returnlat,long
 end

 --[[
 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))then
 error(L10n.error.noCoords,0)
 end
 localcoordStatements=mw.wikibase.getBestStatements(item_id,'P625')
 ifnotcoordStatementsor#coordStatements==0then
 error(L10n.error.wikidataCoords,0)
 end
 localhasNoValue=(coordStatements[1].mainsnakand(coordStatements[1].mainsnak.snaktype=='novalue'orcoordStatements[1].mainsnak.snaktype=='somevalue'))
 ifhasNoValuethen
 error(L10n.error.wikidataCoords,0)
 end
 localwdCoords=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 licence

 localfunctionoffset(cLat,cLon,distance,bearing)
 locallat1=math.rad(cLat)
 locallon1=math.rad(cLon)
 localdByR=distance/6378137-- distance divided by 6378137 (radius of the earth) wgs84
 locallat=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)}
 end

 localcoordinates={};
 locali=0;
 whilei<ndo
 table.insert(coordinates,
 offset(lat,long,radius,(2*math.pi*i*-1)/n)
 )
 i=i+1
 end
 table.insert(coordinates,offset(lat,long,radius,0))
 returncoordinates
 end


 --[[
 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=0
 fork,vinpairs(t)do
 count=count+1
 end
 returncount
 end

 --[[
 For a table where the values are all tables, returns either the util.tableCount
 of 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=nil
 fork,vinpairs(t)do
 ifcount==nilthen
 count=util.tableCount(v)
 elseifcount~=util.tableCount(v)then
 returnnil
 end
 end
 returncount
 end

 --[[
 Splits a list into a table sequence. The items in the list may be separated by
 commas, or by semicolons (if items may contain commas), or by "###" (if items
 may contain semicolons).
 @param {string} listString
 @returns {table} sequence of list items
 ]]--
 functionutil.tableFromList(listString)
 iftype(listString)~="string"orlistString==""thenreturnnilend
 localseparator=(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')then
 coordsDerivedFromFeatures=true-- Kartographer should be able to automatically calculate coords from raw geoJSON
 returnutil.getParameterValue(args,'raw')
 end

 localcontent={}

 localargsExpanded={}
 fork,vinpairs(args)do
 localindex=string.match(k,'^[^0-9]+([0-9]*)$')
 ifindex~=nilthen
 localindexNumber=''
 ifindex~=''then
 indexNumber=tonumber(index)
 else
 indexNumber=1
 end

 ifargsExpanded[indexNumber]==nilthen
 argsExpanded[indexNumber]={}
 end
 argsExpanded[indexNumber][string.gsub(k,index,'')]=v
 end
 end

 forcontentIndex,contentArgsinpairs(argsExpanded)do
 localargType=util.getParameterValue(contentArgs,"type")
 -- Kartographer automatically calculates coords if geolines/shapes are used (T227402)
 ifnotcoordsDerivedFromFeaturesthen
 coordsDerivedFromFeatures=(argType==L10n.str.lineorargType==L10n.str.shape)andtrueorfalse
 end
 ifargType==L10n.str.namedthen
 localnamedCoords=util.getNamedCoords(util.getParameterValue(contentArgs,"from"))
 localtypeKey=type(L10n.para.type)=="table"andL10n.para.type[1]orL10n.para.type
 localcoordKey=type(L10n.para.coord)=="table"andL10n.para.coord[1]orL10n.para.coord
 localtitleKey=type(L10n.para.title)=="table"andL10n.para.title[1]orL10n.para.title
 localdescKey=type(L10n.para.description)=="table"andL10n.para.description[1]orL10n.para.description
 for_,namedCoordinpairs(namedCoords)do
 contentArgs[typeKey]="point"
 contentArgs[coordKey]=namedCoord.coord
 contentArgs[titleKey]=namedCoord.name
 contentArgs[descKey]=namedCoord.description
 content[#content+1]=make.contentJson(contentArgs)
 end
 else
 content[#content+1]=make.contentJson(contentArgs)
 end
 end

 --Single item, no array needed
 if#content==1thenreturncontent[1]end

 --Multiple items get placed in a FeatureCollection
 localcontentArray='[\n'..table.concat(content,',\n')..'\n]'
 returncontentArray
 end

 --[[
 Make coordinates from the coord arg, or the id arg, or the current page's
 Wikidata 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,long
 localframe=mw.getCurrentFrame()
 ifutil.getParameterValue(args,'coord')then
 coords=frame:preprocess(util.getParameterValue(args,'coord'))
 lat,long=util.parseCoords(coords)
 else
 lat,long=util.wikidataCoords(util.getParameterValue(args,'id')ormw.wikibase.getEntityIdForCurrentPage())
 end
 ifplainOutputthen
 returnlat,long
 end
 return{[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')
 ifnotradiusthen
 radius=util.getParameterValue(args,'radiusKm')andtonumber(util.getParameterValue(args,'radiusKm'))*1000
 ifnotradiusthen
 radius=util.getParameterValue(args,'radiusMi')andtonumber(util.getParameterValue(args,'radiusMi'))*1609.344
 ifnotradiusthen
 radius=util.getParameterValue(args,'radiusFt')andtonumber(util.getParameterValue(args,'radiusFt'))*0.3048
 end
 end
 end
 localedges=util.getParameterValue(args,'edges')orL10n.defaults.edges
 ifnotlatornotlongthen
 error(L10n.error.noCircleCoords,0)
 elseifnotradiusthen
 error(L10n.error.noRadius,0)
 elseiftonumber(radius)<=0then
 error(L10n.error.negativeRadius,0)
 elseiftonumber(edges)<=0then
 error(L10n.error.negativeEdges,0)
 end
 returnutil.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.circlethen
 localisCircle=util.getParameterValue(contentArgs,'type')==L10n.str.circle
 data.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 function
 data.properties.stroke=util.getParameterValue(contentArgs,'strokeColor')orL10n.defaults.strokeColor
 data.properties["stroke-width"]=tonumber(util.getParameterValue(contentArgs,'strokeWidth'))orL10n.defaults.strokeWidth
 localstrokeOpacity=util.getParameterValue(contentArgs,'strokeOpacity')
 ifstrokeOpacitythen
 data.properties['stroke-opacity']=tonumber(strokeOpacity)
 end
 localfill=util.getParameterValue(contentArgs,'fill')
 iffillthen
 data.properties.fill=fill
 localfillOpacity=util.getParameterValue(contentArgs,'fillOpacity')
 data.properties['fill-opacity']=fillOpacityandtonumber(fillOpacity)or0.6
 end
 else-- is a point
 localmarkerSymbol=util.getParameterValue(contentArgs,'marker')orL10n.defaults.marker
 -- allow blank to be explicitly specified, for overriding infoboxes or other templates with a default value
 ifmarkerSymbol~="blank"then
 data.properties["marker-symbol"]=markerSymbol
 end
 data.properties["marker-color"]=util.getParameterValue(contentArgs,'markerColor')orL10n.defaults.markerColor
 data.properties["marker-size"]=util.getParameterValue(contentArgs,'markerSize')orL10n.defaults.markerSize
 end
 else
 data.type="ExternalData"

 ifutil.getParameterValue(contentArgs,'type')==L10n.str.dataorutil.getParameterValue(contentArgs,'from')then
 data.service="page"
 elseifutil.getParameterValue(contentArgs,'type')==L10n.str.linethen
 data.service="geoline"
 elseifutil.getParameterValue(contentArgs,'type')==L10n.str.shapethen
 data.service="geoshape"
 elseifutil.getParameterValue(contentArgs,'type')==L10n.str.shapeInversethen
 data.service="geomask"
 end

 ifutil.getParameterValue(contentArgs,'id')or(not(util.getParameterValue(contentArgs,'from'))andmw.wikibase.getEntityIdForCurrentPage())then
 data.ids=util.getParameterValue(contentArgs,'id')ormw.wikibase.getEntityIdForCurrentPage()
 else
 data.title=util.getParameterValue(contentArgs,'from')
 end

 data.properties={
 stroke=util.getParameterValue(contentArgs,'strokeColor')orL10n.defaults.strokeColor,
 ["stroke-width"]=tonumber(util.getParameterValue(contentArgs,'strokeWidth'))orL10n.defaults.strokeWidth
 }
 localstrokeOpacity=util.getParameterValue(contentArgs,'strokeOpacity')
 ifstrokeOpacitythen
 data.properties['stroke-opacity']=tonumber(strokeOpacity)
 end
 localfill=util.getParameterValue(contentArgs,'fill')
 iffilland(data.service=="geoshape"ordata.service=="geomask")then
 data.properties.fill=fill
 localfillOpacity=util.getParameterValue(contentArgs,'fillOpacity')
 iffillOpacitythen
 data.properties['fill-opacity']=tonumber(fillOpacity)
 end
 end
 end

 data.properties.title=util.getParameterValue(contentArgs,'title')ormw.title.getCurrentTitle().text
 ifutil.getParameterValue(contentArgs,'description')then
 data.properties.description=util.getParameterValue(contentArgs,'description')
 end

 returnmw.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')then
 attribs.zoom=util.getParameterValue(args,'zoom')
 end
 ifutil.isDeclined(util.getParameterValue(args,'icon'))then
 attribs.class="no-icon"
 end
 ifutil.getParameterValue(args,'type')==L10n.str.pointandnotcoordsDerivedFromFeaturesthen
 locallat,long=make.coords(args,'plainOutput')
 attribs.latitude=tostring(lat)
 attribs.longitude=tostring(long)
 end
 ifutil.isAffirmed(util.getParameterValue(args,'frame'))andnot(isTitle)then
 attribs.width=util.getParameterValue(args,'frameWidth')orL10n.defaults.frameWidth
 attribs.height=util.getParameterValue(args,'frameHeight')orL10n.defaults.frameHeight
 ifutil.getParameterValue(args,'frameCoordinates')then
 localframeLat,frameLong=util.parseCoords(util.getParameterValue(args,'frameCoordinates'))
 attribs.latitude=frameLat
 attribs.longitude=frameLong
 else
 ifutil.getParameterValue(args,'frameLatitude')then
 attribs.latitude=util.getParameterValue(args,'frameLatitude')
 end
 ifutil.getParameterValue(args,'frameLongitude')then
 attribs.longitude=util.getParameterValue(args,'frameLongitude')
 end
 end
 ifnotattribs.latitudeandnotattribs.longitudeandnotcoordsDerivedFromFeaturesthen
 localsuccess,lat,long=pcall(util.wikidataCoords,util.getParameterValue(args,'id')ormw.wikibase.getEntityIdForCurrentPage())
 ifsuccessthen
 attribs.latitude=tostring(lat)
 attribs.longitude=tostring(long)
 end
 end
 ifutil.getParameterValue(args,'frameAlign')then
 attribs.align=util.getParameterValue(args,'frameAlign')
 end
 ifutil.isAffirmed(util.getParameterValue(args,'plain'))then
 attribs.frameless="1"
 else
 attribs.text=util.getParameterValue(args,'text')orL10n.defaults.text
 end
 else
 attribs.text=util.getParameterValue(args,'text')orL10n.defaults.text
 end
 returnattribs
 end

 --[[
 Makes maplink wikitext that will be located in the top-right of the title of the
 page (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')then
 tagName='mapframe'
 end

 returnmw.text.tag(tagName,make.tagAttribs(args),tagContent)
 end


 --[[
 Makes the HTML required for the swicther to work, including the templatestyles
 tag.
 @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"then
 container:addClass("float"..options.alignment)
 else-- alignment is "center"
 container:addClass("center")
 end
 fori=1,#paramsdo
 container
 :tag("div")
 :wikitext(params[i].map)
 :tag("span")
 :addClass("switcher-label")
 :css("display","none")
 :wikitext(mw.text.trim(params[i].label))
 end
 ifnotoptions.isThumbnailthen
 returnstyles..tostring(container)
 end
 localclasslist=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"then
 outerCountainer:addClass("t"..options.alignment)
 else-- alignment is "center"
 outerCountainer
 :addClass("tnone")
 :addClass("center")
 end
 outerCountainer
 :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 work
 tag.
 @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"then
 containerFloatClass="center"
 end
 localcontainerStyle={
 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"then
 containerStyle["margin-left"]="auto"
 containerStyle["margin-right"]="auto"
 end
 localcontainer=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"then
 overlayStyle.right=options.horizontalOffset.."px"
 else
 overlayStyle.left=options.horizontalOffset.."px"
 end
 ifoptions.verticalAlignment=="bottom"then
 overlayStyle.bottom=options.verticalOffset.."px"
 else
 overlayStyle.top=options.verticalOffset.."px"
 end
 localoverlayDiv=mw.html.create("div")
 :css(overlayStyle)
 :wikitext(overlayMap)

 container
 :node(overlayDiv)
 :wikitext(baseMap)

 ifnotoptions.isThumbnailthen
 returntostring(container)
 end
 localclasslist=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"then
 outerCountainer:addClass("t"..options.align)
 else-- alignment is "center"
 outerCountainer
 :addClass("tnone")
 :addClass("center")
 end
 outerCountainer
 :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 templates
 functionp.main(frame)
 localparent=frame.getParent(frame)
 -- Check for overlay option
 localoverlay=util.getParameterValue(parent.args,'overlay')
 localhasOverlay=overlayandmw.text.trim(overlay)~=""
 -- Check for switch option
 localswitch=util.getParameterValue(parent.args,'switch')
 localisMulti=switchandmw.text.trim(switch)~=""
 -- Create output by choosing method to suit options
 localoutput
 ifhasOverlaythen
 output=p.withOverlay(parent.args)
 elseifisMultithen
 output=p.multi(parent.args)
 else
 output=p._main(parent.args)
 end
 -- Preprocess output before returning it
 returnframe:preprocess(output)
 end

 -- Entry points for modules
 functionp._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.title
 localdisplayInline=display[1]==L10n.str.inlineordisplay[2]==L10n.str.inline

 localoutput
 ifdisplayInTitleanddisplayInlinethen
 output=make.titleOutput(args,tagContent)..make.inlineOutput(args,tagContent)
 elseifdisplayInTitlethen
 output=make.titleOutput(args,tagContent)
 elseifdisplayInlinethen
 output=make.inlineOutput(args,tagContent)
 else
 error(L10n.error.badDisplayPara)
 end

 returnoutput
 end

 functionp.multi(_args)
 localargs=util.trimArgs(_args)
 ifnotargs[L10n.para.switch]thenerror(L10n.error.noSwitchPara,0)end
 localswitchParamValue=util.getParameterValue(args,'switch')
 localswitchLabels=util.tableFromList(switchParamValue)
 if#switchLabels==1thenerror(L10n.error.oneSwitchLabel,0)end

 localmapframeArgs={}
 localswitchParams={}
 forname,valinpairs(args)do
 -- Copy to mapframeArgs, if not the switch labels or a switch parameter
 ifval~=switchParamValueandnotstring.match(val,"^"..L10n.str.switch..":")then
 mapframeArgs[name]=val
 end
 -- 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~=nilthen
 localvalues=util.tableFromList(switchList)
 if#values==1then
 error(string.format(L10n.error.oneSwitchValue,name),0)
 end
 switchParams[name]=values
 end
 end
 ifutil.tableCount(switchParams)==0then
 error(L10n.error.noSwitchLists,0)
 end
 localswitchCount=util.subTablesCount(switchParams)
 ifnotswitchCountthen
 error(L10n.error.switchMismatches,0)
 elseifswitchCount>#switchLabelsthen
 error(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,switchCountdo
 locallabel=switchLabels[i]
 forname,valuesinpairs(switchParams)do
 mapframeArgs[name]=values[i]
 end
 table.insert(switcher,{
 map=p._main(mapframeArgs),
 label="Show "..label
 })
 end
 returnmake.switcherHtml(switcher,{
 alignment=args["frame-align"]or"right",
 isThumbnail=(args.frameandnotargs.plain)andtrueorfalse,
 width=args["frame-width"]orL10n.defaults.frameWidth,
 caption=args.text
 })
 end

 functionp.withOverlay(_args)
 -- Get and trim wikitext for overlay map
 localoverlayMap=_args.overlay
 iftype(overlayMap)=='string'then
 overlayMap=overlayMap:match('^%s*(.-)%s*$')
 end
 localisThumbnail=(util.getParameterValue(_args,"frame")andnotutil.getParameterValue(_args,"plain"))andtrueorfalse
 -- Get base map using the _main function, as a plain map
 localargs=util.trimArgs(_args)
 args.plain="yes"
 localbasemap=p._main(args)
 -- Extract overlay options from args
 localoverlayOptions={
 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 maps
 returnmake.overlayHtml(overlayMap,basemap,overlayOptions)
 end

 returnp

AltStyle によって変換されたページ (->オリジナル) /