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

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


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


-- Загрузка JSON базы данных
-- Загрузка данных из data.json
local function loadData(dataParam)
local function loadData(args)
     if not dataParam or dataParam == '' then
     local dataParam = args['данные'] or args[1] or 'Муроморского'
        error('Не указана страница данных JSON')
    end
   
     local candidates = {
     local candidates = {
         dataParam,
         dataParam,
Строка 30: Строка 27:
     }
     }
      
      
     for _, page in ipairs(candidates) do
     for _, title in ipairs(candidates) do
         local success, json = pcall(mw.loadJsonData, page)
         local success, jsonData = pcall(mw.loadJsonData, title)
         if success and json then return json end
         if success and jsonData then return jsonData end
          
          
         local titleObj = mw.title.new(page)
         local titleObj = mw.title.new(title)
         if titleObj and titleObj.exists then
         if titleObj and titleObj.exists then
             local raw = titleObj:getContent()
             local raw = titleObj:getContent()
Строка 40: Строка 37:
                 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, dec = pcall(mw.text.jsonDecode, mw.text.trim(raw))
                 local ok, decoded = pcall(mw.text.jsonDecode, mw.text.trim(raw))
                 if ok and dec then return dec end
                 if ok and decoded then return decoded end
             end
             end
         end
         end
     end
     end
     error('Страница данных не найдена. Проверено: ' .. table.concat(candidates, ', '))
     error('Страница данных JSON не найдена: ' .. dataParam)
end
end


-- Генерация нативной кликабельной интерактивной карты
-- Парсер координат "X Y"
function p.map(frame)
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
 
