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

Clamp latitude value to valid Web Mercator range when projecting to y #6918

Merged
merged 1 commit into from
Aug 7, 2018

Conversation

ryanhamley
Copy link
Contributor

@ryanhamley ryanhamley commented Jul 5, 2018

Fixes #6906

Launch Checklist

  • briefly describe the changes in this PR
    • clamps latitude values to valid Web Mercator range
    • handles large, wrapped longitude values by reducing them to a +-180 range and avoiding world spin animation
  • write tests for all new functionality
  • manually test the debug page

@hyperknot
Copy link

That should take care of latitude (as long as clamp works correctly with Infnity as well).

Longitude on the other hand is tricky:

  • It should support wrapped bounds
  • Yet, if a too big lon value is given, it starts a crazy world rotating animation.
    Like:
fitBounds([0,-40,10000,40])

Actually, fitBounds([0,-40,10000,40]) is wrong for an other reason: it zooms out the map, thus breaking the lat constraints. I believe it should do a mod 360 for exampe.

My idea is that for longitude, there should be some kind of reasonable clamping so that Infinity works, as well as a mod 360 operation.

@@ -295,6 +295,12 @@ class Transform {
* @returns {number} pixel coordinate
*/
latY(lat: number) {
const validLatitude = 85.05113;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declare as static constant outside this method, maybe in util.js or on LngLat

Copy link

@hyperknot hyperknot Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is this.latRange = [-85.05113, 85.05113]; in the same file in an other place as well, as well as a couple of -85.0511 values in style-spec/reference/v8.json.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll refactor these references to a single place.

Copy link

@hyperknot hyperknot Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You probably cannot refactor the json one, but maybe it's a good idea to have the same precision level in all 3 places (that is, probably stick with 85.0511)

Copy link
Contributor Author

@ryanhamley ryanhamley Jul 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it should be declared in LngLat because the way I understand it is that the LngLat is based around EPSG84 coordinates which are valid up to +-90° so the issue is more in projecting from those coordinates to valid Web Mercator ones (EDIT: well in projecting from y to to Web Mercator lat anyway). I'm leaning towards using the latRange property in Transform.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using latRange would prevent general transform/projection calls from using otherwise valid calls with LatLng outside the bounds specified in Map#setMaxBound. Im not sure that would be clear/expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhh, yeah I was misunderstanding what latRange was. Thanks for the clarification.

@ryanhamley ryanhamley changed the title Clamp latitude value to valid Web Mercator range when projection to y… Clamp latitude value to valid Web Mercator range when projecting to y Jul 5, 2018
@mourner
Copy link
Member

mourner commented Jul 5, 2018

I recently tried to tackle the same longitude issue described by @hyperknot in Supercluster — perhaps you could reuse some of that logic: https://github.com/mapbox/supercluster/blob/master/index.js#L72-L84

@hyperknot
Copy link

Ah, I looked into this, the silly negative modulo of JS really makes it complicated. Picking @mourner's solution verbatim is probably the best idea.

(BTW, why the 180 special case? This way 180 maps to 180 and 540 maps to -180. Was the original aim to make min between -180 .. 179.99 and max between -179.99 .. 180?)

@@ -295,6 +297,9 @@ class Transform {
* @returns {number} pixel coordinate
*/
latY(lat: number) {
if (lat > this.maxValidLatitude || lat < this.maxValidLatitude * -1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any point in using an if here? Why not just call clamp directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's probably fine. Originally, I had a console warning inside the if statement but I think we've decided to favor documentation over a warning so this can be removed.

@hyperknot
Copy link

hyperknot commented Jul 26, 2018

Is there anything blocking this? I thought copying @mourner's snippet from supercluster would solve longitude. We just need to fix the 540 bug there (mapbox/supercluster#96).

@ryanhamley
Copy link
Contributor Author

No, I just have been pushing to get some other stuff finished and this was put on the back burner. I can finish up this PR for just the latitude fix if you're interested in putting together a PR for the longitude fix separately @hyperknot. Otherwise, it will probably be next week before I finish this.

@hyperknot
Copy link

hyperknot commented Jul 26, 2018

I'm happy to do a PR for lon fix from the supercluster snippet.

@ryanhamley ryanhamley force-pushed the 6906-clamp-latitude-projections branch 2 times, most recently from e477c1c to eb58382 Compare August 1, 2018 20:26
@ryanhamley
Copy link
Contributor Author

@mourner Can you take a look at my latest implementation, particularly the change to transform#lngX? I copied over your math from the Supercluster code you linked to earlier. Is that all that's needed to properly handle large longitude values? It looks as if it's working correctly just manually debugging but I'm not sure if there's something I'm not thinking of.

@hyperknot
Copy link

@ryanhamley I think @mourner's code is having two cases to make sure that -180 gets mapped to -180 when it's the min side and and +180 to +180 when it's the max side.

Copy link
Member

@mourner mourner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm now hesitating about the longitude wrapping — it can bring some unintended consequences. Maybe let's leave that for a different PR?


this._renderWorldCopies = renderWorldCopies === undefined ? true : renderWorldCopies;
this._minZoom = minZoom || 0;
this._maxZoom = maxZoom || 22;

this.setMaxBounds();
this.latRange = [this.maxValidLatitude * -1, this.maxValidLatitude];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move this back to setMaxBounds to make sure the behavior is consistent (and setMaxBounds(null) resets the map to the state it was by default).

Also nit: -this.maxValidLatitude is more readable than this.maxValidLatitude * -1.

@ryanhamley
Copy link
Contributor Author

I'm now hesitating about the longitude wrapping — it can bring some unintended consequences. Maybe let's leave that for a different PR?

@mourner Agreed. I'm reverting that change and will just close out the original latitude clamping issue with this PR. I think there's potential to combine longitude wrapping with #6985 in a single PR.

@ryanhamley
Copy link
Contributor Author

@mourner I think this PR is good to go from my standpoint if you can look it over and give it a 👍 if it looks good.

@@ -503,7 +503,7 @@ test('camera', (t) => {
const camera = createCamera({ zoom: 0 });
camera.rotateTo(90, { around: [5, 0], duration: 0 });
t.equal(camera.getBearing(), 90);
t.deepEqual(fixedLngLat(camera.getCenter()), fixedLngLat({ lng: 4.999999999999972, lat: 0.000014144426558004852 }));
t.deepEqual(fixedLngLat(camera.getCenter()), fixedLngLat({ lng: 4.999999999999972, lat: 0.000002552471840999715 }));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question — why is value here different? Latitude is close to 0 so isn't supposed to be affected by clamping here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is caused by a slight change in the maxValidLatitude value from 85.05113 to 85.051129. Should I revert to the older value?

@ryanhamley ryanhamley force-pushed the 6906-clamp-latitude-projections branch from fb1de16 to cfd24b6 Compare August 7, 2018 17:32
@ryanhamley ryanhamley merged commit cfd24b6 into master Aug 7, 2018
@ryanhamley ryanhamley deleted the 6906-clamp-latitude-projections branch August 7, 2018 17:45
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

Successfully merging this pull request may close these issues.

4 participants