Modul:Mapframe

Documentation icon Moduldokumentation

Modulet laver et maplink- eller mapframe-tag med indhold specificeret i den kaldende skabelon og/eller fra Wikidata.

På dansk Wikipedia anvendes dette modul af skabelonerne {{kortlink}} og {{Infoboks mapframe}}. Se disse for vejledning til brug for kort i artikler.

Brug

{{#invoke:Mapframe|main}}

-- Note: Originally written on English Wikipedia at https://www.search.com.vn/wiki/en/Module:Mapframe--[[---------------------------------------------------------------------------- ##### Localisation (L10n) settings ##### Replace values in quotes ("") with localised values----------------------------------------------------------------------------]]--local L10n = {}-- Modue dependencieslocal transcluder -- 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' and L10n.para.coord[1] or L10n.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' and L10n.para.switch[1] or L10n.para.switch ) .. "=",oneSwitchLabel    = "Found only one label in |" .. ( type(L10n.para.switch)== 'table' and L10n.para.switch[1] or L10n.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' and L10n.para.switch[1] or L10n.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 paramterline= "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= "Kort",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----------------------------------------------------------------------------]]--local util = {}--[[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]]--function util.getParameterValue(args, param_id, suffix)suffix = suffix or ''if type( L10n.para[param_id] ) ~= 'table' thenreturn args[L10n.para[param_id]..suffix]endfor _i, paramAlias in ipairs(L10n.para[param_id]) doif args[paramAlias..suffix] thenreturn args[paramAlias..suffix]endendreturn nilend--[[Trim whitespace from args, and remove empty args. Also fix control characters.@param {table} argsTable@returns {table} trimmed args table]]--function util.trimArgs(argsTable)local cleanArgs = {}for key, val in pairs(argsTable) doif type(key) == 'string'  and type(val) == 'string' thenval = val:match('^%s*(.-)%s*$')if val ~= '' 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 contentif util.matchesParam('text', key) or util.matchesParam('description', key, key:gsub('^%D+(%d+)$', '%1') ) thencleanArgs[key] = valelsecleanArgs[key] = val:gsub('%c',' ')endendelsecleanArgs[key] = valendendreturn cleanArgsend--[[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]]--function util.matchesParam(key, name, suffix)local param = L10n.para[key]suffix = suffix or ''if type(param) == 'table' thenfor _, v in pairs(param) doif (v .. suffix) == name then return true endendreturn falseendreturn ((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]]--function util.isAffirmed(val)if not(val) then return false endreturn string.find(L10n.str.affirmedWords, ' '..val..' ', 1, true ) and true or falseend--[[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]]--function util.isDeclined(val)if not(val) then return false endreturn string.find(L10n.str.declinedWords , ' '..val..' ', 1, true ) and true or falseend--[[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]]--function util.isCoordTemplateOrWrapper(name)name = mw.text.trim(name)local inputTitle = mw.title.new(name, 'Template')if not inputTitle thenreturn falseend-- 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 calledif not L10n.template.titles thenL10n.template.titles = {}for _, v in pairs(L10n.template.coord) dotable.insert(L10n.template.titles, mw.title.new(v, 'Template'))endendfor _, templateTitle in pairs(L10n.template.titles) doif mw.title.equals(inputTitle, templateTitle) thenreturn trueendendreturn falseend--[[Recursively extract coord templates which have a name parameter.@param {string} wikitext@returns {table} table sequence of coord templates]]--function util.extractCoordTemplates(wikitext)local output = {}local templates = mw.ustring.gmatch(wikitext, '{%b{}}')local subtemplates = {}for template in templates dolocal templateName = mw.ustring.match(template, '{{([^}|]+)')local nameParam = mw.ustring.match(template, "|%s*name%s*=%s*[^}|]+")if util.isCoordTemplateOrWrapper(templateName) thenif nameParam then table.insert(output, template) endelseif mw.ustring.find(mw.ustring.sub(template, 2), "{{") thenlocal subOutput = util.extractCoordTemplates(mw.ustring.sub(template, 2))for _, t in pairs(subOutput) dotable.insert(output, t)endendend-- ensure coords are not using title displayfor k, v in pairs(output) dooutput[k] = mw.ustring.gsub(v, "|%s*display%s*=[^|}]+", "|display=inline")endreturn outputend--[[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.]]--function util.getNamedCoords(page)if transcluder == nil then-- load [[Module:Transcluder]] lazily so it is only transcluded on pages that-- actually use named coordinatestranscluder = require("Module:Transcluder")endlocal parts = mw.text.split(page or "", "#", true)local name = parts[1] == "" and mw.title.getCurrentTitle().prefixedText or parts[1]local section = parts[2]local pageWikitext = transcluder.get(section and name.."#"..section or name)local coordTemplates = util.extractCoordTemplates(pageWikitext)if #coordTemplates == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) endlocal frame = mw.getCurrentFrame()local sep = "________"local expandedContent = frame:preprocess(table.concat(coordTemplates, sep))local expandedTemplates = mw.text.split(expandedContent, sep)local namedCoords = {}for _, expandedTemplate in pairs(expandedTemplates) dolocal coord = mw.ustring.match(expandedTemplate, "<span class=\"geo%-dec\".->(.-)</span>")if coord thenlocal name = (-- 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)local description = name ~= coord and coordlocal coord = mw.ustring.gsub(coord, "[° ]", "_")table.insert(namedCoords, {coord=coord, name=name, description=description})endendif #namedCoords == 0 then error(string.format(L10n.error.noNamedCoords, page or name), 0) endreturn namedCoordsend--[[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]]--function util.parseCoords(coords)local coordsPattif mw.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]'endlocal parts = mw.text.split((mw.ustring.match(coords, coordsPatt) or ''), '_')local lat_d = tonumber(parts[1])local lat_m = tonumber(parts[2]) -- nil if coords are in decimal formatlocal lat_s = lat_m and tonumber(parts[3]) -- nil if coords are either in decimal format or degrees and minutes onlylocal lat = lat_d + (lat_m or 0)/60 + (lat_s or 0)/3600if parts[#parts/2] == 'S' thenlat = lat * -1endlocal long_d = tonumber(parts[1+#parts/2])local long_m = tonumber(parts[2+#parts/2]) -- nil if coords are in decimal formatlocal long_s = long_m and tonumber(parts[3+#parts/2]) -- nil if coords are either in decimal format or degrees and minutes onlylocal long = long_d + (long_m or 0)/60 + (long_s or 0)/3600if parts[#parts] == 'W' thenlong = long * -1endreturn lat, 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"]]--function util.wikidataCoords(item_id)if not (item_id and mw.wikibase.isValidEntityId(item_id) and mw.wikibase.entityExists(item_id)) thenerror(L10n.error.noCoords, 0)endlocal coordStatements = mw.wikibase.getBestStatements(item_id, 'P625')if not coordStatements or #coordStatements == 0 thenerror(L10n.error.wikidataCoords, 0)endlocal hasNoValue = ( coordStatements[1].mainsnak and (coordStatements[1].mainsnak.snaktype == 'novalue' or coordStatements[1].mainsnak.snaktype == 'somevalue') )if hasNoValue thenerror(L10n.error.wikidataCoords, 0)endlocal wdCoords = coordStatements[1]['mainsnak']['datavalue']['value']return tonumber(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]]--function util.circleToPolygon(lat, long, radius, n) -- n is number of edges-- Based on https://github.com/gabzim/circle-to-polygon, ISC licencelocal function offset(cLat, cLon, distance, bearing)local lat1 = math.rad(cLat)local lon1 = math.rad(cLon)local dByR = distance / 6378137 -- distance divided by 6378137 (radius of the earth) wgs84local lat = math.asin(math.sin(lat1) * math.cos(dByR) +math.cos(lat1) * math.sin(dByR) * math.cos(bearing))local lon = 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)}endlocal coordinates = {};local i = 0;while i < n dotable.insert(coordinates,offset(lat, long, radius, (2*math.pi*i*-1)/n))i = i + 1endtable.insert(coordinates, offset(lat, long, radius, 0))return coordinatesend--[[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]]--function util.tableCount(t)local count = 0for k, v in pairs(t) docount = count + 1endreturn countend--[[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]]--function util.subTablesCount(t)local count = nilfor k, v in pairs(t) doif count == nil thencount = util.tableCount(v)elseif count ~= util.tableCount(v) thenreturn nilendendreturn countend--[[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]]--function util.tableFromList(listString)if type(listString) ~= "string" or listString == "" then return nil endlocal separator = (mw.ustring.find(listString, "###", 0, true ) and "###") or(mw.ustring.find(listString, ";", 0, true ) and ";") or ","local pattern = "%s*"..separator.."%s*"return mw.text.split(listString, pattern)end-- Boolean in outer scope indicating if Kartographer should be able to-- automatically calculate coordinates (see phab:T227402)local coordsDerivedFromFeatures = 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.----------------------------------------------------------------------------]]--local make = {}--[[Makes content to go inside the maplink or mapframe tag.@param {table} args@returns {string} tag content]]--function make.content(args)if util.getParameterValue(args, 'raw') thencoordsDerivedFromFeatures = true -- Kartographer should be able to automatically calculate coords from raw geoJSONreturn util.getParameterValue(args, 'raw')endlocal content = {}    local argsExpanded = {}    for k, v in pairs(args) dolocal index = string.match( k, '^[^0-9]+([0-9]*)$' )if index ~= nil thenlocal indexNumber = ''if index ~= '' thenindexNumber = tonumber(index)elseindexNumber = 1endif argsExpanded[indexNumber] == nil thenargsExpanded[indexNumber] = {}endargsExpanded[indexNumber][ string.gsub(k, index, '') ] = vend    endfor contentIndex, contentArgs in pairs(argsExpanded) dolocal argType = util.getParameterValue(contentArgs, "type")-- Kartographer automatically calculates coords if geolines/shapes are used (T227402)if not coordsDerivedFromFeatures thencoordsDerivedFromFeatures = ( argType == L10n.str.line or argType == L10n.str.shape ) and true or falseendif argType == L10n.str.named thenlocal namedCoords = util.getNamedCoords(util.getParameterValue(contentArgs, "from"))local typeKey = type(L10n.para.type) == "table" and L10n.para.type[1] or L10n.para.typelocal coordKey = type(L10n.para.coord) == "table" and L10n.para.coord[1] or L10n.para.coordlocal titleKey = type(L10n.para.title) == "table" and L10n.para.title[1] or L10n.para.titlelocal descKey = type(L10n.para.description) == "table" and L10n.para.description[1] or L10n.para.descriptionfor _, namedCoord in pairs(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==1 then return content[1] end--Multiple items get placed in a FeatureCollectionlocal contentArray = '[\n' .. table.concat( content, ',\n') .. '\n]'return contentArrayend--[[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)]]--function make.coords(args, plainOutput)local coords, lat, longlocal frame = mw.getCurrentFrame()if util.getParameterValue(args, 'coord') thencoords = frame:preprocess( util.getParameterValue(args, 'coord') )lat, long = util.parseCoords(coords)elselat, long = util.wikidataCoords(util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())endif plainOutput thenreturn lat, 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]]--function make.circleCoords(args)local lat, long = make.coords(args, true)local radius = util.getParameterValue(args, 'radius')if not radius thenradius = util.getParameterValue(args, 'radiusKm') and tonumber(util.getParameterValue(args, 'radiusKm'))*1000if not radius thenradius = util.getParameterValue(args, 'radiusMi') and tonumber(util.getParameterValue(args, 'radiusMi'))*1609.344if not radius thenradius = util.getParameterValue(args, 'radiusFt') and tonumber(util.getParameterValue(args, 'radiusFt'))*0.3048endendendlocal edges = util.getParameterValue(args, 'edges') or L10n.defaults.edgesif not lat or not long thenerror(L10n.error.noCircleCoords, 0)elseif not radius thenerror(L10n.error.noRadius, 0)elseif tonumber(radius) <= 0 thenerror(L10n.error.negativeRadius, 0)elseif tonumber(edges) <= 0 thenerror(L10n.error.negativeEdges, 0)endreturn util.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]]--function make.contentJson(contentArgs)local data = {}if util.getParameterValue(contentArgs, 'type') == L10n.str.point or util.getParameterValue(contentArgs, 'type') == L10n.str.circle thenlocal isCircle = util.getParameterValue(contentArgs, 'type') == L10n.str.circledata.type = "Feature"data.geometry = {type = isCircle and "LineString" or "Point",coordinates = isCircle and make.circleCoords(contentArgs) or make.coords(contentArgs)}data.properties = {title = util.getParameterValue(contentArgs, 'title') or mw.getCurrentFrame():getParent():getTitle()}if isCircle then-- TODO: This is very similar to below, should be extracted into a functiondata.properties.stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColordata.properties["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidthlocal strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')if strokeOpacity thendata.properties['stroke-opacity'] = tonumber(strokeOpacity)endlocal fill = util.getParameterValue(contentArgs, 'fill')if fill thendata.properties.fill = filllocal fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')data.properties['fill-opacity'] = fillOpacity and tonumber(fillOpacity) or 0.6endelse -- is a pointlocal markerSymbol = util.getParameterValue(contentArgs, 'marker') or L10n.defaults.marker-- allow blank to be explicitly specified, for overriding infoboxes or other templates with a default valueif markerSymbol ~= "blank" thendata.properties["marker-symbol"] = markerSymbolenddata.properties["marker-color"] = util.getParameterValue(contentArgs, 'markerColor') or L10n.defaults.markerColordata.properties["marker-size"] = util.getParameterValue(contentArgs, 'markerSize') or L10n.defaults.markerSizeendelsedata.type = "ExternalData"if util.getParameterValue(contentArgs, 'type') == L10n.str.data or util.getParameterValue(contentArgs, 'from') thendata.service = "page"elseif util.getParameterValue(contentArgs, 'type') == L10n.str.line thendata.service = "geoline"elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shape thendata.service = "geoshape"elseif util.getParameterValue(contentArgs, 'type') == L10n.str.shapeInverse thendata.service = "geomask"endif util.getParameterValue(contentArgs, 'id') or (not (util.getParameterValue(contentArgs, 'from')) and mw.wikibase.getEntityIdForCurrentPage()) thendata.ids = util.getParameterValue(contentArgs, 'id') or mw.wikibase.getEntityIdForCurrentPage()elsedata.title = util.getParameterValue(contentArgs, 'from')enddata.properties = {stroke = util.getParameterValue(contentArgs, 'strokeColor') or L10n.defaults.strokeColor,["stroke-width"] = tonumber(util.getParameterValue(contentArgs, 'strokeWidth')) or L10n.defaults.strokeWidth}local strokeOpacity = util.getParameterValue(contentArgs, 'strokeOpacity')if strokeOpacity thendata.properties['stroke-opacity'] = tonumber(strokeOpacity)endlocal fill = util.getParameterValue(contentArgs, 'fill')if fill and (data.service == "geoshape" or data.service == "geomask") thendata.properties.fill = filllocal fillOpacity = util.getParameterValue(contentArgs, 'fillOpacity')if fillOpacity thendata.properties['fill-opacity'] = tonumber(fillOpacity)endendenddata.properties.title = util.getParameterValue(contentArgs, 'title') or mw.title.getCurrentTitle().textif util.getParameterValue(contentArgs, 'description') thendata.properties.description = util.getParameterValue(contentArgs, 'description')endreturn mw.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]]--function make.tagAttribs(args, isTitle)local attribs = {}if util.getParameterValue(args, 'zoom') thenattribs.zoom = util.getParameterValue(args, 'zoom')endif util.isDeclined(util.getParameterValue(args, 'icon')) thenattribs.class = "no-icon"endif util.getParameterValue(args, 'type') == L10n.str.point and not coordsDerivedFromFeatures thenlocal lat, long = make.coords(args, 'plainOutput')attribs.latitude = tostring(lat)attribs.longitude = tostring(long)endif util.isAffirmed(util.getParameterValue(args, 'frame')) and not(isTitle) thenattribs.width = util.getParameterValue(args, 'frameWidth') or L10n.defaults.frameWidthattribs.height = util.getParameterValue(args, 'frameHeight') or L10n.defaults.frameHeightif util.getParameterValue(args, 'frameCoordinates') thenlocal frameLat, frameLong = util.parseCoords(util.getParameterValue(args, 'frameCoordinates'))attribs.latitude = frameLatattribs.longitude = frameLongelseif util.getParameterValue(args, 'frameLatitude') thenattribs.latitude = util.getParameterValue(args, 'frameLatitude')endif util.getParameterValue(args, 'frameLongitude') thenattribs.longitude = util.getParameterValue(args, 'frameLongitude')endendif not attribs.latitude and not attribs.longitude and not coordsDerivedFromFeatures thenlocal success, lat, long = pcall(util.wikidataCoords, util.getParameterValue(args, 'id') or mw.wikibase.getEntityIdForCurrentPage())if success thenattribs.latitude = tostring(lat)attribs.longitude = tostring(long)endendif util.getParameterValue(args, 'frameAlign') thenattribs.align = util.getParameterValue(args, 'frameAlign')endif util.isAffirmed(util.getParameterValue(args, 'plain')) thenattribs.frameless = "1"elseattribs.text = util.getParameterValue(args, 'text') or L10n.defaults.textendelseattribs.text = util.getParameterValue(args, 'text') or L10n.defaults.textendreturn attribsend--[[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}]]--function make.titleOutput(args, tagContent)local titleTag = mw.text.tag('maplink', make.tagAttribs(args, true), tagContent)local spanAttribs = {style = "font-size: small;",id = "coordinates"}return mw.text.tag('span', spanAttribs, titleTag)end--[[Makes maplink or mapframe wikitext that will be located inline.@param {table} args@param {string} tagContent  Content for the maplink tag@returns {string}]]--function make.inlineOutput(args, tagContent)local tagName = 'maplink'if util.getParameterValue(args, 'frame') thentagName = 'mapframe'endreturn mw.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]]--function make.switcherHtml(params, options)options = options or {}local frame = mw.getCurrentFrame()local styles = frame:extensionTag{name = "templatestyles",args = {src = "Template:Maplink/styles-multi.css"}}local container = mw.html.create("div"):addClass("switcher-container"):addClass("mapframe-multi-container")if options.alignment == "left" or options.alignment == "right" thencontainer:addClass("float"..options.alignment)else -- alignment is "center"container:addClass("center")endfor i = 1, #params docontainer:tag("div"):wikitext(params[i].map):tag("span"):addClass("switcher-label"):css("display", "none"):wikitext(mw.text.trim(params[i].label))endif not options.isThumbnail thenreturn styles .. tostring(container)endlocal classlist = container:getAttr("class")classlist = mw.ustring.gsub(classlist, "%a*"..options.alignment, "")container:attr("class", classlist)local outerCountainer = mw.html.create("div"):addClass("mapframe-multi-outer-container"):addClass("mw-kartographer-container"):addClass("thumb")if options.alignment == "left" or options.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.caption and mw.html.create("div"):addClass("thumbcaption"):wikitext(options.caption))return styles .. 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]]--function make.overlayHtml(overlayMap, baseMap, options)options = options or {}local containerFloatClass = "float"..(options.align or "none")if options.align == "center" thencontainerFloatClass = "center"endlocal containerStyle = {position = "relative",width = options.width .. "px",height = options.height .. "px",overflow = "hidden" -- mobile/minerva tends to add scrollbars for a couple of pixels}if options.align == "center" thencontainerStyle["margin-left"] = "auto"containerStyle["margin-right"] = "auto"endlocal container = mw.html.create("div"):addClass("mapframe-withOverlay-container"):addClass(containerFloatClass):addClass("noresize"):css(containerStyle)local overlayStyle = {position = "absolute",["z-index"] = "1",border = options.border or "1px solid white"}if options.horizontalAlignment == "right" thenoverlayStyle.right = options.horizontalOffset .. "px"elseoverlayStyle.left = options.horizontalOffset .. "px"endif options.verticalAlignment == "bottom" thenoverlayStyle.bottom = options.verticalOffset .. "px"elseoverlayStyle.top = options.verticalOffset .. "px"endlocal overlayDiv = mw.html.create("div"):css(overlayStyle):wikitext(overlayMap)container:node(overlayDiv):wikitext(baseMap)if not options.isThumbnail thenreturn tostring(container)endlocal classlist = container:getAttr("class")classlist = mw.ustring.gsub(classlist, "%a*"..options.align, "")container:attr("class", classlist)local outerCountainer = mw.html.create("div"):addClass("mapframe-withOverlay-outerContainer"):addClass("mw-kartographer-container"):addClass("thumb")if options.align == "left" or options.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.caption and mw.html.create("div"):addClass("thumbcaption"):wikitext(options.caption))return tostring(outerCountainer)end--[[---------------------------------------------------------------------------- Package to be exported, i.e. methods which will available to templates and other modules.----------------------------------------------------------------------------]]--local p = {}-- Entry point for templatesfunction p.main(frame)local parent = frame.getParent(frame)-- Check for overlay optionlocal overlay = util.getParameterValue(parent.args, 'overlay')local hasOverlay = overlay and mw.text.trim(overlay) ~= ""-- Check for switch optionlocal switch = util.getParameterValue(parent.args, 'switch')local isMulti = switch and mw.text.trim(switch) ~= ""-- Create output by choosing method to suit optionslocal outputif hasOverlay thenoutput = p.withOverlay(parent.args)elseif isMulti thenoutput = p.multi(parent.args)elseoutput = p._main(parent.args)end-- Preprocess output before returning itreturn frame:preprocess(output)end-- Entry points for modulesfunction p._main(_args)local args = util.trimArgs(_args)local tagContent = make.content(args)local display = mw.text.split(util.getParameterValue(args, 'display') or L10n.defaults.display, '%s*' .. L10n.str.dsep .. '%s*')local displayInTitle = display[1] ==  L10n.str.title or display[2] ==  L10n.str.titlelocal displayInline = display[1] ==  L10n.str.inline or display[2] ==  L10n.str.inlinelocal outputif displayInTitle and displayInline thenoutput = make.titleOutput(args, tagContent) .. make.inlineOutput(args, tagContent)elseif displayInTitle thenoutput = make.titleOutput(args, tagContent)elseif displayInline thenoutput = make.inlineOutput(args, tagContent)elseerror(L10n.error.badDisplayPara)endreturn outputendfunction p.multi(_args)local args = util.trimArgs(_args)if not args[L10n.para.switch] then error(L10n.error.noSwitchPara, 0) endlocal switchParamValue = util.getParameterValue(args, 'switch')local switchLabels = util.tableFromList(switchParamValue)if #switchLabels == 1 then error(L10n.error.oneSwitchLabel, 0) endlocal mapframeArgs = {}local switchParams = {}for name, val in pairs(args) do-- Copy to mapframeArgs, if not the switch labels or a switch parameterif val ~= switchParamValue and not string.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.local switchList = string.match(val, "^"..L10n.str.switch..":(.+)")if switchList ~= nil thenlocal values = util.tableFromList(switchList)if #values == 1 thenerror(string.format(L10n.error.oneSwitchValue, name), 0)endswitchParams[name] = valuesendendif util.tableCount(switchParams) == 0 thenerror(L10n.error.noSwitchLists, 0)endlocal switchCount = util.subTablesCount(switchParams)if not switchCount thenerror(L10n.error.switchMismatches, 0)elseif switchCount > #switchLabels thenerror(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"local switcher = {}for i = 1, switchCount dolocal label = switchLabels[i]for name, values in pairs(switchParams) domapframeArgs[name] = values[i]endtable.insert(switcher, {map = p._main(mapframeArgs),label = "Show "..label})endreturn make.switcherHtml(switcher, {alignment = args["frame-align"] or "right",isThumbnail = (args.frame and not args.plain) and true or false,width = args["frame-width"] or L10n.defaults.frameWidth,caption = args.text})endfunction p.withOverlay(_args)-- Get and trim wikitext for overlay maplocal overlayMap = _args.overlayif type(overlayMap) == 'string' thenoverlayMap = overlayMap:match('^%s*(.-)%s*$')endlocal isThumbnail = (util.getParameterValue(_args, "frame") and not util.getParameterValue(_args, "plain")) and true or false-- Get base map using the _main function, as a plain maplocal args = util.trimArgs(_args)args.plain = "yes"local basemap = p._main(args)-- Extract overlay options from argslocal overlayOptions = {width = util.getParameterValue(args, "frameWidth") or L10n.defaults.frameWidth,height = util.getParameterValue(args, "frameHeight") or L10n.defaults.frameHeight,align = util.getParameterValue(args, "frameAlign") or L10n.defaults.frameAlign,border = util.getParameterValue(args, "overlayBorder") or L10n.defaults.overlayBorder,horizontalAlignment = util.getParameterValue(args, "overlayHorizontalAlignment") or L10n.defaults.overlayHorizontalAlignment,horizontalOffset = util.getParameterValue(args, "overlayHorizontalOffset") or L10n.defaults.overlayHorizontalOffset,verticalAlignment = util.getParameterValue(args, "overlayVerticalAlignment") or L10n.defaults.overlayVerticalAlignment,verticalOffset = util.getParameterValue(args, "overlayVerticalOffset") or L10n.defaults.overlayVerticalOffset,isThumbnail = isThumbnail,caption = util.getParameterValue(args, "text") or L10n.defaults.text}-- Make the HTML for the overlaying mapsreturn make.overlayHtml(overlayMap, basemap, overlayOptions)endreturn p