Jump to content

Module:Infobox dim/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Hike395 (talk | contribs) at 23:07, 6 October 2024 (more general tweaks). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
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 math.max(length,width)
	end
	if length then return length end
	if width then return width end
	if area then return 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

function multiplyIfNumber(x,c)
	x = tonumber(x)
	return x and x*c or x
end

-- Module entry points
function p._dim(args)
	if args.dim then return args.dim end
	local dim = multiplyIfNumber(convertDim(args),0.5)
	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 = multiplyIfNumber(convertDim(args),args.zoom and 1 or 0.5)
		args.units = 'km'
		scale = dim and geohackDimToScale(dim, args)
	end
	if args.type and not scale then
		args.units = ''
		scale = multiplyIfNumber(geohackTypeToScale(args),args.zoom and 2.4 or 1.0)
	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)
	args.zoom = true
	local scale = p._scale(args)
	if scale then
		local zoom = geohackScaleToMapZoom(scale)
		return zoom and math.floor(zoom)
	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