This module clips out a small region from a set of equirectangular world maps. It can cross boundaries between maps, pasting together pieces of up to four maps per figure. It adds markings for longitude and latitude, feature markings and annotations. You can click on marked features to go to the target articles.
((#invoke:MapClip|map|parameters))
Note that all parameters can be provided either as a single number (fractional degrees) or three numbers (fractional degrees, minutes, seconds).
Note that marks for degrees, minutes, seconds are ignored - the numbers are simply assumed to be deg, min, sec in that order, with any other numbers ignored.
Each of these values can be fractional. "W", "S", or "-" causes the latitude/longitude to be counted as negative (the module doesn't actually check to see it is in the right direction).
Giving a south value greater than a north value causes an error; east and west values can be swapped to show the other half of the world.
Each of these uses an independent number N. If a feature is present it must have a featureNlat and a featureNlong; the others are optional per feature. Numbers should start from 1 and not skip any for best results.
((#invoke:MapClip|map|regionwestedge=-82|regioneastedge=-80|regionnorthedge=26|regionsouthedge=24|feature1=Key West|feature1lat=24°33′33″N|feature1long=81°47′03″W|feature2=Old Rhodes Key|feature2lat=25.365957°N|feature2long=80.241866°W|feature3=Old Totten Key|feature3lat=25.3796°N|feature3long=80.2484°W|feature4=Reid Key|feature4lat=25.392779°N|feature4long=80.240149°W|feature5=Duck Key|feature5lat=24°46′32″N|feature5long=80°54′39″W|feature6=Pigeon Key|feature6lat=24.703991°N|feature6long=81.155308°W|feature7=Summerland Key|feature7lat=24.657°N|feature7long=81.441°W|feature8text=[[Lower Keys]]|feature8lat=24.4|feature8long=-81.8|feature9text=[[Middle Keys]]|feature9lat=24.55|feature9long=-81.13|grid=grey))
produces
--- The purpose of this module is to clip out a segment from a set of files that makes up a map
--- various annotations and scale bars should be added.
--- The spritedraw function is being considered as a possible direct copy (future "require")
--- from Module:Sprite - however, both modules are too inchoate at this time to do that confidently,
--- and some modification may be needed.
local p={}
function processdegrees(degreestring)
local neg=mw.ustring.match(degreestring,"^%s*%-") or mw.ustring.match(degreestring,"S") or mw.ustring.match(degreestring,"W")
if neg then neg=-1 else neg=1 end
local onenumber=mw.ustring.match(degreestring,"^[^%d%.]*([%d%.]+)[^%d%.]*$")
if onenumber then
return (neg*tonumber(onenumber))
else local deg=mw.ustring.match(degreestring,"^[^%d%.]*([%d%.]+)")
if not(deg) then return nil end
local min=mw.ustring.match(degreestring,"^[^%d%.]*[%d%.]+[^%d%.]*([%d%.]+)")
local sec=mw.ustring.match(degreestring,"^[^%d%.]*[%d%.]+[^%d%.]*[%d%.]+[^%d%.]*([%d%.]+)")
return neg*(tonumber(deg)+tonumber(min or 0)/60+tonumber(sec or 0)/3600)
end
end
function spritedraw(left,right,top,bottom,image,imagewidth,scale,float)
top=math.floor(top*scale)
bottom=math.ceil(bottom*scale)
left=math.floor(left*scale)
right=math.ceil(right*scale)
local scalestring=""
if scale~=1 then scalestring=math.floor(imagewidth*scale)..'px|' end
output='<div style="position:absolute;overflow:visible;'..float..'top:'..(15-top)..'px;left:'..(40-left)..'px;clip:rect('..top..'px,'..right..'px,'..bottom..'px,'..left..'px);">[[File:'..image..'|'..scalestring..']]</div>'
return output
end
function p.map(frame)
--- variables "map" refer to the original image file
--- variables "region" refer to the clipped area to be displayed
local debuglog=""
local args=frame.args
local parent=frame.getParent(frame)
local pargs=parent.args
--- pixel values (setting regionwidth forces scaling.
--- Regionheight may not be implemented because there's no way to 1-way scale I know of
local mapwidthpx=args.mapwidthpx or pargs.mapwidthpx
local mapheightpx=args.mapheightpx or pargs.mapheightpx
local directions={'north','south','east','west'}
local north,south,east,west=1,2,3,4
local worldedge={90,-90,180,-180}
local mapedgestring,mapedge,regionedgestring,regionedge={},{},{},{}
for d =1,4 do
mapedgestring[d]=args['map'..directions[d]..'edge'] or args['map'..directions[d]..'edge'] or ""
mapedge[d]=processdegrees(mapedgestring[d]) or worldedge[d]
regionedgestring[d]=args['region'..directions[d]..'edge'] or args['region'..directions[d]..'edge'] or ""
regionedge[d]=processdegrees(regionedgestring[d]) or worldedge[d]
end
local mapwidthdeg=mapedge[east]-mapedge[west]
if mapwidthdeg<=0 then mapwidthdeg=mapwidthdeg+360 end
local regionwidthdeg=regionedge[east]-regionedge[west]
if regionwidthdeg<=0 then regionwidthdeg=regionwidthdeg+360 end
local mapfile=args.mapfile or pargs.mapfile or ""
local mapfiles={}
local row=0
mapfile=mapfile.."|" -- last row will be processed like the others
while mw.ustring.match(mapfile,"|") do
row=row+1
local rowtext=mw.ustring.match(mapfile,"^([^|]*)|")
mapfiles[row]={}
prowl=mw.ustring.gmatch(rowtext,"%[%[([^%[%]])*%]%]")
repeat
local f=prowl()
if not f then break;end
table.insert(mapfiles[row],f)
until false
mapfile=mw.ustring.gsub(mapfile,"^[^|]*|","")
end
if not mapfiles[1][1] then
mapedge={90,-90,180,-180} -- ad hoc calibration was done here, but turned out to be a bug!
if regionwidthdeg<=60 then
mapwidthpx=1800
mapheightpx=1800
mapfiles=
(('Topographic30deg_N60W150.png',
'Topographic30deg_N60W120.png',
'Topographic30deg_N60W90.png',
'Topographic30deg_N60W60.png',
'Topographic30deg_N60W30.png',
'Topographic30deg_N60W0.png',
'Topographic30deg_N60E0.png',
'Topographic30deg_N60E30.png',
'Topographic30deg_N60E60.png',
'Topographic30deg_N60E90.png',
'Topographic30deg_N60E120.png',
'Topographic30deg_N60E150.png'},
{'Topographic30deg_N30W150.png',
'Topographic30deg_N30W120.png',
'Topographic30deg_N30W90.png',
'Topographic30deg_N30W60.png',
'Topographic30deg_N30W30.png',
'Topographic30deg_N30W0.png',
'Topographic30deg_N30E0.png',
'Topographic30deg_N30E30.png',
'Topographic30deg_N30E60.png',
'Topographic30deg_N30E90.png',
'Topographic30deg_N30E120.png',
'Topographic30deg_N30E150.png'},
{'Topographic30deg_N0W150.png',
'Topographic30deg_N0W120.png',
'Topographic30deg_N0W90.png',
'Topographic30deg_N0W60.png',
'Topographic30deg_N0W30.png',
'Topographic30deg_N0W0.png',
'Topographic30deg_N0E0.png',
'Topographic30deg_N0E30.png',
'Topographic30deg_N0E60.png',
'Topographic30deg_N0E90.png',
'Topographic30deg_N0E120.png',
'Topographic30deg_N0E150.png'},
{'Topographic30deg_S0W150.png',
'Topographic30deg_S0W120.png',
'Topographic30deg_S0W90.png',
'Topographic30deg_S0W60.png',
'Topographic30deg_S0W30.png',
'Topographic30deg_S0W0.png',
'Topographic30deg_S0E0.png',
'Topographic30deg_S0E30.png',
'Topographic30deg_S0E60.png',
'Topographic30deg_S0E90.png',
'Topographic30deg_S0E120.png',
'Topographic30deg_S0E150.png'},
{'Topographic30deg_S30W150.png',
'Topographic30deg_S30W120.png',
'Topographic30deg_S30W90.png',
'Topographic30deg_S30W60.png',
'Topographic30deg_S30W30.png',
'Topographic30deg_S30W0.png',
'Topographic30deg_S30E0.png',
'Topographic30deg_S30E30.png',
'Topographic30deg_S30E60.png',
'Topographic30deg_S30E90.png',
'Topographic30deg_S30E120.png',
'Topographic30deg_S30E150.png'},
{'Topographic30deg_S60W150.png',
'Topographic30deg_S60W120.png',
'Topographic30deg_S60W90.png',
'Topographic30deg_S60W60.png',
'Topographic30deg_S60W30.png',
'Topographic30deg_S60W0.png',
'Topographic30deg_S60E0.png',
'Topographic30deg_S60E30.png',
'Topographic30deg_S60E60.png',
'Topographic30deg_S60E90.png',
'Topographic30deg_S60E120.png',
'Topographic30deg_S60E150.png'))
else
mapwidthpx=1991
mapheightpx=1990
mapfiles=(('WorldMap_180-0-270-90.png','WorldMap_270-0-360-90.png','WorldMap_0-0-90-90.png','WorldMap_90-0-180-90.png'},{'WorldMap_-180,-90,-90,0.png','WorldMap_-90,-90,-0,0.png','WorldMap_0,-90,90,0.png','WorldMap_-270,-90,-180,0.png'))
end
end
if not (mapwidthpx and mapheightpx) then return "Module:MapClip error: mapwidthpx and mapheightpx must be supplied if a map image file is specified" end
mapwidthpx=tonumber(mapwidthpx);mapheightpx=tonumber(mapheightpx)
local totalmapwidthpx=mapwidthpx*#mapfiles[1]
local totalmapheightpx=mapheightpx*#mapfiles
local mapheightdeg=mapedge[north]-mapedge[south]
if mapheightdeg<=0 then return "[[Module:MapClip]] error: mapnorthedge is south of mapsouthedge" end
if ((regionedge[north]-regionedge[south])<0) then return "[[Module:MapClip]] error: regionnorthedge is south of regionsouthedge" end
local widthratio=totalmapwidthpx/mapwidthdeg
local heightratio=totalmapheightpx/mapheightdeg
local left=(regionedge[west]-mapedge[west])*widthratio
local xfile=math.floor(left/mapwidthpx)
left=left-xfile*mapwidthpx
local right=(regionedge[east]-mapedge[west])*widthratio-xfile*mapwidthpx
local top=(mapedge[north]-regionedge[north])*heightratio
local yfile=math.floor(top/mapheightpx)
top=top-yfile*mapheightpx
local bottom=(mapedge[north]-regionedge[south])*heightratio-yfile*mapheightpx
local imagewidth=mapwidthpx
local displaywidth=args.displaywidth or pargs.displaywidth or 220
local float=args.float or pargs.float or nil
if float then float="float:"..float..";" else float="" end
local nowiki=args.nowiki or pargs.nowiki
local i,featurelat,featurelong,featurename,featureimage,featuresize,featuretext=0,{},{},{},{},{},{}
repeat -- import all feature names, longitude, latitude
i=i+1
featurename[i]=args['feature'..i] or pargs['feature'..i]
featurelat[i]=args['feature'..i..'lat'] or pargs['feature'..i..'lat']
featurelong[i]=args['feature'..i..'long'] or pargs['feature'..i..'long']
featureimage[i]=args['feature'..i..'image'] or pargs['feature'..i..'image']
featuresize[i]=args['feature'..i..'size'] or pargs['feature'..i..'size']
featuretext[i]=args['feature'..i..'text'] or pargs['feature'..i..'text']
if (featurelong[i]) then featurelong[i]=processdegrees(featurelong[i]) else featurelat[i]=nil end
if (featurelat[i]) then featurelat[i]=processdegrees(featurelat[i]) end
until (not featurelat[i])
local output=""
-- first map to display
local image=mapfiles[yfile+1][xfile+1] or error("Module:MapClip error: "..tostring(yfile)..":"..tostring(xfile).." in "..tostring(mapfile).." not found")
local scale=displaywidth/(right-left)
output,errcode=spritedraw(left,right,top,bottom,image,imagewidth,scale,float)
if right>mapwidthpx then
local xnew=xfile+2
if xnew>#mapfiles[1] then xnew=1 end
if bottom>mapheightpx then
local ynew=yfile+2
if ynew>#mapfiles then ynew=1 end
local image=mapfiles[ynew][xfile+1] or error("Module:MapClip error: "..tostring(yfile)..":"..tostring(xfile).." in "..tostring(mapfile).." not found")
local output2,errcode2=spritedraw(left,right,top-mapheightpx,bottom-mapheightpx,image,imagewidth,scale,float)
output=output..output2;errcode=errcode or errcode2
local image=mapfiles[yfile+1][xnew]
local output2,errcode2=spritedraw(left-mapwidthpx,right-mapwidthpx,top,bottom,image,imagewidth,scale,float)
output=output..output2;errcode=errcode or errcode2
local image=mapfiles[ynew][xnew]
local output2,errcode2=spritedraw(left-mapwidthpx,right-mapwidthpx,top-mapheightpx,bottom-mapheightpx,image,imagewidth,scale,float)
output=output..output2;errcode=errcode or errcode2
else
local image=mapfiles[yfile+1][xnew]
local output2,errcode2=spritedraw(left-mapwidthpx,right-mapwidthpx,top,bottom,image,imagewidth,scale,float)
output=output..output2;errcode=errcode or errcode2
end
elseif bottom>mapheightpx then
local ynew=yfile+2
if ynew>#mapfiles then ynew=1 end
local image=mapfiles[ynew][xfile+1] or error("Module:MapClip error: "..tostring(yfile)..":"..tostring(xfile).." in "..tostring(mapfile).." not found")
local output2,errcode2=spritedraw(left,right,top-mapheightpx,bottom-mapheightpx,image,imagewidth,scale,float)
output=output..output2;errcode=errcode or errcode2
end
local grid=args.grid or pargs.grid
if grid then -- for now only implementing an automagic grid
md=regionedge[east]-regionedge[west]
if md<0 then md=md+360 end
if md<=30 then md=math.abs(md/2) else md=math.abs(md/3) end -- must be at least two divisions
local pt=10
if pt<=md then
if (pt<=md/3) then pt=pt*3 end -- multiples of 30 degrees
if (pt<=md/3) then pt=pt*3 end -- multiples of 90 degrees
else while (pt>md) do
if pt/2<md then pt=pt/2;break end -- first digit 5
if pt/5<md then pt=pt/5;break end -- first digit 2
pt=pt/10
if pt<md then break end -- first digit 1
end
end
local yheight=math.ceil((bottom-top)*scale)
for gridline=math.ceil(regionedge[west]/pt)*pt,math.floor(regionedge[east]/pt)*pt,pt do
local xpos=math.floor(((gridline-mapedge[west])*widthratio-xfile*mapwidthpx-left)*scale)
output=output..'<div style="position:absolute;overflow:visible;border:solid '..grid..';border-width:0 1px 0 0;'..float..'top:15px;width:0px;height:'..yheight..'px;left:'..(xpos+40)..'px;"></div><div style="position:absolute;top:-2px;width:40px;font-size:75%;color:'..grid..';text-align:right;left:'..(xpos+10)..'px;">'..tostring(math.abs(gridline))..((gridline<0) and "W" or "E")..'</div>'
end
for gridline=math.floor(regionedge[north]/pt)*pt,math.ceil(regionedge[south]/pt)*pt,-1*pt do
local ypos=math.floor(((regionedge[north]-gridline)*heightratio)*scale)
output=output..'<div style="position:absolute;overflow:visible;border:solid '..grid..';border-width:0 0 1px 0;'..float..'top:'..(ypos+15)..'px;height:0px;width:'..displaywidth..'px;left:40px;"></div><div style="position:absolute;top:'..(ypos+6)..'px;width:40px;font-size:75%;color:'..grid..';text-align:right;left:0px;">'..tostring(math.abs(gridline))..((gridline<0) and "S" or "N")..'</div>'
end
end
if featurelat[1] then
for i=1,#featurelat do
if featuretext[i] then
output=output..'<div style="position:absolute;overflow:visible;top:'..math.floor(((regionedge[north]-featurelat[i])*heightratio)*scale+3)..'px;left:'..math.floor(((featurelong[i]-mapedge[west])*widthratio-xfile*mapwidthpx-left)*scale+33)..'px;">'..featuretext[i]..'</div>'
else
local linkstring=''
if featurename[i] then linkstring='|link='..featurename[i]..'|'..featurename[i] end
output=output..'<div style="position:absolute;overflow:visible;height:15px;width:15px;top:'..math.floor(((regionedge[north]-featurelat[i])*heightratio)*scale+10.5-(featuresize[i] or 15)/2)..'px;left:'..math.floor(((featurelong[i]-mapedge[west])*widthratio-xfile*mapwidthpx-left)*scale+40.5-(featuresize[i] or 15)/2)..'px;">[[File:'..(featureimage[i] or 'Full Star Yellow.svg')..'|'..(featuresize[i] or '15')..'px'..linkstring..']]</div>'
end
end
end
output = '<div style="position:relative;overflow:hidden;'..float..'width:'..(displaywidth+60)..'px;height:'..math.ceil((bottom-top)*scale+22)..'px;">'..output..'</div>'
if nowiki or errcode then return frame:preprocess("<nowiki>"..output..debuglog.."</nowiki>") end
return output
end
return p