vasturiano / three-globe

WebGL Globe Data Visualization as a ThreeJS reusable 3D object
https://vasturiano.github.io/three-globe/example/links/
MIT License
1.22k stars 147 forks source link

Is it possible to to easily get the position of the mesh representing the satellite #92

Closed zen85 closed 6 months ago

zen85 commented 7 months ago

in the satellite example i wondered if i can create a labelfunction with 2d html labels flying along the meshes. i did this successfully in globe.gl utilising the getScreenCoords(lat, lng [,altitude]).

but i actually need this in my three-globe code where this is not available and so i need to write something on my own. i cant find out how to get the position of the meshes the library is making for me to have a basis for that function.

i guess this should happen somewhere here: ` const updatedSatData = satData.map((d) => {

    const eci = propagate(d.satrec, time);
    if (eci.position && typeof eci.position === 'object') {

      const gdPos = eciToGeodetic(eci.position, gmst);

      return {
        ...d,
        lat: radiansToDegrees(gdPos.latitude),
        lng: radiansToDegrees(gdPos.longitude),
        alt: gdPos.height / EARTH_RADIUS_KM,
      };

    }

    return d;

  `});``

  what would be the most efficent way to find out the three.js position of the meshes since i cant find the correct way?
vasturiano commented 7 months ago

@zen85 using the utility method getCoords you can convert from spherical (lat, lng, alt) coords to cartesian (x, y, z).

But perhaps even easier, if you use the html elements layer, you can add html label items directly by specifying spherical coords.

zen85 commented 7 months ago

oh thank you for that answer but this part works perfectly fine already. but i need to label objectData. So the html labels "fly" with the satellites . pretty much the same effect i get when i do a mouseover on a satellite with globe.gl but permamently without having to do a mouseover so i can build a function that works like getScreenCords but have it in three-globe and not just in globe.gl

zen85 commented 7 months ago

oh... now i get you. i indeed might have thought too complicated... thank you thank you!

zen85 commented 7 months ago

and while this seems so close to the solution i cant find a way to apply "altidude" to the gdata example in the html-marker example. i can only add an altitude like this:

const Globe = new ThreeGlobe()
      .globeImageUrl('//unpkg.com/three-globe/example/img/earth-blue-marble.jpg')
      .bumpImageUrl('//unpkg.com/three-globe/example/img/earth-topology.png')
      .objectLat('lat')
      .objectLng('lng')
      .objectAltitude('alt')
      .objectFacesSurface(false)

      .htmlElementsData(gData) 
      .htmlAltitude(10)
      .htmlElement((d: any) => { // Use 'any' or a broader type here
        const el = document.createElement('div');
        el.innerHTML = markerSvg;
        el.style.color = d.color;
        el.style.width = `${d.size}px`;
        return el;
      });

but doing something like:

const gData = [...Array(N).keys()].map(() => ({
lat: (Math.random() - 0.5) * 180,
lng: (Math.random() - 0.5) * 360,
alt: 10,
size: 10,
color: 'red'
}));

has no effect since "alt" is not working like "lat" and "lng" and to let the labels track the satellites i would need to be able to set this dynamically too. is there a way for me to do that i did not think about yet?

btw: i admire this library and your work - i play around with it time and time again. something about visualising things on the globe with it is simply beautiful: https://www.youtube.com/watch?v=xrfQ1elY4qM

vasturiano commented 7 months ago

@zen85 .htmlAltitude(10) should be working. But, I should mention that it seems like a really high value of altitude. That's essentially 10x the globe radius. Not sure that's the intention.

If that's not the issue, maybe you can make a simple example on https://codesandbox.io/ so we can have a closer look.

zen85 commented 6 months ago

i tried my best and had some success but still there are very interessting behaviours i can not get behind:

Unfortunatly i couldnt get it to run on codesandbox but i made an example of my progress here: https://josefwagner.net/threeglobetest.html

