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

Pull Request for multi-layer support #129

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

Conversation

lucasvw
Copy link

@lucasvw lucasvw commented Oct 3, 2018

Hi there,

I just finished some refactoring for multi-layer support. I basically implemented a lower level Map class and all appropriate Layer classes. This was needed because the current *Viz classes are a combination of setting up the main Map instance as well as adding the (Source and) and Layers with the actual data. I am not a big fan of the Viz-collection-approach because each Viz instance can have its own specification of the actual Map (for example the "size" or "base-layer" etc). I think its more appropriate to get closer to the actual JS implementation and have Python classes for Map and Layers as well.

Most of the logic now happens in these lower Map and Layer classes, the *Viz classes just pass the logic onto these lower-level classes (Map and a single Layer). I didnt want to remove them for backwards compatibility but the idea for the future is that people start using the Map and Layer API's. I will try to add an example as soon as possible.

I have tested locally an example with multiple choroplethlayers and have run the examples contained in the project. I think some of the tests are broken so that has to be checked as well.

@lucasvw
Copy link
Author

lucasvw commented Oct 5, 2018

Hi @ryanbaumann,

I have seen there are multiple pull-requests for this issue so its probably a good idea to continue with only one of them. I still have time next week to put in some work but to prevent 'double-work' its perhaps best to decide which multi-layer extension has your/mapbox's preference.

Would be great to have some feedback!

@akacarlyann
Copy link
Collaborator

@lucasvw I like the idea of a layer-based api. Overall cleaner. Mine was definitely more of an experiment from a few months back. Curious to see how you handle layer ordering. Looking forward to checking it out!

@lucasvw
Copy link
Author

lucasvw commented Oct 5, 2018

Agreed, I also think its the right approach. I also kind of like the "json specs" approach although I have my doubts: (1) i guess the python api will never be as complete as the Javascript lib, its more a quick way to create visualizatios based on data that you wrangled in python.. if you need full control over the visualization, youll always need to get down to mapboxgl-js. (2) you probably cant get rid of javascript code snippets in this repo, I am not totally familiar with the expressiveness of the json spec but I guess you ll always need some JS glue to make everything work (can you declare legends for example in the spec?)

Currently havent done anything extra related to layer ordering, e.g. the belowLayer functionality is copied over to the Layer level.

Ill make an example with multilayers as soon as possible, probably sunday or monday.

@akacarlyann
Copy link
Collaborator

akacarlyann commented Oct 5, 2018

I think the JSON layer specification is still important to develop (#23) but I don't think it is mutually exclusive with using a layer-based API :)

And yup, agree it would be great to reduce JavaScript snippets in this particular project.

@lucasvw
Copy link
Author

lucasvw commented Oct 5, 2018 via email

@ryanbaumann
Copy link
Contributor

Agree with @akacarlyann @lucasvw, this is a great implementation. Let's move forward with this approach for multi-layer management.

@lucasvw
Copy link
Author

lucasvw commented Oct 8, 2018

Great!

I just added a new example: examples/notebooks/multi-layer-example.ipynb

It shows the Map and Layer API as well as a multi-layer example.

Feedback is welcome =)

@ryanbaumann
Copy link
Contributor

Nice! I'll check it out later this week. Thanks @lucasvw !

@lucasvw
Copy link
Author

lucasvw commented Oct 10, 2018

Cool.

To summarize:

  • layers.py contains all logic related to layers, there is an abstract layer class and all other layers inherit from this class
  • map.py contains a single Map class
  • Most logic from all the Viz-classes is removed. Viz-classes just create a Map instance under the hood as well as the associated Layer instance
  • I tried as good as possible to split attributes between either the Map class (size of div, center of map, basic legend layout etc) and the individual Layer classes (data and layer and legedn specific stuff) but have a look if it all makes sense.
  • To make sure that attribute assignment on the Viz classes still works (as I saw is often used in the examples) I pass this assignment to either the associated Map or Layer instance through overriding the __setattr__ method
  • The Map class has an array layers and an add_layer(), remove_layer() method
  • Creation on HTML and the template structure is also slighly changed: Layer templates no longer inherit from the base, main and map templates. There is a single template for the map and individual templates for the layers. Upon creation of the HTML (for .show() method ) the templates from Map and all individual layers are gathered and merged.
  • If there are more than one layers with legends the respective legend elements should be rendered below each other (created a new legend-container div)

Some interactive layer control (turn on/off layers) would perhaps be a good next step..?

@ryanbaumann
Copy link
Contributor

Thanks for the architecture overview.

Some interactive layer control (turn on/off layers) would perhaps be a good next step..?

Agree, that would be a great addition in a new PR!

@lucasvw
Copy link
Author

lucasvw commented Oct 18, 2018

@ryanbaumann any updates on checking this?

@vincentsarago
Copy link
Collaborator

thanks @lucasvw, can't wait to see it live, @ryanbaumann let me know if you need help here

@ryanbaumann
Copy link
Contributor

@vincentsarago added you to contributors - yes, please take this PR review if you have time. Thank you!

@ryanbaumann
Copy link
Contributor

ryanbaumann commented Oct 21, 2018

Just merged in #120 to master, which will cause a few conflicts on this branch. Excited to get this branch merged in - and it will be high time for a relase after that!

Next steps:

This was referenced Oct 21, 2018
Copy link

@emakarov emakarov left a comment

Choose a reason for hiding this comment

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

Nice work! left some comments for your reference.

