Mô đun:dialect map
Giao diện
- Trang mô đun này thiếu trang con tài liệu. Xin hãy tạo trang tài liệu này.
- Liên kết hữu ích: danh sách trang con • liên kết • nhúng • trường hợp kiểm thử • chỗ thử
local export = {}
local m_links = require("Module:links")
local m_lang = require("Module:languages")
local RADIUS = 0
function export.map_header(text)
return tostring(
mw.html.create("h2")
:wikitext(text)
:done()
)
end
function export.get_coords(lat, long, i, n)
if n > 1 then
return { long + RADIUS * math.cos(2 * math.pi * i / n), lat + RADIUS * math.sin(2 * math.pi * i / n) }
else
return { long, lat }
end
end
function export.make_point(lat, long, color, term, term_url, location, i, n, group, marker_lang, group_id, term_group, wikitext, legend_wikitext)
local properties = {
["term"] = term,
["loc"] = location,
["marker-color"] = color,
["marker-group"] = group,
["marker-lang"] = marker_lang,
["group-id"] = group_id,
["term-group"] = term_group,
["wikitext"] = wikitext,
}
if term_url then
properties["url"] = term_url
end
if legend_wikitext then
properties["legendWikitext"] = legend_wikitext
end
return {
type = "Feature",
properties = properties,
geometry = {
type = "Point",
coordinates = export.get_coords(lat, long, i, n)
}
}
end
function export.make_polygon(text, color, points)
local coords = {}
for _, pt in ipairs(points) do
table.insert(coords, { pt.long, pt.lat })
end
table.insert(coords, { points[1].long, points[1].lat })
return {
type = "Feature",
properties = {
title = text,
["stroke-width"] = 0,
["fill"] = color,
["fill-opacity"] = 0.2,
},
geometry = {
type = "Polygon",
coordinates = { coords },
}
}
end
function export.make_map(frame, geojson, config)
return tostring(
mw.html.create("div")
:addClass("thumb")
:addClass("dialect-map__container")
:wikitext(frame:extensionTag("mapframe", mw.text.jsonEncode(geojson), {
class = "dialect-map__map",
mapstyle = config.mapstyle or "osm-intl",
width = config.width or "100%",
height = config.height or 600,
latitude = config.lat,
longitude = config.long,
zoom = config.zoom
}))
:done()
)
end
function export.show(frame)
local geojson = {}
local params = {
[1] = { required = false, sublist = true },
}
local args = require("Module:parameters").process(frame:getParent().args, params)
local terms = args[1]
local lang_code = nil
local id = nil
if terms and terms[1] and terms[1]:find(":") then
local first_part = terms[1]
local colon_pos = first_part:find(":")
if colon_pos then
local prefix = first_part:sub(1, colon_pos - 1)
if prefix:match("^%w[%w%-]*$") then
lang_code = prefix
end
end
end
if not terms then
local title_text = mw.title.getCurrentTitle().text
local parts = mw.text.split(title_text, "/")
if not lang_code then lang_code = parts[2] end
if parts[3] then
terms = { parts[3] }
end
if not id then id = parts[4] end
end
if not lang_code then
local t_str = terms and table.concat(terms, ", ") or ""
return "Error: Language code could not be determined from term '" .. t_str .. "'."
end
local is_multi_term = #terms > 1
local locs = {}
local n = {}
local used_langs = {}
local map_configs = {}
local map_groups = {}
local function load_map_config(code)
if not used_langs[code] then
local success, map_data = pcall(require, "Mô đun:dialect map/data/" .. code)
if success and map_data then
used_langs[code] = true
table.insert(map_configs, map_data.config)
if map_data.groups then
for _, g in ipairs(map_data.groups) do
table.insert(map_groups, g)
end
end
end
end
end
load_map_config(lang_code)
local map_config = map_configs[1]
local lang = m_lang.getByCode(map_config.lang_code or lang_code)
local synonym_properties = nil
local data_module = require("Mô đun:dialect synonyms")
local function parse_map_term(raw_term, default_code)
local t = mw.text.trim(raw_term)
local code = default_code
local id = nil
local colon_pos = t:find(":")
if colon_pos then
local prefix = t:sub(1, colon_pos - 1)
if prefix:match("^%w[%w%-]*$") then
code = prefix
t = t:sub(colon_pos + 1)
end
end
local id_match = t:match("<id:([^>]+)>")
if id_match then
id = id_match
t = t:gsub("<id:[^>]+>", "")
end
return code, t, id
end
for _, raw_term in ipairs(terms) do
local current_full_term = mw.text.trim(raw_term)
local target_lang, term_text, current_id = parse_map_term(raw_term, lang_code)
if is_multi_term and not current_id then
current_id = nil
elseif not is_multi_term and not current_id then
current_id = id
end
load_map_config(target_lang)
local dataset, properties, validation
if term_text == ".all" then
local path = "Mô đun:dialect synonyms/" .. target_lang
if mw.title.new(path).exists then
local lang_data = require(path)
dataset = { varieties = lang_data.varieties, properties = { title = "Tất cả địa danh" } }
end
else
dataset, _, _ = data_module.get_data(target_lang, term_text, current_id, nil, true)
end
if dataset then
synonym_properties = dataset.properties
local varieties = dataset.varieties
local function __traverse(nodes, group_path)
for _, node in ipairs(nodes) do
if node[1] then
local child_path = group_path
if node.name then
child_path = group_path .. (group_path ~= "" and "/" or "") .. node.name
end
__traverse(node, child_path)
elseif (term_text == ".all" and node.name) or (node.syns and node.syns[1]) then
n[node.name] = (n[node.name] or 0)
local i = 0
local current_path = group_path
if node.name then
current_path = group_path .. (group_path ~= "" and "/" or "") .. node.name
end
if term_text == ".all" then
n[node.name] = n[node.name] + 1
local point_index = n[node.name]
local key_form = node.name
locs[key_form] = locs[key_form] or {}
local parsed = { term = node.name }
table.insert(locs[key_form], { node, current_path, point_index, parsed, target_lang })
else
for _, parsed in ipairs(node.syns) do
if type(parsed) == "table" and (parsed.term or parsed.ipa) then
local key_form = parsed.group or parsed.alt or parsed.term or parsed.ipa
if is_multi_term then
key_form = current_full_term .. "::" .. key_form
end
if parsed.tr and not parsed.group then
key_form = key_form .. "::tr:" .. parsed.tr
end
n[node.name] = n[node.name] + 1
local point_index = n[node.name]
locs[key_form] = locs[key_form] or {}
table.insert(locs[key_form], { node, current_path, point_index, parsed, target_lang })
end
end
end
end
end
end
__traverse(varieties, "")
end
end
if #map_configs > 1 then
local min_lat, max_lat = 90, -90
local min_long, max_long = 180, -180
local sum_lat, sum_long, sum_zoom = 0, 0, 0
for _, cfg in ipairs(map_configs) do
local c_lat = cfg.lat or 0
local c_long = cfg.long or 0
sum_lat = sum_lat + c_lat
sum_long = sum_long + c_long
sum_zoom = sum_zoom + (cfg.zoom or 6)
if c_lat < min_lat then min_lat = c_lat end
if c_lat > max_lat then max_lat = c_lat end
if c_long < min_long then min_long = c_long end
if c_long > max_long then max_long = c_long end
end
map_config = mw.clone(map_config)
map_config.lat = sum_lat / #map_configs
map_config.long = sum_long / #map_configs
local avg_zoom = sum_zoom / #map_configs
local lat_spread = max_lat - min_lat
local long_spread = max_long - min_long
local max_spread = math.max(lat_spread, long_spread)
local margin = 360 / (2 ^ avg_zoom)
local target_span = max_spread + margin
if target_span > 0 then
map_config.zoom = math.floor(math.log(720 / target_span) / math.log(2))
else
map_config.zoom = math.floor(avg_zoom)
end
end
map_config.legend_format = map_config.legend_format or "grid"
if map_config.legend_format ~= "grid" and map_config.legend_format ~= "table" then
return "Error: Invalid map legend format '" .. tostring(map_config.legend_format) .. "'. Supported formats are 'grid' (default) and 'table'."
end
if not next(locs) then
return "Error: Could not load dialect data for any provided terms."
end
local forms = {}
for form, _ in pairs(locs) do
table.insert(forms, form)
end
local function get_handler(code)
if not code then return nil end
local success, map_handler = pcall(require, "Mô đun:dialect map/handlers/" .. code)
if success and map_handler then
return map_handler
end
end
local function make_cache_key(parsed)
local keys = {}
for k in pairs(parsed) do
table.insert(keys, k)
end
table.sort(keys)
local parts = {}
for _, k in ipairs(keys) do
local v = parsed[k]
if type(v) == "boolean" then
elseif type(v) == "table" then
table.insert(parts, k .. ":" .. table.concat(v, "|"))
else
table.insert(parts, k .. ":" .. tostring(v or ""))
end
end
return table.concat(parts, "::")
end
local link_cache = {}
local function __get_formatted_term(lect, parsed)
local key = make_cache_key(parsed)
if link_cache[key] then
return link_cache[key]
end
local link_data = {}
for k, v in pairs(parsed) do
link_data[k] = v
end
if not link_data.lang then
link_data.lang = lang
end
if link_data.q and not link_data.qq then
link_data.qq = link_data.q
link_data.q = nil
elseif link_data.q and link_data.qq then
if type(link_data.q) ~= "table" then link_data.q = {link_data.q} end
if type(link_data.qq) ~= "table" then link_data.qq = {link_data.qq} end
for _, v in ipairs(link_data.q) do
table.insert(link_data.qq, v)
end
link_data.q = nil
end
local word
local handler = get_handler(lect.code_main)
if handler and handler.format_term then
word = handler.format_term(lect, link_data)
elseif handler and handler.make_link then
word = handler.make_link(link_data)
else
if link_data.qq then
link_data.show_qualifiers = true
end
word = m_links.full_link(link_data)
end
link_cache[key] = word
return word
end
local function format_term_legend(data_variety, data)
if type(data) == "string" then return data end
local term = data.term or ""
local alt = data.alt or term
local display_term = (alt and alt ~= term) and alt or term
local temp_data = {
term = display_term,
alt = alt ~= term and alt or nil,
ipa = data.ipa or nil,
tr = data.tr or nil,
ts = data.ts or nil,
lang = data.lang or data_variety.lang or nil,
show_qualifiers = false,
}
local word
local handler = get_handler(data_variety.code_main)
if handler and handler.format_term_legend then
word = handler.format_term_legend(data_variety, temp_data)
elseif handler and handler.format_term then
word = handler.format_term(data_variety, temp_data)
elseif handler and handler.make_link then
word = handler.make_link(temp_data)
else
word = m_links.full_link(temp_data)
end
return word
end
local function __get_legend_formatted_term(lect, parsed)
local key = "legendterm::" .. make_cache_key(parsed)
if link_cache[key] then
return link_cache[key]
end
local link_data = {}
for k, v in pairs(parsed) do
link_data[k] = v
end
if not link_data.lang then
link_data.lang = lang
end
local word = format_term_legend(lect, link_data)
link_cache[key] = word
return word
end
local handler = get_handler(lang_code)
table.sort(forms, function(a, b)
return #locs[a] > #locs[b] or (#locs[a] == #locs[b] and a < b)
end)
local function __get_coordinates(lect)
if lect.lat and lect.long then
return lect.lat, lect.long
end
if mw.wikibase and lect.wikidata then
local qid = lect.wikidata
if type(qid) == "number" then qid = "Q" .. qid end
local entity = mw.wikibase.getEntity(qid)
if entity and entity.claims and entity.claims.P625 and entity.claims.P625[1] then
local snak = entity.claims.P625[1].mainsnak
if snak and snak.datavalue and snak.datavalue.value then
return snak.datavalue.value.latitude, snak.datavalue.value.longitude
end
end
end
return nil, nil
end
for idx, form_key in ipairs(forms) do
local group_id = "term-group-" .. idx
for _, lect_i in ipairs(locs[form_key]) do
local lect, group_path, i, parsed, dataset_lang = lect_i[1], lect_i[2], lect_i[3], lect_i[4], lect_i[5]
local term_display
if handler and handler.make_map_term_display then
term_display = handler.make_map_term_display(parsed.term or parsed.ipa, parsed)
else
term_display = parsed.alt or parsed.term or parsed.ipa
end
local term_lang = parsed.lang or lang
local marker_code = term_lang:getCode()
local dataset_lang_obj = m_lang.getByCode(dataset_lang)
local full_group_path = dataset_lang_obj and dataset_lang_obj:getCanonicalName() or dataset_lang
if group_path and group_path ~= "" then
full_group_path = full_group_path .. "/" .. group_path
end
local term_url = nil
if handler and handler.make_map_term_url then
term_url = handler.make_map_term_url(parsed.term or parsed.ipa, term_lang, parsed)
end
local location_name = lect.text_display or lect.name
local loc_label = location_name
if handler and handler.format_map_location_name then
loc_label = handler.format_map_location_name(lect, parsed) or loc_label
end
local lat, long = __get_coordinates(lect)
if lat and long then
local term_wikitext = __get_formatted_term(lect, parsed)
local legend_wikitext = __get_legend_formatted_term(lect, parsed)
if handler and handler.make_map_point then
table.insert(geojson, handler.make_map_point(lat, long, color,
term_display, term_url, loc_label, i, n[lect.name], full_group_path, marker_code, group_id, parsed.group, term_wikitext, legend_wikitext))
else
table.insert(geojson, export.make_point(lat, long, color,
term_display, term_url, loc_label, i, n[lect.name], full_group_path, marker_code, group_id, parsed.group, term_wikitext, legend_wikitext))
end
end
end
end
local title = (terms[1] or ""):gsub("[0-9%-]", "")
local term_link
local is_mixed_langs = false
local lang_count = 0
for _ in pairs(used_langs) do lang_count = lang_count + 1 end
if lang_count > 1 then is_mixed_langs = true end
if is_multi_term then
local links = {}
local mixed_links = {}
for _, raw_term in ipairs(terms) do
local target_lang_code, t_term, _ = parse_map_term(raw_term, lang_code)
local t_lang = m_lang.getByCode(target_lang_code) or lang
local link = m_links.full_link({
lang = t_lang,
term = t_term,
never_call_transliteration_module = true,
no_generate_forms = true,
})
table.insert(links, link)
table.insert(mixed_links, t_lang:getCanonicalName() .. " " .. link)
end
if is_mixed_langs then
term_link = table.concat(mixed_links, ", ")
else
term_link = table.concat(links, ", ")
end
else
term_link = m_links.full_link(
{
lang = lang,
term = title,
gloss = synonym_properties.gloss or synonym_properties.meaning,
never_call_transliteration_module = true,
no_generate_forms = true,
}
)
end
local map_header
if is_mixed_langs then
local mixed_list_text = mw.text.listToText(mw.text.split(term_link, ", "), ", ", " và ")
local header_text = "Bản đồ của " .. mixed_list_text
map_header = export.map_header(header_text)
elseif handler and handler.make_map_header then
map_header = handler.make_map_header(lang, title, synonym_properties.gloss or synonym_properties.meaning, term_link)
else
local header_text
if terms[1] == ".all" then
header_text = "Tất cả địa danh"
elseif handler and handler.get_map_title then
header_text = handler.get_map_title(lang, term_link)
else
header_text = "Bản đồ biến thể và ngôn ngữ của " .. term_link .. " trong " .. lang:getCanonicalName()
end
map_header = export.map_header(header_text)
end
if map_groups then
for _, fam in ipairs(map_groups) do
table.insert(geojson, export.make_polygon(fam.name, fam.color, fam.points))
end
end
local function compress_data(geo_data)
local dictionary = {}
local reverse_dict = {}
local next_id = 0
local function get_id(str)
if not str then return nil end
if reverse_dict[str] then
return reverse_dict[str]
end
local id = "@@" .. next_id .. "@@"
dictionary[tostring(next_id)] = str
reverse_dict[str] = id
next_id = next_id + 1
return id
end
local compressed_features = {}
for _, feature in ipairs(geo_data) do
if feature.geometry and feature.geometry.type == "Point" and feature.properties then
local p = feature.properties
local g = feature.geometry
local coords = g.coordinates
local lat = math.floor(coords[2] * 10000 + 0.5) / 10000
local lon = math.floor(coords[1] * 10000 + 0.5) / 10000
local row = {
0,
lat,
lon,
get_id(p["wikitext"]) or "",
get_id(p["legendWikitext"]) or "",
get_id(p["term"]) or "",
get_id(p["url"]) or "",
get_id(p["term-group"]) or "",
get_id(p["marker-group"]) or "",
p["marker-color"] or "",
p["marker-lang"] or "",
p["group-id"] or "",
get_id(p["loc"]) or ""
}
table.insert(compressed_features, row)
else
local new_feat = { ["t"] = "f" }
if feature.properties then
local p = feature.properties
if p.wikitext then p.wikitext = get_id(p.wikitext) end
if p.legendWikitext then p.legendWikitext = get_id(p.legendWikitext) end
if p.term then p.term = get_id(p.term) end
if p.url then p.url = get_id(p.url) end
if p["term-group"] then p["term-group"] = get_id(p["term-group"]) end
if p["marker-group"] then p["marker-group"] = get_id(p["marker-group"]) end
if p["loc"] then p["loc"] = get_id(p["loc"]) end
new_feat["p"] = p
end
if feature.geometry then
local g = feature.geometry
local new_g = {}
if g.type == "Polygon" then new_g["t"] = "pl" else new_g["t"] = g.type end
new_g["c"] = g.coordinates
new_feat["g"] = new_g
end
table.insert(compressed_features, new_feat)
end
end
return {
d = dictionary,
f = compressed_features
}
end
local compressed_data = compress_data(geojson)
local map_json = mw.text.jsonEncode(compressed_data)
map_json = mw.text.nowiki(map_json)
local map = tostring(
mw.html.create("div")
:addClass("thumb")
:addClass("dialect-map__container")
:addClass("dialect-map-app")
:attr("data-mapstyle", map_config.mapstyle or "osm-intl")
:attr("data-mode", terms[1] == ".all" and "all" or "term")
:attr("data-lat", tostring(map_config.lat))
:attr("data-lon", tostring(map_config.long))
:attr("data-zoom", tostring(map_config.zoom))
:attr("data-show-group", map_config.show_group and "true" or "false")
:css("position", "relative")
:css("width", (map_config.width or "100%"))
:tag("div")
:addClass("dialect-map-placeholder")
:css("width", "100%")
:css("height", (map_config.height or 600) .. "px")
:css("background-color", "#f8f9fa")
:css("border", "1px solid #c8ccd1")
:css("display", "flex")
:css("align-items", "center")
:css("justify-content", "center")
:css("color", "#72777d")
:wikitext("Loading map...")
:done()
:tag("div")
:addClass("dialect-map-data")
:css("display", "none")
:wikitext(map_json)
:done()
:done()
)
local category = ""
local current_title = mw.title.getCurrentTitle().prefixedText
if current_title:find("^Bản mẫu:dialect map/") then
local categories = {}
for code, _ in pairs(used_langs) do
local cat_lang = m_lang.getByCode(code)
if cat_lang then
local sort_key = cat_lang:makeSortKey(terms[1] or "")
table.insert(categories, "[[Thể loại:Bản đồ phương ngữ tương đương " .. cat_lang:getCanonicalName() .. "|" .. sort_key .. "]]")
end
end
category = table.concat(categories, "")
end
return map_header .. map .. category
end
return export