mapa/index6.php
2025-07-04 12:34:51 -04:00

370 lines
13 KiB
PHP

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mapa de Conexiones Global en 3D</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: linear-gradient(135deg, #0c1445, #1a237e, #283593);
min-height: 100vh;
overflow: hidden;
}
#globe-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.stars {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.star {
position: absolute;
background-color: white;
border-radius: 50%;
animation: twinkle var(--duration, 5s) infinite var(--delay, 0s);
}
@keyframes twinkle {
0%, 100% { opacity: 0.2; }
50% { opacity: 1; }
}
.loading {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 15px;
background: rgba(0, 10, 30, 0.7);
z-index: 100;
color: white;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(100, 200, 255, 0.3);
border-top: 4px solid #64c8ff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status-text {
font-size: 1.2rem;
text-align: center;
max-width: 300px;
}
.timestamp {
position: absolute;
bottom: 20px;
right: 20px;
color: rgba(255, 255, 255, 0.7);
font-size: 0.9rem;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
z-index: 10;
}
</style>
</head>
<body>
<!-- Fondo de estrellas -->
<div class="stars" id="stars"></div>
<!-- Contenedor del globo -->
<div id="globe-container">
<div class="loading" id="loading">
<div class="spinner"></div>
<div class="status-text">Cargando datos de la API...</div>
</div>
<div class="timestamp" id="last-update"></div>
</div>
<script>
// Variables globales
let scene, camera, renderer, controls;
let globe, connectionBoxes = [];
let currentData = [];
let autoRotate = true;
let refreshInterval;
// Variables para normalización de tamaños
let minConnections = Infinity;
let maxConnections = 0;
// Inicializar la escena Three.js
function init() {
// Crear la escena
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0c1445);
scene.fog = new THREE.Fog(0x0c1445, 10, 30);
// Crear la cámara
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 25;
// Crear el renderizador
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.getElementById('globe-container').appendChild(renderer.domElement);
// Añadir controles de órbita
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.autoRotate = autoRotate;
controls.autoRotateSpeed = 0.9;
// Añadir iluminación
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 3, 5);
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0x64c8ff, 1, 50);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
// Crear la esfera (globo terráqueo)
createGlobe();
// Crear estrellas de fondo
createStars();
// Iniciar animación
animate();
// Manejar redimensionamiento
window.addEventListener('resize', onWindowResize, false);
// Cargar datos iniciales
fetchData();
// Configurar intervalo para actualizar cada minuto
refreshInterval = setInterval(fetchData, 60000);
}
// Crear el globo terráqueo
function createGlobe() {
// Crear la geometría de la esfera
const geometry = new THREE.SphereGeometry(10, 64, 64);
// Cargar textura de la Tierra
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_atmos_2048.jpg');
const bumpMap = textureLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_normal_2048.jpg');
const specularMap = textureLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_specular_2048.jpg');
// Crear material
const material = new THREE.MeshPhongMaterial({
map: texture,
bumpMap: bumpMap,
bumpScale: 0.05,
specularMap: specularMap,
specular: new THREE.Color(0x333333),
shininess: 50
});
// Crear la esfera
globe = new THREE.Mesh(geometry, material);
scene.add(globe);
// Añadir nubes
const cloudsGeometry = new THREE.SphereGeometry(10.05, 64, 64);
const cloudsMaterial = new THREE.MeshPhongMaterial({
map: textureLoader.load('https://raw.githubusercontent.com/mrdoob/three.js/master/examples/textures/planets/earth_clouds_1024.png'),
transparent: true,
opacity: 0.8
});
const clouds = new THREE.Mesh(cloudsGeometry, cloudsMaterial);
scene.add(clouds);
}
// Crear estrellas de fondo
function createStars() {
const starsContainer = document.getElementById('stars');
for (let i = 0; i < 200; i++) {
const star = document.createElement('div');
star.classList.add('star');
star.style.width = `${Math.random() * 3}px`;
star.style.height = star.style.width;
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 100}%`;
star.style.setProperty('--duration', `${Math.random() * 5 + 3}s`);
star.style.setProperty('--delay', `${Math.random() * 5}s`);
starsContainer.appendChild(star);
}
}
// Actualizar el mapa con nuevos datos
function updateMap(data) {
// Calcular min y max de conexiones para normalización
minConnections = Infinity;
maxConnections = 0;
data.forEach(item => {
if (item.connections < minConnections) minConnections = item.connections;
if (item.connections > maxConnections) maxConnections = item.connections;
});
// Eliminar rectángulos antiguos
connectionBoxes.forEach(box => scene.remove(box));
connectionBoxes = [];
// Filtrar puntos con coordenadas válidas (excluir (0,0))
const filteredData = data.filter(item => item.lat !== 0 && item.lon !== 0);
// Crear nuevos rectángulos 3D
filteredData.forEach(item => {
// Convertir lat/lon a coordenadas 3D
const lat = item.lat * Math.PI / 180;
const lon = -item.lon * Math.PI / 180;
const radius = 10.1;
// Calcular posición en la esfera
const x = radius * Math.cos(lat) * Math.cos(lon);
const y = radius * Math.sin(lat);
const z = radius * Math.cos(lat) * Math.sin(lon);
// Calcular tamaño basado en conexiones (normalizado)
const baseSize = 0.1;
const normalizedConnections = (item.connections - minConnections) /
(maxConnections - minConnections || 1);
// Dimensiones del rectángulo
const width = baseSize * (0.5 + normalizedConnections * 1.5);
const height = baseSize * (0.5 + normalizedConnections * 1.5);
const depth = baseSize * (1.5 + normalizedConnections * 3); // Profundidad proporcional
// Crear geometría del rectángulo
const geometry = new THREE.BoxGeometry(width, height, depth);
// Asignar color basado en conexiones
let color;
if (item.connections === 1) color = new THREE.Color(0x4287f5);
else if (item.connections <= 3) color = new THREE.Color(0x42f5ef);
else if (item.connections <= 6) color = new THREE.Color(0x42f56e);
else if (item.connections <= 9) color = new THREE.Color(0xf5f542);
else color = new THREE.Color(0xf54242);
const material = new THREE.MeshPhongMaterial({
color: color,
transparent: true,
opacity: 0.9,
shininess: 60,
specular: new THREE.Color(0xffffff)
});
// Crear el rectángulo
const box = new THREE.Mesh(geometry, material);
box.position.set(x, y, z);
// Orientar el rectángulo hacia la cámara (apuntando radialmente hacia afuera)
const vector = new THREE.Vector3(x, y, z).normalize();
box.quaternion.setFromUnitVectors(
new THREE.Vector3(0, 0, 1),
vector
);
// Rotar para que quede vertical
box.rotateX(Math.PI / 2);
// Añadir a la escena y a la lista
scene.add(box);
connectionBoxes.push(box);
});
}
// Obtener datos de la API
async function fetchData() {
try {
document.getElementById('loading').style.display = 'flex';
document.querySelector('.status-text').textContent = "Conectando a la API...";
// Usar tu API real
const response = await fetch('http://192.168.6.63/mapa/data.php');
if (!response.ok) throw new Error('Error en la respuesta de la API');
const data = await response.json();
currentData = data;
updateMap(currentData);
// Actualizar marca de tiempo
const now = new Date();
document.getElementById('last-update').textContent =
`Última actualización: ${now.toLocaleTimeString()}`;
// Actualizar estado
document.querySelector('.status-text').textContent = "Datos cargados correctamente";
// Ocultar spinner después de un breve retraso
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 1000);
} catch (error) {
console.error('Error al obtener datos:', error);
document.querySelector('.status-text').textContent = "Error al conectar con la API";
// Ocultar spinner después de un breve retraso
setTimeout(() => {
document.getElementById('loading').style.display = 'none';
}, 1000);
}
}
// Manejar redimensionamiento de ventana
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Función de animación
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
// Iniciar la aplicación cuando el DOM esté listo
document.addEventListener('DOMContentLoaded', () => {
init();
});
</script>
</body>
</html>