def create_html(self):
options = dict(
opacity=self.opacity,
belowLayer=self.below_layer,

Choose a reason for hiding this comment

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

I understand this is artefact from previous code, but CamelCase in python looks like an anti-pattern. Would be great if this can be avoided

Copy link
Author

Choose a reason for hiding this comment

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

I guess this is done because the variables end up in HTMl/JS files where camelCase is quite common. But actually the variables don't enter into JS, since they are just used as placeholder in the templating, so I guess you are right! It will take some time changing all the variable names though.. :)

def get_layer_specific_variables(self, options):
"""Update map template variables specific to heatmap visual"""
options.update(dict(
geojson_data=json.dumps(self.data, ensure_ascii=False),

Choose a reason for hiding this comment

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

here and in some other places, json.dumps potentially can raise Exception that some objects are not JSON serializable. So, I think it would be useful to pass a custom serializer class into Layer's init somehow.
And in some corresponding/or similar situations be able to assign custom serializer

Copy link
Author

Choose a reason for hiding this comment

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

Hmm interesting, Ill see if I can figure something out. Any suggestions are welcome!

Choose a reason for hiding this comment

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

Some suggestion
maybe, AbstractLayer can receive two types of data in __init__

  • dict
  • str (in this case, user is responsible for serialization as he wants)
  • __init__ can receive additional kwarg data_serializator_class which (if not None) will be used in json.dumps(data, cls=data_serializator_class)
  • json.dumps can be moved to separate function of base class, like def serialize_data which will check whether data has type str or dict and make serialization accordingly

Choose a reason for hiding this comment

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

this approach will also give user ability to override def serialize_data for example, to use another serialization library (for ex. nssjson or ultrajson) if he wants to..

legend_key_shape='circle',
*args,
**kwargs):
"""Construct a Mapviz object

Choose a reason for hiding this comment

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

Docstring seems to be misleading. Why not Construct ClusteredCircleLayer object ?

Copy link
Author

Choose a reason for hiding this comment

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

Good catch, I still need to go to all the docstrings and update them.


<style type='text/css'>

Choose a reason for hiding this comment

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

probably it makes sense to separate CSS and JS code at least to different {% include %} tags

Copy link
Author

Choose a reason for hiding this comment

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

Yes, agreed

@lucasvw
Copy link
Author

lucasvw commented Oct 22, 2018

@emakarov Thanks a lot for your review and comments, much appreciated!

Copy link
Collaborator

@vincentsarago vincentsarago left a comment

Choose a reason for hiding this comment

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

Few general comments for now.
Also we should update the tests

@@ -0,0 +1,165 @@
import codecs
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if we could name this sub-module differently.
I'm afraid some people could do from mapboxgl import map which will override map function. That's said I don't have a better name.
maybe @perrygeo has some thought on that


from mapboxgl.utils import color_map, height_map
from mapboxgl import templates
from mapboxgl.utils import img_encode, numeric_map
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can merge all the .utils import in one line


GL_JS_VERSION = 'v0.49.0'


class MapViz(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is something I'm not sure to understand, to me MapViz should inherit from Map class because from what I understood, Viz are just a Map + Layer

Copy link
Collaborator

Choose a reason for hiding this comment

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

and also I think there might be a better way to reduce code duplication

Copy link
Collaborator

Choose a reason for hiding this comment

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

Well the more I think about the more I think we should maybe deprecate the Viz, for now it works but it also means there will be a lot of code duplication and it might result in a bit of a nightmare when we either update the Map or Layers classes.

cc @ryanbaumann @lucasvw

Copy link
Author

Choose a reason for hiding this comment

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

The Viz classes are just wrappers around certain Map + Layer combinations to offer backwards compatibility. MapViz doesn't inherit from Map but contains a Map

legend_text_numeric_precision=None,
legend_key_shape='square',
min_zoom=0,
max_zoom=24,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also noting, we should maybe rename min_zoom and max_zoom because it can be confusing between min_zoom/max_zoom for the map instance. layer_min_zoom might be better

@lucasvw
Copy link
Author

lucasvw commented Oct 24, 2018

Hi everyone, thanks for all the feedback! I just started a new position and Iam kind of busy with that.. so I am afraid it will take a little until I have spare-time to spend on this. Feel free to fork and continue if somebody has earlier availability!

@ryanbaumann
Copy link
Contributor

ryanbaumann commented Nov 27, 2018

This is a huge feature and very close @lucasvw - do you have time to finish off for the coming release at the end of December 2019? If not, no worries - let's transfer ownership.

@lucasvw
Copy link
Author

lucasvw commented Nov 27, 2018

Sorry man, no time. As stated before, feel free to fork and continue from where I left off.

@emakarov
Copy link

so, what is the current status for this PR? seems like the desired feature is not going forward...

@ryanbaumann
Copy link
Contributor

ryanbaumann commented Jan 14, 2019

We'll need to find a new owner for this PR @emakarov to finish off. It is the top-requested feature at the moment for mapboxgl-jupyter.

@emakarov
Copy link

For those who interested in alternative (simple, yet flexible) multi-layer lib:

https://github.com/emakarov/mapboxgl_notebook

@akacarlyann
Copy link
Collaborator

@emakarov Nice! I especially like the way you formulated a Layout class.

@ryanbaumann
Copy link
Contributor

Agreed @emakarov your implementation is an excellent approach. I'd love to see if we can adopt a similar approach in mapboxgl-jupyter to take advantage of all the other features that are built into this library (raster layers, plugins, helper functions, etc).

I will cut a few tickets to capture this issue and link back to this PR. Let's continue the discussion on the issues.

@rmcarthur rmcarthur mentioned this pull request Jul 29, 2019
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants