Aller au contenu

Module:Population

Une page de Wikipédia, l'encyclopédie libre.
Ceci est une version archivée de cette page, en date du 8 mars 2021 à 20:26 et modifiée en dernier par Zolo (discuter | contributions). Elle peut contenir des erreurs, des inexactitudes ou des contenus vandalisés non présents dans la version actuelle.

 Documentation[créer] [purger]
local p = {}


--
-- Les modules et sous-modules dont on aura besoin
--
local wd = require "Module:Wikidata"
local mathmod = require "Module:math"
local chartes = require "Module:Chartes"
local demographie = require "Module:Démographie"


-- sous-modules 
p.intro = require("Module:Population de France/Introductions")


local texts = {
	["no item"] = "élément Wikidata non trouvé pour le $param",
	["no datatable"] = "page de table de donnée trouvée pour l'élément $param",
	["invalid datatable"] = "la page de données $param n'a pas pu être correctement analysée",
	["missing param"] = "paramètre manquant : $param",
	["error"] = "erreur dans Module:Population",
	["errorcat"] = "Page avec un problème sur les donnnées démographiques",
	["decrease"] = "en diminution de $num par rapport à $date",
	["increase"] = "en augmentation de $num par rapport à $date",
	["stable"] = "en stagnation par rapport à $date",
	["cat"] = "Article avec module Population de France",
	["graph title"] =  "Histogramme de l'évolution démographique",
	["table title"] = "Évolution de la population",
}


-- Fonctions d'erreur
local  function err(errtype,param)
	local cat, mesg
	
	-- message à afficher sur la page
	if errtype == "no item" then
		msg = texts["no item"]
	elseif errtype == "no datatable" then
		msg = texts["no datatable"]
	elseif errtype then
		msg = texts[errtype] or errtype
	else
		msg = texts["error"]
	end
	
	-- catégorie de maintenance à ajouter à la page
	-- une seule catégorie d'erreur. Plus simple et suffisant ?
	cat = texts["errorcat"]
	if cat then
		cat = "[[Category:" .. cat .. "]]"
	end
	if msg then
		msg = '<span style="color:red">' .. mw.ustring.gsub(msg, "$param", (param or "")) .. "</span>"
	end
	return (msg or "") .. (cat or "")
end

-- Récupération des données Commmons, à partir d'un identifiant Wikidata
local function getTable(qid)
	if (not qid) then
		return nil
	end
	
	local filename = wd.formatStatements{entity = qid, property = 'P4179', numval = 1}
	local file = mw.ext.data.get(filename)
	
	if (not filename) or (not file) then
		return nil
	end

	return filename, file
end

-- essai de récupération du nom de l'éntité géographique et de son identifiant Wikidata, en fonction des infos disponibles
local function getIdAndName(id, name)
	if (not id) then -- si pas d'id Wikidata, prendre celui de la page active
		id = mw.wikibase.getEntityIdForTitle(name or  mw.title.getCurrentTitle().subpageText)
	end
	if (not name) then
		name = wd.getLabel(name) or mw.title.getCurrentTitle().subpageText
	end
	return id, name
end

-- Cherche le numéro de colonne du fichier Commons pour les différentes infos nécessaires
local function analyseData(data)
	local fields = data.schema.fields
	local fieldnums = {}

	for num, j in ipairs(fields) do
		if string.find(fields[num].name, "P585") then
			fieldnums["date"] = num
		elseif string.find(fields[num].name, "P1082") then
			fieldnums["population"] = num
		elseif string.find(fields[num].name, "P459") then
			fieldnums["method"] = num
		elseif string.find(fields[num].name, "P1013") then
			fieldnums["criterion"] = num
		elseif string.find(fields[num].name, "S248") then
			fieldnums["source"] = num
		elseif string.find(fields[num].name, "S854") then
			fieldnums["sourceurl"] = num
		end
	end
	
	-- exits if no field for population or date
	if not (fieldnums["population"] and fieldnums["date"]) then
		return error()
	end
	return fieldnums
end

-- extrait l'année d'une date en format pseudoISO, à améliorer
local function getYear(data, fields)
	local d = data[fields.date]
	d = string.gsub(d, "+", "")
	return string.sub(d, 1, 4)
end

--[[
  Sous-module de Module:Tableau population d'article de commune de France contenant
  les fonctions de génération des sources associées à des données de population
--]]



