|
- const MAP_WIDTH = 2982;
- const MAP_HEIGHT = 1592;
-
- document.addEventListener('DOMContentLoaded', () => {
- loadBeacons();
- loadGateways();
- loadFingerprints();
- setupFloorSelector();
-
- // Setup mouse tracking
- const wrapper0 = document.getElementById('wrapper-floor-0');
- if (wrapper0) wrapper0.addEventListener('click', (e) => handleClick(e, 0));
-
- const wrapper1 = document.getElementById('wrapper-floor-1');
- if (wrapper1) wrapper1.addEventListener('click', (e) => handleClick(e, 1));
- });
-
- function setupFloorSelector() {
- const selector = document.getElementById('floor-select');
- selector.addEventListener('change', (e) => {
- const selectedFloor = e.target.value;
- // Hide all floors
- document.getElementById('container-floor-0').classList.add('hidden');
- document.getElementById('container-floor-1').classList.add('hidden');
-
- // Show selected
- const target = document.getElementById(`container-floor-${selectedFloor}`);
- if (target) target.classList.remove('hidden');
- });
- }
-
- async function loadBeacons() {
- try {
- const response = await fetch('beacon.csv'); // Adjust path if needed relative to index.html
- if (!response.ok) throw new Error('Failed to load beacon.csv');
- const text = await response.text();
- const beacons = parseCSV(text);
- renderIcons(beacons, 'beacon');
- } catch (error) {
- console.error('Error loading beacons:', error);
- showError('Failled to load beacon.csv. If you are opening this file locally, you might need to run a local server (e.g., "python3 -m http.server") due to CORS restrictions.');
- }
- }
-
- async function loadGateways() {
- try {
- const response = await fetch('gateway.csv');
- if (!response.ok) throw new Error('Failed to load gateway.csv');
- const text = await response.text();
- const gateways = parseCSV(text);
- renderIcons(gateways, 'gateway');
- } catch (error) {
- console.error('Error loading gateways:', error);
- showError('Failed to load gateway.csv. Check console for details.');
- }
- }
-
- function showError(message) {
- const errorDiv = document.createElement('div');
- errorDiv.style.backgroundColor = '#fee';
- errorDiv.style.color = 'red';
- errorDiv.style.padding = '10px';
- errorDiv.style.margin = '10px 0';
- errorDiv.style.border = '1px solid red';
- errorDiv.textContent = message;
- document.body.insertBefore(errorDiv, document.querySelector('.floor-container'));
- }
-
- function parseCSV(text) {
- const lines = text.trim().split('\n');
- const headers = lines[0].split(';').map(h => h.trim());
-
- // We expect headers to include: Floor, X, Y, etc.
- // CSV format based on file view: Position;Floor;RoomName;X;Y;Z;[Name];MAC
-
- const data = [];
- for (let i = 1; i < lines.length; i++) {
- const line = lines[i].trim();
- if (!line) continue;
-
- const values = line.split(';');
- const entry = {};
-
- headers.forEach((header, index) => {
- entry[header] = values[index] ? values[index].trim() : '';
- });
-
- // Parse numbers
- entry.Floor = parseInt(entry.Floor, 10);
- entry.X = parseFloat(entry.X);
- entry.Y = parseFloat(entry.Y);
-
- data.push(entry);
- }
- return data;
- }
-
-
- // Fingerprint data storage
- let fingerprintData = {
- 0: [], // Floor 0
- 1: [] // Floor 1
- };
-
- async function loadFingerprints() {
- try {
- const [response0, response1] = await Promise.all([
- fetch('fingerprints-floor0.json'),
- fetch('fingerprints-floor1.json')
- ]);
-
- if (response0.ok) fingerprintData[0] = await response0.json();
- else console.error('Failed to load fingerprints-floor0.json');
-
- if (response1.ok) fingerprintData[1] = await response1.json();
- else console.error('Failed to load fingerprints-floor1.json');
-
- console.log('Fingerprints loaded:', fingerprintData);
-
- } catch (error) {
- console.error('Error loading fingerprints:', error);
- }
- }
-
-
- function renderIcons(items, type) {
- items.forEach(item => {
- // Validation: Ensure valid coordinates and floor
- if (isNaN(item.X) || isNaN(item.Y) || isNaN(item.Floor)) {
- console.warn('Invalid item skipped:', item);
- return;
- }
-
- const overlayId = `overlay-floor-${item.Floor}`;
- const overlay = document.getElementById(overlayId);
-
- if (!overlay) {
- console.warn(`Overlay not found for floor: ${item.Floor}`);
- return;
- }
-
- // Container: Absolute at coordinates
- const iconContainer = document.createElement('div');
- // Structure: Flex column, centered horizontally.
- // Positioning:
- // left/top put the top-left corner of div at the coordinate.
- // -translate-x-1/2 centers it horizontally.
- // -translate-y-[20px] moves it up by 20px (half of 40px icon), so the CENTER of the icon is at the coordinate.
- // If we used -translate-y-1/2, it would center the whole text+icon group, which varies in height.
- iconContainer.className = `absolute flex flex-col items-center pointer-events-auto cursor-pointer transform -translate-x-1/2 -translate-y-[20px] z-30 group`;
-
- // Add identifiers for gateways
- if (type === 'gateway') {
- iconContainer.classList.add('gateway-icon');
- iconContainer.setAttribute('data-gateway-name', item.GatewayName);
- // Set transition for smooth opacity change
- iconContainer.style.transition = 'opacity 0.2s ease-in-out';
- }
-
- const leftPercent = (item.X / MAP_WIDTH) * 100;
- const topPercent = (item.Y / MAP_HEIGHT) * 100;
-
- iconContainer.style.left = `${leftPercent}%`;
- iconContainer.style.top = `${topPercent}%`;
-
- // Icon
- const iconSpan = document.createElement('span');
- iconSpan.className = 'iconify text-[#008EED] drop-shadow-sm';
- iconSpan.style.width = '40px';
- iconSpan.style.height = '40px'; // Explicit size
-
- // Label (BeaconName or GatewayName)
- const labelDiv = document.createElement('div');
- // Label styling: small text, centered, semi-transparent bg for readability if overlapping
- labelDiv.className = 'text-[10px] font-bold text-gray-700 bg-white/70 px-1 rounded mt-[-4px] whitespace-nowrap shadow-sm';
- labelDiv.textContent = (type === 'beacon' ? item.BeaconName : item.GatewayName) || item.Position;
-
- if (type === 'beacon') {
- iconSpan.setAttribute('data-icon', 'heroicons:signal');
- } else {
- iconSpan.setAttribute('data-icon', 'lsicon:online-gateway-outline');
- iconContainer.style.zIndex = '40'; // Gateways on top
- }
-
- iconContainer.appendChild(iconSpan);
- iconContainer.appendChild(labelDiv);
-
- // Optional Tooltip for details (MAC)
- const tooltip = document.createElement('div');
- tooltip.className = `hidden group-hover:block absolute bottom-full mb-1 px-2 py-1 bg-black text-white text-xs rounded z-50 whitespace-nowrap`;
- tooltip.textContent = `MAC: ${item.MAC}`;
- iconContainer.appendChild(tooltip);
-
- overlay.appendChild(iconContainer);
- });
- }
-
- function handleClick(e, floor) {
- if (!fingerprintData[floor] || fingerprintData[floor].length === 0) return;
-
- // Get wrapper relative coordinates
- const wrapper = e.currentTarget;
- const rect = wrapper.getBoundingClientRect();
-
- // Mouse relative to the wrapper
- const mouseX = e.clientX - rect.left;
- const mouseY = e.clientY - rect.top;
-
- // Convert to map coordinates
- if (rect.width === 0 || rect.height === 0) return;
-
- const mapX = (mouseX / rect.width) * MAP_WIDTH;
- const mapY = (mouseY / rect.height) * MAP_HEIGHT;
-
- // Round to nearest 50
- let roundedX = Math.round(mapX / 50) * 50;
- let roundedY = Math.round(mapY / 50) * 50;
-
- // If not ending in 50, add 50
- if (String(roundedX).slice(-2) !== '50') roundedX += 50;
- if (String(roundedY).slice(-2) !== '50') roundedY += 50;
-
- console.log(`Click at: ${mapX.toFixed(0)}, ${mapY.toFixed(0)} -> Rounded: ${roundedX}, ${roundedY}`);
-
- // Find exact match
- // Note: JSON coords are numbers, strict equality should work if they are exactly 50.0 etc.
- // Use a tolerance just in case, but user said "same position".
- const match = fingerprintData[floor].find(p => p.X === roundedX && p.Y === roundedY);
-
- if (match) {
- console.log('Match found:', match);
- updateGatewayOpacity(floor, match.gateways);
- } else {
- console.log('No data at rounded coordinates.');
- }
- }
-
- function updateGatewayOpacity(floor, signalMap) {
- const container = document.getElementById(`overlay-floor-${floor}`);
- if (!container) return;
-
- const gateways = container.querySelectorAll('.gateway-icon');
-
- gateways.forEach(gw => {
- const name = gw.getAttribute('data-gateway-name');
- if (name && signalMap.hasOwnProperty(name)) {
- const signal = signalMap[name];
-
- // Logic: -60 (best) -> opacity 1, -100 (worst) -> opacity 0
- // Range is 40 (-60 to -100)
- // Normalized: (signal - (-100)) / (-60 - (-100)) = (signal + 100) / 40
-
- let opacity = (signal + 100) / 40;
-
- // Clamp
- if (opacity < 0) opacity = 0;
- if (opacity > 1) opacity = 1;
-
- gw.style.opacity = opacity;
- } else {
- // No signal data for this gateway at this point -> 0 opacity
- gw.style.opacity = 0;
- }
- });
- }
|