Module:Map

From Wikimedia Commons, the free media repository
Jump to navigation Jump to search
Lua

CodeDiscussionEditHistoryLinksLink count Subpages:DocumentationTestsResultsSandboxLive code All modules Module to support {{Map}} template.

Code

--[[  
  __  __           _       _        __  __             
 |  \/  | ___   __| |_   _| | ___ _|  \/  | __ _ _ __  
 | |\/| |/ _ \ / _` | | | | |/ _ (_) |\/| |/ _` | '_ \ 
 | |  | | (_) | (_| | |_| | |  __/_| |  | | (_| | |_) |
 |_|  |_|\___/ \__,_|\__,_|_|\___(_)_|  |_|\__,_| .__/ 
                                                |_|                                                                                                
This module is intended to be the engine behind "Template:Map.

Please do not modify this code without applying the changes first at
"Module:Map/sandbox" and testing at "Module:Map/testcases".

Authors and maintainers:
* User:Jarekt - original version 
]]
-- =======================================
-- === Dependencies ======================
-- =======================================
require('strict') -- used for debugging purposes as it detects cases of unintended global variables
local ISOdate =   require('Module:ISOdate')._ISOdate -- date localization
local labels  =   require("Module:I18n/map")                        -- internationalization of 
local core    =   require('Module:Core')
local formatnum = require('Module:Formatnum')

-- ==================================================
-- === Internal functions ===========================
-- ==================================================

-------------------------------------------------------------------------------
local function getBareLabel(id, userLang) 
-- code equivalent to require("Module:Wikidata label")._getLabel with Wikidata=- option
	local label, link
	-- build language fallback list
	local langList = mw.language.getFallbacksFor(userLang)
	table.insert(langList, 1, userLang)
	for _, lang in ipairs(langList) do  -- loop over language fallback list looking for label in the specific language
		label = mw.wikibase.getLabelByLang(id, lang)
		if label then break end                    -- label found and we are done
	end	
	return label or id
end

-------------------------------------------------------------------------------
local function message(name, lang)
	return mw.message.new( 'wm-license-'..name ):inLanguage(lang):plain()
end

-- ====================================================================
-- === This function is just responsible for producing HTML of the  ===
-- === template. At this stage all the fields are already filed     ===
-- ====================================================================
local function build_html(args, cats)
	local lang = args.lang -- user's language
	local dir  = mw.language.new( lang ):getDir()    -- get text direction
	local desTag = mw.ustring.format('<span class="summary fn" style="display:none">%s</span>', args.pagename)
	local prmTag = mw.ustring.format("<br /><small>([[%s|%s]])</small>", message('information-permission-reusing-link', lang), 
								 message('information-permission-reusing-text', lang))
	
	-- files with no source will be flagged
	if (not args.source) and (args.strict==true) and (args.demo or (args.namespace==6)) then
		args.nosource = mw.getCurrentFrame():expandTemplate{ title = 'Source missing' }
	end
	
	-- boolean field controling if horizontal section bar will be added
	args.addGeotemporalBar   = args.demo or args.location or args.map_date or args.scale or args.zoom or args.projection or args.centroid or args.heading or args.latitude or (args.warp_status and not args.warp_status=="skip") or args.other_fields_2
	args.addBibliographicBar = args.demo or args.set or args.sheet or args.book_title or args.book_author or args.volume or args.page or args.language or args.other_fields_3 or args.place_of_publication or args.publisher or args.printer or args.publication_date or args.authority
	args.addArchivalBar      = args.demo or args.institution or args.id or args.dimensions or args.scan_resolution or args.other_fields_4 or args.medium or args.inscriptions or args.notes or args.references or args.other_fields_5
	args.addDigitalBar		 = args.demo
	
	local nCol = 2
	if not args.image and args.demo then
		args.image = args.demo_image
	end
	if args.image  then
		nCol = 3
	end
	
	-- Top line 
	local top, results = {}, {}
	if args.name then
		table.insert(top, string.format('<span class="fn" id="artwork"><bdi>%s\n</bdi></span>', args.name ) )
	end
	if args.linkback then -- Wikidata Link
		table.insert(top, string.format('[[File:Blue pencil.svg|15px|%s|link=%s]]', args.linkback, args.linkback) )
	end	
	if args.wikidata then -- Wikidata Link
		table.insert(top, string.format('[[File:Wikidata-logo.svg|20px|wikidata:%s|link=wikidata:%s]]', args.wikidata, args.wikidata) )
	end
	if args.QS then -- quick_statement link to upload missing info to wikidata
		table.insert(top, string.format('%s', args.QS) )
	end
	if #top>0 then
		local line = string.format('<th colspan="%i" style="background-color:#ccf; font-weight:bold; border:1px solid #aaa" text-align="left">%s</th>', nCol, table.concat(top, '&nbsp;')) 
		table.insert(results, string.format('<tr valign="top">\n%s\n</tr>\n', line))
	end
	
	-- add other fields
	local params = {
		-- field name                   machine readable tag                         field name i18n approach                     field value wrapper
--		{field='representation'       , id='fileinfotpl_representation'            , tag='P6243'},
		{field='title'                , id='fileinfotpl_art_title'                 , tag='P1476',              wrapper='<div class="fn">\n%s</div>'},
		{field='subtitle'             , id='fileinfotpl_art_subtitle'              , tag='P1680'},
		{field='description'          , id='fileinfotpl_desc'                      , tag='Q1200750',    wrapper='<div class="description">\n%s</div>', tag2=desTag},
		{field='other_fields_1'},
		{field='legend'			      , id='fileinfotpl_map_legend'				   , tag='legend'},
		{field='adjacent_sheets'      , id='fileinfotpl_adjacent_sheets'		   , tag='adjacentsheets'},
		{field='sheet_index'          , id='fileinfotpl_sheet_index'		       , tag='sheetindex'},
		{field='other_fields'},
		{field='date'                 , id='fileinfotpl_date'                      , tag='wm-license-information-date'},
		{field='source'               , id='fileinfotpl_src'                       , tag='wm-license-information-source'}, 
		{field='nosource'             , id='fileinfotpl_nosrc'                     , tag='wm-license-information-source'},
		{field='author'               , id='fileinfotpl_aut'                       , tag='P170',         wrapper='<div class="fn value">\n%s</div>'},
		{field='contributor'          , id='fileinfotpl_contrib'                   , tag='Q20204892',    wrapper='<div class="fn value">\n%s</div>'},
		{field='credit_line'          , id='fileinfotpl_art_credit_line'           , tag='wm-license-artwork-credit-line'},
		{field='permission'           , id='fileinfotpl_perm'                      , tag='wm-license-information-permission', tag2=prmTag},
		-- Geotemporal Data
		{field='addGeotemporalBar'                                                 , tag='geotemporal_data'},
		{field='map_date'             , id='fileinfotpl_map_date'                  , tag='P2913'},
		{field='location'             , id='fileinfotpl_map_location'              , tag='maplocation'},
		{field='type'                 , id='fileinfotpl_type'                      , tag='type'},
		{field='projection'           , id='fileinfotpl_map_projection'            , tag='P3037'},
		{field='scale'                , id='fileinfotpl_map_scale'                 , tag='P1752'},
		{field='zoom'	              , id='fileinfotpl_map_zoom'           	   , tag='P6592'},
		{field='heading'              , id='fileinfotpl_map_heading'               , tag='heading'},
		{field='bbox'                 , id='fileinfotpl_map_bbox'		           , tag='limits'},
		{field='centroid'             , id='fileinfotpl_map_centroid'              , tag='Q511093'},
		{field='warp'                 , id='fileinfotpl_map_warped'		           , tag='warper'},
		{field='other_fields_2'},
		-- Bibliographic Data
		{field='addBibliographicBar'                                               , tag='bibliographic_data'},
		{field='set'                  , id='fileinfotpl_map_parent'				   , tag='P179'},
		{field='sheet'                , id='fileinfotpl_map_sheet'				   , tag='sheet'},
		{field='book_title'           , id='fileinfotpl_book_title'                , tag='Q732577',              wrapper='<div class="fn">\n%s</div>'},
		{field='book_author'          , id='fileinfotpl_aut'                       , tag='wm-license-information-author',         wrapper='<div class="fn value">\n%s</div>'},
		{field='volume'               , id='fileinfotpl_book_volume'               , tag='P478'},
		{field='page'                 , id='fileinfotpl_book_volume'               , tag='P304'},
		{field='language'             , id='fileinfotpl_book_language'             , tag='Q34770'},
		{field='other_fields_3'},
		{field='publication_place'    , id='fileinfotpl_book_place-of-publication' , tag='wm-license-book-place-of-publication',  },
		{field='publisher'            , id='fileinfotpl_book_publisher'            , tag='wm-license-book-publisher',             wrapper='<div class="fn value">\n%s</div>'},
		{field='printer'              , id='fileinfotpl_book_printer'              , tag='P872',               wrapper='<div class="fn value">\n%s</div>'},
		{field='publication_date'     , id='fileinfotpl_publication_date'          , tag='P577'},
		{field='authority'            , id='fileinfotpl_art_authority'             , tag='Q36524'},
		-- Archival Data
		{field='addArchivalBar'                                                    , tag='archival_data'},
		{field='institution'          , id='fileinfotpl_art_gallery'               , tag='Q2668072'},
		{field='id'                   , id='fileinfotpl_art_id'                    , tag='wm-license-artwork-id',                 wrapper='<div class="identifier">\n%s</div>'},
		{field='dimensions'           , id='fileinfotpl_art_dimensions'            , tag='wm-license-artwork-dimensions'},
		{field='other_fields_4'},
		{field='medium'               , id='fileinfotpl_art_medium'                , tag='wm-license-artwork-medium'},
		{field='inscriptions'         , id='fileinfotpl_art_inscriptions'          , tag='wm-license-artwork-inscriptions'},
		{field='notes'                , id='fileinfotpl_art_notes'                 , tag='wm-license-artwork-notes'},
		{field='references'           , id='fileinfotpl_art_references'            , tag='artwork-references'},
		{field='other_fields_5'},
		-- Data about the digital copy
		{field='addDigitalBar'                                              	   , tag='digital_data'},
		{field='resolution'	          , id='fileinfotpl_map_scan_resolution'	   , tag='resolution'},
		{field='other_versions'       , id='fileinfotpl_ver'                       , tag='wm-license-information-other-versions'}, 
		{field='image'},        --  (former: imgen)    {{Igen/map}}
		-- unused extras:
		-- {field='imgen'},     --  deprecated 'image' alias
		-- {field='help_warp'}, --  ignore deprecated relict, but don't show param error
		-- {field='artist'               , id='fileinfotpl_aut'                       , tag='artwork-artist',             wrapper='<div class="fn value">\n%s</div>'},
		-- {field='editor'               , id='fileinfotpl_book_editor'               , tag='book-editor',                wrapper='<div class="fn value">\n%s</div>'},
		-- {field='translator'           , id='fileinfotpl_book_translator'           , tag='book-translator',            wrapper='<div class="fn value">\n%s</div>'},
		-- {field='illustrator'          , id='fileinfotpl_book_illustrator'          , tag='book-illustrator',           wrapper='<div class="fn value">\n%s</div>'},
		-- {field='architect'            , id='fileinfotpl_aut'                       , tag='Q42973',                                wrapper='<div class="fn value">\n%s</div>'},
		-- {field='designer'             , id='fileinfotpl_aut'                       , tag='Q5322166',                              wrapper='<div class="fn value">\n%s</div>'},
		-- {field='photographer'         , id='fileinfotpl_aut'                       , tag='Q33231',                                wrapper='<div class="fn value">\n%s</div>'},
		-- {field='subtitle'             , id='fileinfotpl_book_subtitle'             , tag='book-subtitle'},
		-- {field='series_title'         , id='fileinfotpl_book_series-title'         , tag='book-series-title'},
		-- {field='edition'              , id='fileinfotpl_edition'                   , tag='book-edition'},
		-- {field='object_type'          , id='fileinfotpl_art_object_type'           , tag='object_type'},
		-- {field='genre'                , id='fileinfotpl_art_genre'                 , tag='Q483394'},
		-- {field='original_description' , id='fileinfotpl_desc'                      , tag='original_description',                  wrapper='<div class="description">\n%s</div>'},
		-- {field='pageoverview'         , id='fileinfotpl_book-page-overview'        , tag='book-page-overview'},
		-- {field='depicted_people'      , id='fileinfotpl_art_depicted_people'       , tag='depicted_people'},
		-- {field='depicted_place'       , id='fileinfotpl_art_depicted_place'        , tag='depicted_place'},
		-- {field='depicted_part'        , id='fileinfotpl_art_depicted_part'         , tag='P5961'},
		-- {field='department'           , id='fileinfotpl_art_location'              , tag='artwork-current-location',   wrapper='<div class="locality">\n%s</div>'},
		-- {field='coordinates'          , id='fileinfo-paramfield'                   , tag='ObjectLocation'}, 
		-- {field='place_of_creation'    , id='fileinfotpl_art_creation_place'        , tag='place_of_creation'},
		-- {field='place_of_discovery'   , id='fileinfotpl_art_discovery_place'       , tag='place_of_discovery'},
		-- {field='object_history'       , id='fileinfotpl_art_object_history'        , tag='artwork-object-history'},
		-- {field='exhibition_history'   , id='fileinfotpl_art_exhibition_history'    , tag='exhibition_history'},

	}
	for _, param in ipairs(params) do
		local field, tag, cell1, cell2, id
		field = args[param.field]
		if param.id then -- skip "other fields" parameter
			id = mw.ustring.format('id="%s" ', param.id)
			if field or (args.demo and param.tag and (param.field ~= 'source')) then  -- skip the row if  no field
				tag = param.tag or 'bad'
				if string.sub(tag,1,10) == 'wm-license' then  -- translate using MediaWiki message
					tag = mw.message.new( tag ):inLanguage(lang):plain() -- label message in args.lang language
				elseif string.match(tag, "^[QP]%d+$") then    -- translate based on Wikidata labels
					tag = mw.language.new(lang):ucfirst(getBareLabel(tag, lang))
				elseif labels[tag] then                       -- translate using LangSwitch and Module:I18n/map
					tag = mw.language.new(lang):ucfirst(core.langSwitch(labels[tag], args.lang))
				end
				field = (param.wrapper and mw.ustring.format(param.wrapper, field or '')) or field -- apply wrapper if provided
				cell1 = mw.ustring.format('<td %sclass="fileinfo-paramfield" lang="%s">%s%s</td>\n', id or '', lang, tag,  param.tag2 or '')
				cell2 = mw.ustring.format('<td>\n%s</td>', field or '')
				field = mw.ustring.format('<tr style="vertical-align: top">\n%s%s\n</tr>\n\n', cell1, cell2)
			end
		elseif field and param.tag then -- do horizontal bar
			tag   = mw.language.new(lang):ucfirst(core.langSwitch(labels[param.tag], args.lang))
			cell1 = mw.ustring.format('<td class="type fileinfo-paramfield" colspan="2" style="text-align:left;font-size:larger;">&nbsp;%s</td>\n', tag)
			field = mw.ustring.format('<tr style="vertical-align: top">\n%s\n</tr>\n\n', cell1)
		end
		table.insert(results, field)
	end
	
	-- add table and outer layers
	local style = string.format('class="fileinfotpl-type-artwork toccolours vevent mw-content-%s" dir="%s" style="width: 100%%" cellpadding="4"', dir, dir)
	results = string.format('<table %s>\n%s\n</table>\n', style, table.concat(results)) -- combine "results", an array of strings into a single string
	results = string.format('<div class="hproduct commons-file-information-table">\n%s\n</div>\n', results)
	return results
end

-- =================================================================
-- === Construct output fields from the input arguments 
-- =================================================================

local function create_infobox(args)

    local cats = ''
    local frame = mw.getCurrentFrame()

        if args.demo then
                args.scale = '100000'
                args.heading = 'N'
                args.latitude = '0.0/0.0/0.0/0.0'
                args.longitude = '0.0/0.0/0.0/0.0'

                 
        end


	-- add formatting to the scale in accordance with current language preference
	if args.scale and tonumber(args.scale) then
		args.scale = string.format('1:%s', formatnum.formatNum(args.scale, args.lang))
	end

	-- compass image for heading
	if args.heading then                   -- note : no input validation currently done
		local img_link = frame:expandTemplate{ title = 'Compass rose file', args = { args.heading, style = 'north' } }
		args.heading = string.format('[[%s|50px]]', img_link)
	end
	
	-- bounding box
	if args.latitude and args.longitude then
		args.bbox = frame:expandTemplate{ title = 'Map/bbox', args = { latitude = args.latitude, longitude = args.longitude } }
	end
	
	-- button with link for Map Warper or external georeferencing
	local title = mw.title.getCurrentTitle()
	local page_id_for_warper = ''
	if 6 == title.namespace then 
		page_id_for_warper = title.id         -- set pageID ready for warper link, but only if we are in the File: namespace
	end
	if ('warped' == args.warp_status) or ('1' == args.warp_status) then
		local warp_msg = mw.language.new(args.lang):ucfirst(core.langSwitch(labels['view_warp'], args.lang))  -- msg from I18n/map
	        args.warp = frame:expandTemplate{ 
			title = 'Clickable button', 
			args = {
				target='//warper.wmflabs.org/wikimaps/new?pageid=' .. page_id_for_warper,
				text= warp_msg,
				external='yes', 
				class='mw-ui-progressive mw-ui-button ui-button-blue'
			}
		}
                cats = cats .. '\n[[Category:Georeferenced maps in Wikimaps Warper]]'
              
	elseif 'external' == args.warp_status then
		local warp_msg = mw.language.new(args.lang):ucfirst(core.langSwitch(labels['external_warp'], args.lang))  -- msg from I18n/map
	    args.warp = frame:expandTemplate{ 
			title = 'Clickable button 2', 
			args = {
				link=args.warp_url,
				text= warp_msg,
				color='blue2', 
			}
		}
	elseif 'skip' == args.warp_status then
	    -- nothing
	else
		local warp_msg = mw.language.new(args.lang):ucfirst(core.langSwitch(labels['help_warp'], args.lang))   -- msg from I18n/map
	    args.warp = frame:expandTemplate{ 
			title = 'Clickable button', 
			args = {
				target='//warper.wmflabs.org/wikimaps/new?pageid=' .. page_id_for_warper,
				text= warp_msg,
				external='yes', 
				class='mw-ui-progressive mw-ui-button ui-button-blue'
			}
		}
	    if not ('unwarped' == args.warp_status) then
			args.warp = mw.ustring.format('%s <small>%s</small>', args.warp, mw.language.new(args.lang):ucfirst(core.langSwitch(labels['skip_warp'], args.lang))) 
		end
	end
	
	-- try to internationalise language code
	if args.language then
	    local language_i18n = mw.language.fetchLanguageName( args.language, args.lang )
		if not ('' == language_i18n) then 
			args.language = language_i18n
		end
	end

    -- internationalise ISO dates
	if args.date then
		-- apply ISODate to function to date string to convert date in ISO format to translated date string
		args.date = ISOdate(args.date, args.lang, '', 'dtstart', '100-999')      
	end 

	if args.map_date then
		-- apply ISODate to function to date string to convert date in ISO format to translated date string
		args.map_date = ISOdate(args.map_date, args.lang, '', 'dtstart', '100-999')      
	end 

	if args.print_date then
		-- apply ISODate to function to date string to convert date in ISO format to translated date string
		args.print_date = ISOdate(args.print_date, args.lang, '', 'dtstart', '100-999')      
	end 

    if args.wikidata_title then
		args.title = string.format('%s [[File:Wikidata-logo.svg|20px|wikidata:%s|link=wikidata:%s]]', args.title or '', args.wikidata_title, args.wikidata_title)
	end

        local results = build_html(args)
	return results .. cats
end

-- ==================================================
-- === External functions ===========================
-- ==================================================
local p = {}

-- ===========================================================================
-- === Version of the function to be called from other LUA codes
-- ===========================================================================

-------------------------------------------------------------------------------
-- _map function creates a wikicode for {{Map}} template based on
-- passed arguments (through "args") and data extracted from SDC. 
-------------------------------------------------------------------------------
-- Dependencies: p._SDC_Description, p._SDC_Source, p._SDC_Author, p._SDC_Date, 
--    Build_html, Module:ISOdate (_date)
-------------------------------------------------------------------------------
function p._map(args)
	local cats = ''
	
	-- ============================================================================================
	-- === add [[Category:Pages using Map template with incorrect parameter]] if needed ===
	-- ============================================================================================
	local page = mw.title.getCurrentTitle()
	local lang = args.lang
	local namespace = page.namespace   -- get page namespace
	if namespace==6 or namespace==10 then
		-- TODO add all allowed fields
		local allowedFields = {'title', 'wikidata_title', 'description', 'legend', 'adjacent_sheets', 'sheet_index', 'author', 'contributor', 'image', 'date', 'source', 'permission', 'map_date', 'location', 'wikidata_location', 'type', 'projection', 'centroid', 'scale', 'zoom', 'heading', 'latitude', 'longitude', 'warp_status', 'warp_url', 'set', 'wikidata_set', 'sheet', 'book_author', 'wd_book_author', 'book_title', 'wikidata_book', 'volume', 'page', 'language', 'publication_place', 'publisher', 'printer', 'print_date', 'ISBN', 'LCCN', 'OCLC', 'institution', 'id', 'dimensions', 'scan_resolution', 'medium', 'credit_line', 'inscriptions', 'notes', 'other_versions', 'references', 'other_fields', 'other_fields_1', 'other_fields_2', 'other_fields_3', 'other_fields_4', 'other_fields_5', 'demo', 'lang', 'resolution', 'other-versions', 'subtitle', 'strict' }
		local set, badField = {}, {}
		for _, field in ipairs(allowedFields) do set[field] = true end
		for field, _ in pairs( args ) do 
			if not set[field] then
				table.insert(badField, field)
			end
		end
		if #badField>0 then
			cats = mw.ustring.format('\n;<span style="color:red">Error in [[Template:Map|{{Map}}'..
				' template]]: unknown parameter "%s".</span>',  table.concat(badField,'", "'))
			cats = cats .. '\n[[Category:Pages using Map template with incorrect parameter]]'
		end
	end

	args.pagename = page.text

	return create_infobox(args) .. cats
end

-- ===========================================================================
-- === Version of the functions to be called from template namespace
-- ===========================================================================

-------------------------------------------------------------------------------
-- information function creates a wikicode for {{Map}} template based on
-- passed arguments (through "frame") and data extracted from SDC. All inputs do not 
-- depend on capitalization and all "_" can be replaced with spaces.
-------------------------------------------------------------------------------
-- Dependencies: p._information
-------------------------------------------------------------------------------
function p.map(frame)
	local args = core.getArgs(frame)
	
	-- map aliases to a single variable (all parameters converted to lowercase with _ instead of spaces
	
	args.id = args.id or args.accession_number
	args.medium = args.medium or args.technique
	args.dimensions = args.dimensions or args.size
	args.permission = args.permission or args.license
	args.location = args.depicted_place or args.location
	args.resolution = args.scan or args.scan_resolution or args.resolution
        args.warp_status = args.warp_status or args.warper or args.warped
	if args.imgen then
		if not args.image then
			args.image = args.imgen 
			args.imgen = nil
		end
	end
	if  args.image and string.sub(args.image, 1, 1 ) ~= '{' then
		args.image = frame:expandTemplate{ title = 'Igen/map', args = { args.image } }
	end	

	-- remove aliases
	args.accession_number = nil
	args.technique = nil
	args.size = nil
	args.license = nil
	args.help_warp = nil
	args.depicted_place = nil
	args.scan_resolution,   args.scan = nil, nil
        args.warper, args.warped = nil, nil

        -- ensure the right format
        args.strict       = core.yesno(args.strict, true)

	return p._map(args)	
end

-------------------------------------------------------------------------------
-- List of exported functions
-------------------------------------------------------------------------------
-- map
 
 --[[
 Calculate coordinates of 4 corners of the map based on 3 georeferenced anchor
 points and image dimensions.
 INPUTS:
 * anchorX - x map coordinate (pixel column) of 3 anchor points on the map
 * anchorY - y map coordinate (pixel  row  ) of 3 anchor points on the map
 * anchorDeg - latitude or longitude of 3 anchor points on the map
 * nx - image width  in pixels
 * ny - image height in pixels
 
 	X   = {7772, 330, 6679}         -- x map coordinate of 4 cities on the map
    Y   = {553, 4122, 5248}         -- y map coordinate of 4 cities on the map
    lat = {52.516, 48.857, 48.133} -- latitude of 4 cities on the map
    lon = {13.383, 2.351, 11.567}   -- longitude of 4 cities on the map
    nx   = 8150                            -- image width
    ny   = 6978                            --image height
 ]]
function p.CalculateCornerLocations(frame)
	-- process inputs
	local anchorX   = mw.text.split(frame.args.anchorX, '/')
	local anchorY   = mw.text.split(frame.args.anchorY, '/')
	local anchorDeg = mw.text.split(frame.args.anchorDeg, '/')
	local nx        = tonumber(frame.args.nx)
	local ny        = tonumber(frame.args.ny)
	local formatstr = tonumber(frame.args.formatstr) or '%8.6f'
	if (#anchorX~=3 or #anchorY~=3 or #anchorDeg~=3) then

	end

	-- do matrix algebra
	local matrix = require('Module:Matrix')
	-- Calculate transformation matrix
	-- MATLAB: M = [X, Y, ones(length(X),1)] \ anchorDeg; 
	C  = matrix{{ anchorX[1], anchorY[1], 1, anchorDeg[1]},
    	        { anchorX[2], anchorY[2], 1, anchorDeg[2]},
    	        { anchorX[3], anchorY[3], 1, anchorDeg[3]}}
	local done = matrix.dogauss( C )
	if done then
		M = matrix.subm( C, 1,4,3,4) -- remove identity matrix from first 3 columns
		-- MATLAB: cornerDeg = [ 1, ny, 1; nx, ny, 1; nx, 1, 1; 1, 1, 1] * M
		cornerPix = matrix{{ 1, ny, 1},{nx, ny, 1},{nx, 1, 1},{1, 1, 1}} -- corner points in pixels
		cornerDeg = cornerPix * M
		-- convert to a string
		local tstr = {}
		for i = 1,#cornerDeg do
			tstr[i] = string.format(formatstr, cornerDeg[i][1])
		end
		str =table.concat(tstr,'/')
	else
		str = '0/0/0/0'
	end
    return str
end

return p