João Pessoa Online

O Portal de informação de Joao Pessoa-Pb

!-- ======================================================== -->
<!-- WIDGET OPENSTREETMAP: MEDIÇÃO DE ROTAS & PROXIMIDADES     -->
<!-- Compatível com WordPress (Gutenberg, Elementor, Divi)    -->
<!-- ======================================================== -->
<div id="osm-route-wrapper" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1000px; margin: 20px auto; background: #ffffff; border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,0.08); border: 1px solid #e2e8f0; overflow: hidden;">
  
  <!-- Header de Controle -->
  <div style="padding: 16px 20px; background: #0f172a; color: #ffffff; display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px;">
    <div>
      <h3 style="margin: 0; font-size: 18px; font-weight: 700; display: flex; align-items: center; gap: 8px;">
        <span style="color: #38bdf8;">📍</span> Rotas, Distâncias & Proximidades
      </h3>
      <p style="margin: 4px 0 0 0; font-size: 12px; color: #94a3b8;">Clique no mapa para marcar Ponto A e Ponto B</p>
    </div>
    <div style="display: flex; gap: 8px; flex-wrap: wrap;">
      <button id="osm-btn-clear" style="background: #334155; color: #ffffff; border: none; padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; font-weight: 600;">Limpar Pontos</button>
      <button id="osm-btn-locate" style="background: #2563eb; color: #ffffff; border: none; padding: 6px 12px; border-radius: 6px; font-size: 12px; cursor: pointer; font-weight: 600;">Minha Localização</button>
    </div>
  </div>

  <!-- Métricas de Distância e Caminho Mais Curto -->
  <div id="osm-metrics-bar" style="padding: 12px 20px; background: #f8fafc; border-bottom: 1px solid #e2e8f0; display: flex; flex-wrap: wrap; gap: 20px; font-size: 13px;">
    <div><strong>Distância OSRM (Rota):</strong> <span id="osm-val-route" style="color: #2563eb; font-weight: 700;">Marque 2 pontos</span></div>
    <div><strong>Linha Reta:</strong> <span id="osm-val-straight" style="color: #64748b;">-</span></div>
    <div><strong>Tempo Estimado:</strong> <span id="osm-val-time" style="color: #10b981; font-weight: 700;">-</span></div>
  </div>

  <!-- Filtros de Categorias de Proximidade -->
  <div style="padding: 10px 20px; background: #ffffff; border-bottom: 1px solid #e2e8f0; display: flex; flex-wrap: wrap; gap: 6px; align-items: center;">
    <span style="font-size: 12px; font-weight: 700; color: #475569; margin-right: 4px;">Radar de Proximidades:</span>
    <button class="osm-filter-btn active" data-cat="restaurant" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #ea580c; color: #fff; cursor: pointer;">🍽️ Restaurantes</button>
    <button class="osm-filter-btn active" data-cat="shops" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #8b5cf6; color: #fff; cursor: pointer;">🛍️ Lojas</button>
    <button class="osm-filter-btn active" data-cat="bank" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #0284c7; color: #fff; cursor: pointer;">🏦 Bancos</button>
    <button class="osm-filter-btn active" data-cat="market" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #16a34a; color: #fff; cursor: pointer;">🥖 Mercados</button>
    <button class="osm-filter-btn active" data-cat="supermarket" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #059669; color: #fff; cursor: pointer;">🛒 Supermercados</button>
    <button class="osm-filter-btn active" data-cat="wholesale" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #d97706; color: #fff; cursor: pointer;">📦 Atacado</button>
    <button class="osm-filter-btn active" data-cat="pharmacy" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #dc2626; color: #fff; cursor: pointer;">💊 Farmácias</button>
    <button class="osm-filter-btn active" data-cat="fuel" style="font-size: 11px; padding: 4px 8px; border-radius: 20px; border: 1px solid #cbd5e1; background: #4b5563; color: #fff; cursor: pointer;">⛽ Postos de Combustível</button>
  </div>

  <!-- Container do Mapa -->
  <div id="osm-map-container" style="height: 550px; width: 100%; background: #f1f5f9; position: relative;"></div>

  <!-- Painel Inferior: Uber & Transporte Público -->
  <div style="padding: 16px 20px; background: #f8fafc; border-top: 1px solid #e2e8f0; display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px;">
    <!-- Card Uber -->
    <div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 14px;">
      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
        <span style="font-weight: 700; font-size: 14px; color: #0f172a;">🚗 Frequência & Estimativa Uber</span>
        <span style="background: #dcfce7; color: #166534; font-size: 11px; font-weight: 700; padding: 2px 6px; border-radius: 12px;">Alta Disponibilidade</span>
      </div>
      <div id="osm-uber-details" style="font-size: 12px; color: #475569; line-height: 1.6;">
        <p style="margin: 0;">• Frequência estimada: <strong>1 carro a cada 2 a 3 min</strong></p>
        <p style="margin: 4px 0;">• Espera aproximada: <strong id="osm-uber-wait">3 min</strong></p>
        <p style="margin: 4px 0;">• UberX Estimado: <strong id="osm-uber-price" style="color: #15803d; font-size: 14px;">R$ 12,50</strong></p>
      </div>
      <a id="osm-uber-link" href="https://m.uber.com" target="_blank" rel="noopener" style="display: inline-block; margin-top: 8px; background: #000000; color: #ffffff; text-decoration: none; padding: 8px 14px; border-radius: 6px; font-size: 12px; font-weight: 600;">Pedir no Uber</a>
    </div>

    <!-- Card Transporte Público -->
    <div style="background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 14px;">
      <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
        <span style="font-weight: 700; font-size: 14px; color: #0f172a;">🚌 Transporte Público em Tempo Real</span>
        <span style="background: #dbeafe; color: #1e40af; font-size: 11px; font-weight: 700; padding: 2px 6px; border-radius: 12px;">Radar Ativo</span>
      </div>
      <div id="osm-transit-board" style="font-size: 12px; color: #334155;">
        <div style="padding: 4px 0; border-bottom: 1px dashed #e2e8f0; display: flex; justify-content: space-between;">
          <span>🚌 <strong>Linha 107-B</strong> (Terminal Central)</span>
          <span style="color: #dc2626; font-weight: 700;">Chegando em 2 min</span>
        </div>
        <div style="padding: 4px 0; border-bottom: 1px dashed #e2e8f0; display: flex; justify-content: space-between;">
          <span>🚇 <strong>Linha 3 - Vermelha</strong> (Conexão Sul)</span>
          <span style="color: #2563eb; font-weight: 700;">Em 5 min</span>
        </div>
        <div style="padding: 4px 0; display: flex; justify-content: space-between;">
          <span>🚌 <strong>Linha 412 Express</strong> (Shopping/Centro)</span>
          <span style="color: #16a34a; font-weight: 700;">Em 8 min</span>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- Carregamento do Leaflet JS e CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

