mapbox / mapboxgl-jupyter

Use Mapbox GL JS to visualize data in a Python Jupyter notebook
MIT License
669 stars 137 forks source link

Support multiple layers in a map visualization #34

Open ryanbaumann opened 6 years ago

ryanbaumann commented 6 years ago

Problem

As we add more visualization types, it will be useful to pull in multiple visualization layers into the same visualization. To do this, we'll need an API to addLayer to an existing visualization.

For example, users may want to create a heatmap visualization at low zoom levels, and a graduatedCircle visualization at high zoom levels.

A user may also want to add two or more layers to a map from different data sources.

Implementation

carsnwd commented 6 years ago

I'll fork it and try to implement this, never done an open source contribution but see how this goes haha.

ryanbaumann commented 6 years ago

@carsnwd awesome, and thank you! This will be a great feature for data science in Jupyter, because it allows us to use different types of data visualizations and data layers based on user interaction (zoom, a layer selection dropdown, etc)

No need to fork either - please feel free to just

a) Clone this repo git clone git@github.com:mapbox/mapboxgl-jupyter.git b) Create a new branch git branch my-branch-name c) Check out the branch git checkout my-branch-name d) Commit your changes e) Push your branch git push origin my-branch-name f) Create a pull request from your branch on this repo

carsnwd commented 6 years ago

I’ve been on and off trying to figure out this problem between classes and work, and wanted to write down what I’m thinking in case anyone else tries to take a stab or just for general thoughts. So when each viz is created, it creates the entire viz with an individual map. Initial idea I had to just create a set of child vizes to a parent...but that child viz is going to create it’s own map object and that’s no good.

My next idea so far is to abstract the map and layers/sources. So each viz starts off with the base.html and also starts off with a base map object the var map = ..... Then, the circle.html/clustered_circle.html/etc will add in layers and sources to the base map object in Jinja. Rather than duplicating code for when each viz type is a parent and to create a map or when each viz type is a child and to just create the layers.

{% extends "main.html" %}

{% block javascript %}
var legend = document.getElementById('legend');
legend.remove();

mapboxgl.accessToken = '{{ accessToken }}';

var map = new mapboxgl.Map({
    container: 'map',
    style: '{{ styleUrl }}', 
    center: {{ center }},
    zoom: {{ zoom }},
    transformRequest: (url, resourceType)=> {
        return {
           url: [url.slice(0, url.indexOf("?")+1), "pluginName=PythonMapboxgl&", url.slice(url.indexOf("?")+1)].join('')
         }
    }
});

map.addControl(new mapboxgl.NavigationControl());

map.on('style.load', function() {

    {% block layers %}
    //Iterate through all parent and child vizes to add layer and sources
    {% endblock %}

});
{% endblock %}

Then I realized that each child viz probably will also have it's own legend properties. Clustered_Circles being calcCircleColorLegend({{ colorStops }}, "Point Density"); or regular circle being calcCircleColorLegend({{ colorStops }}, "{{ colorProperty }}"); and more...supporting future viz types. So to support each legend property that's outside the map.on function...would have to iterate through them and create them.

{% extends "main.html" %}

{% block javascript %}
var legend = document.getElementById('legend');
legend.remove();

mapboxgl.accessToken = '{{ accessToken }}';

var map = new mapboxgl.Map({
    container: 'map',
    style: '{{ styleUrl }}', 
    center: {{ center }},
    zoom: {{ zoom }},
    transformRequest: (url, resourceType)=> {
        return {
           url: [url.slice(0, url.indexOf("?")+1), "pluginName=PythonMapboxgl&", url.slice(url.indexOf("?")+1)].join('')
         }
    }
});

map.addControl(new mapboxgl.NavigationControl());

{% block legend %}
//Iterate through each vizes legend
{% endblock %}

map.on('style.load', function() {

    {% block layers %}
    //Iterate through all parent and child vizes to add layer and sources
    {% endblock %}

});
{% endblock %}

