mapbox / mapbox-gl-js

Interactive, thoroughly customizable maps in the browser, powered by vector tiles and WebGL
https://docs.mapbox.com/mapbox-gl-js/
Other
11.04k stars 2.21k forks source link

Map-independent projection/unprojection #5687

Open mike-marcacci opened 6 years ago

mike-marcacci commented 6 years ago

This is opened in response to this comment on PR #5662, suggesting I open an issue to discuss this use case.

My Use Case

I have a react app which uses a map to geographically filter lots of results which appear in a sidebar – think Zillow-style navigation.

As is the idiom of react and other compositional frameworks, data flows unidirectionaly from the outermost component down the heirarchy of children. My goal here is to be able to calculate a GeoJSON polygon that represents the visible region of the globe, independant of actually rendering the map component.

Given the center point (Lng/Lat), zoom, pitch, bearing (all of which are stored in the URL), and the screen resolution, I need to get a LngLat for a given Point on the screen.

It's important that it's seperate from the rendered map for a few reasons:

  1. This needs to be calculated to populate the list during server side rendering, where we can't actually draw the map.
  2. We need to calculate this in different places:
    • API calls over-fetch (essentially using a smaller zoom)
    • Client side filtering considers panels that cover part of the map
  3. OCD and the fact that we've maintained unidirectional data flow elsewhere πŸ˜‰

Of course, the resulting polygon must be identical to the field of view in the map itself. This would be trivial, except for pitch and bearing.

Initial Design

My initial strategy was to use the same Transform class used internally by the map. This worked quite well in the browser, but my PR to export it exposed more internals than was desired. Additionally, when I began using it server-side, its requirement for WebGL broke in our current SSR strategy.

Here's the kind of thing I would do using the Transform class:

const transform = new Transform();
transform.center = { lng: geoLongitude, lat: geoLatitude };
transform.bearing = geoBearing;
transform.pitch = geoPitch;
transform.resize(window.innerWidth, window.innerHeight);

// get bounds for a search query
transform.zoom = geoZoom - 1;
const nw = transform.pointLocation(new Point(0, 0)).toArray();
const ne = transform.pointLocation(new Point(window.innerWidth, 0)).toArray();
const se = transform.pointLocation(new Point(window.innerWidth, window.innerHeight)).toArray();
const sw = transform.pointLocation(new Point(0, window.innerHeight)).toArray();
const coordinates = [[nw, ne, se, sw, nw]];

// figure out the "visible" center, taking into consideration the list and panels
transform.zoom = geoZoom;
const [ visibleLongitude, visibleLatitude ] = transform.pointLocation(new Point((transform.width + 654) / 2, transform.height / 2)).toArray();

Is there any reasonable way to accomplish this without the heavy internals of Transform?

Thanks!

anandthakker commented 6 years ago

πŸ‘ I think it would be great to expose the transformation and projection logic that currently exists in Transform as a public module. Ideally, we would do this by extracting pure functions for those difficult calculations out of this class and export those as a utility module, keeping this state-wielding class private.

mike-marcacci commented 6 years ago

That would be awesome, and extra πŸ‘πŸΌπŸ‘πŸΌ and style points for the suggestion to use pure functions!