-- Генерация карты на чистом HTML/CSS
function p.canvas(frame)
     local args = getArgs(frame)
     local args = getArgs(frame)
    local dataName = args['данные'] or args[1] or 'Муроморского'
     local data = loadData(args)
     local data = loadData(dataName)
      
      
     local file = args['файл'] or args['изображение'] or data.file or 'Схема_Муроморского_метрополитена.png'
     local mapWidth = tonumber(args['ширина'] or 850) or 850
    local width = args['ширина'] or data.width or '850px'
     local mapHeight = tonumber(args['высота'] or 620) or 620
    local alt = args['alt'] or args['название'] or data.title or 'Интерактивная карта скоростного транспорта'
     local defaultRadius = args['радиус'] or data.defaultRadius or '16'
   
    local mapText = 'Файл:' .. file .. '|' .. width .. '|center|alt=' .. alt .. '\n'
    mapText = mapText .. '# Координаты станций и линий из базы данных JSON\n'
      
      
    local out = mw.html.create('div')
        :addClass('ts-html-transport-map')
        :css({
            ['position'] = 'relative',
            ['width'] = mapWidth .. 'px',
            ['height'] = mapHeight .. 'px',
            ['max-width'] = '100%',
            ['background-color'] = '#ffffff',
            ['background-image'] = 'radial-gradient(#e1e4e8 1px, transparent 1px)',
            ['background-size'] = '24px 24px',
            ['border'] = '1px solid #d0d7de',
            ['border-radius'] = '10px',
            ['overflow'] = 'hidden',
            ['margin'] = '0 auto',
            ['user-select'] = 'none',
            ['box-shadow'] = '0 3px 10px rgba(0,0,0,0.04)',
            ['font-family'] = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
        })
    -- Плашка логотипа в левом верхнем углу
    local header = out:tag('div')
        :css({
            ['position'] = 'absolute',
            ['top'] = '16px',
            ['left'] = '18px',
            ['display'] = 'flex',
            ['align-items'] = 'center',
            ['gap'] = '10px',
            ['z-index'] = '5',
            ['background'] = 'rgba(255,255,255,0.92)',
            ['padding'] = '6px 12px',
            ['border-radius'] = '8px',
            ['border'] = '1px solid #e1e4e8',
            ['box-shadow'] = '0 2px 6px rgba(0,0,0,0.04)'
        })
    header:tag('div')
        :css({
            ['width'] = '24px',
            ['height'] = '24px',
            ['background'] = '#ED1B24',
            ['color'] = '#fff',
            ['font-weight'] = 'bold',
            ['font-size'] = '13px',
            ['display'] = 'flex',
            ['align-items'] = 'center',
            ['justify-content'] = 'center',
            ['border-radius'] = '50%'
        }):wikitext('M')
    header:tag('div')
        :css({['text-align'] = 'left'})
        :tag('div'):css({['font-weight'] = 'bold', ['font-size'] = '13px', ['color'] = '#1f2328'}):wikitext(data.title or 'Муроморский скоростной транспорт'):done()
        :tag('div'):css({['font-size'] = '10px', ['color'] = '#57606a', ['text-transform'] = 'uppercase', ['letter-spacing'] = '0.5px'}):wikitext('Интерактивная схема линий'):done()
     if data.lines then
     if data.lines then
        -- 1. Слой путей (линий между станциями)
         for _, line in ipairs(data.lines) do
         for _, line in ipairs(data.lines) do
             -- Зона плашки линии в легенде
             local color = line.color or '#888888'
            if line.box and line.article then
            local stations = line.stations or {}
                mapText = mapText .. 'rect ' .. line.box .. ' [[' .. line.article .. '|' .. (line.displayName or line.name or line.article) .. ']]\n'
            end
              
              
             -- Кастомные векторные зоны/полигоны линий (если заданы)
             for i = 1, #stations - 1 do
            if line.shapes then
                local st1, st2 = stations[i], stations[i+1]
                for _, shape in ipairs(line.shapes) do
                local x1, y1 = parseCoords(st1.coords)
                     mapText = mapText .. shape .. '\n'
                local x2, y2 = parseCoords(st2.coords)
               
                if x1 and y1 and x2 and y2 then
                    local dx = x2 - x1
                    local dy = y2 - y1
                    local length = math.sqrt(dx * dx + dy * dy)
                    local angle = math.deg(math.atan2(dy, dx))
                    local isFuture = (st1.future and st2.future)
                      
                    local track = out:tag('div')
                        :css({
                            ['position'] = 'absolute',
                            ['left'] = x1 .. 'px',
                            ['top'] = (y1 - 3) .. 'px',
                            ['width'] = length .. 'px',
                            ['height'] = isFuture and '0px' or '6px',
                            ['background-color'] = isFuture and 'transparent' or color,
                            ['border-top'] = isFuture and ('5px dashed ' .. color) or 'none',
                            ['transform-origin'] = '0 50%',
                            ['transform'] = 'rotate(' .. angle .. 'deg)',
                            ['border-radius'] = '3px',
                            ['z-index'] = '1',
                            ['pointer-events'] = 'none'
                        })
                 end
                 end
             end
             end
        end
        -- 2. Слой станций и подписей
        for _, line in ipairs(data.lines) do
            local color = line.color or '#888888'
              
              
             -- Станции линии
             for _, st in ipairs(line.stations or {}) do
            if line.stations then
                local x, y = parseCoords(st.coords)
                for _, st in ipairs(line.stations) do
                if x and y then
                    if st.coords and st.article then
                    local name = st.name or st.article or ''
                        local r = st.radius or defaultRadius
                    local article = st.article or name
                        local name = st.name or st.article
                    local isFuture = (st.future == true)
                         mapText = mapText .. 'circle ' .. st.coords .. ' ' .. r .. ' [[' .. st.article .. '|' .. name .. ']]\n'
                   
                    end
                    -- Кружок станции
                    local node = out:tag('div')
                        :css({
                            ['position'] = 'absolute',
                            ['left'] = (x - 7) .. 'px',
                            ['top'] = (y - 7) .. 'px',
                            ['width'] = '14px',
                            ['height'] = '14px',
                            ['border-radius'] = '50%',
                            ['background-color'] = '#ffffff',
                            ['border'] = isFuture and ('2.5px dashed ' .. color) or ('3px solid ' .. color),
                            ['box-sizing'] = 'border-box',
                            ['box-shadow'] = '0 1px 3px rgba(0,0,0,0.25)',
                            ['z-index'] = '3',
                            ['cursor'] = 'pointer'
                         })
                    node:wikitext('[[' .. article .. '|<span style="display:block;width:100%;height:100%;" title="' .. name .. '"></span>]]')
                   
                    -- Текстовая подпись станции
                    local isRightSide = (x >= 430)
                    local label = out:tag('div')
                        :css({
                            ['position'] = 'absolute',
                            ['left'] = isRightSide and ((x + 11) .. 'px') or ((x - 11) .. 'px'),
                            ['top'] = y .. 'px',
                            ['transform'] = isRightSide and 'translateY(-50%)' or 'translate(-100%, -50%)',
                            ['text-align'] = isRightSide and 'left' or 'right',
                            ['white-space'] = 'nowrap',
                            ['font-size'] = '11.5px',
                            ['font-weight'] = '500',
                            ['z-index'] = '4',
                            ['text-shadow'] = '0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 3px #ffffff, 0 0 4px #ffffff'
                        })
                   
                    local labelStyle = isFuture and 'color: #6e7781; font-style: italic; text-decoration: none;' or 'color: #1f2328; text-decoration: none;'
                    label:wikitext('[[' .. article .. '|<span style="' .. labelStyle .. '">' .. name .. '</span>]]')
                 end
                 end
             end
             end
         end
         end
     end
     end
   
 
    -- Дополнительные объекты (вокзалы, пересадочные узлы)
     return tostring(out)
    local extras = {'Вокзалы', 'Ж/д станции', 'Терминалы', 'Аэропорты'}
    for _, group in ipairs(extras) do
        if data[group] then
            for _, item in ipairs(data[group]) do
                if item[1] and item[2] then
                    mapText = mapText .. 'circle ' .. item[2] .. ' ' .. defaultRadius .. ' ' .. item[1] .. '\n'
                end
            end
        end
    end
   
    mapText = mapText .. 'desc none\n'
   
    -- Рендер через движок MediaWiki
     return frame:preprocess('{{#tag:imagemap|' .. mapText .. '}}')
end
end


return p
return p