Spaces:
Running
Running
Download src/utils/globe3d.js from Luca448/APP-Backend: direct link, hf CLI and curl.
- Browser
- Download file 7.72 kB
-
https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/utils/globe3d.js
- Command line
-
hf download hf://spaces/Luca448/APP-Backend/src/utils/globe3d.js
-
curl -L -o globe3d.js https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/utils/globe3d.js
7.72 kB
| export class Globe3D { | |
| constructor(svgElement) { | |
| this.svg = svgElement; | |
| this.R = 175; | |
| this.cx = 200; | |
| this.cy = 200; | |
| this.rot = 0; // current rotation in radians | |
| // Flat paths representing the world (x: 0-600, y: 0-400) | |
| const flatContinents = [ | |
| "M295,110 L310,102 L330,108 L340,118 L336,140 L325,152 L310,155 L298,148 L288,135 L285,120 Z", // Europe | |
| "M300,160 L320,155 L335,175 L340,210 L335,250 L322,268 L308,272 L295,258 L290,225 L288,195 L292,175 Z", // Africa | |
| "M350,80 L390,72 L440,80 L475,100 L490,130 L480,155 L455,165 L420,162 L385,155 L360,145 L345,125 L340,105 Z", // Asia | |
| "M120,75 L160,62 L200,68 L225,85 L230,110 L218,135 L195,152 L168,158 L142,150 L120,132 L108,108 L110,90 Z", // NA | |
| "M185,180 L205,172 L218,190 L215,225 L205,260 L190,278 L175,275 L165,255 L160,225 L165,200 L175,188 Z", // SA | |
| "M480,228 L510,222 L530,235 L528,260 L515,272 L495,270 L480,258 L475,242 Z", // Australia | |
| "M215,42 L240,38 L255,48 L252,65 L238,72 L220,68 L212,55 Z", // Greenland | |
| "M100,330 L200,325 L320,328 L420,325 L500,330 L520,345 L480,355 L350,358 L200,355 L80,350 Z" // Antarctica | |
| ]; | |
| // Data arcs origins and targets | |
| const ox = 310, oy = 125; | |
| const flatTargets = [ | |
| { x: 160, y: 105 }, { x: 190, y: 230 }, { x: 420, y: 140 }, { x: 465, y: 110 }, | |
| { x: 320, y: 260 }, { x: 505, y: 248 }, { x: 140, y: 72 }, { x: 400, y: 80 } | |
| ]; | |
| // Parse to 3D spherical coordinates | |
| this.continents3D = flatContinents.map(path => this.parsePath(path)); | |
| this.origin3D = this.toSpherical(ox, oy); | |
| this.targets3D = flatTargets.map(t => this.toSpherical(t.x, t.y)); | |
| // Create SVG elements | |
| this.group = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| this.group.setAttribute("clip-path", "url(#earth-clip)"); | |
| this.pathElements = this.continents3D.map(() => { | |
| const p = document.createElementNS("http://www.w3.org/2000/svg", "path"); | |
| p.setAttribute("class", "dr-earth-continent"); | |
| this.group.appendChild(p); | |
| return p; | |
| }); | |
| this.arcElements = this.targets3D.map(() => { | |
| const p = document.createElementNS("http://www.w3.org/2000/svg", "path"); | |
| p.setAttribute("class", "dr-arc"); | |
| this.group.appendChild(p); | |
| return p; | |
| }); | |
| this.endpointElements = this.targets3D.map(() => { | |
| const c = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| c.setAttribute("class", "dr-endpoint"); | |
| c.setAttribute("r", "3"); | |
| this.group.appendChild(c); | |
| return c; | |
| }); | |
| this.originElement = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| this.originElement.setAttribute("class", "dr-origin"); | |
| this.originElement.setAttribute("r", "5"); | |
| this.group.appendChild(this.originElement); | |
| this.drawGrid(); | |
| this.svg.appendChild(this.group); | |
| // Start loop | |
| this.animate = this.animate.bind(this); | |
| this.raf = requestAnimationFrame(this.animate); | |
| } | |
| toSpherical(x, y) { | |
| const lon = (x / 600) * Math.PI * 2; | |
| const lat = ((200 - y) / 200) * (Math.PI / 2); | |
| return { lon, lat }; | |
| } | |
| parsePath(pathStr) { | |
| const points = []; | |
| const regex = /([ML])\s*([-\d.]+),([-\d.]+)/g; | |
| let match; | |
| while ((match = regex.exec(pathStr)) !== null) { | |
| const cmd = match[1]; | |
| const x = parseFloat(match[2]); | |
| const y = parseFloat(match[3]); | |
| points.push({ cmd, ...this.toSpherical(x, y) }); | |
| } | |
| const isClosed = /Z$/i.test(pathStr); | |
| return { points, isClosed }; | |
| } | |
| project(lon, lat, currentRot, radiusOffset = 0) { | |
| let currentLon = lon - currentRot; | |
| // Normalize to -PI to PI | |
| while (currentLon > Math.PI) currentLon -= 2 * Math.PI; | |
| while (currentLon < -Math.PI) currentLon += 2 * Math.PI; | |
| let isFront = true; | |
| if (currentLon > Math.PI / 2) { | |
| currentLon = Math.PI / 2; | |
| isFront = false; | |
| } else if (currentLon < -Math.PI / 2) { | |
| currentLon = -Math.PI / 2; | |
| isFront = false; | |
| } | |
| const R = this.R + radiusOffset; | |
| const x3d = R * Math.cos(lat) * Math.sin(currentLon); | |
| const y3d = -R * Math.sin(lat); | |
| return { x: this.cx + x3d, y: this.cy + y3d, isFront }; | |
| } | |
| animate() { | |
| this.rot += 0.005; // rotation speed | |
| if (this.rot > Math.PI * 2) this.rot -= Math.PI * 2; | |
| // Draw continents | |
| this.continents3D.forEach((continent, i) => { | |
| let d = ""; | |
| continent.points.forEach(pt => { | |
| const proj = this.project(pt.lon, pt.lat, this.rot); | |
| d += `${pt.cmd}${proj.x},${proj.y} `; | |
| }); | |
| if (continent.isClosed) d += "Z"; | |
| this.pathElements[i].setAttribute("d", d); | |
| }); | |
| // Draw Origin | |
| const originProj = this.project(this.origin3D.lon, this.origin3D.lat, this.rot); | |
| if (originProj.isFront) { | |
| this.originElement.style.display = 'block'; | |
| this.originElement.setAttribute("cx", originProj.x); | |
| this.originElement.setAttribute("cy", originProj.y); | |
| } else { | |
| this.originElement.style.display = 'none'; | |
| } | |
| // Draw Arcs & Endpoints | |
| this.targets3D.forEach((target, i) => { | |
| const targetProj = this.project(target.lon, target.lat, this.rot); | |
| // Update endpoint | |
| if (targetProj.isFront) { | |
| this.endpointElements[i].style.display = 'block'; | |
| this.endpointElements[i].setAttribute("cx", targetProj.x); | |
| this.endpointElements[i].setAttribute("cy", targetProj.y); | |
| } else { | |
| this.endpointElements[i].style.display = 'none'; | |
| } | |
| // Draw arc (if either start or end is on front) | |
| if (originProj.isFront || targetProj.isFront) { | |
| let arcD = `M${originProj.x},${originProj.y} `; | |
| const segments = 10; | |
| for (let s = 1; s <= segments; s++) { | |
| const t = s / segments; | |
| // Interpolate lon and lat | |
| // Handle shortest path across the dateline | |
| let dLon = target.lon - this.origin3D.lon; | |
| if (dLon > Math.PI) dLon -= 2 * Math.PI; | |
| if (dLon < -Math.PI) dLon += 2 * Math.PI; | |
| const iLon = this.origin3D.lon + dLon * t; | |
| const iLat = this.origin3D.lat + (target.lat - this.origin3D.lat) * t; | |
| // Add altitude | |
| const alt = Math.sin(t * Math.PI) * 40; | |
| const p = this.project(iLon, iLat, this.rot, alt); | |
| arcD += `L${p.x},${p.y} `; | |
| } | |
| this.arcElements[i].style.display = 'block'; | |
| this.arcElements[i].setAttribute("d", arcD); | |
| } else { | |
| this.arcElements[i].style.display = 'none'; | |
| } | |
| }); | |
| this.raf = requestAnimationFrame(this.animate); | |
| } | |
| drawGrid() { | |
| const gridColor = "rgba(122, 186, 255, 0.08)"; | |
| const createPath = (d) => { | |
| const p = document.createElementNS("http://www.w3.org/2000/svg", "path"); | |
| p.setAttribute("d", d); | |
| p.setAttribute("fill", "none"); | |
| p.setAttribute("stroke", gridColor); | |
| p.setAttribute("stroke-width", "0.5"); | |
| this.group.insertBefore(p, this.group.firstChild); | |
| }; | |
| // Equator and Longitude Center | |
| createPath(`M25,200 L375,200`); | |
| createPath(`M200,25 L200,375`); | |
| // Latitudes (Ellipses) | |
| createPath(`M 52 100 A 148 50 0 0 0 348 100`); | |
| createPath(`M 52 300 A 148 50 0 0 1 348 300`); | |
| createPath(`M 28 150 A 172 40 0 0 0 372 150`); | |
| createPath(`M 28 250 A 172 40 0 0 1 372 250`); | |
| // Longitudes (Ellipses) | |
| createPath(`M 200 25 A 60 175 0 0 0 200 375`); | |
| createPath(`M 200 25 A 120 175 0 0 0 200 375`); | |
| createPath(`M 200 25 A 60 175 0 0 1 200 375`); | |
| createPath(`M 200 25 A 120 175 0 0 1 200 375`); | |
| } | |
| destroy() { | |
| cancelAnimationFrame(this.raf); | |
| this.group.remove(); | |
| } | |
| } | |