Module:Sandbox/trappist the monk/nts

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

local nts = require ('Module:Canada NTS'); -- for extents_from_grid(), nts_series_validate, nts_are_validate, nts_sheet_validate

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

--[[--------------------------< 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 ; nil else

]]

local function wikidata_lat_lon_get (qid)

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, -1.0 * value_t.longitude; -- return coordinates from value_t; longitude normalized

end

end

end

--[[--------------------------< T A B B I N G >----------------------------------------------------------------

return the number of tabs necessary to more-or-less position comments at column 80 (the right-side margin line

in the wiki code editor)

]]

local function tabbing (str)

return math.ceil ((80 - 4 - str:len()) / 4); -- the -4 because every line begins with a tab character

end

--[[--------------------------< S O R T >----------------------------------------------------------------------

sort by keys; ascending order numerically by series then by area (alpha sort) then numerically by sheet

]]

local function sort (a, b)

local a_key_series, a_key_area, a_key_sheet = a:match ('(%d+)(%u)(%d*)'); -- extract series, area, sheet NTS id parts from

local b_key_series, b_key_area, b_key_sheet = b:match ('(%d+)(%u)(%d*)'); -- extract series, area, sheet NTS id parts from

a_key_series = tonumber (a_key_series); -- convert numerical parts of key from to numbers for comparison

a_key_sheet = tonumber (a_key_sheet); -- nil when is empty string

a_key_sheet = a_key_sheet and a_key_sheet or 0 -- optional so when omitted set to 0 for comparison

b_key_series = tonumber (b_key_series); -- convert numerical parts of key from to numbers for comparison

b_key_sheet = tonumber (b_key_sheet); -- nil when is empty string

b_key_sheet = b_key_sheet and b_key_sheet or 0 -- optional so when omitted set to 0 for comparison

if a_key_series ~= b_key_series then -- do the comparisons; by series first

return a_key_series < b_key_series;

elseif a_key_area ~= b_key_area then -- then by area

return a_key_area < b_key_area;

else

return a_key_sheet < b_key_sheet; -- and lastly by sheet

end

end

--[[--------------------------< L A T _ B O U N D S _ C H E C K >----------------------------------------------

is same as or north of and is same as or south of ?

returns direction 'north' or 'south' when out of bounds; nil else

]]

local function lat_bounds_check (lat, north, south)

if lat < south then

return 'south'; -- out of bounds south of

elseif lat > north then

return 'north'; -- out of bounds north of

end

end

--[[--------------------------< L O N _ B O U N D S _ C H E C K >----------------------------------------------

is same as or east of and is same as or west of ?

returns direction 'east' or 'west' when out of bounds; nil else

]]

local function lon_bounds_check (lon, west, east)

if lon < east then

return 'east'; -- out of bounds east of

elseif lon > west then

return 'west'; -- out of bounds west of

end

end

--[[--------------------------< I S _ I N _ B O U N D S >------------------------------------------------------

get map extents using series, area, sheet. check to see that lat/lon fall within the map extents.

returns empty string for concatenation when lat/lon fall within map extents; an error message else

only one error mesage is returned; if both and are out of bounds, this function returns an error

message for only one of them

because series, area, sheet are taken from Module:Canada NTS/data, they are presumed to be correct

]]

local function is_in_bounds (series, area, sheet, lat, lon)

local extents_t = nts.extents_from_grid (tonumber(series), area, tonumber(sheet)); -- fetch extents for this nts key

local lat_fail, lon_fail; -- flags

local north, west, south, east; -- map extents

if '' == sheet then -- is this searies, area, sheet an area map?

north, west, south, east = extents_t.area_north, extents_t.area_west, extents_t.area_south, extents_t.area_east;

lat_fail = lat_bounds_check (lat, north, south);

lon_fail = lon_bounds_check (lon, west, east);

else -- here when sheet map

north, west, south, east = extents_t.north, extents_t.west, extents_t.south, extents_t.east;

lat_fail = lat_bounds_check (lat, north, south);

lon_fail = lon_bounds_check (lon, west, east);

end

if lat_fail or lon_fail then -- build error message

if 'north' == lat_fail then

return string.format ('P625 %s latitude out of bounds: WD lat/lon: <%s>,%s (NW: <%s>,%s / SE: %s,%s)',

lat_fail, lat, lon, north, west, south, east);

elseif 'south' == lat_fail then

return string.format ('P625 %s latitude out of bounds: WD lat/lon: <%s>,%s (NW: %s,%s / SE: <%s>,%s)',

lat_fail, lat, lon, north, west, south, east);

elseif 'west' == lon_fail then

return string.format ('P625 %s longitude out of bounds: WD lat/lon: %s,<%s> (NW: %s,<%s> / SE: %s,%s)',

lon_fail, lat, lon, north, west, south, east);

elseif 'east' == lon_fail then

return string.format ('P625 %s longitude out of bounds: WD lat/lon: %s,<%s> (NW: %s,%s / SE: %s,<%s>)',

lon_fail, lat, lon, north, west, south, east);

end

end

return ''; -- in bounds so return empty string message for concatenation