local function sources(data, country)
	if country == "Q142" then
		return require "Module:Population/France".sources(data)
	end
	if data.sources then
		return data.sources
	end
	return nil
end


-- sources pour les graphiques
local function sources_graphique(data, country)
	if country == "Q142" then
		return require "Module:Population/France".sources_graphique(data)
	end
	if data.sources then
		return data.sources
	end
	return nil
end

	
local function keepVal(data, fields, country)
	if country == "Q142" then -- France
		return require"Module:Population/France".keepval(data, fields)
	end
	return true -- on garde par défaut
end

local function notes(data, vnom, country)
	if country == "Q142" then
		return require "Module:Population/France".notes(data, vnom)
	end
	return nil
end


local function color2rgb(color)
	-- on retire d'abord l'éventuel "nowiki" : <nowiki>#</nowiki>RRGGBB ou <nowiki />#RRGGBB
	color = mw.text.unstripNoWiki(color)
	-- on retire ensuite le '#' si présent : #RRGGBB ou &#35;RRGGBB
	color = color:gsub('^#', ''):gsub('^&#35;', '')

	local r, g, b
	if (color:match('^%x%x%x%x%x%x$')) then
		r = color:sub(1, 2)
		g = color:sub(3, 4)
		b = color:sub(5, 6)
	elseif (color:match('^%x%x%x$')) then
		r = color:sub(1, 1):rep(2)
		g = color:sub(2, 2):rep(2)
		b = color:sub(3, 3):rep(2)
	else
		return nil
	end
	local vr = tonumber(r, 16)
	local vg = tonumber(g, 16)
	local vb = tonumber(b, 16)
	return mathmod._round(vr/255, 2) .. "," .. mathmod._round(vg/255, 2) .. "," .. mathmod._round(vb/255, 2)
end

local function clean(val)
	if (val == nil) then
		return val
	end
	if (type(val) ~= "string") then
		return val
	end
	if ( mw.text.trim(val) == "") then
		return nil
	end
	return val
end
	
local function getData(id, name)
	local id, name =  getIdAndName(id, name)
	if (not id) then
		return error(), "no item", name
	end
	
	local datafile, data = getTable(id, name)
	
	if not (datafile and data) then
		return error(), "no datable", id
	end
	
	local fields = analyseData (data)
	
	if (not fields) or (fields == nil) then
		return error(), "invalid datatable", datafile
	end
	
	return name, id, datafile, data, fields
end


