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

[WIP] Replace leaflet with mapbox-gl #122

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

gnestor
Copy link
Collaborator

@gnestor gnestor commented Apr 4, 2018

Closes #116

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 4, 2018

I ran into a Typescript block:

In the @types/mapbox-gl, we have:

declare namespace mapboxgl {
    var accessToken: string;
    ...
}

In the extension, we have:

mapboxgl.accessToken = ACCESS_TOKEN;

And the Typescript error is: Cannot assign to 'accessToken' because it is a constant or a read-only property.

@ian-r-rose @blink1073 Any ideas?

@ian-r-rose
Copy link
Member

ian-r-rose commented Apr 4, 2018

According to this issue, ES6 module imports are immutable, and so you can't change the access token (even though it is declared using let in the typings). The fix they suggest is to change the import statement to an ES5 style:

import mapboxgl = require('mapbox-gl');

I tried this, and it seemed to fix the compilation error.

@ryanbaumann
Copy link

Let me know if you want any 👀 on this when ready for review! I'm the maintainer of mapboxgl-jupyter.

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 4, 2018

Thanks @ian-r-rose!

@ryanbaumann Excellent! Please take a look now. It's rendering but all of the maps are blank:

image

You can probably debug must quicker than I 👍

Copy link

@ryanbaumann ryanbaumann left a comment

Choose a reason for hiding this comment

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

@gnestor added comments to the PR - let me know if you have any questions. This is the example I'd recommend referencing https://www.mapbox.com/mapbox-gl-js/example/multiple-geometries/ as you build.

