Module:Infobox dim

From English Wikipedia @ Freddythechick

require('strict')
local getArgs = require('Module:Arguments').getArgs
local p = {}

local log2 = 0.693147181
local ppm = 1000/0.3  -- pixels per meter, from 0.3 mm / pixel

-- Convert from Geohack's scale and dim levels to OSM style zoom levels as used by <maplink>
local function geohackScaleToMapZoom(scale)
	scale = tonumber(scale)
	if not scale or scale <= 0 then return end
	-- Approximation of https://wiki.openstreetmap.org/wiki/Zoom_levels
    -- pixel/m from OSM is exact, so use zoom level 9 and 0.3 mm/pixel
	return math.log(305.748*ppm/scale)/log2 + 9
end

local function geohackDimToScale(dim, args)
	dim = tonumber(dim)
	args = args or {}
	if not dim or dim <= 0 then return end
	local units = args.units
	if units and string.lower(units) == 'km' then
		dim = dim*1000
	end
	-- geohack assumes a map width of 10cm on the screen, so use this as default
	local viewport = args.viewport_cm and args.viewport_cm / 100 
	              or args.viewport_px and args.viewport_px / ppm or 0.1
	return dim / viewport
end

-- inverse of above function, returning dim in km
local function geohackScaleToDim(scale, args)
	scale = tonumber(scale)
	args = args or {}
	if not scale or scale <= 0 then return end
	local viewport = args.viewport_cm and args.viewport_cm / 100 
	              or args.viewport_px and args.viewport_px / ppm or 0.1	
	return scale * viewport * 1e-3
end

-- Convert from Geohack's types to Geohack's scale levels
local function geohackTypeToScale(args)
	args = args or {}
	local type = args.type
	local typeScale = mw.loadData('Module:Infobox_dim/data')
	local scale
	if typeScale[type] then
		scale = typeScale[type]
	end
	local population = tonumber(args.population)
	if type == 'city' and population and population > 0 then
		-- assume city is a circle with density of 1000/square kilometer
		-- compute diameter, in meters
		local diam = 35.68*math.sqrt(population)
		-- convert to scale
		scale = geohackDimToScale(diam, args)
		-- don't zoom in too far
		if scale < 25000 then
			scale = 25000
		end
	end
	return scale
end

-- Convert from dimension of object to Geohack dim level
local function computeDim(length,width,area)
	if length and width then
		return 0.5*math.max(length,width)
	end
	if length then return 0.5*length end
	if width then return 0.5*width end
	if area then return 0.977*math.sqrt(area) end
end

local function convertDim(args)
	for _, arg in pairs({'length_mi','length_km','width_mi','width_km',
		'area_mi2','area_km2','area_acre','area_ha'}) do
		args[arg] = args[arg] and mw.ustring.gsub(args[arg],",","")
		args[arg] = tonumber(args[arg])
		if args[arg] and args[arg] <= 0 then args[arg] = nil end
	end
	local length = args.length_mi and 1.60934*args.length_mi or args.length_km
	local width = args.width_mi and 1.60934*args.width_mi or args.width_km
	local area = args.area_acre and 0.00404686*args.area_acre or 
		args.area_ha and 0.01*args.area_ha or 
		args.area_mi2 and 2.58999*args.area_mi2 or args.area_km2
	local dim = computeDim(length, width, area)
	return dim
end

-- Module entry points
function p._dim(args)
	if args.dim then return args.dim end
	local dim = convertDim(args)
	if not dim then
		local scale = args.scale or geohackTypeToScale(args)
		dim = geohackScaleToDim(scale, args)
	end
	return dim and tostring(math.floor(dim+0.5))..'km'
end

function p._scale(args)
	if args.scale then return args.scale end
	local dim, units, scale
	if args.dim then
		dim, units = mw.ustring.match(args.dim,"^([-%d%.]+)%s*(%D*)")
		args.units = units
		scale = geohackDimToScale(dim, args)
	end
	if not scale then
		dim = convertDim(args)
		args.units = 'km'
		scale = dim and geohackDimToScale(dim, args)
	end
	if args.type and not scale then
		args.units = ''
		scale = geohackTypeToScale(args)
	end
	if not scale then return end
	scale = math.floor(scale+0.5)
	-- keep scale within sane bounds
	if scale < 2000 then
		scale = 2000
	end
	if scale > 250e6 then
		scale = 250e6
	end
	return scale
end

function p._zoom(args)
	local scale = p._scale(args)
	if scale then
		local zoom = geohackScaleToMapZoom(scale)
		return zoom and math.floor(zoom+0.5)
	end
end		

-- Template entry points
function p.dim(frame)
	return p._dim(getArgs(frame)) or ''
end

function p.scale(frame)
	return p._scale(getArgs(frame)) or ''
end

function p.zoom(frame)
	return p._zoom(getArgs(frame)) or ''
end

return p