function p.tableau_m(param)
	local dataloaded, name, id, datafile, data, fields = pcall(getData, param["id"], param["name"])
	
	if not dataloaded then
		return err(name, id) -- retourne une erreur avec les deux paramètres retournés en cas d'erreur par la fonction getData
	end	
		

	local country = wd.formatStatements{entity = id, property = "P17", numval = 1, displayformat = "raw"}

	local tableFormat = {
	-- on prépare les paramètres à fournir au module Démographie
	["largeur_tableau"] = "48em",
	["taille_police"] = "95%",
	["marge_interlignes"] = "10px",
	["hauteur_lignes"] = "110%",
	["population_fond"] = "#F9F9F9",
	["style_notes"] = "centré",
	}


	-- couleur (on passe par Chartes avec un "truc" pour commune nouvelle)
	local dest = tableFormat
	if (data["division"] == "commune nouvelle") then
		dest["annees_fond"] = chartes.charte_m("geographie", "secondaire", "commune", true)
	else
		dest["annees_fond"] = chartes.charte_m("geographie", "secondaire", data["division"], true)
	end

	-- le titre du tableau
	dest["titre"] = (param["titre"] or texts["table title"])
	.. " &nbsp;<small>[&#8202;[[:c:Data:" .. datafile .. "|modifier]]&#8202;]</small>"

	-- notes FIXME
	local tmp = notes(data, vnom, country)
	if ((tmp ~= nil) and (mw.text.trim(tmp) ~= "")) then
		dest["notes"] = tmp
	end

	-- sources 
	tmp = sources(data, country)
	if ((tmp ~= nil) and (mw.text.trim(tmp) ~= "")) then
		dest["sources"] = tmp
	end
	
	-- récupération des chiffres 
	for i, j in ipairs(data.data) do
		if ( keepVal(j, fields, country) or i == #data.data) then -- on garde les données correspondant au critères + la plus récente
			local annee = tonumber(getYear(j, fields))
			dest[annee] = j[fields["population"]]
		end
	end

	-- on effectue l'appel terminal et on retourne (et on ajoute la catégorie)
	return demographie.demographie_m(dest) .. "[[Category:" .. texts["cat"] .. "]]"
end


-- fonction "wrapper" pour la précédente, pour appel depuis un modèle
function p.tableau(frame)
		local param = {}
		-- deux paramètres : nom et titre
		param["name"] = clean(frame.args["nom"] or frame:getParent().args["nom"] or nil)
		param["id"] = clean(frame.args["id"] or frame:getParent().args["id"] or nil)
		param["title"] = clean(frame.args["titre"] or frame:getParent().args["title"] or nil)

	-- on appelle la vraie fonction
	return p.tableau_m(param)
end

--  Fonction créant un Wikitexte présentant les données de population 
function p.introduction_m(param)
	local resu
	
	-- le nom de la cible (param. nom ou titre page)
	local id, name =  getIdAndName(param["id"], param["nom"])
	if not id then return
		err("no item", name)
	end
	local datafile, data = getTable(id, name)

	local success, fields = pcall(analyseData, data)
	if (not success) or (fields == nil) then
		return err("invalid datatable", datafile)
	end

	local country = wd.formatStatements{entity = id, property = "P17", numval = 1, displayformat = "raw"}
	if country == "Q142" then
		return require "Module:Population/France".introduction(param, id)
	end
	return nil
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.introduction(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération de l'unique paramètre (nom)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	param["id"] = clean(frame.args["id"] or pframe.args["id"] or nil)
	return p.introduction_m(param)
end


--[[
  Fonction créant un graphique de population de commune
--]]
function p.graphique_m(param)
	local resu

	local dataloaded, name, id, datafile, data, fields = pcall(getData, param["id"], param["nom"])
	
	if not dataloaded then
		return err(name, id) -- retourne une erreur avec les deux paramètres retournés en cas d'erreur par la fonction getData
	end

	local country = wd.formatStatements{entity = id, property = "P17", numval = 1, displayformat = "raw"}

	-- seuil (nombre de données) à afficher
	param["seuil"] = tonumber(param["seuil"])
	if ((param["seuil"] == nil) or (param["seuil"] <= 0)) then
		param["seuil"] = 5 -- valeur par défaut
	end


	local prm = {  }
	
	-- Paramètre requis par [[Modèle:Histogramme population manuel]]
	local nval, maxpop = 0, 0

	-- récupération des données pertinentes de la table
	local year, pop, lastyear, lastpop
	for i, j in ipairs(data.data) do
		if ( keepVal(j, fields, country) or i == #data.data) then 
			-- on garde les données correspondant au critères + la plus récente
			year = getYear(j, fields)
			pop = j[fields["population"]]
			if year ~= lastyear then -- pour des raisons de lisibilité, seulement 1 donnée par an, à affiner
				table.insert(prm, year) 
				table.insert(prm, pop)
				nval, maxpop = nval + 1, math.max(pop, maxpop) -- pour Modèle:Histogramme
			end
		end
		lastyear, lastpop = year, pop
	end 
	-- si pas assez de valeurs, on ne retourne "rien"
	if (nval < param["seuil"]) then
		return ""  
	end
	prm["nombre"] = nval
	prm["max"] = maxpop
	prm["largeur"] = 710
	prm["hauteur"] = 340
	prm["couleurs"] = "rgb(1,1,1)"
	

	local cb
	if (data["division"] == "commune nouvelle") then
		cb = chartes.charte_m("geographie", "primaire", "commune", true)
	else
		cb = chartes.charte_m("geographie", "primaire", data["division"], true)
	end
	cb = color2rgb(cb)
	if (cb == nil) then
		prm["couleur-barres"] = "rgb(0.7,0.9,0.7)" -- couleur "par défaut"
	else
		prm["couleur-barres"] = "rgb(" .. cb .. ")"
	end

	prm["nom"] = param["nom"]
	prm["titre"] = param["titre"] or texts["graph title"]
	local tmp = {} 


	prm["sources"] = sources_graphique(data, country)

	-- précaution
	if (nbp == 0) then
		err("invalid data", datafile)
	end
	-- on "appelle" le modèle
	return mw.getCurrentFrame():expandTemplate{ title = 'Histogramme population manuel', args = prm }
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.graphique(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	param["id"] = clean(frame.args["id"] or pframe.args["nom"] or nil)
	param["titre"] = clean(frame.args["titre"] or pframe.args["titre"] or nil)
	param["seuil"] = clean(frame.args["seuil"] or pframe.args["seuil"] or nil)
	return p.graphique_m(param)
end



function p.derniere_population_m(params)
	-- Renvoie la date si prm.type = date, une table {valeur, pop} si type = table, la population sinon
	-- Pourrait plus simplement utiliser Wikidata 

	local dataloaded, name, id, datafile, data, fields = pcall(getData, param["id"], param["nom"])
	
	if not dataloaded then
		return err(name, id) -- retourne une erreur avec les deux paramètres retournés en cas d'erreur par la fonction getData
	end

	local pop = data.data[#data.data][fields.population]
	local year = getYear( data.data[#data.data], fields)
	if params["type"] == "date" then
		return year
	elseif params["type"] == "table" then
		return {year, pop}
	else
		return pop
	end
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.derniere_population(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args[2] or pframe.args[2] or nil)
	param["type"] = frame.args[1] or pframe.args[1] or nil
	return p.derniere_population_m(param)
end


--[[
  Retourne une phrase décrivant la variation de population de la commune
    courante (ou "nom") ou un pictograme si "type" = "picto"
--]]
function p.variation_texte_m(param)

	local dataloaded, name, id, datafile, data, fields = pcall(getData, param["id"], param["nom"])
	if not dataloaded then
		return err(name, id) -- retourne une erreur avec les deux paramètres retournés en cas d'erreur par la fonction getData
	end
	
	local pop = data.data[#data.data][fields.population]
	local year = getYear( data.data[#data.data], fields)
	
	local prevyear, prevpop
	for i, j in ipairs(data.data) do
		local d = getYear( data.data[#data.data +1 -i], fields)
		if (year - d) == 5 then
			prevyear = d
			prevpop = data.data[#data.data +1 -i][fields.population]
			break
		end
	end
	
	if not (prevpop and prevyear) then
		return err("invalid datatable", datafile)
	end
	local pctdiff = mathmod._round(100 * (pop - prevpop)/prevpop, 2)
	local txt
	if pctdiff > 0 then
		txt = texts["increase"]
	elseif pctdiff < 0 then
		txt = texts["decrease"]
		pctdiff = - pctdiff
	else
		txt = texts["stagnation"]
	end
	
	txt = mw.ustring.gsub(txt, "$date", year)
	txt = mw.ustring.gsub(txt, "$num", mw.language.getContentLanguage():formatNum(pctdiff) .. "%")
	
	if (param["type"] ~= "picto") then
		return txt
	end
	-- on crée le pictogramme
	if (pctdiff > 0) then
		return "[[Fichier:Increase2.svg|11px|link=|class=noviewer|" .. txt .. "]]"
	elseif (pctdiff < 0) then
		return "[[Fichier:Decrease2.svg|11px|link=|class=noviewer|" .. txt .. "]]"
	else -- stagnation
		return "[[fichier:steady.svg|10px|link=|class=noviewer|" .. txt .. "]]"
	end
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.variation_texte(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	param["type"] = frame.args["type"] or pframe.args["type"] or nil
	return p.variation_texte_m(param)
end


--[[
  Retourne la densité de la commune. Si "précision" indiquée utilise celle-ci,
  sinon précision 1 par défaut
--]]

function p.densite_m(params)
	local name, id = getIdAndName( params["nom"], params["id"])

	if not id then
		return nil -- or error ?
	end

	-- population et densité par Wikdata. Ou prendre la pop des données tabulaires par cohérence ?
	local pop = wd.formatStatements{entity = id, property = 'P1082', numval = 1, displayformat = "raw"}
	local area = wd.formatStatements{entity = id, property = 'P2046', numval = 1, targetunit = "sqkm", displayformat = "raw", showunit = "-"}
	-- TODO: test supplémentaire pour vérifier que la date et la population portent sur la même date ?

	if not (pop and area) then
		return err("invalid data", qid .. " (invalid area or population)")
	end
	pop, area = tonumber(pop), tonumber(area)
	local prc = 1
	if (type(params["précision"]) == "number") then
		prc = params["précision"]
	end


	if (params["format"] ~= nil) then
		return mw.language.getContentLanguage():formatNum(mathmod._round(pop / area, prc))
	else
		return mathmod._round(pop / area, prc)
	end
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.densite(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	param["précision"] = tonumber(frame.args["précision"] or pframe.args["précision"] or nil)
	param["format"] = frame.args["format"] or pframe.args["format"] or nil
	return p.densite_m(param)
end


-- retourne la superificie en km2, sous forme de nombre
function p.superficie_m(param)
	local qid = getIdAndName(param["id"], param["nom"])
	return wd.formatStatements{property = "P2046", displayformat = "raw", targetunit = "sqkm", entity = qid, numval = 1, excludespecal = true}
end
-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.superficie(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	return p.superficie_m(param)
end


function p.graphique_mtext(param)
	local resu

	local dataloaded, name, id, datafile, data, fields = pcall(getData, param["id"], param["nom"])
	
	if not dataloaded then
		return err(name, id) -- retourne une erreur avec les deux paramètres retournés en cas d'erreur par la fonction getData
	end

	local country = wd.formatStatements{entity = id, property = "P17", numval = 1, displayformat = "raw"}

	-- seuil (nombre de données) à afficher
	param["seuil"] = tonumber(param["seuil"])
	if ((param["seuil"] == nil) or (param["seuil"] <= 0)) then
		param["seuil"] = 5 -- valeur par défaut
	end


	local prm = {  }
	
	-- Paramètre requis par [[Modèle:Histogramme population manuel]]
	local nval, maxpop = 0, 0

	-- récupération des données pertinentes de la table
	local year, pop, lastyear, lastpop
	local years, pops = "", ""
	for i, j in ipairs(data.data) do
		if ( keepVal(j, fields, country) or i == #data.data) then 
			-- on garde les données correspondant au critères + la plus récente
			year = getYear(j, fields)
			pop = j[fields["population"]]
			pops = pops .. "," .. pop
			if year ~= lastyear then -- pour des raisons de lisibilité, seulement 1 donnée par an, à affiner
				years = years .. "," .. year
				pops = pops .. "," .. pop
			end
		end 
		lastyear, lastpop = year, pop
	end
	local txt = "{{Graph:Chart|width=800|height=150 e|x=" .. years .. "|y=" .. pops .. "|linewidth=1 |showSymbols = |symbolsShape= cross|yAxisMin= 0}}"
	if true then return mw.getCurrentFrame():preprocess(txt) end
		
	-- si pas assez de valeurs, on ne retourne "rien"
	if (nval < param["seuil"]) then
		return ""  
	end
	prm["nombre"] = nval
	prm["max"] = maxpop
	prm["largeur"] = 710
	prm["hauteur"] = 340
	prm["couleurs"] = "rgb(1,1,1)"
	

	local cb
	if (data["division"] == "commune nouvelle") then
		cb = chartes.charte_m("geographie", "primaire", "commune", true)
	else
		cb = chartes.charte_m("geographie", "primaire", data["division"], true)
	end
	cb = color2rgb(cb)
	if (cb == nil) then
		prm["couleur-barres"] = "rgb(0.7,0.9,0.7)" -- couleur "par défaut"
	else
		prm["couleur-barres"] = "rgb(" .. cb .. ")"
	end

	prm["nom"] = param["nom"]
	prm["titre"] = param["titre"] or texts["graph title"]
	local tmp = {} 


	prm["sources"] = sources_graphique(data, country)

	-- précaution
	if (nbp == 0) then
		err("invalid data", datafile)
	end
	-- on "appelle" le modèle
	return mw.getCurrentFrame():expandTemplate{ title = 'Histogramme population manuel', args = prm }
end

-- fonction "wrapper" pour la fonction précédente, pour appel depuis un modèle
function p.graphiquetest(frame)
	local pframe = frame:getParent()
	local param = {}
	-- récupération des paramètre (nom et titre)
	param["nom"] = clean(frame.args["nom"] or pframe.args["nom"] or nil)
	param["id"] = clean(frame.args["id"] or pframe.args["nom"] or nil)
	param["titre"] = clean(frame.args["titre"] or pframe.args["titre"] or nil)
	param["seuil"] = clean(frame.args["seuil"] or pframe.args["seuil"] or nil)
	return p.graphique_m(param)
end

return p