Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

method to convert word-coordinates to normalized device coordinates (screen coordinates) #11

Closed
FrissAnalytics opened this issue Jun 16, 2020 · 2 comments

Comments

@FrissAnalytics
Copy link

Dear Vasco,

Hope you are doing well in these challenging times. Thanks again for all your amazing work, really cool stuff!

I was wondering, is there a way to convert lat/long/altitude or x,y,z coordinates used by threejs to convert them to normalized device coordinates or x,y screen coordinates in relation to say a container element?

This would be very helpful to create annotations (say via an overlay div), showing additional information on globe positions, in which the annotation is connected via a line to the globe.

This is sort of what I'm aiming for: (the globe etc is already based on your work :-)

image

For a cool effect it would be nice that when the camera view changes, the annotation connection line updates as well. I'm aware of the getCoords and toGeoCoords functions, but I guess they serve a different purpose.

There are some references on the web that solve the coordinate conversion in relation to the current camera view, in particular see this three-js-annotations example and this stackoverflow question.

In the spirit of one of your own examples, let's say I set some labels on a globe (myGlobe) via labelsData and let's say I want to create an annotation for the first label.

In what is suggested in the references above, I hoped something like this would work to get the x,y position information I was looking for:

  let pos = new THREE.Vector3();
  let obj = myGlobe.labelsData()[1]
  pos = pos.setFromMatrixPosition(obj.__threeObj.matrixWorld);
  pos.project(myGlobe.camera);
  console.log("position", pos);

Unfortunately it doesn't. Somehow at the end it seems to go wrong in this threejs function:

applyMatrix3: function ( m ) {
     		
    var x = this.x, y = this.y, z = this.z;
    var e = m.elements;
    this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
    this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
    this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;

    return this;
 }

In both references the applyMatrix3 function errors out saying that elements does not exist.

Do you have any clue what I can do?

I think the ability to add annotations, be it only by providing a method to get appropriate x,y coordinates in relation to the camera view, could be helpful to others as well, hence this feature request.

Personally I would already be super grateful if I can manually do the conversion, but I seem to lack the knowledge on what to do here. Any help is greatly appreciated.

best wishes and have a great day! Herman

@FrissAnalytics
Copy link
Author

hi again!

my bad, I now realize that to use the camera one needs to say: myGlobe.camera(), instead of myGlobe.camera!

For those still interested in x, y screen coordinates for e.g. a label that gets clicked, you can get the coordinates as follows (assuming we have setup a globe myGlobe, and have some label data stored in places, that holds the lat, long data etc.) (see examples in this repo):

// get the globe and setup some label data
myGlobe
	.labelsData(places)
	.labelLat(d => d.properties.latitude)
	.labelLng(d => d.properties.longitude)
	.labelText(d => d.properties.name)
	.onLabelClick(d => {
		// on label click, extract the __threeObj object from `d`, clone it (otherwise the original data gets overwritten!), and get the position
		const position = d.__threeObj.__data.__threeObj.clone().position;

		// use the project method and pass it the camera to get the device independent coordinates i.e. x,y,z coordinates between -1 and 1; 
                // note that if the camera position changes, these coordinates change as well!
		const coordinates = position.project(myGlobe.camera())

	        // next, scale these coordinates in relation to the available canvas size
               
                // grab the canvas that holds the globe e.g. using d3
		const canvas = d3.select("#globeViz").select("canvas");

		// get the width and height
		const width = canvas.attr("width");
		const height = canvas.attr("height");

		// scale the x, y coordinates from range -1,1 to the available width and height
		const x = Math.round((0.5 + coordinates.x / 2) * (width / window.devicePixelRatio));
		const y = Math.round((0.5 - coordinates.y / 2) * (height / window.devicePixelRatio));

		// the actual x,y coordinates in relation to the canvas
		// these can be used to e.g. create annotations via an overlay div
                // note we need these x,y coordinates to attach the annotation in the overlay div
                // to the data point on the globe e.g. via a line (see screenshot above)
		console.log("screen coordinates - x:", x, " y:", y);
	})

@vasturiano I guess it would still be handy for others to add a method that makes it easier to get these coordinates, assuming some would like to create annotations as I did. Anyways, great library, really loving it! :-)

@vasturiano
Copy link
Owner

@FrissAnalytics thanks for your suggestion.

I think it's a nice addition in any case. So I've added a new method that facilitates this: getScreenCoords.

It receives spherical coordinates (lat, lng, ?alt) and returns the viewport coordinates of where that location is rendered on the screen.
The method is quite similar to the one you use above. It first converts to cartesian coordinates and then projects them via the camera to retrieve the screen position.

Thanks again for the nice idea. 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants