Mapa da api não aparece no meu dash

<%@ page language=“java” contentType=“text/html; charset=UTF-8” pageEncoding=“UTF-8” %>
<%@ taglib uri=“Oracle Java Technologies | Oracle” prefix=“c” %>
<%@ taglib prefix=“snk” uri=“/WEB-INF/tld/sankhyaUtil.tld” %>
<%@ taglib prefix=“fmt” uri=“Oracle Java Technologies | Oracle” %>

Dashboard Geográfico de Vendas - Sankhya
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/ol.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">

<style>
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    
    body {
        font-family: 'Segoe UI', Arial, sans-serif;
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        min-height: 100vh;
        color: #333;
    }
    
    .container {
        max-width: 1400px;
        margin: 20px auto;
        background: rgba(255, 255, 255, 0.95);
        border-radius: 20px;
        padding: 30px;
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
    }
    
    .header {
        text-align: center;
        margin-bottom: 30px;
    }
    
    .title {
        font-size: 2.5rem;
        font-weight: 700;
        background: linear-gradient(135deg, #667eea, #764ba2);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        margin-bottom: 10px;
    }
    
    .subtitle {
        color: #666;
        font-size: 1.1rem;
        margin-bottom: 20px;
    }
    
    .stats-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 20px;
        margin: 30px 0;
    }
    
    .stat-card {
        background: linear-gradient(135deg, #ffffff, #f8f9fa);
        border-radius: 12px;
        padding: 25px;
        text-align: center;
        box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
        border-top: 3px solid #667eea;
        transition: transform 0.3s ease;
    }
    
    .stat-card:hover {
        transform: translateY(-5px);
    }
    
    .stat-icon {
        font-size: 2.5rem;
        color: #667eea;
        margin-bottom: 15px;
    }
    
    .stat-number {
        font-size: 2.5rem;
        font-weight: 700;
        color: #333;
        margin: 10px 0;
    }
    
    .stat-label {
        color: #666;
        font-size: 0.9rem;
        font-weight: 500;
        text-transform: uppercase;
        letter-spacing: 0.5px;
    }
    
    .info-section {
        background: #f8f9fa;
        border-radius: 12px;
        padding: 20px;
        margin: 20px 0;
        border-left: 4px solid #667eea;
    }
    
    #map {
        width: 100%;
        height: 600px;
        border-radius: 12px;
        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
        border: 3px solid rgba(255, 255, 255, 0.3);
        margin: 20px 0;
        display: none;
    }
    
    .loading, .error {
        text-align: center;
        padding: 40px;
        border-radius: 12px;
        margin: 20px 0;
    }
    
    .loading {
        background: linear-gradient(135deg, #e3f2fd, #bbdefb);
        color: #1976d2;
        border: 2px solid rgba(25, 118, 210, 0.2);
    }
    
    .error {
        background: linear-gradient(135deg, #ffebee, #ffcdd2);
        color: #d32f2f;
        border: 2px solid rgba(211, 47, 47, 0.2);
        display: none;
    }
    
    .spinner {
        border: 4px solid #f3f3f3;
        border-top: 4px solid #667eea;
        border-radius: 50%;
        width: 50px;
        height: 50px;
        animation: spin 1s linear infinite;
        margin: 20px auto;
    }
    
    @keyframes spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }
    
    .btn {
        padding: 12px 24px;
        border: none;
        border-radius: 8px;
        background: linear-gradient(135deg, #667eea, #764ba2);
        color: white;
        cursor: pointer;
        font-weight: 600;
        transition: all 0.3s ease;
        margin: 5px;
    }
    
    .btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
    }
    
    @media (max-width: 768px) {
        .container {
            margin: 10px;
            padding: 20px;
        }
        
        .title {
            font-size: 2rem;
        }
        
        #map {
            height: 400px;
        }
    }
</style>
SELECT :DTINI AS DATA_INICIAL, :DTFIN AS DATA_FINAL FROM DUAL
<c:set var="DTINI" value="${dadosPeriodo.rows[0].DATA_INICIAL}" />
<c:set var="DTFIN" value="${dadosPeriodo.rows[0].DATA_FINAL}" />

