Closed zen85 closed 6 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.
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
oh... now i get you. i indeed might have thought too complicated... thank you thank you!
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
@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.
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?
@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.
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
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!
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) => {