now i have the html divs flying at the position of the satellites but i cant see the meshes at the same time. when i remove: Globe.htmlElementsData(satData);

i can see the meshes again but of course withouth the html-divs.

is there a way to have both?

vasturiano commented 6 months ago

@zen85 I can't see your code so I can't tell for sure, but is there a chance you may be passing the exact same data objects to more than one layer? If you do that they will definitely conflict among each other and only one of the layers will function correctly. If that's the case please clone the objects so that what you are passing to the separate layers are actually different object references.

zen85 commented 6 months ago

so close...

i indeed did what you were suspecting.

i could fix that but now am back to my original problem. the htmlDiv should hover at the same altitude as the mesh. my minimal code showing the problem looks like this:

<head>
  <style>
    body { margin: 0; }

    #time-log {
      position: absolute;
      font-size: 12px;
      font-family: sans-serif;
      padding: 5px;
      border-radius: 3px;
      background-color: rgba(200, 200, 200, 0.1);
      color: lavender;
      bottom: 10px;
      right: 10px;
    }
  </style>

  <script type="importmap">{ "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three/examples/jsm/"
  }}</script>
  <script type="module">
    import * as THREE from 'three';
    window.THREE = THREE;
  </script>

  <script src="//unpkg.com/three-globe" defer></script>
  <!--  <script src="../../dist/three-globe.js" defer></script>-->

  <script src="//unpkg.com/satellite.js/dist/satellite.min.js"></script>
</head>

<body>
  <div id="globeViz"></div>
  <div id="time-log"></div>

 <script type="module">
  import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
  import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';

  const markerSvg = `<div>Sat</div>`;

  const EARTH_RADIUS_KM = 6371; // km
  const SAT_SIZE = 180; // km
  const TIME_STEP = 3 * 1000; // per frame

  const timeLogger = document.getElementById('time-log');

  // Directly define TLE data within the code
  const tleData = [
    ["VANGUARD 2", "1    11U 59001A   22053.83197560  .00000847  00000-0  45179-3 0  9996", "2    11  32.8647 264.6509 1466352 126.0358 248.5175 11.85932318689790"],
    ["VANGUARD 3", "1 00020U 59007A   22053.60170665  .00000832  00000-0  32375-3 0  9992", "2 00020  33.3540 150.1993 1666456 290.4879  52.4980 11.56070084301793"],
    ["EXPLORER 7", "1 00022U 59009A   22053.49750630  .00000970  00000-0  93426-4 0  9997", "2 00022  50.2831  94.4956 0136813  90.0531 271.6094 14.96180956562418"]
  ];

  const satData = tleData.map(([name, line1, line2]) => ({
    satrec: satellite.twoline2satrec(line1, line2),
    name: name.trim().replace(/^0 /, '')
  }));

    const satDataLabel = satData.map(item => ({
      ...item,
      satrec: { ...item.satrec }
    }));

  const Globe = new ThreeGlobe()
    .globeImageUrl('//unpkg.com/three-globe/example/img/earth-blue-marble.jpg')
    .objectLat('lat')
    .objectLng('lng')
    .objectAltitude('alt')
    .objectFacesSurface(false);

  const satGeometry = new THREE.OctahedronGeometry(SAT_SIZE * Globe.getGlobeRadius() / EARTH_RADIUS_KM / 2, 0);
  const satMaterial = new THREE.MeshLambertMaterial({ color: 'palegreen', transparent: true, opacity: 0.7 });
  Globe.objectThreeObject(() => new THREE.Mesh(satGeometry, satMaterial));

  // time ticker
  let time = new Date();
  (function frameTicker() {
    requestAnimationFrame(frameTicker);

    time = new Date(+time + TIME_STEP);
    timeLogger.innerText = time.toString();

    // Update satellite positions
    const gmst = satellite.gstime(time);
    satData.forEach(d => {
      const eci = satellite.propagate(d.satrec, time);
      if (eci.position) {
        const gdPos = satellite.eciToGeodetic(eci.position, gmst);
        d.lat = satellite.radiansToDegrees(gdPos.latitude);
        d.lng = satellite.radiansToDegrees(gdPos.longitude);
        d.alt = gdPos.height / EARTH_RADIUS_KM

        Globe.objectsData(satData);

      }

    });

    const satDataLabel = satData.map(item => ({
      ...item,
      satrec: { ...item.satrec }
    }));

    satDataLabel.forEach(d => {

        Globe.htmlElement(d => {
            const el = document.createElement('div');
            el.innerHTML = markerSvg;
            el.style.color = "green";
            el.style.width = `40px`;
            return el;
        });

        Globe.htmlElementsData(satDataLabel);

    });

  })();

  // Setup renderers
    const renderers = [new THREE.WebGLRenderer(), new CSS2DRenderer()];
    renderers.forEach((r, idx) => {
      r.setSize(window.innerWidth, window.innerHeight);
      if (idx > 0) {
        // overlay additional on top of main renderer
        r.domElement.style.position = 'absolute';
        r.domElement.style.top = '0px';
        r.domElement.style.pointerEvents = 'none';
      }
      document.getElementById('globeViz').appendChild(r.domElement);
    });

  // Setup scene
  const scene = new THREE.Scene();
  scene.add(Globe);
  scene.add(new THREE.AmbientLight(0xcccccc, Math.PI));
  scene.add(new THREE.DirectionalLight(0xffffff, 0.6 * Math.PI));

  // Setup camera
  const camera = new THREE.PerspectiveCamera();
  camera.aspect = window.innerWidth/window.innerHeight;
  camera.updateProjectionMatrix();
  camera.position.z = 400;

  // Add camera controls
  const tbControls = new TrackballControls(camera, renderers[0].domElement);
  tbControls.minDistance = 101;
  tbControls.rotateSpeed = 5;
  tbControls.zoomSpeed = 0.8;

  // Update pov when camera moves
  Globe.setPointOfView(camera.position, Globe.position);
  tbControls.addEventListener('change', () => Globe.setPointOfView(camera.position, Globe.position));

  // Kick-off renderers
    (function animate() { // IIFE
      // Frame cycle
      tbControls.update();
      renderers.forEach(r => r.render(scene, camera));
      requestAnimationFrame(animate);
    })();