<fmt:parseDate value="${DTINI}" var="dataIni" pattern="yyyy-MM-dd HH:mm:ss.S"/>
<fmt:parseDate value="${DTFIN}" var="dataFin" pattern="yyyy-MM-dd HH:mm:ss.S"/>

<snk:query var="vendasCidades">
    SELECT
        UPPER(TRIM(CID.NOMECID)) AS CIDADE,
        UPPER(TRIM(UFS.UF)) AS ESTADO,
        COUNT(CAB.NUNOTA) AS QTD_VENDAS,
        COALESCE(SUM(CAB.VLRNOTA), 0) AS VALOR_TOTAL,
        COALESCE(ROUND(AVG(CAB.VLRNOTA), 2), 0) AS TICKET_MEDIO
    FROM TGFCAB CAB
    INNER JOIN TGFPAR PAR ON PAR.CODPARC = CAB.CODPARC
    INNER JOIN TSICID CID ON CID.CODCID = PAR.CODCID
    INNER JOIN TSIUFS UFS ON UFS.CODUF = CID.UF
    INNER JOIN TGFNAT NAT ON NAT.CODNAT = CAB.CODNAT
    WHERE (:DTINI IS NULL OR CAB.DTFATUR >= :DTINI)
      AND (:DTFIN IS NULL OR CAB.DTFATUR <= :DTFIN)
      AND NAT.CODNAT IN (1010104, 1010103, 1010101, 1010102, 1010200)
      AND CID.NOMECID IS NOT NULL
      AND UFS.UF IS NOT NULL
    GROUP BY CID.NOMECID, UFS.UF
    HAVING COUNT(CAB.NUNOTA) > 0
    ORDER BY SUM(CAB.VLRNOTA) DESC
</snk:query>

<snk:query var="estatisticas">
    SELECT
        COUNT(DISTINCT CID.CODCID) AS TOTAL_CIDADES,
        COUNT(DISTINCT UFS.CODUF) AS TOTAL_ESTADOS,
        COUNT(CAB.NUNOTA) AS TOTAL_VENDAS,
        COALESCE(SUM(CAB.VLRNOTA), 0) AS VALOR_TOTAL_GERAL
    FROM TGFCAB CAB
    INNER JOIN TGFPAR PAR ON PAR.CODPARC = CAB.CODPARC
    INNER JOIN TSICID CID ON CID.CODCID = PAR.CODCID
    INNER JOIN TSIUFS UFS ON UFS.CODUF = CID.UF
    INNER JOIN TGFNAT NAT ON NAT.CODNAT = CAB.CODNAT
    WHERE (:DTINI IS NULL OR CAB.DTFATUR >= :DTINI)
      AND (:DTFIN IS NULL OR CAB.DTFATUR <= :DTFIN)
      AND NAT.CODNAT IN (1010104, 1010103, 1010101, 1010102, 1010200)
</snk:query>

