Module:Infobox dim: Difference between revisions

Content deleted Content added
typo
update latitude where zoom is correct to be closer to NA and Europe articles
 
(14 intermediate revisions by 2 users not shown)
Line 4:
 
local log2 = 0.693147181
local ppm = 1000/0.3 -- pixels per meter, from 0.3 mm / pixel from https://wiki.openstreetmap.org/wiki/Zoom_levels
 
-- To convert to OSM zoom level, we need to know meters per pixel at zoom level 9
-- Convert from Geohack's scale and dim levels to OSM style zoom levels as used by <maplink>
-- On the equator, it's 305.748 meters/pixel according to https://wiki.openstreetmap.org/wiki/Zoom_levels
-- This quantity depends on the latitude (which we don't have easy access to)
-- Instead, we'll be correct at 38 degrees N, given en-WP bias towards NA and Europe
-- 38N was chosen because:
-- * It's the furthest north where scale=100000 maps to zoom=12
-- * One hemisphere (20000km) maps to zoom=1
local metersPerPixelLevel9 = 305.748*math.cos(math.rad(38))
 
-- Convert from Geohack's scale 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
return math.log(metersPerPixelLevel9*ppm/scale)/log2 + 9
-- Approximation of https://wiki.openstreetmap.org/wiki/Zoom_levels
end
-- 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
-- compute the viewport size (on screen) in meters, assuming ppm pixels per meter on screen
local function computeViewport(args)
local viewport_cm = tonumber(args.viewport_cm)
local viewport_px = tonumber(args.viewport_px)
return viewport_cm and viewport_cm / 100 or viewport_px and viewport_px / ppm
or tonumber(args.default_viewport) or 0.1
end
 
-- convert from geohack dim (knowing the viewpoint size on screen) to geohack scale
local function geohackDimToScale(dim, units)
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
return dim / computeViewport(args)
-- geohack assumes a map width of 10cm on the screen
-- which seems nuts, but we have to be compatible
-- thus, dim has to correspond to 10cm, so scale is dim (in meters) * 10
return 10 * dim
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
return scale * computeViewport(args) * 1e-43
end
 
local oddShape = 2.09 --- length/sqrt(area) of Boston (to choose an example)
-- Convert from Geohack's types to Geohack's scale levels
 
local function geohackTypeToScale(type, population)
-- Convert from Geohack's types to Geohack dim
local typeScale = {
local function geohackTypeToDim(args)
country = 10000000,
local t = args.type
satellite = 10000000,
if not t then return end
state = 3000000,
local typeDim = mw.loadData('Module:Infobox_dim/data')
adm1st = 1000000,
local dim = typeDim[t]
adm2nd = 300000,
local population = tonumber(args.population)
adm3rd = 100000,
if t == 'city' and population and population > 0 then
city = 100000,
isle = 100000,
mountain = 100000,
river = 100000,
waterbody = 100000,
edu = 10000,
event = 50000,
forest = 50000,
glacier = 50000,
airport = 30000,
landmark = 10000,
railwaystation = 10000,
edu = 10000,
pass = 10000,
camera = 10000
}
local scale
if typeScale[type] then
scale = typeScale[type]
end
population = tonumber(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. Then multiply by oddShape to account for weird shapes
local diamdim = 35.6868e-3*math.sqrt(population)*oddShape
-- convert to scale
scale = geohackDimToScale(diam)
-- don't zoom in too far
if scaledim < 250005 then
scaledim = 250005
end
end
return scaledim
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.977oddShape*math.sqrt(area) end
end
 
-- compute geohack dim from unit arguments (e.g., length_mi)
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
Line 104 ⟶ 92:
end
 
local function computeScale(args)
-- 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.type, args.population)
dim = geohackScaleToDim(scale)
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*)")
scaleargs.units = geohackDimToScale(dim, units)
args.default_viewport = 0.1 -- default geohack viewpoirt
scale = geohackDimToScale(dim, args)
end
if not scale then
dim = convertDim(args) or geohackTypeToDim(args)
args.units = 'km'
scale = dim and geohackDimToScale(dim, 'km')
args.default_viewport = 0.2 --- when object dimensions or type is specified, assume 20cm viewport
scale = dim and geohackDimToScale(dim, args)
end
if args.type and not scale then return end
scale = math.floor(scale+0.5)
scale = geohackTypeToScale(args.type, args.population)
-- keep scale within sane bounds (OSM zoom levels 1-17)
if scale < 1600 then
scale = 1600
end
returnif scale and> math.floor(scale+0.5)200e6 then
scale = 200e6
end
return scale
end
 
-- Argument checking
local positiveNumericArgs = {viewport_cm=true,viewport_px=true,length_mi=true,length_km=true,
width_mi=true,width_km=true,area_mi2=true,area_km2=true,
area_acre=true,area_ha=true,scale=true,population=true}
 
local function cleanArgs(args)
local clean = {}
if type(args) == 'table' then
for k, v in pairs(args) do
if positiveNumericArgs[k] then
v = v and mw.ustring.gsub(v,",","") -- clean out any commas
v = tonumber(v) -- ensure argument is numeric
if v and v <= 0 then -- if non-positive, ignore value
v = nil
end
end
clean[k] = v
end
end
return clean
end
 
-- Module entry points
function p._dim(args)
args = cleanArgs(args)
if args.dim then return args.dim end
-- compute scale for geohack
local scale = args.scale
local dim
if not scale then
args.default_viewport = 0.2 -- when specifying a object dimension or type, assume output spans 20cm
dim = convertDim(args) or geohackTypeToDim(args)
args.units = 'km'
scale = dim and geohackDimToScale(dim, args)
end
-- reset back to 10cm viewport for correct geohack dim output
args.viewport_cm = 10
dim = scale and geohackScaleToDim(scale, args)
return dim and tostring(math.floor(dim+0.5))..'km'
end
 
function p._scale(args)
args = cleanArgs(args)
return computeScale(args)
end
 
function p._zoom(args)
local scale args = p._scalecleanArgs(args)
args.viewport_px = args.viewport_px or 200 --- viewport for Kartographer is 200px high
local scale = computeScale(args)
if scale then
local zoom = geohackScaleToMapZoom(scale)
return zoom and math.floor(zoom+0.5)
end
end