Модуль:ТранспортнаяСхема: различия между версиями

мНет описания правки
мНет описания правки
Строка 1: Строка 1:
local p = {}
local p = {}


-- Извлечение аргументов
-- Извлечение параметров вызова
local function getArgs(frame)
local function getArgs(frame)
     local args = {}
     local args = {}
Строка 16: Строка 16:
end
end


-- Загрузка JSON данных
-- Загрузка JSON базы данных
local function loadData(args)
local function loadData(dataParam)
    local dataParam = args['данные'] or args[1]
     if not dataParam or dataParam == '' then
     if not dataParam or dataParam == '' then
         error('Не указано имя базы данных или страница JSON (параметр 1 или |данные=)')
         error('Не указана страница данных JSON')
     end
     end
      
      
     local candidates = {}
     local candidates = {
    if mw.ustring.find(dataParam, ':') or mw.ustring.find(dataParam, '%.json$') then
        dataParam,
         table.insert(candidates, dataParam)
         'Шаблон:Интерактивная схема ' .. dataParam .. ' метрополитена/data.json',
    else
         'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json',
        table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. ' метрополитена/data.json')
         'Шаблон:' .. dataParam .. '/data.json',
         table.insert(candidates, 'Шаблон:Интерактивная схема ' .. dataParam .. '/data.json')
         dataParam .. '/data.json'
         table.insert(candidates, 'Шаблон:' .. dataParam .. '/data.json')
     }
         table.insert(candidates, dataParam)
     end
      
      
     for _, title in ipairs(candidates) do
     for _, page in ipairs(candidates) do
         local success, jsonData = pcall(mw.loadJsonData, title)
         local success, json = pcall(mw.loadJsonData, page)
         if success and jsonData then return jsonData end
         if success and json then return json end
          
          
         local titleObj = mw.title.new(title)
         local titleObj = mw.title.new(page)
         if titleObj and titleObj.exists then
         if titleObj and titleObj.exists then
             local raw = titleObj:getContent()
             local raw = titleObj:getContent()
Строка 43: Строка 40:
                 raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '')
                 raw = mw.ustring.gsub(raw, '<noinclude>.-</noinclude>', '')
                 raw = mw.ustring.gsub(raw, '<!--.-?-->', '')
                 raw = mw.ustring.gsub(raw, '<!--.-?-->', '')
                 local ok, decoded = pcall(mw.text.jsonDecode, mw.text.trim(raw))
                 local ok, dec = pcall(mw.text.jsonDecode, mw.text.trim(raw))
                 if ok and decoded then return decoded end
                 if ok and dec then return dec end
             end
             end
         end
         end
     end
     end
     error('Страница данных не найдена: ' .. dataParam)
     error('Страница данных не найдена. Проверено: ' .. table.concat(candidates, ', '))
end
 
-- Функция парсинга координат "X Y"
local function parseCoords(str)
    if not str then return nil, nil end
    local x, y = mw.ustring.match(str, '(%d+%.?%d*)%s+(%d+%.?%d*)')
    return tonumber(x), tonumber(y)
end
end


