Module:Sandbox/trappist the monk/nts map

require('strict'); -- alarm when global variables etc are used

local get_args = require ('Module:Arguments').getArgs; -- simplfy frame and parent frame argument fetching

--[[--------------------------< W I K I D A T A _ L A T _ L O N G _ G E T >------------------------------------

returns latitude and longitude from wikidata for current page; nil else

Nanaimo BC is Q16461 → latitude: 49.164166666667; longitude: -123.93638888889

TODO: accept a argument? if called with fetch lat/lon for that qid and not for current page

]]

local function wikidata_lat_lon_get()

local qid = mw.wikibase.getEntityIdForCurrentPage(); -- get the qid for the current page

if qid then

local value_t = mw.wikibase.getBestStatements (qid, 'P625')[1]; -- attempt to get P625; nil when article does not have P625

if value_t then

value_t = value_t.mainsnak.datavalue.value; --point to the value table

return value_t.latitude, value_t.longitude; -- return coordinates from value_t

end

end

end

--[[--------------------------< A R T I C L E _ N A M E _ F R O M _ Q I D _ G E T >----------------------------

returns local article name for this wiki using ; nil else

TODO: if no local article fallback to en.wiki

]]

local function article_name_from_qid_get (qid)

local this_wiki_code = mw.language.getContentLanguage():getCode(); -- get this wiki's language code

if 'wikidata' == mw.site.server then

this_wiki_code = mw.getCurrentFrame():callParserFunction('int', {'lang'}); -- on Wikidata so use interface language setting instead

end

local wd_article = mw.wikibase.getSitelink (qid, this_wiki_code .. 'wiki'); -- fetch article title from WD; nil when no title available at this wiki

if wd_article then

wd_article = table.concat ({':', this_wiki_code, ':', wd_article}); -- interwiki-style link without brackets if taken from WD; leading colon required

end

return wd_article; -- article title from WD; nil else

end

--[[--------------------------< _ N A M E >--------------------------------------------------------------------

This function takes in a National Tiling System ID and outputs the name of the National Topographic System map

sheet with that ID. If no map sheet has been published under that ID, the output will be blank.

]]

local function _name (args_t)

local data_t = mw.loadData ('Module:Canada NTS/data'); -- load the ~/data module

if not data_t[args_t[1]] then -- nil when NTS ID not listed in ~/data

return "";

elseif '' == data_t[args_t[1]] then -- when sheet has no title

return '(untitled)'; -- return a place-holder; lowercase here because capitalized implies that 'Untitled' is the 'title'

end

local title_parts_t = mw.text.split (data_t[args_t[1]], '|'); -- title_parts[1] is link part of a wikilink; title_parts[2] is wikilink label (map name) or nil

if mw.title.getCurrentTitle().text == title_parts_t[1] then -- don't create wikilink to the current article

return title_parts_t[2] or title_parts_t[1]; -- return plaintext wikilink label or plain text article title (wikilink)

end

return string.format ('%s', data_t[args_t[1]]); -- return wikilinked map title

end

