Module:MapClip

--- 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=' ' 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 else 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 end 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 '..tostring(math.abs(gridline))..((gridline<0) and "W" or "E")..' ' 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..' '..tostring(math.abs(gridline))..((gridline<0) and "S" or "N")..' ' end end if featurelat[1] then for i=1,#featurelat do          if featuretext[i] then output=output..''..featuretext[i]..' ' else local linkstring='' if featurename[i] then linkstring='|link='..featurename[i]..'|'..featurename[i] end output=output..' ' end end end output = ''..output..' ' if nowiki or errcode then return frame:preprocess(" "..output..debuglog.." ") end return output end

return p