-
-
Notifications
You must be signed in to change notification settings - Fork 78
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
base: main
Are you sure you want to change the base?
Conversation
I ran into a Typescript block: In the declare namespace mapboxgl {
var accessToken: string;
...
} In the extension, we have: mapboxgl.accessToken = ACCESS_TOKEN; And the Typescript error is: @ian-r-rose @blink1073 Any ideas? |
According to this issue, ES6 module imports are immutable, and so you can't change the access token (even though it is declared using import mapboxgl = require('mapbox-gl'); I tried this, and it seemed to fix the compilation error. |
Let me know if you want any 👀 on this when ready for review! I'm the maintainer of mapboxgl-jupyter. |
Thanks @ian-r-rose! @ryanbaumann Excellent! Please take a look now. It's rendering but all of the maps are blank: You can probably debug must quicker than I 👍 |
There was a problem hiding this 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', |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two items here:
- 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.
- First create a geojson source (map.addSource) and a geojson layer (map.addLayer) from that source. Here's a super basic example that uses the same approach - https://gist.github.com/ryanbaumann/cbad147abda4fbff76bd95b386cea45b#file-index-html-L77-L98
- Then in the renderModay loop, you'll just run map.setData(myGeojsonData) like this https://www.mapbox.com/mapbox-gl-js/api#geojsonsource#setdata
- You'll need to pass some
data
to thedata
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', |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
@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. |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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.
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 Install: 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 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: |
@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 A user could swap the See https://github.com/mapbox/mapboxgl-jupyter/blob/master/mapboxgl/viz.py#L27 for an example of what that looks like in Mapboxgl-Jupyter |
@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 👍 |
@gnestor, not this week, unfortunately - I'll revisit next week. |
I guess since Mapbox switched to a proprietary license, mapbox-gl is not an option anymore. |
Closes #116