--[[--------------------------< N A M E >----------------------------------------------------------------------

external invoke interface

This function takes in a National Tiling System ID and outputs the name of the National Topographic System map sheet with that ID.

If no map sheet has been published under that ID, the output will be blank.

{{#invoke:Canada NTS|name|}}

]]

local function name (frame)

local args_t = get_args (frame);

if not args_t[1] then

return 'National Tiling System ID required'; -- TODO: better, more informative error handling

end

return _name (args_t);

end

--[[--------------------------< C O O R D _ L I M I T S _ T >--------------------------------------------------

a sequence of sequences. Each sequence in has two coordinate sequences that defines a rectangle

identified by the northwest and southeast corners of a subsection in the NTS series map. The first coordinate sequence identifies the northwest

corner of the subsection; the second sequence identifies the southwest corner of the subsection.

]]

local coord_limits_t = {

{{44, 88}, {40, 56}}, -- series: 40, 30, 20, 10

{{48, 88}, {44, 48}}, -- series: 41, 31, 21, 11, 1

{{56, 136}, {48, 48}}, -- series: 102, 92, ..., 2; 103, 93, ..., 3

{{68, 144}, {56, 56}}, -- series: 114, 104, ..., 14; 115, 105, ..., 15; 116, 106, ..., 16

{{72, 144}, {68, 64}}, -- series: 117, 107, ..., 27

{{76, 128}, {72, 72}}, -- series: 98, 88, ..., 38

{{80, 128}, {76, 64}}, -- series: 99, 89, ..., 29

{{84, 104}, {80, 56}}, -- series: 560, 340, 120

}

--[[--------------------------< L A T _ L O N _ V A L I D A T E >----------------------------------------------

validates and to be inside one of the rectangles defined in

returns true when in bounds; false else

]]

local function lat_lon_validate (lat, lon)

for _, coord_limit_t in ipairs (coord_limits_t) do -- loop through the rectangle sequences in

local lat_N = coord_limit_t[1][1]; -- for readability ...

local lat_S = coord_limit_t[2][1];

local lon_W = coord_limit_t[1][2];

local lon_E = coord_limit_t[2][2];

if (lat >= lat_S) and (lat < lat_N) and (lon >= lon_E) and (lon < lon_W) then

return true; -- and locate a point within bounds so done

end

end

return false; -- and locate a point out of bounds so done

end

--[[--------------------------< E X T E N T S >----------------------------------------------------------------

Calculates bounding boxes for 1:50,000 and 1:250,000 scale map sheets/areas from latitude and longitude

]]

local function extents (lat, lon)

local belt = math.floor((lat - 40) * 4);

local strip = math.floor((lon - 48) * 2);

local s, n, e, w -- 1:50,000 scale bounding box

local a_s, a_n, a_e, a_w -- 1:250,000 scale bounding box

local lat_limits = -- Latitude limits of bounding box

{ s = belt / 4 + 40,

n = belt / 4 + 40.25,

a_s = math.floor(lat),

a_n = math.floor(lat + 1)

}

local lon_limits = {e, w, a_e, a_w}

-- Calculation of longitude limits is different depending on zone

if lat >= 40 and lat < 68 then -- Southern zone

lon_limits["e"] = strip / 2 + 48;

lon_limits["w"] = strip / 2 + 48.5;

lon_limits["a_e"] = math.floor(strip / 4) * 2 + 48;

lon_limits["a_w"] = math.floor(strip / 4) * 2 + 50;

elseif lat >= 68 and lat < 80 then -- Arctic zone

lon_limits["e"] = math.floor(strip / 2) + 48;

lon_limits["w"] = math.floor(strip / 2) + 49;

lon_limits["a_e"] = math.floor(strip / 8) * 4 + 48;

lon_limits["a_w"] = math.floor(strip / 8) * 4 + 52;

elseif lat >= 80 and lat < 88 then -- High Arctic zone

lon_limits["e"] = math.floor(strip / 2) + 48;

lon_limits["w"] = math.floor(strip / 2) + 49;

lon_limits["a_e"] = math.floor(strip / 8) * 4 + 48;

lon_limits["a_w"] = math.floor(strip / 8) * 4 + 52;

end

return {

south = lat_limits["s"], north = lat_limits["n"], east = lon_limits["e"], west = lon_limits["w"], -- 1:50,000 scale maps

area_south = lat_limits["a_s"], area_north = lat_limits["a_n"], area_east = lon_limits["a_e"], area_west = lon_limits["a_w"] -- 1:250,000 scale maps

}

end

--[[--------------------------< N T S _ I D _ F R O M _ L A T _ L O N _ G E T >--------------------------------

calculates NTS identifier from latitude and longitude. If and not supplied, attempts to fetch

coordinates from wikidata

when successful, returns NTS identifier; nil else

]]

local function nts_id_from_lat_long_get (lat, lon)

if not (lat and lon) then -- nil when missing, empty, or not a number

lat, lon = wikidata_lat_lon_get(); -- / template param(s) invalid or missing; attempt to get / from wikidata

end

-- wikidata uses negative numbers for west (and south) so must account for that

if lat then -- normalize coords; assumes that given coords are intended to be on canada nts map

if 0 > lat then

lat = lat * -1.0; -- required because wikidata coords are signed

end

if 0 > lon then

lon = lon * -1.0; -- required because wikidata coords are signed

end

else

return nil;

end

if not lat_lon_validate (lat, lon) then

return nil; -- / out of bounds; TODO: better error handling/messaging

end

local series, numarea, area, sheet_inter, sheet

if lat >= 40 and lat < 68 then -- Southern zone

series = (math.floor((lon - 48) / 8) * 10) + math.floor((lat - 40) / 4) -- Calculate 1:1,000,000 map series ID

numarea = tonumber(math.floor(((lat - 40) / 4) % 1 * 4) * 10 + math.floor(((lon - 48) / 8) % 1 * 4)) -- Calculate 1:250,000 map area ID

local southern_zone_t = {[0]='A', [1]='B', [2]='C', [3]='D', [13]='E', [12]='F', [11]='G', [10]='H', [20]='I', [21]='J', [22]='K', [23]='L', [33]='M', [32]='N', [31]='O', [30]='P'};

area = southern_zone_t[numarea]; -- translate

sheet_inter = math.floor((lat % 1) * 4) * 10 + math.floor((((lon - 48) / 8) % 1 * 4) % 1 * 4) -- Calculate 1:50,000 map sheet ID

elseif lat >= 68 and lat < 80 then -- Arctic zone

series = (math.floor((lon - 48) / 8) * 10) + math.floor((lat - 40) / 4) -- Calculate 1:1,000,000 map series ID

numarea = (math.floor(lat % 4) * 10) + math.floor((lon / 4) % 2) -- Calculate 1:250,000 map area ID

local arctic_zone_t = {[0]='A', [1]='B', [11]='C', [10]='D', [20]='E', [21]='F', [31]='G', [30]='H'};

area = arctic_zone_t[numarea]; -- translate

sheet_inter = math.floor((lat % 1) * 4) * 10 + math.floor(lon % 4) -- Calculate 1:50,000 map sheet ID

elseif lat >= 80 and lat < 88 then -- High Arctic zone

if lon >= 56 and lon < 72 then -- Calculate 1:1,000,000 map series ID

if lat >= 84 then series = 121 else series = 120 end -- These are correct - Go to , select "Overlay reference layers", then "National Tiling System grid coverage"

elseif lon >= 72 and lon < 88 then -- there are no maps above 84°N so series 121, 341, and 561 do not exist in National Topographic System, but do exist in National Tiling System

if lat >= 84 then series = 341 else series = 340 end

elseif lon >= 88 and lon < 104 then

if lat >= 84 then series = 561 else series = 560 end

elseif lon >= 104 and lon < 120 then -- These are correct - Go to , select "Overlay reference layers", then "National Tiling System grid coverage"

if lat >= 84 then series = 781 else series = 780 end -- 780, 781, 910, or 911

elseif lon >= 120 and lon < 136 then

if lat >= 84 then series = 911 else series = 910 end

end -- Remember the difference between the National Topographic System and the National Tiling System

numarea = (math.floor(lat % 4) * 10) + (math.floor((lon / 8) + 1) % 2) -- Calculate 1:250,000 map area ID

local high_arctic_zone_t = {[0]='A', [1]='B', [11]='C', [10]='D', [20]='E', [21]='F', [31]='G', [30]='H'};

area = high_arctic_zone_t[numarea]; -- translate

sheet_inter = math.floor((lat % 1) * 4) * 10 + math.floor((lon % 8) / 2) -- Calculate 1:50,000 map sheet ID

end

local sheet_t = {[0]=1, [1]=2, [2]=3, [3]=4, [13]=5, [12]=6, [11]=7, [10]=8, [20]=9, [21]=10, [22]=11, [23]=12, [33]=13, [32]=14, [31]=15, [30]=16}

sheet = sheet_t[sheet_inter]

return series, area, sheet, lat, lon;

end

--[[--------------------------< N T S _ S E R I E S _ V A L I D A T E >----------------------------------------

return as a number when valid and in-bounds; nil else

– must be a map series number as enumerated in File:NTS Zones and Map Series Numbers.png; leading zeros allowed

]]

local function nts_series_validate (series)

series = (series and tonumber (series)); -- convert series to a number

if not series then

return nil; -- something other than a number; declare failure and abandon

end

local series_limits_t = {{1, 3}, {10, 16}, {20, 27}, {29, 49}, {52, 59}, {62, 69}, {72, 79}, {82, 89}, {92, 99}, {102, 107}, {114, 117}, {120, 120}, {340, 340}, {560, 560}}

for _, limits_t in ipairs (series_limits_t) do -- loop through the series limits

if (series >= limits_t[1]) and (series <= limits_t[2]) then -- is series within these limits?

return series; -- yes, return series as a number

end

end

end

--[[--------------------------< N T S _ A R E A _ V A L I D A T E >--------------------------------------------

return when valid and in-bounds; nil else

– must be a single uppercase letter; when identifies a map series in the

High Arctic and Arctic zones: [A-H]

Southern zone: [A-P]

]]

local function nts_area_validate (series, area)

local is_northern; -- a flag

local northern_series_t = {{27, 27}, {29, 29}, {37, 39}, {47, 49}, {57, 59}, {67, 69}, {77, 79}, {87, 89}, {97, 99}, {107, 107}, {117, 117}, {120, 120}, {340, 340}, {560, 560}}

for _, limits_t in ipairs (northern_series_t) do

if (series >= limits_t[1]) and (series <= limits_t[2]) then -- is series in arctic or high arctic?

is_northern = true; -- yes, set the flag

break; -- and go on to the next tests

end

end

if is_northern then -- if arctic or high arctic series

if not area:match ('^[A-H]$') then -- area must be a single uppercase letter in the range A-H

return nil; -- out of bounds, declare failure and abandon

end

else -- here when southern series

if not area:match ('^[A-P]$') then -- area must be a single uppercase letter in the range A-P

return nil; -- out of bounds, declare failure and abandon

end

end

return area;

end

--[[--------------------------< N T S _ S H E E T _ V A L I D A T E >------------------------------------------

return when valid and in-bounds; nil else

– optional; if present must be a number: [1-16]; leading zeros allowed

]]

local function nts_sheet_validate (sheet)

sheet = (sheet and tonumber (sheet)) or nil; -- sheet as a number; or if not a number or not present, nil

if not sheet then

return nil; -- something other than a number; declare failure and abandon

end

if (1 > sheet) or (16 < sheet) then -- must be a number in the range 1–16

return nil; -- out of bounds, declare failure and abandon

end

return sheet; -- return sheet as a number

end

--[[--------------------------< E X T E N T S _ F R O M _ G R I D >--------------------------------------------

Calculates bounding boxes of 1:50,000 and 1:250,000 scale map sheets/areas from map sheet ID

is 1:1,000,000 scale map series – a 1-to-three digit number

is 1:250,000 scale map area – an uppercase letter A-P

is 1:50,000 scale map sheet – a number 1–16

]]

local function extents_from_grid (series, area, sheet)

local belt -- 192 belts between 40°N and 88°N, each 0.25° of latitude in breadth

local belt_area_south = {["A"]=0, ["B"]=0, ["C"]=0, ["D"]=0, ["E"]=4, ["F"]=4, ["G"]=4, ["H"]=4, ["I"]=8, ["J"]=8, ["K"]=8, ["L"]=8, ["M"]=12, ["N"]=12, ["O"]=12, ["P"]=12}

local belt_area_north = {["A"]=0, ["B"]=0, ["C"]=4, ["D"]=4, ["E"]=8, ["F"]=8, ["G"]=12, ["H"]=12}

local belt_sheet = {[1]=0, [2]=0, [3]=0, [4]=0, [5]=1, [6]=1, [7]=1, [8]=1, [9]=2, [10]=2, [11]=2, [12]=2, [13]=3, [14]=3, [15]=3, [16]=3}

if series >= 120 then

belt = 160 + series % 10 * 16 + (belt_area_north[area] or 0) + (belt_sheet[sheet] or 0)

elseif series < 120 and series % 10 * 16 >= 112 then

belt = series % 10 * 16 + (belt_area_north[area] or 0) + (belt_sheet[sheet] or 0)

else

belt = series % 10 * 16 + (belt_area_south[area] or 0) + (belt_sheet[sheet] or 0)

end

local strip -- 192 belts between 48°W and 144°W, each 0.5° of longitude in breadth

local strip_series_high_arctic = {[12]=16, [34]=48, [56]=80, [78]=112, [91]=144}

local strip_area_southern = {["A"]=0, ["B"]=4, ["C"]=8, ["D"]=12, ["E"]=12, ["F"]=8, ["G"]=4, ["H"]=0, ["I"]=0, ["J"]=4, ["K"]=8, ["L"]=12, ["M"]=12, ["N"]=8, ["O"]=4, ["P"]=0}

local strip_area_arctic = {["A"]=0, ["B"]=8, ["C"]=8, ["D"]=0, ["E"]=0, ["F"]=8, ["G"]=8, ["H"]=0}

local strip_area_high_arctic = {["A"]=0, ["B"]=16, ["C"]=16, ["D"]=0, ["E"]=0, ["F"]=16, ["G"]=16, ["H"]=0}

local strip_sheet_southern = {[1]=0, [2]=1, [3]=2, [4]=3, [5]=3, [6]=2, [7]=1, [8]=0, [9]=0, [10]=1, [11]=2, [12]=3, [13]=3, [14]=2, [15]=1, [16]=0}

local strip_sheet_arctic = {[1]=0, [2]=2, [3]=4, [4]=6, [5]=6, [6]=4, [7]=2, [8]=0, [9]=0, [10]=2, [11]=4, [12]=6, [13]=6, [14]=4, [15]=2, [16]=0}

local strip_sheet_high_arctic = {[1]=0, [2]=4, [3]=8, [4]=12, [5]=12, [6]=8, [7]=4, [8]=0, [9]=0, [10]=4, [11]=8, [12]=12, [13]=12, [14]=8, [15]=4, [16]=0}

local east_limit, west_limit; -- For 1:50,000 scale map sheet

local area_east_limit, area_west_limit; -- For 1:250,000 scale map area

if series >= 120 then -- High Arctic zone

strip = strip_series_high_arctic[math.floor(series / 10)] + (strip_area_high_arctic[area] or 0) + (strip_sheet_high_arctic[sheet] or 0);

east_limit = strip * 0.5 + 48

west_limit = (strip + 4) * 0.5 + 48

area_east_limit = math.floor(strip / 16) * 8 + 48

area_west_limit = math.floor((strip + 16) / 16) * 8 + 48

elseif series < 120 and math.floor(series % 10) >= 7 then -- Arctic zone

strip = math.floor(series / 10) * 16 + (strip_area_arctic[area] or 0) + (strip_sheet_arctic[sheet] or 0);

east_limit = strip * 0.5 + 48

west_limit = (strip + 2) * 0.5 + 48

area_east_limit = math.floor(strip / 8) * 4 + 48

area_west_limit = math.floor((strip + 8) / 8) * 4 + 48

else -- Southern zone

strip = math.floor(series / 10) * 16 + (strip_area_southern[area] or 0) + (strip_sheet_southern[sheet] or 0);

east_limit = strip * 0.5 + 48

west_limit = (strip + 1) * 0.5 + 48

area_east_limit = math.floor(strip / 4) * 2 + 48

area_west_limit = math.floor((strip + 4) / 4) * 2 + 48

end

local grid_limits = {

["south"] = belt * 0.25 + 40,

["north"] = belt * 0.25 + 40.25,

["area_south"] = math.floor(belt / 4) + 40,

["area_north"] = math.floor(belt / 4) + 41,

["east"] = east_limit,

["west"] = west_limit,

["area_east"] = area_east_limit,

["area_west"] = area_west_limit

}

return grid_limits

end

--[[--------------------------< G R I D >----------------------------------------------------------------------

This is a hacked version of Module:Canada NTS that renders a mapframe map with NTS map bounding box, NTS map center

point, and a marker at the lat/long (either provided in the invoke or from wikidata for the current article)

{{#invoke:Sandbox/trappist_the_monk/nts_map|grid|link=yes}} -- for now, |link=yes is required

This function takes a Canadian National Tiling System map sheet ID, or latitude and longitude either as input or

from the wikidata qid of the current page and creates a url for the map sheet. Latitude and longitude are in

decimal degrees, and automatically assumed to be north and west, as no Canadian territory lies in the southern

or eastern hemispheres.

{{#invoke:Canada NTS|grid}} -- For 1:50,000 scale map sheet ID, using coordinates from current article's Wikidata entry

{{#invoke:Canada NTS|grid|||}} -- For 1:50,000 scale map sheet ID, using NTS ID (see Template:Canada NTS Map Sheet)

{{#invoke:Canada NTS|grid|lat=|lon=}} -- For 1:50,000 scale map sheet ID, using coordinates specified in argument

optional parameters:

|area= to obtain a 1:250,000 scale map area ID instead

|link= to create an appropriate link to the Canadian government's Geospatial Data Extraction tool

|name= to append the map name (from Module:Canada_NTS/data) to the output

]]

local function grid (frame)

local args_t = get_args (frame); -- fetch frame and parent frame parameters into a single table

local lat, lon = tonumber(args_t.lat), tonumber(args_t.lon);

-- flags to control what the output looks like

local print_area = 'yes' == args_t.area; -- when true, render only the 'area' portion '30M' instead of sheet '30M11'

local print_link = 'yes' == args_t.link; -- when true, format the area or sheet output as an external link

local print_name = 'yes' == args_t.name; -- when true, append map name from ~/data

local series, area, sheet;

for k, v in ipairs (args_t) do

if 1 == k then -- args_t[1] to series; must be a one-to-three digit number

series = nts_series_validate (args_t[1]);

if not series then

return 'invalid NTS series input';

end

lat = nil; -- unset these if present because we won't be using them when we have nts id (which may or may not be good)

lon = nil;

elseif 2 == k then -- args_t[2] to area; must one uppercase letter A-P

area = nts_area_validate (series, args_t[2]);

if not area then

return 'invalid NTS area input';

end

elseif 3 == k then -- args_t[3] to sheet; must be a number 1-16

sheet = nts_sheet_validate (args_t[3]);

if not sheet then

return 'invalid NTS sheet input';

end

end

end

if series and not sheet then -- if we have nts id without sheet (an area-only)

print_area = true; -- set this so that later we create the correct output

end

if not series or not area then

series, area, sheet, lat, lon = nts_id_from_lat_long_get (lat, lon);

if not series then

return 'lat/long input fail'; -- TODO: better, more informative error handling

end

end

local output = print_area and (series .. area) or (series .. area .. sheet);

print_name = print_name and (' ' .. _name ({output})) or ''; -- reuse to hold name or empty string

local extents_t = {}; -- to hold bounding box coordinates for url

if print_link and lat then -- when we have lat/lon

extents_t = extents (lat, lon); -- get a table of sheet and area extents from lat/lon

elseif print_link then -- when we have nts id

extents_t = extents_from_grid (series, area, sheet); -- get a table of sheet and area extents from nts id parts

if extents_t == nil then

return 'NTS identifier input fail'; -- TODO: better, more informative error handling

end

else

print_link = nil; -- unset because we can't create a link

end

if print_link then

local ext_link_fmt_str = '[https://maps.canada.ca/czs/index-en.html?bbox=-%s,%s,-%s,%s&name=NTS_map_sheet_%s %s]';

output = print_area and string.format (ext_link_fmt_str, extents_t.area_west, extents_t.area_south, extents_t.area_east, extents_t.area_north, output, output) or

string.format (ext_link_fmt_str, extents_t.west, extents_t.south, extents_t.east, extents_t.north, output, output);

end

-- output = output .. print_name; -- append name or empty string

-- return output; -- and done

local place_name = mw.title.getCurrentTitle().text; -- where does this come from? article title?

local nts = series .. area .. (sheet or '');

local center_lat, center_lon; -- center point of NTS map

local polygon_t = {};

local north, west, south, east;

if sheet then

north = extents_t.north;

west = extents_t.west;

south = extents_t.south;

east = extents_t.east

else

north = extents_t.area_north;

west = extents_t.area_west;

south = extents_t.area_south;

east = extents_t.area_east

end

center_lat = (north + south) / 2.0; -- calculate map center point

center_lon = (west + east) / 2.0;

for _, v_t in ipairs ({{west, north}, {east, north}, {east, south}, {west, south}, {west, north}}) do

table.insert (polygon_t, string.format ('[-%s, %s]', v_t[1], v_t[2])); -- make longitudes negative here

end

local map_frame_tag_open = string.format ('',

place_name, output, _name ({nts}), center_lat, center_lon, sheet and 9 or 7);

local map_frame_tag_close = ''

local feature_collection = '{"type": "FeatureCollection","features":[{"type": "Feature",';

feature_collection = string.format ('%s"properties": {"fill": "#ff0000","fill-opacity": 0,"stroke": "#00F","stroke-width": 1},', feature_collection);

feature_collection = string.format ('%s"geometry": {', feature_collection);

feature_collection = string.format ('%s"type": "Polygon",', feature_collection);

feature_collection = string.format ('%s"coordinates": [[', feature_collection);

feature_collection = string.format ('%s%s', feature_collection, table.concat (polygon_t, ', '));

feature_collection = string.format ('%s]]', feature_collection);

feature_collection = string.format ('%s}', feature_collection);

feature_collection = string.format ('%s},', feature_collection);

feature_collection = string.format ('%s{', feature_collection);

feature_collection = string.format ('%s"type": "Feature",', feature_collection);

feature_collection = string.format ('%s"geometry": {"type": "Point", "coordinates": [-%s, %s]},', feature_collection, center_lon, center_lat);

feature_collection = string.format ('%s"properties": {"title": "%s","marker-color":"#00F"}', feature_collection, nts .. ' map center');

feature_collection = string.format ('%s},', feature_collection);

feature_collection = string.format ('%s{', feature_collection);

feature_collection = string.format ('%s"type": "Feature",', feature_collection);

feature_collection = string.format ('%s"geometry": {"type": "Point", "coordinates": [-%s, %s]},', feature_collection, lon, lat);

feature_collection = string.format ('%s"properties": {"title": "%s"}', feature_collection, place_name);

feature_collection = string.format ('%s}', feature_collection);

feature_collection = string.format ('%s]', feature_collection);

feature_collection = string.format ('%s}', feature_collection);

return frame:preprocess (map_frame_tag_open .. feature_collection .. map_frame_tag_close)

end

--[[--------------------------< D O C _ S U P P O R T >--------------------------------------------------------

]]

local function doc_support (frame)

local args_t = get_args (frame); -- fetch frame and parent frame parameters into a single table

local data = mw.loadData ('Module:Canada NTS/data'); -- load the ~/data module

local lang_obj = mw.language.getContentLanguage(); -- get language object for number formatting

local count = 0; -- a generic counter

local area_count = 0; -- counter for area maps (1:250,000)

local sheet_count = 0; -- counter for sheet maps (1:50,000)

if 'count' == args_t[1] then -- count the number of entries in ~/data

for k, _ in pairs (data) do -- don't care about key and value

if k:match ('[A-P]$') then

area_count = area_count + 1; -- bump the counter

else

sheet_count = sheet_count + 1; -- bump the counter

end

end

return string.format ('%s (1:250,000: %s; 1:50,000: %s)', lang_obj:formatNum (sheet_count + area_count), lang_obj:formatNum (area_count), lang_obj:formatNum (sheet_count));

elseif 'untitled' == args_t[1] then -- count the number of entries in ~/data that do not have a NTS title

for _, v in pairs (data) do -- don't care about key

if '' == v then -- when value is empty string, no title so

count = count + 1; -- bump the counter

end

end

end

return lang_obj:formatNum(count); -- make all pretty-like and done

end

--[[--------------------------< E X P O R T E D F U N C T I O N S >------------------------------------------

]]

return {

grid = grid, -- entry points from invokes

name = name,

doc_support = doc_support,

extents_from_grid = extents_from_grid, -- entry points when this module is require()d into other modules

nts_series_validate = nts_series_validate,

nts_area_validate = nts_area_validate,

nts_sheet_validate = nts_sheet_validate,

}