digidem / react-mapfilter

Visualizing, exploring, filtering and printing geographic data and geotagged photos and video
https://lab.digital-democracy.org/mapfilter
29 stars 11 forks source link

Multiple Tile Sets, Layers on one MapView #73

Closed GoGoCarl closed 6 years ago

GoGoCarl commented 6 years ago

Feature Request:

Allow users to toggle between multiple tile sets when viewing a Map. This could be achieved by adding a button to the map like:

screen shot 2017-12-18 at 11 32 22 am

That, when clicked, displays a set of options for different tile sets, with the current tile set in use marked as selected:

screen shot 2017-12-18 at 11 31 25 am

The current tile set should specified be an optional controllable props, defaulting to the first tile set.

On change, update the map to use the selected tile set, and fire a change event.

A tile set could be a prop like:

tileSets = PropTypes.arrayOf(PropTypes.shape({
  /** A unique identifier for this tile set */
  id: PropTypes.string,
  /** A localized display name for the tile set */
  name: PropTypes.string,
  /** The URL to use to retrieve tiles for this tile set */
  url: PropTypes.string
}))

A question here would be with custom tiles -- generally the URL isn't an actual URL, but a URL template, which is filled dynamically based on the map position. This may need to be accounted for as well.

Note: while building this functionality, it would be useful to consider the possibility down the line of adding support for custom layers as well -- layers that would appear on the map independent of any tile set chosen.

gmaclennan commented 6 years ago

Ok I researched this a bit more this weekend, this should not be too hard to add. We can build upon https://github.com/developmentseed/mapbox-gl-layers I suggest the following strategy:

v1 (minimal viable implementation): we autogenerate the layer list by analyzing the style.json and finding any raster layers. Use <option> to switch between them, add a none option. We can check layer visibility in the style.json to see if one should be selected by default. Adding the raster layers to the style can be left as an implementation detail.

v2: Add vector overlays. These can be implemented with <input type="checkbox"> since it makes sense to have multiple. Add an 'add' button to the control, for selecting a geojson file that will be added to the map. Create a default style that works for points, lines or areas. Don't do any persistence between sessions.

v3: Can include optional overlay layers in the Mapbox style by creating a group in the Studio editor, which would appear in the style.json metadata. Use that to populate a list of optional layers that can be toggled on and off.

okdistribute commented 6 years ago

Sounds good, except I also agree with @GoGoCarl that it'd be nice to specify which layers will be visible for the user to select.

Mapbox-gl-layers is incompatible with the latest version of mapbox-gl and there don't seem to be viable alternatives, so going to try to update that module.

okdistribute commented 6 years ago

@GoGoCarl this should be supported now if you'd like to try it, after this branch is merged and published on NPM (or just include directly from github to try it now): https://github.com/digidem/mapbox-gl-layers/tree/rewrite

var LayerControl = require('mapbox-gl-layers') // will probably rename it to something like 'mapbox-layer-control'

Then you can use it two different ways, the first being the most simple where you already know the layers you want to use. Underlays are select one and Overlays are select multiple.

  var overlays = [{
    name: 'Streets',
    ids: overlayIds
  }]
  var underlays = [{
    name: 'Mapbox Satellite',
    ids: ['mapbox-mapbox-satellite']
  }, {
    name: 'Bing Satellite',
    ids: ['bing']
}]

var  layerControl = new LayerControl({overlays, underlays})
<MapFilter controls=${[layerControl]} />

And the other where you can generate a little dummy control that dynamically fetches the style and creates layers on the fly:

var myControl = {
  onAdd: function (map) {
    var style = map.getStyle()
    var layers = getLayersFromStyle(style)
    layerControl = new LayerControl(layers)
    return layerControl.onAdd(map)
  },
  onRemove: function () {
    return layerControl.onRemove()
  }
}

<MapFilter controls=${[myControl]} />
GoGoCarl commented 6 years ago

Thanks @karissa! I'll give this a try once things get merged up. Very exciting!

okdistribute commented 6 years ago

@GoGoCarl take a look at it now, it is available and published under @digidem/mapbox-gl-layers

GoGoCarl commented 6 years ago

@karissa Do you have a working example of this available?

gmaclennan commented 6 years ago

Hi @GoGoCarl see the example here: https://github.com/digidem/mapbox-gl-layers/blob/master/example.js

For using with react-mapfilter you need to wrap it with your own control so that you can access the map instance, see: https://gist.github.com/gmaclennan/cb42669c77dc07672e019208891884b0

okdistribute commented 6 years ago

FYI @GoGoCarl @gmaclennan the name of the package is @digidem/mapbox-gl-layers https://github.com/digidem/mapbox-gl-layers

GoGoCarl commented 6 years ago

@gmaclennan / @karissa I'm sorry, but I'm still having trouble getting this to work, is there an actual runnable example that uses MapFilter that I could see, or one of you could put together?

And can you clarify, should I be using LayerControl with overlay/underlay props when passing the prop, or the custom object with onAdd/onRemove?

Also, these examples are using <MapFilter controls={blah} ... /> but I believe the prop is actually mapControls.

Thanks for the help!

okdistribute commented 6 years ago

Hey @GoGoCarl

Here is a minimal example: https://github.com/digidem/mapfilter-desktop/commit/12c551a42fac9a8ac3c55b825022b0d70aee2abc

You are right, it is mapControls. I also realized we didn't add controls after map.on('load') which was causing issues accessing the style property within the map. That bug is fixed now.

GoGoCarl commented 6 years ago

Thanks @karissa!

So, I was finally able to get the map control to appear!!

Two issue that I ran into, that I was able to work around in order to get things to appear, but probably need investigating (let me know if/how to organize these into tickets):

1) There's a line in MapView that still references this.props.controls that needs to be removed; otherwise, it still tries to add data from the (now null) controls prop. 2) The mapControls props is called twice on initial load. The first time, map.style.stylesheet is null, and calling map.getStyle() causes a NPE in mapbox-gl. The second time, map.style.stylesheet is not null, and calling map.getStyle() works as expected. So, some odd behavior there.

I should note that, with my implementation, I'm doing all my data initialization first, THEN adding MapFilter to the document, so everything essentially happens on mount as opposed to on update.

Finally, to clarify, the way the LayerControl helper works is that for a given style, specified by the mapStyle prop, one can (only) toggle between layers that are specified within that style, correct? So, the idea would be to set mapStyle to some MapBox style, then set the layer toggle up with options within that stylesheet? Also, is there any way to specify which layer should be initially selected?

I'm quite the novice when it comes to MapBox so apologies for all the questions!

Thanks!

okdistribute commented 6 years ago

Yes, you'd select some layers you want by specifying their ids (in the mapbox style) and visible names, as overlays and underlays (underlays are select one -- like mapbox satellite vs. streets).

okdistribute commented 6 years ago

Fixed the props.controls reference, should not give an NPE anymore