-- Главная функция отрисовки векторной схемы
-- Генерация нативной кликабельной интерактивной карты
function p.vector(frame)
function p.map(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
     local data = loadData(args)
    local dataName = args['данные'] or args[1] or 'Муроморского'
     local data = loadData(dataName)
      
      
     local width = tonumber(args['ширина_svg'] or 850) or 850
     local file = args['файл'] or args['изображение'] or data.file or 'Схема_Муроморского_метрополитена.png'
     local height = tonumber(args['высота_svg'] or 620) or 620
     local width = args['ширина'] or data.width or '850px'
     local viewBox = args['viewbox'] or ('0 0 ' .. width .. ' ' .. height)
     local alt = args['alt'] or args['название'] or data.title or 'Интерактивная карта скоростного транспорта'
    local defaultRadius = args['радиус'] or data.defaultRadius or '16'
      
      
     local svg = {}
     local mapText = 'Файл:' .. file .. '|' .. width .. '|center|alt=' .. alt .. '\n'
    table.insert(svg, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="' .. viewBox .. '" width="100%" height="auto" style="max-width:' .. width .. 'px; background:#ffffff; border-radius:8px; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif; user-select:none;">')
    mapText = mapText .. '# Координаты станций и линий из базы данных JSON\n'
      
      
    -- Стили подсветки и анимации при наведении (CSS)
    table.insert(svg, '<style>')
    table.insert(svg, '.ts-v-line { transition: opacity 0.25s ease, stroke-width 0.2s ease; cursor: pointer; }')
    table.insert(svg, '.ts-v-station { transition: transform 0.2s ease, r 0.2s ease; cursor: pointer; }')
    table.insert(svg, '.ts-v-station:hover circle { r: 6.5px; stroke-width: 3.5px; }')
    table.insert(svg, '.ts-v-label { font-size: 11px; fill: #24292f; font-weight: 500; pointer-events: all; text-shadow: 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff; transition: fill 0.2s ease; }')
    table.insert(svg, '.ts-v-label:hover { fill: #0969da; font-weight: bold; }')
    table.insert(svg, 'svg:hover .ts-v-group:not(:hover) { opacity: 0.35; }')
    table.insert(svg, '.ts-v-group { transition: opacity 0.25s ease; }')
    table.insert(svg, '</style>')
    -- 1. Слой сетки/фона (декоративный)
    table.insert(svg, '<rect width="100%" height="100%" fill="#fafbfc" rx="8" />')
    -- 2. Слой линий и путей
     if data.lines then
     if data.lines then
         for i, line in ipairs(data.lines) do
         for _, line in ipairs(data.lines) do
             local color = line.color or '#888888'
             -- Зона плашки линии в легенде
             local points = {}
            if line.box and line.article then
            local futurePoints = {}
                mapText = mapText .. 'rect ' .. line.box .. ' [[' .. line.article .. '|' .. (line.displayName or line.name or line.article) .. ']]\n'
             end
              
              
             table.insert(svg, '<g class="ts-v-group ts-v-line-' .. (line.number or i) .. '">')
             -- Кастомные векторные зоны/полигоны линий (если заданы)
            if line.shapes then
                for _, shape in ipairs(line.shapes) do
                    mapText = mapText .. shape .. '\n'
                end
            end
              
              
             -- Сбор координат станций
             -- Станции линии
             if line.stations then
             if line.stations then
                 for _, st in ipairs(line.stations) do
                 for _, st in ipairs(line.stations) do
                     local x, y = parseCoords(st.coords)
                     if st.coords and st.article then
                    if x and y then
                        local r = st.radius or defaultRadius
                         table.insert(points, x .. ',' .. y)
                        local name = st.name or st.article
                         mapText = mapText .. 'circle ' .. st.coords .. ' ' .. r .. ' [[' .. st.article .. '|' .. name .. ']]\n'
                     end
                     end
                 end
                 end
             end
             end
           
        end
            -- Отрисовка основной сплошной трассы
    end
            if #points > 1 then
   
                table.insert(svg, '<polyline class="ts-v-line" points="' .. table.concat(points, ' ') .. '" fill="none" stroke="' .. color .. '" stroke-width="5.5" stroke-linecap="round" stroke-linejoin="round" />')
    -- Дополнительные объекты (вокзалы, пересадочные узлы)
            end
    local extras = {'Вокзалы', 'Ж/д станции', 'Терминалы', 'Аэропорты'}
           
    for _, group in ipairs(extras) do
            -- Отрисовка станций и подписей
        if data[group] then
            if line.stations then
            for _, item in ipairs(data[group]) do
                for _, st in ipairs(line.stations) do
                if item[1] and item[2] then
                    local x, y = parseCoords(st.coords)
                    mapText = mapText .. 'circle ' .. item[2] .. ' ' .. defaultRadius .. ' ' .. item[1] .. '\n'
                    if x and y then
                        local stName = st.name or st.article or ''
                        local article = st.article or stName
                        local isFuture = st.future == true
                       
                        table.insert(svg, '<a href="/wiki/' .. mw.uri.encode(article, 'WIKI') .. '" title="' .. mw.text.nowiki(stName) .. '">')
                        table.insert(svg, '<g class="ts-v-station">')
                       
                        -- Кружок станции
                        if isFuture then
                            table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="4.5" fill="#ffffff" stroke="' .. color .. '" stroke-width="2.5" stroke-dasharray="2,2" />')
                        else
                            table.insert(svg, '<circle cx="' .. x .. '" cy="' .. y .. '" r="5" fill="#ffffff" stroke="' .. color .. '" stroke-width="3" />')
                        end
                       
                        -- Текстовая подпись станции (с небольшим смещением)
                        local textOffset = (x > (width / 2)) and (x + 9) or (x - 9)
                        local textAnchor = (x > (width / 2)) and 'start' or 'end'
                        local textColor = isFuture and '#6e7781' or '#24292f'
                       
                        table.insert(svg, '<text class="ts-v-label" x="' .. textOffset .. '" y="' .. (y + 3.5) .. '" text-anchor="' .. textAnchor .. '" fill="' .. textColor .. '">' .. mw.text.nowiki(stName) .. '</text>')
                        table.insert(svg, '</g></a>')
                    end
                 end
                 end
             end
             end
           
            table.insert(svg, '</g>')
         end
         end
     end
     end
 
   
     table.insert(svg, '</svg>')
     mapText = mapText .. 'desc none\n'
     return table.concat(svg, '\n')
   
    -- Рендер через движок MediaWiki
     return frame:preprocess('{{#tag:imagemap|' .. mapText .. '}}')
end
end


return p
return p