js-three icon indicating copy to clipboard operation
js-three copied to clipboard

Converting coordinates (Vector3 to latlngAltitude)

Open ashkalor opened this issue 2 years ago • 8 comments

Hello,

I've noticed that the library provides support for converting from latitude, longitude, and altitude (latLngAltitude) to vector3 coordinates. However, I'm unable to figure out how to convert from vector3 coordinates to a latLng literal. Is this something that is planned for the future roadmap? I did see a pull request mentioning this (#618), but it seems that the pull request has been closed.

It would be a great help if you could provide an update on this matter.

Thanks.

ashkalor avatar Oct 26 '23 17:10 ashkalor

If you would like to upvote the priority of this issue, please comment below or react on the original post above with :+1: so we can see what is popular when we triage.

@ashkalor Thank you for opening this issue. 🙏 Please check out these other resources that might help you get to a resolution in the meantime:

This is an automated message, feel free to ignore.

wangela avatar Oct 26 '23 17:10 wangela

The minmal functionality for that is still there: https://github.com/googlemaps/js-three/blob/main/src/util.ts#L89-L96

That function will convert an [x, y] coordinate (in mercator-projection) to a LatLngLiteral.

To get from your three.js coordinates to mercator-meters you need to apply the spherical scale factor of the mercator-projection – cos(degToRad(reference.lat))). So to fully convert that'd be something like

function vec3ToLatLngAlt(vec3: Vector3, anchor: LatLngAltitudeLiteral) {
  const z = vec3.z;

  vec3.multiplyScalar(1 / cos(degToRad(anchor.lat))));
  const {lat, lng} = xyToLatLng([vec3.x, vec3.y]);

  return {lat, lng, altitude: anchor.altitude + z};
}

(this is assuming that you're using the default up-axis, otherwise you still have to apply the axis rotation before everything else).

All that said, I'm open to adding this as a method to the overlay class when I get around to do it.

usefulthink avatar Oct 26 '23 19:10 usefulthink

@usefulthink I am having the same problem described in this thread. I tried applying the function you provided above, but it doesn't seem to be right.

The values did not match. However, I've noticed that the values are extremely close if the function is something like this:

const vec3ToLatLngAlt = (vec3: Vector3, anchor: LatLngAltitudeLiteral) => {
    const z = vec3.z;

    vec3.multiplyScalar(1 / Math.cos(MathUtils.degToRad(anchor.lat)));
    const { lat, lng } = xyToLatLng([vec3.x, vec3.y]);

    return { lat: anchor.lat + lat, lng: anchor.lng + lng, altitude: anchor.altitude + z };
};

The values seem to be correct when the return value is the sum of the computed lat/lng with the anchor lat/lng. Moreover, I've noticed that the lng matches the exact geographic coordinate (listened in a Google Maps Event), but lat has a small deviation from the original coordinate.

Is there any calculation that may be missing in order to achieve the exact lat value? Or could this be a floating point problem?

Thank you in advance.

MstrD avatar May 16 '24 11:05 MstrD

That's a very good point, I did somehow miss adding the anchor lat/lng values to the result.

but lat has a small deviation from the original coordinate.

How small are we talking here? Can you provide some concrete values for vec3, anchor and the expected and actual results?

usefulthink avatar May 17 '24 01:05 usefulthink

@usefulthink yes, of course.

How small are we talking here?

It seems that, the further away from the anchor, the larger the deviation is.

In the following examples, my anchor point is:

{
    "altitude": 0,
    "lat": 40.7127753,
    "lng": -74.0059728
}

The first example is a click very close to the anchor point. When I click on the map, the Google Maps listener event tells me this point has the following geographic coordinates:

{
    "lat": 40.713043413517056,
    "lng": -74.00622962150632
}

This point converts to the following Vector3 in local coordinates:

{
    "x": -21.64611191882111,
    "y": 29.81296967957644,
    "z": 0
}

And when I try to reconvert it to geographic coordinates by using the function I provided above, this is the result:

{
    "lat": 40.7131290176473,
    "lng": -74.00622962150632
}

Notice how the lng values between the expected and the actual result are exactly the same, but with lat having that small discrepancy (in this case, with a value of 0.00008560413024127911).

Visually, the deviation corresponds to the following: 3jsoverlayview

Finally, an example with the same anchor, but using a point much further away from it. Its Google Maps coordinates are:

{
    "lat": 41.27495703856301,
    "lng": -73.9395450002507
}

These coordinates translate to the following Vector3, in local space:

{
    "x": 5598.84414844186,
    "y": 62778.270074034874,
    "z": 0
}

Which, when reconverted to geographic coordinates using that same method, return the following value:

{
    "lat": 41.45759062062852,
    "lng": -73.9395450002507
}

Please notice again how the lng value is the same, but how the deviation in lat has increased regarding the previous example. This time, its value is 0.18263358206551317 (a very considerable value).

Thank you very much for your support.

MstrD avatar May 17 '24 08:05 MstrD

@usefulthink after spending some time researching and studying how proj4 works and why the values were not matching (also inspired by what I've read in another issue of this project), I ended up fixing this function. It should be something like this:

const vec3ToLatLngAlt = (vec3: Vector3, anchor: any = _threejsOverlay.anchor) => {
    vec3.applyQuaternion(_threejsOverlay.rotationInverse.clone().invert());

    const z = vec3.z;

    const [anchorX, anchorY] = latLngToXY(anchor);

    vec3.multiplyScalar(1 / Math.cos(MathUtils.degToRad(anchor.lat)));

    const { lat, lng } = xyToLatLng([vec3.x + anchorX, vec3.y + anchorY]);

    return { lat, lng, altitude: anchor.altitude + z };
};

The return values of this function (lat and lng) are now exactly the same as the ones read in a Google Maps event (or, worst case scenario, only rounded to the 15th decimal place - which, for my use case, is neglegible).

It would be pretty nice to have this function added to the source code, since it's useful and it's not that trivial to make these calculations.

MstrD avatar May 20 '24 16:05 MstrD

That makes a lot of sense, thanks for taking the time to figure this out! Do you want to prepare a PR or should I just take your code and integrate it?

usefulthink avatar May 22 '24 07:05 usefulthink

You can just integrate this code if you want. I'll just add that the vec3 passed as an argument should be cloned before performing these calculations, because it shouldn't be modified. So, in the end, the function would be something like this:

const vector3ToLatLngAlt = (vec3: Vector3, anchor: google.maps.LatLngAltitude = overlay.anchor) => {
    const vector3 = vec3.clone();

    vector3.applyQuaternion(overlay.rotationInverse.clone().invert());

    const z = vector3.z;

    const [anchorX, anchorY] = latLngToXY(anchor);

    vector3.multiplyScalar(1 / Math.cos(MathUtils.degToRad(anchor.lat)));

    const { lat, lng } = xyToLatLng([vector3.x + anchorX, vector3.y + anchorY]);

    return { lat, lng, altitude: anchor.altitude + z };
};

Thank you.

MstrD avatar May 22 '24 08:05 MstrD