<div class="container">
    <header class="header">
        <h1 class="title">
            <i class="fas fa-map-marked-alt"></i>
            Dashboard Geográfico de Vendas
        </h1>
        <p class="subtitle">
            <i class="fas fa-chart-line"></i>
            Visualização interativa das vendas por localização
        </p>
    </header>

    <div class="info-section">
        <h3><i class="fas fa-calendar-alt"></i> Período de Análise</h3>
        <p>
            <strong>De:</strong> <fmt:formatDate value="${dataIni}" pattern="dd/MM/yyyy"/> 
            <strong>até:</strong> <fmt:formatDate value="${dataFin}" pattern="dd/MM/yyyy"/>
        </p>
        <p><strong>Total de cidades com vendas:</strong> ${vendasCidades.rowCount}</p>
    </div>

    <div class="stats-grid">
        <div class="stat-card">
            <div class="stat-icon"><i class="fas fa-city"></i></div>
            <div class="stat-number">${estatisticas.rows[0].TOTAL_CIDADES}</div>
            <div class="stat-label">Cidades com Vendas</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-icon"><i class="fas fa-map-marked-alt"></i></div>
            <div class="stat-number">${estatisticas.rows[0].TOTAL_ESTADOS}</div>
            <div class="stat-label">Estados Atendidos</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-icon"><i class="fas fa-shopping-cart"></i></div>
            <div class="stat-number">
                <fmt:formatNumber value="${estatisticas.rows[0].TOTAL_VENDAS}" type="number"/>
            </div>
            <div class="stat-label">Total de Vendas</div>
        </div>
        
        <div class="stat-card">
            <div class="stat-icon"><i class="fas fa-dollar-sign"></i></div>
            <div class="stat-number">
                <fmt:formatNumber value="${estatisticas.rows[0].VALOR_TOTAL_GERAL}" 
                                type="currency" currencySymbol="R$ " minFractionDigits="0" maxFractionDigits="0"/>
            </div>
            <div class="stat-label">Valor Total</div>
        </div>
    </div>

    <div id="loading-status" class="loading">
        <div class="spinner"></div>
        <p><i class="fas fa-sync fa-spin"></i> Carregando mapa e processando dados geográficos...</p>
        <small>Aguarde enquanto localizamos as cidades no mapa</small>
    </div>
    
    <div id="error-status" class="error">
        <p><i class="fas fa-exclamation-triangle"></i> <strong>Erro ao carregar o mapa</strong></p>
        <p id="error-details">Detalhes do erro aparecerão aqui</p>
        <button class="btn" onclick="location.reload()">
            <i class="fas fa-redo"></i> Tentar Novamente
        </button>
    </div>

    <div id="map"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/ol.js"></script>