end

--[[--------------------------< M A I N >----------------------------------------------------------------------

fetch entries from Module:Canada NTS/data. For each of those, attempt to get qid that matches the

en.wiki article title from wikidata. Replace article title with the qid, sort and make all pretty like for

manual replacement in ~/data.

to use the results of this function, Module:Canada NTS requires support for qids in lieu of article names which,

as of 2022-04-12, does not yet exist

This function may be called from an invoke or from the debug console

is a text string when called from debug console:

=p.main ('92')

{{#invoke:Sandbox/trappist the monk/nts|main|94}}

]]

local function main (frame)

local map_series; -- number of the series that we are operating on; for invokes, this is only number or start of a range

local map_series_end; -- for invokes only; end of a range of map series

local invoked;

if 'table' == type (frame) then

map_series = frame.args[1]; -- for an invoke

if frame.args[2] then

map_series_end = frame.args[2];

end

invoked = true; -- flag used to modify final output rendering

else

map_series = frame; -- when called from the debug console

end

local temp_t = {} -- output goes in this sequence table until rendering

for k, v in pairs (data) do -- for each entry in the data mofule

local series, area, sheet = k:match ('^(%d+)(%u)(%d*)'); -- fetch series, area, sheet from the entry's key

if tonumber(series) >= tonumber(map_series) and tonumber(series) <= tonumber(map_series_end or map_series) then -- when this map is one of the map series we're looking for

local map_title_parts = mw.text.split (v, '|'); -- extract an article title

local qid = mw.wikibase.getEntityIdForTitle (map_title_parts[1]); -- does this article title have a wikidata entry

local lat, lon = wikidata_lat_lon_get (qid); -- attempt to get P625 lat/lon from wikidata's entry for the article title

local bounds_msg = ''; -- gets an error message if lat/lon from wikidata not within bounds of NTS map

if qid and lat then -- when article title has wikidata entry an wikidata has P625 (latitude/longitude)

bounds_msg = is_in_bounds (series, area, sheet, lat, lon); -- empty string when in bounds; message else

elseif qid then -- lat can be nil when qid points to dab page or article does not have P625

bounds_msg = string.format ('%s missing P625', qid); -- no lat/lon so no P625

end

local str;

if not qid then -- when article title doesn't have a wikidata entry

str = string.format ('\t["%s"] = "%s",', k, v); -- mimic the original

elseif not lat then -- has qid but does not have P625

str = string.format ('["%s"] = "%s",', k, v); -- mimic the original + bounds_msg

str = string.format ('\t%s%s-- %s', str, string.rep ('\t', tabbing (str)), bounds_msg);

elseif map_title_parts[2] then -- for piped article links

str = string.format ('["%s"] = "%s|%s",', k, qid, map_title_parts[2]);

str = string.format ('\t%s%s-- %s; %s', str, string.rep ('\t', tabbing (str)), map_title_parts[1], bounds_msg);

else -- map name same as en.wiki article title

str = string.format ('["%s"] = "%s",', k, qid, map_title_parts[1]);

str = string.format ('\t%s%s-- %s; %s', str, string.rep ('\t', tabbing (str)), map_title_parts[1], bounds_msg);

end

if invoked then -- when this function called through an invoke

str = str:gsub ('\t', ' ') -- replace plain-text tabs with html numeric entities

end

table.insert (temp_t, str); -- save

end

end

table.sort (temp_t, sort); -- ascending numerical sort by key (NTS id)

for i=#temp_t, 1, -1 do -- working backwards through the table

if temp_t[i]:match ('%d+%u"%]') then -- if this key is an area key (no sheet)

table.insert (temp_t, i, ''); -- insert an empty string to separate area-from-area

elseif i > 1 and temp_t[i]:match ('%["(%d+)') ~= temp_t[i-1]:match ('%["(%d+)') then -- because some series list don't begin with area maps (15 for example)

table.insert (temp_t, i, ''); -- insert an empty string to separate area-from-area

end

end

if invoked then -- when this function called through an invoke

return table.concat ({'

', table.concat (temp_t, '
'),'
'}); -- concatenate into a big string using
tags and wrapped in
...
tagsand done

else

return table.concat (temp_t, '\n'); -- concatenate into a big string using plain-text newlines and done

end

end

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

debug console function to makes sure that all keys in Module:Canada NTS/data have the correct form

=p.nts_key_validate()

]]

local function nts_key_validate()

for k, _ in pairs (data) do

local series, area, sheet = k:match ('^(%d+)(%u)(%d*)$');

if not series then

return 'invalid key: ' .. k .. '';

end

if not nts.nts_series_validate (tonumber(series)) then

return 'invalid series: ' .. k .. '';

end

if not nts.nts_area_validate (tonumber(series), area) then

return 'invalid area: ' .. k .. '';

end

if '' ~= sheet and not nts.nts_sheet_validate (tonumber(sheet)) then

return 'invalid sheet: ' .. k .. '';

end

end

return 'keys are valid';

end

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

]]

return {

main = main,

nts_key_validate = nts_key_validate,

}