if (this._map) {
this._map.addLayer({
id: 'layer',
type: 'symbol',

Choose a reason for hiding this comment

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

I would recommend using a circle layer here for points, line for linestrings, and fill for polygons. You can create all three of these layers from the same geojson source, using filters to select only a certain geometry type to render in the layer. See https://www.mapbox.com/mapbox-gl-js/example/multiple-geometries/

type: 'symbol',
source: {
type: 'geojson',
data

Choose a reason for hiding this comment

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

Two items here:

  1. I recommend creating all of your sources and layers you'll need for the user's map at the same time you create the base map object itself.
  2. You'll need to pass some data to the data option in geojson format. See https://www.mapbox.com/mapbox-gl-js/example/multiple-geometries/

@@ -125,16 +87,22 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer {
* A message handler invoked on an `'after-attach'` message.
*/
protected onAfterAttach(msg: Message): void {
this._map = new mapboxgl.Map({
container: this.node,
style: 'mapbox://styles/mapbox/light-v9',

Choose a reason for hiding this comment

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

Use mapbox://styles/mapbox/light-v9 ==> mapbox://styles/mapbox/light-v9?optimize=true for a slightly faster map to load. This removes any data that isn't used in the style.

container: this.node,
style: 'mapbox://styles/mapbox/light-v9',
minZoom: 0,
maxZoom: 18

Choose a reason for hiding this comment

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

I'd remove the minZoom and maxZoom setting here. The defaults will work great.

// Enable scroll zoom on map focus
this._map.on('blur', (event) => {
this._map.scrollWheelZoom.disable();
this._map.on('blur', (event: Event) => {

Choose a reason for hiding this comment

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

There isn't a map blur event in GL JS. https://www.mapbox.com/mapbox-gl-js/api#events. By map focus do you mean full screen? https://www.mapbox.com/mapbox-gl-js/api#fullscreencontrol

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The purpose here is to not allow the map to intercept scroll events when a user is scrolling the notebook. If the user wants to zoom the map, he/she can click on the map (focus) and then use the scroll wheel/trackpad. When the user clicks outside of the map (blur), scroll events will once again scroll the notebook.

});
// Disable scroll zoom on blur
this._map.on('focus', (event) => {
this._map.scrollWheelZoom.enable();
this._map.on('focus', (event: Event) => {

Choose a reason for hiding this comment

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

Likewise for line 100 comment above

if (this.isVisible) this._map.invalidateSize();
// Update map size after panel/window is resized
this._map.fitBounds(this._geoJSONLayer.getBounds());
if (this._map && this.isVisible) {

Choose a reason for hiding this comment

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

This will happen automatically in GL JS - the map container will resize based on the current div size of the notebook container. It can be removed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I tested this and it appears that it doesn't work automatically. One unique thing about phosphorjs (which is what provides JupyterLab it's Widgets among many other things) is that it can detect resize events on a node-level, so that when a panel is resized within lab, the affected widgets can handle those resizes themselves.

// Create GeoJSON layer from data and add to map
this._geoJSONLayer = leaflet.geoJSON(data).addTo(this._map);
this.update();
// Add GeoJSON layer to map

Choose a reason for hiding this comment

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

Before you add any layers or sources, you'll want to make sure the map style is loaded. See the basic example in the comment below for how to wait for the map.on('style.load') event.

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 5, 2018

@ryanbaumann I've made the changes that you suggested AFAIK, but the map still appears the same as before. It would be great if you could install this repo locally and make the remaining necessary changes to get it working. To do this:

This will allow you to test a locally installed geojson-extension, make changes, and automatically rebuild the extension and JupyterLab in response to those changes.

Copy link

@ryanbaumann ryanbaumann left a comment

Choose a reason for hiding this comment

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

Added some comments - I won't have time to fork and check for several days, hope the comment helps!

@@ -69,14 +69,8 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer {
return new Promise<void>((resolve, reject) => {
// Add GeoJSON layer to map
if (this._map) {

Choose a reason for hiding this comment

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

I'd check to make sure the map exists here at any time when this function runs. Otherwise, this setData() function may never run, which is what adds the geojson data to the map.

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 6, 2018

Ok, made some progress:

image

Is there a convenient way to fit the bounds of the GeoJSON data?

@ryanbaumann
Copy link

ryanbaumann commented Apr 6, 2018

Amazing @gnestor this is going to be great!

To fit the bounds of your map to a geojson feature, I recommend using a subpackage of turf.js to calculate the bounds. Then use map.fitBounds() to set the map camera viewport.

Install:
npm install --save @turf/bbox
npm install --save mapboxgl-js

Use:

import turfBbox = require('@turf/bbox');
import mapboxgl = require('mapboxgl-js');
...
// Run this in the same spot where you add the geojson source data to the map, right after map.getSource('my-soruce').setData(myGeojsonData);

var bbox = turfBbox(myGeojsonData);
map.fitBounds( bbox, { padding: 100 } );

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 6, 2018

Some progress but this flyover animation is very unnecessary...

geojson

@ryanbaumann
Copy link

@gnestor nice, glad that worked! I'd recommend setting the map center and zoom to fit the bounding box of the data. Here's how I'd recommend initializing the map to the bounds instead of flying to the bounds:

mapbox/mapbox-gl-js#1970 (comment)

@gnestor
Copy link
Collaborator Author

gnestor commented Apr 7, 2018

Ok, looking much better:

image

Now, what if I want to map a point on Mars? This was possible using leaflet by providing a different tileset via the url_template argument. Does mapbox-gl support raster tilesets?

@ryanbaumann
Copy link

@gnestor loving that the primary use case is a map on mars ;)

Yes, Mapbox GL JS supports a ton of layers, everything from Raster Tiles, to Vector Tiles, to Geojson, to Images, Video, and WebGL Canvas layers. Here's an example using a Raster tilemap, which you can simply drop in the URL to your raster tile layer and go (while getting all of the rendering benefits of GL JS for adding GeoJSON data to the map for data visualization) -

https://www.mapbox.com/mapbox-gl-js/example/map-tiles/

As for how to handle accepting different base maps, I'd recommend allowing the user to set a style, which can be a link to a Mapbox GL Style Sheet, or a locally created style sheet. You're already doing this with the mapbox://styles/mapbox/light-v9?optimize=true URL specified today.

A user could swap the style out for any mapbox:// URL, or a custom style sheet that has - for example - raster tiles of mars.

See https://github.com/mapbox/mapboxgl-jupyter/blob/master/mapboxgl/viz.py#L27 for an example of what that looks like in Mapboxgl-Jupyter

@gnestor
Copy link
Collaborator Author

gnestor commented May 1, 2018

@ryanbaumann Do you have any cycles available to polish this? I'm pretty busy at the moment but I'd like to get this merged 👍

@ryanbaumann
Copy link

@gnestor, not this week, unfortunately - I'll revisit next week.

@davidbrochart
Copy link
Contributor

I guess since Mapbox switched to a proprietary license, mapbox-gl is not an option anymore.
I opened #276 which is based on MapLibre GL.

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.

[Enhancement] Render with Mapbox GL JS
4 participants