!-- ======================================================== -->
<!-- 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: '© <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>