So seems like a lot of abstracting the individual parts of each viz (layers/sources and legend) into it's own creation that is iterated through and added for the parent viz and each child viz. Plus also extracting out the creation of the map itself from each viz type html to not duplicate maps. Then also making sure layers and sources don't have conflicting names and such...so variables aren't redefined between vizes.

Just my thoughts on the problem so far picking at it 🤔, open to suggestions and comments. Will see if I make any progress.

ryanbaumann commented 6 years ago

That's spot on with how I'd approach it too @carsnwd! I think you can just tackle the first part in an initial pull request - abstract the mapboxgl.map variable creation from the creation of visualization layers. The main thing to ensure then is that each viz added to the map contains a unique source_id and layer_id schema. That should be pretty straightforward:

data-circle-1 data-circle-2

data-graduatedCircle-1 data-clusteredCircle-1

Layer names should already be unique.

After that's done, we can tackle the legend stuff 🚀

ryanbaumann commented 6 years ago

@carsnwd did you get a chance to look at adding in the next step for this issue - abstracting each viz layer to add to a base map? Looking at what to include in the next release.

carsnwd commented 6 years ago

I have, still haven't gotten it done yet though. Today was the first day I've made some progress in a week or so, some other things in life have gotten in the way.

I'll try to get it sorted out and make a PR before the end of the month 😬

ryanbaumann commented 6 years ago

@carsnwd no rush here, but if you have a branch that's in progress, please submit a PR - we'd love to get multiple layer functionality into the next release in March.

ryanbaumann commented 6 years ago

Let's make this a push to finish for 0.7.0, which we're targeting to release at the end of April 2018.

bchowTWC commented 6 years ago

Hi @carsnwd - just wondering if you managed to make any progress on this since your last update? No hurries or worries if not.

If you're up for it, I'd be more than happy to take a look and see what I can do. Thanks!

akacarlyann commented 6 years ago

@bchowTWC and @carsnwd I've been experimenting with this too though I don't have image or raster layers worked in yet. It's kind of wrapped up in generating layer styles in Python, so still needs some adjustments. I can clean up what I have and share if you're interested :)

@carsnwd thanks for opening this issue with your outline, btw!! It really got me thinking :)

bchowTWC commented 6 years ago

@akacarlyann - Definitely interested, if you don't mind. Those are actually the two layers I need most... good timing.

Much appreciated!

lucasvw commented 6 years ago

Hi all, this seems definitly like a good next step as it comes closer to a more natural way of building up a map: creating a single map instance and adding layers for the stuff you want to see visualized.

Just wanted to check: the initial use-case seems to be about having one single visualisation (layer) turned on at a time:

For example, users may want to create a heatmap visualization at low zoom levels, and a graduatedCircle visualization at high zoom levels.

As well as adding only one layer of each type:

The main thing to ensure then is that each viz type contains a unique source_id and layer_id schema.

This seems imho to be unnecessary restrictive.. why not have multiple layers on at the same time (and of the same type)?

ryanbaumann commented 6 years ago

@lucasvw ah, yes, the intention for this feature is to allow a user to add multiple layers to a map at the same time, including of the same layer type (two circle layers, for example). I'll update the ticket scope to clarify.

emakarov commented 6 years ago

@ryanbaumann is this still in progress?

emakarov commented 6 years ago

@ryanbaumann I created some very fresh draft, where I managed to visualise two layers on same map in point-viz-categorical-example.ipynb

https://github.com/mapbox/mapboxgl-jupyter/pull/126

Any feedback is welcomed. Hopefully will have time to finalise it for merge

ryanbaumann commented 6 years ago

Excellent, thank you @emakarov! Will check your your PR.

emakarov commented 6 years ago

@ryanbaumann I've checked on several my local examples, layers are working (maybe need more testing) what I need, and what is missing 1) Need to re-write the Legend for Multi-layer case. Not clear how to do it better though. 2) Need to have some menu to toggle layers visibility

nahidpervez commented 4 years ago

@ryanbaumann Any idea when this feature will be released?