{"id":1174,"date":"2026-04-13T09:38:11","date_gmt":"2026-04-13T09:38:11","guid":{"rendered":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/?page_id=1174"},"modified":"2026-04-14T18:06:12","modified_gmt":"2026-04-14T18:06:12","slug":"student-tracking","status":"publish","type":"page","link":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/student-tracking\/","title":{"rendered":"student tracking"},"content":{"rendered":"\r\n<div id=\"map-wrapper\" style=\"position:relative;\">\r\n  <button id=\"fullViewBtn\" style=\"\r\n    position:absolute;\r\n    top:10px;\r\n    right:10px;\r\n    z-index:9999;\r\n    padding:8px 10px;\r\n    background:#d96129;\r\n    border:1px solid #ccc;\r\n    cursor:pointer;\r\n    border-radius:4px;\r\n  \">Full View<\/button>\r\n\r\n  <div id=\"map\" style=\"height:500px;width:100%;\"><\/div>\r\n<\/div>\r\n\r\n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet\/dist\/leaflet.css\" \/>\r\n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/MarkerCluster.css\" \/>\r\n<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/MarkerCluster.Default.css\" \/>\r\n\r\n<script src=\"https:\/\/unpkg.com\/leaflet\/dist\/leaflet.js\"><\/script>\r\n<script src=\"https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/leaflet.markercluster.js\"><\/script>\r\n\r\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/8.10.0\/firebase-app.js\"><\/script>\r\n<script src=\"https:\/\/www.gstatic.com\/firebasejs\/8.10.0\/firebase-database.js\"><\/script>\r\n\r\n<script>\r\nlet map;\r\nlet myMarker;\r\nlet userId;\r\n\r\n\/\/ \ud83d\udd25 Firebase config\r\nconst firebaseConfig = {\r\n  apiKey: \"AIzaSyAHT_f4DoCa6OrchSLVl-FwEylnY1TZ_po\",\r\n  authDomain: \"testfirebase1-d6623.firebaseapp.com\",\r\n  databaseURL: \"https:\/\/testfirebase1-d6623-default-rtdb.europe-west1.firebasedatabase.app\",\r\n  projectId: \"testfirebase1-d6623\"\r\n};\r\n\r\nfirebase.initializeApp(firebaseConfig);\r\nconst db = firebase.database();\r\n\r\n\/\/ \ud83d\udfe2 ALL MARKERS STORAGE\r\nwindow.markers = {};\r\n\r\n\/\/ \ud83d\udfe2 CLUSTER GROUP (NEW)\r\nlet markerCluster;\r\n\r\nfunction initMap() {\r\n\r\n  userId = \"user_\" + Math.random().toString(36).substr(2, 9);\r\n\r\n  const userRef = db.ref(\"locations\/\" + userId);\r\n  userRef.onDisconnect().remove();\r\n\r\n  map = L.map('map').setView([40.6401, 22.9444], 13);\r\n\r\n  L.tileLayer('https:\/\/{s}.tile.openstreetmap.org\/{z}\/{x}\/{y}.png', {\r\n    attribution: '&copy; OpenStreetMap contributors'\r\n  }).addTo(map);\r\n\r\n  \/\/ \ud83d\udd25 INIT CLUSTER\r\n  markerCluster = L.markerClusterGroup({\r\n    chunkedLoading: true,\r\n    spiderfyOnMaxZoom: true,\r\n    showCoverageOnHover: false,\r\n    disableClusteringAtZoom: 17\r\n  });\r\n\r\n  map.addLayer(markerCluster);\r\n\r\n  \/\/ LIVE USERS\r\n  db.ref(\"locations\").on(\"value\", (snap) => {\r\n\r\n    const data = snap.val() || {};\r\n    const now = Date.now();\r\n\r\n    \/\/ cleanup old users\r\n    for (let id in data) {\r\n      if (now - data[id].timestamp > 30000) {\r\n        db.ref(\"locations\/\" + id).remove();\r\n      }\r\n    }\r\n\r\n    \/\/ add \/ update markers\r\n    for (let id in data) {\r\n\r\n      const user = data[id];\r\n      const pos = [user.lat, user.lng];\r\n\t\t\r\n\t\t\r\n\t\t\r\n\r\n\t\t\r\n\t\t\r\n\t\t\r\n\r\n      if (window.markers[id]) {\r\n\r\n        window.markers[id].setLatLng(pos);\r\n        window.markers[id].getPopup()?.setContent(user.name || \"User\");\r\n\r\n      } else {\r\n\r\n        window.markers[id] = L.marker(pos)\r\n          .bindPopup(user.name || \"User\");\r\n\r\n        \/\/ \ud83d\udd25 IMPORTANT: add to cluster (NOT map)\r\n        markerCluster.addLayer(window.markers[id]);\r\n      }\r\n    }\r\n\r\n    \/\/ remove deleted users\r\n    for (let id in window.markers) {\r\n      if (!data[id]) {\r\n        markerCluster.removeLayer(window.markers[id]);\r\n        delete window.markers[id];\r\n      }\r\n    }\r\n  });\r\n\r\n  startTracking();\r\n}\r\n\r\nfunction startTracking() {\r\n\r\n  const name = \"User_\" + Math.random().toString(36).substr(2, 5);\r\n\r\n  if (!navigator.geolocation) return;\r\n\r\n  let lastPos = null;\r\n\r\n  navigator.geolocation.watchPosition((pos) => {\r\n\r\n    const lat = pos.coords.latitude;\r\n    const lng = pos.coords.longitude;\r\n    const accuracy = pos.coords.accuracy;\r\n\r\n    \/\/ \ud83d\udd25 throttle movement\r\n    if (lastPos) {\r\n      const diff = Math.sqrt(\r\n        Math.pow(lat - lastPos.lat, 2) +\r\n        Math.pow(lng - lastPos.lng, 2)\r\n      );\r\n      if (diff < 0.00001) return;\r\n    }\r\n\r\n    lastPos = { lat, lng };\r\n\r\n    \/\/ \ud83d\udd25 Firebase update\r\n    db.ref(\"locations\/\" + userId).set({\r\n      name: name,\r\n      lat: lat,\r\n      lng: lng,\r\n      accuracy: accuracy,\r\n      timestamp: Date.now()\r\n    });\r\n\r\n    \/\/ \ud83d\udd25 CREATE ONLY ONCE + UPDATE AFTER\r\n    if (!window.markers[userId]) {\r\n\r\n      window.markers[userId] = L.marker([lat, lng])\r\n        .bindPopup(\"Me\");\r\n\r\n      markerCluster.addLayer(window.markers[userId]);\r\n\r\n    } else {\r\n\r\n      window.markers[userId].setLatLng([lat, lng]);\r\n\r\n    }\r\n\r\n    \/\/ optional: follow user\r\n    map.setView([lat, lng]);\r\n\r\n  }, console.error, {\r\n    enableHighAccuracy: true,\r\n    maximumAge: 3000,\r\n    timeout: 10000\r\n  });\r\n}\r\n\r\n\/\/ FULL VIEW\r\nlet isFull = false;\r\n\r\ndocument.getElementById(\"fullViewBtn\").addEventListener(\"click\", function () {\r\n\r\n  const wrapper = document.getElementById(\"map-wrapper\");\r\n  const mapDiv = document.getElementById(\"map\");\r\n\r\n  if (!isFull) {\r\n    wrapper.style.position = \"fixed\";\r\n    wrapper.style.top = \"0\";\r\n    wrapper.style.left = \"0\";\r\n    wrapper.style.width = \"100vw\";\r\n    wrapper.style.height = \"100vh\";\r\n    wrapper.style.zIndex = \"9999\";\r\n    mapDiv.style.height = \"100%\";\r\n    this.innerHTML = \"Exit Full View\";\r\n    isFull = true;\r\n  } else {\r\n    wrapper.style.position = \"relative\";\r\n    wrapper.style.width = \"100%\";\r\n    wrapper.style.height = \"500px\";\r\n    wrapper.style.zIndex = \"1\";\r\n    mapDiv.style.height = \"500px\";\r\n    this.innerHTML = \"Full View\";\r\n    isFull = false;\r\n  }\r\n\r\n  setTimeout(() => map.invalidateSize(), 200);\r\n});\r\n\r\ninitMap();\r\n\r\n<\/script>\r\n\r\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_eb_attr":"","footnotes":""},"class_list":["post-1174","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/pages\/1174","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/comments?post=1174"}],"version-history":[{"count":4,"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/pages\/1174\/revisions"}],"predecessor-version":[{"id":1201,"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/pages\/1174\/revisions\/1201"}],"wp:attachment":[{"href":"https:\/\/nyiannios.sites.sch.gr\/wordpress\/wp-json\/wp\/v2\/media?parent=1174"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}