</script>

</body>

and i have uploaded it here too: https://josefwagner.net/threeglobetest.html

zen85 commented 6 months ago

oh great success! i just learned something about the accessor :)

the key to my solution is:

(function frameTicker() {
    requestAnimationFrame(frameTicker);

    time = new Date(+time + TIME_STEP);
    timeLogger.innerText = time.toString();

    // Update satellite positions
    const gmst = satellite.gstime(time);
    satData.forEach(d => {
        const eci = satellite.propagate(d.satrec, time);
        if (eci.position) {
            const gdPos = satellite.eciToGeodetic(eci.position, gmst);
            d.lat = satellite.radiansToDegrees(gdPos.latitude);
            d.lng = satellite.radiansToDegrees(gdPos.longitude);
            d.alt = gdPos.height / EARTH_RADIUS_KM; // Ensure this calculation is correct and altitude is in terms of globe radius units
        }
    });

    Globe.objectsData(satData);

    // Prepare the satellite data with HTML elements
    const satDataLabel = satData.map(item => ({
        ...item,
        satrec: { ...item.satrec }
    }));

    satDataLabel.forEach(d => {
        Globe.htmlElement(() => {
            const el = document.createElement('div');
            el.innerHTML = `<div>${d.name}</div>`;
            el.style.color = "green";
            el.style.width = `40px`;
            return el;
        });
    });

    // Set the htmlAltitude as an accessor function
    Globe.htmlAltitude(d => d.alt);
    Globe.htmlTransitionDuration(0);
    Globe.htmlElementsData(satDataLabel);

})();

so: Globe.htmlAltitude(d => d.alt); does the trick!