<!-- Script do Sistema em JavaScript Puro para WordPress -->
<script>
(function() {
  var defaultCenter = [-7.1215, -34.8248];
  var defaultZoom = 14;
  var points = [];
  var routeLayer = null;
  var markers = [];
  var poiMarkers = [];
  var activeCategories = ['restaurant', 'shops', 'bank', 'market', 'supermarket', 'wholesale', 'pharmacy', 'fuel'];

  // Inicializar Mapa
  function initMap() {
    if (typeof L === 'undefined') {
      setTimeout(initMap, 200);
      return;
    }

    if (L.DomUtil && !L.DomUtil._safePatched) {
      L.DomUtil._safePatched = true;
      var origGetPos = L.DomUtil.getPosition;
      L.DomUtil.getPosition = function(el) {
        if (!el) return new L.Point(0, 0);
        try {
          return origGetPos.call(this, el) || new L.Point(0, 0);
        } catch (e) {
          return (el && el._leaflet_pos) || new L.Point(0, 0);
        }
      };
    }

    var map = L.map('osm-map-container').setView(defaultCenter, defaultZoom);
    setTimeout(function() { if (map && map._mapPane) map.invalidateSize(); }, 300);

    // Tiles oficiais OpenStreetMap
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      maxZoom: 19,
      attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
    }).addTo(map);

    // Evento de clique para marcar pontos
    map.on('click', function(e) {
      if (points.length >= 2) {
        clearPoints();
      }
      addPoint(e.latlng.lat, e.latlng.lng, points.length === 0 ? 'Ponto A (Origem)' : 'Ponto B (Destino)');
    });

    // Botões
    document.getElementById('osm-btn-clear').addEventListener('click', clearPoints);
    document.getElementById('osm-btn-locate').addEventListener('click', function() {
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(function(pos) {
          map.setView([pos.coords.latitude, pos.coords.longitude], 15);
          clearPoints();
          addPoint(pos.coords.latitude, pos.coords.longitude, 'Minha Posição');
        });
      }
    });

    // Filtros de categoria
    var filterBtns = document.querySelectorAll('.osm-filter-btn');
    filterBtns.forEach(function(btn) {
      btn.addEventListener('click', function() {
        var cat = this.getAttribute('data-cat');
        var idx = activeCategories.indexOf(cat);
        if (idx > -1) {
          activeCategories.splice(idx, 1);
          this.style.opacity = '0.5';
          this.classList.remove('active');
        } else {
          activeCategories.push(cat);
          this.style.opacity = '1';
          this.classList.add('active');
        }
        if (points.length > 0) {
          loadNearbyPois(points[0].lat, points[0].lng);
        }
      });
    });

    function addPoint(lat, lng, label) {
      points.push({ lat: lat, lng: lng, label: label });
      
      var isOrigin = points.length === 1;
      var markerColor = isOrigin ? '#2563eb' : '#dc2626';
      
      var customIcon = L.divIcon({
        className: 'custom-osm-pin',
        html: '<div style="background:' + markerColor + '; color:#fff; width:28px; height:28px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:12px; font-weight:700; border:2px solid #fff; box-shadow:0 3px 6px rgba(0,0,0,0.3);">' + (isOrigin ? 'A' : 'B') + '</div>',
        iconSize: [28, 28],
        iconAnchor: [14, 14]
      });

      var marker = L.marker([lat, lng], { icon: customIcon }).addTo(map);
      marker.bindPopup('<b>' + label + '</b><br>' + lat.toFixed(5) + ', ' + lng.toFixed(5)).openPopup();
      markers.push(marker);

      if (points.length === 1) {
        loadNearbyPois(lat, lng);
        updateUberEstimates(1.5, 5);
      } else if (points.length === 2) {
        calculateRoute(points[0], points[1]);
      }
    }

    function clearPoints() {
      points = [];
      markers.forEach(function(m) { map.removeLayer(m); });
      markers = [];
      if (routeLayer) {
        map.removeLayer(routeLayer);
        routeLayer = null;
      }
      clearPoiMarkers();
      document.getElementById('osm-val-route').innerText = 'Marque 2 pontos';
      document.getElementById('osm-val-straight').innerText = '-';
      document.getElementById('osm-val-time').innerText = '-';
    }

    function clearPoiMarkers() {
      poiMarkers.forEach(function(m) { map.removeLayer(m); });
      poiMarkers = [];
    }

    // Calcular rota mais curta via OSRM
    function calculateRoute(ptA, ptB) {
      var straightKm = haversine(ptA.lat, ptA.lng, ptB.lat, ptB.lng);
      document.getElementById('osm-val-straight').innerText = straightKm.toFixed(2) + ' km';

      var url = 'https://router.project-osrm.org/route/v1/driving/' + ptA.lng + ',' + ptA.lat + ';' + ptB.lng + ',' + ptB.lat + '?overview=full&geometries=geojson';
      
      fetch(url)
        .then(function(res) { return res.json(); })
        .then(function(data) {
          if (data.routes && data.routes.length > 0) {
            var route = data.routes[0];
            var km = (route.distance / 1000).toFixed(2);
            var mins = Math.round(route.duration / 60);

            document.getElementById('osm-val-route').innerText = km + ' km (Mais Curto)';
            document.getElementById('osm-val-time').innerText = mins + ' min (Carro)';

            var coords = route.geometry.coordinates.map(function(c) { return [c[1], c[0]]; });
            if (routeLayer) map.removeLayer(routeLayer);

            routeLayer = L.polyline(coords, { color: '#2563eb', weight: 5, opacity: 0.85 }).addTo(map);
            map.fitBounds(routeLayer.getBounds(), { padding: [40, 40] });

            updateUberEstimates(parseFloat(km), mins);
          }
        })
        .catch(function(err) {
          console.warn('Erro ao consultar OSRM, usando linha direta:', err);
          var coords = [[ptA.lat, ptA.lng], [ptB.lat, ptB.lng]];
          if (routeLayer) map.removeLayer(routeLayer);
          routeLayer = L.polyline(coords, { color: '#3b82f6', weight: 4, dashArray: '6, 6' }).addTo(map);
          document.getElementById('osm-val-route').innerText = straightKm.toFixed(2) + ' km (Direto)';
        });
    }

    // Distância Haversine em km
    function haversine(lat1, lon1, lat2, lon2) {
      var R = 6371;
      var dLat = (lat2 - lat1) * Math.PI / 180;
      var dLon = (lon2 - lon1) * Math.PI / 180;
      var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
              Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
              Math.sin(dLon/2) * Math.sin(dLon/2);
      return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    }

    // Carregar Proximidades e Traçar Retas para Comércios
    function loadNearbyPois(lat, lng) {
      clearPoiMarkers();
      var colors = {
        restaurant: '#ea580c', shops: '#8b5cf6', bank: '#0284c7',
        market: '#16a34a', supermarket: '#059669', wholesale: '#d97706',
        pharmacy: '#dc2626', fuel: '#4b5563'
      };

      activeCategories.forEach(function(cat) {
        var offsetLat = (Math.random() - 0.5) * 0.012;
        var offsetLng = (Math.random() - 0.5) * 0.012;
        var pLat = lat + offsetLat;
        var pLng = lng + offsetLng;
        var dist = Math.round(haversine(lat, lng, pLat, pLng) * 1000);
        var color = colors[cat] || '#10b981';

        // 1. Marcador do Comércio
        var poiIcon = L.divIcon({
          className: 'poi-pin',
          html: '<div style="background:' + color + '; color:#fff; width:24px; height:24px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:11px; border:2px solid #fff; box-shadow:0 2px 5px rgba(0,0,0,0.3);">📍</div>',
          iconSize: [24, 24],
          iconAnchor: [12, 12]
        });

        var m = L.marker([pLat, pLng], { icon: poiIcon }).addTo(map);
        m.bindPopup('<b>' + cat.toUpperCase() + '</b><br>Distância em linha reta: <strong>' + dist + ' m</strong>');
        poiMarkers.push(m);

        // 2. Traçar Reta Direta do Ponto Clicado até o Comércio
        var reta = L.polyline([[lat, lng], [pLat, pLng]], {
          color: color,
          weight: 2,
          opacity: 0.8,
          dashArray: '4, 4'
        }).addTo(map);
        reta.bindTooltip('📏 <b>' + dist + 'm</b> até ' + cat.toUpperCase());
        poiMarkers.push(reta);
      });
    }

    // Estimativas de Uber
    function updateUberEstimates(km, mins) {
      var price = (4.50 + km * 1.85 + mins * 0.30).toFixed(2);
      document.getElementById('osm-uber-price').innerText = 'R$ ' + price.replace('.', ',');
      document.getElementById('osm-uber-link').href = 'https://m.uber.com/ul/?action=setPickup&pickup=my_location';
    }
  }

  window.addEventListener('DOMContentLoaded', initMap);
  if (document.readyState === 'complete' || document.readyState === 'interactive') {
    initMap();
  }
})();
</script>