<script>
    'use strict';
    
    // Configuração global
    const CONFIG = {
        center: [-47.93, -15.78], // Centro do Brasil
        zoom: 5,
        maxZoom: 15,
        minZoom: 3
    };

    // Variáveis globais
    let map = null;
    let vectorSource = null;
    let cidadesBrasil = [];

    // Dados das vendas do Sankhya (usando escape HTML)
    const dadosVendas = [
        <c:forEach items="${vendasCidades.rows}" var="row" varStatus="status">
        {
            cidade: '<c:out value="${row.CIDADE}"/>',
            estado: '<c:out value="${row.ESTADO}"/>',
            qtdVendas: ${row.QTD_VENDAS},
            valorTotal: ${row.VALOR_TOTAL},
            ticketMedio: ${row.TICKET_MEDIO}
        }<c:if test="${!status.last}">,</c:if>
        </c:forEach>
    ];

    // Funções auxiliares
    function log(message) {
        console.log('🗺️ Dashboard:', message);
    }

    function showError(message) {
        document.getElementById('error-details').textContent = message;
        document.getElementById('error-status').style.display = 'block';
        document.getElementById('loading-status').style.display = 'none';
        log('ERRO: ' + message);
    }

    function showSuccess() {
        document.getElementById('map').style.display = 'block';
        document.getElementById('loading-status').style.display = 'none';
        log('Mapa carregado com sucesso!');
    }

    // Normalizar string para comparação
    function normalizeString(str) {
        if (!str) return '';
        return str.toString()
                 .normalize('NFD')
                 .replace(/[\u0300-\u036f]/g, '')
                 .toUpperCase()
                 .trim()
                 .replace(/\s+/g, ' ');
    }

    // Carregar dados das cidades
    async function carregarCidadesBrasil() {
        log('Carregando dados geográficos...');
        
        try {
            const response = await fetch('cidades_br.json', {
                method: 'GET',
                headers: {
                    'Accept': 'application/json',
                    'Cache-Control': 'no-cache'
                }
            });
            
            if (!response.ok) {
                throw new Error('Erro HTTP ' + response.status + ': ' + response.statusText);
            }
            
            const data = await response.json();
            
            if (!Array.isArray(data) || data.length === 0) {
                throw new Error('Arquivo de cidades inválido ou vazio');
            }
            
            cidadesBrasil = data;
            log(data.length + ' cidades brasileiras carregadas');
            return true;
            
        } catch (error) {
            log('Erro ao carregar cidades: ' + error.message);
            throw new Error('Não foi possível carregar os dados geográficos: ' + error.message);
        }
    }

    // Encontrar coordenadas de uma cidade
    function encontrarCoordenadas(nomeCidade, uf) {
        if (!cidadesBrasil || cidadesBrasil.length === 0) {
            return null;
        }
        
        const cidadeNorm = normalizeString(nomeCidade);
        const estadoNorm = normalizeString(uf);
        
        // Busca exata
        let cidade = cidadesBrasil.find(function(c) {
            return normalizeString(c.nome) === cidadeNorm && normalizeString(c.uf) === estadoNorm;
        });
        
        // Se não encontrou, tentar busca parcial
        if (!cidade) {
            cidade = cidadesBrasil.find(function(c) {
                return normalizeString(c.nome).indexOf(cidadeNorm) >= 0 && normalizeString(c.uf) === estadoNorm;
            });
        }
        
        if (cidade) {
            return {
                latitude: parseFloat(cidade.lat),
                longitude: parseFloat(cidade.lon)
            };
        }
        
        return null;
    }

    // Inicializar mapa OpenLayers
    function inicializarMapa() {
        log('Inicializando mapa OpenLayers...');
        
        try {
            if (typeof ol === 'undefined') {
                throw new Error('Biblioteca OpenLayers não foi carregada');
            }
            
            // Criar fonte de vetores
            vectorSource = new ol.source.Vector();
            
            // Criar mapa
            map = new ol.Map({
                target: 'map',
                layers: [
                    new ol.layer.Tile({
                        source: new ol.source.OSM({
                            attributions: [
                                ol.source.OSM.ATTRIBUTION,
                                ' | Dashboard Sankhya ERP'
                            ]
                        })
                    }),
                    new ol.layer.Vector({
                        source: vectorSource
                    })
                ],
                view: new ol.View({
                    center: ol.proj.fromLonLat(CONFIG.center),
                    zoom: CONFIG.zoom,
                    maxZoom: CONFIG.maxZoom,
                    minZoom: CONFIG.minZoom
                })
            });
            
            log('Mapa OpenLayers inicializado');
            return true;
            
        } catch (error) {
            log('Erro ao inicializar mapa: ' + error.message);
            throw error;
        }
    }

    // Processar dados e criar marcadores
    function processarDados() {
        log('Processando dados de vendas...');
        
        try {
            if (!dadosVendas || dadosVendas.length === 0) {
                throw new Error('Nenhum dado de vendas disponível');
            }
            
            vectorSource.clear();
            
            const valorMaximo = Math.max.apply(Math, dadosVendas.map(function(item) {
                return parseFloat(item.valorTotal) || 0;
            }));
            
            let marcadoresCriados = 0;
            let marcadoresIgnorados = 0;
            
            // Processar cada cidade
            for (let i = 0; i < dadosVendas.length; i++) {
                const venda = dadosVendas[i];
                
                try {
                    const coords = encontrarCoordenadas(venda.cidade, venda.estado);
                    
                    if (coords && isFinite(coords.latitude) && isFinite(coords.longitude)) {
                        const marcador = criarMarcador(venda, coords, valorMaximo);
                        if (marcador) {
                            vectorSource.addFeature(marcador);
                            marcadoresCriados++;
                        }
                    } else {
                        marcadoresIgnorados++;
                        log('Coordenadas não encontradas: ' + venda.cidade + '-' + venda.estado);
                    }
                    
                } catch (error) {
                    marcadoresIgnorados++;
                    log('Erro ao processar ' + venda.cidade + ': ' + error.message);
                }
            }
            
            if (marcadoresCriados === 0) {
                throw new Error('Nenhum marcador pôde ser criado no mapa');
            }
            
            // Ajustar visualização
            ajustarVisualizacao();
            
            log('Processamento concluído: ' + marcadoresCriados + ' marcadores criados, ' + marcadoresIgnorados + ' ignorados');
            
        } catch (error) {
            log('Erro no processamento: ' + error.message);
            throw error;
        }
    }

    // Criar marcador individual
    function criarMarcador(dadosVenda, coordenadas, valorMaximo) {
        try {
            const posicao = ol.proj.fromLonLat([coordenadas.longitude, coordenadas.latitude]);
            
            if (!isFinite(posicao[0]) || !isFinite(posicao[1])) {
                return null;
            }
            
            const feature = new ol.Feature({
                geometry: new ol.geom.Point(posicao),
                cidade: dadosVenda.cidade,
                estado: dadosVenda.estado,
                qtdVendas: dadosVenda.qtdVendas,
                valorTotal: dadosVenda.valorTotal,
                ticketMedio: dadosVenda.ticketMedio
            });
            
            // Calcular tamanho e cor baseado no valor
            const valorNormalizado = parseFloat(dadosVenda.valorTotal) || 0;
            const percentual = valorMaximo > 0 ? valorNormalizado / valorMaximo : 0;
            
            let cor = '#3498db'; // Azul para valores baixos
            if (percentual > 0.7) {
                cor = '#e74c3c'; // Vermelho para valores altos
            } else if (percentual > 0.3) {
                cor = '#f39c12'; // Laranja para valores médios
            }
            
            const tamanho = 8 + (percentual * 16); // Tamanho de 8 a 24
            
            feature.setStyle(new ol.style.Style({
                image: new ol.style.Circle({
                    radius: tamanho,
                    fill: new ol.style.Fill({ color: cor }),
                    stroke: new ol.style.Stroke({ color: '#ffffff', width: 2 })
                })
            }));
            
            return feature;
            
        } catch (error) {
            log('Erro ao criar marcador: ' + error.message);
            return null;
        }
    }

    // Ajustar visualização do mapa
    function ajustarVisualizacao() {
        if (vectorSource.getFeatures().length > 0) {
            const extent = vectorSource.getExtent();
            map.getView().fit(extent, {
                padding: [50, 50, 50, 50],
                maxZoom: CONFIG.maxZoom - 2,
                duration: 800
            });
        }
    }

    // Configurar interações do mapa
    function configurarInteracoes() {
        // Click nos marcadores
        map.on('singleclick', function(event) {
            const feature = map.forEachFeatureAtPixel(event.pixel, function(feature) {
                return feature;
            });

            if (feature) {
                const dados = {
                    cidade: feature.get('cidade'),
                    estado: feature.get('estado'),
                    qtdVendas: feature.get('qtdVendas'),
                    valorTotal: feature.get('valorTotal'),
                    ticketMedio: feature.get('ticketMedio')
                };
                
                const valorFormatado = new Intl.NumberFormat('pt-BR', {
                    style: 'currency',
                    currency: 'BRL'
                }).format(dados.valorTotal);
                
                const ticketFormatado = new Intl.NumberFormat('pt-BR', {
                    style: 'currency',
                    currency: 'BRL'
                }).format(dados.ticketMedio);
                
                alert(
                    dados.cidade + ' - ' + dados.estado + '\n\n' +
                    'Vendas: ' + dados.qtdVendas + '\n' +
                    'Valor Total: ' + valorFormatado + '\n' +
                    'Ticket Médio: ' + ticketFormatado
                );
            }
        });

        // Cursor pointer nos marcadores
        map.on('pointermove', function(event) {
            const feature = map.forEachFeatureAtPixel(event.pixel, function(feature) {
                return feature;
            });
            map.getTargetElement().style.cursor = feature ? 'pointer' : '';
        });
    }

    // Função principal de inicialização
    async function inicializar() {
        log('Iniciando Dashboard Geográfico Sankhya...');
        log(dadosVendas.length + ' registros de vendas carregados');
        
        try {
            await carregarCidadesBrasil();
            inicializarMapa();
            processarDados();
            configurarInteracoes();
            showSuccess();
            
            setTimeout(function() {
                if (map) {
                    map.updateSize();
                }
            }, 100);
            
        } catch (error) {
            showError(error.message);
        }
    }

    // Inicialização quando o DOM estiver carregado
    document.addEventListener('DOMContentLoaded', function() {
        log('DOM carregado, iniciando sistema...');
        inicializar();
    });
    
</script>

fica carregando conforme a imagem