visgl / react-map-gl

React friendly API wrapper around MapboxGL JS
http://visgl.github.io/react-map-gl/
Other
7.84k stars 1.35k forks source link

RFC: DrawControl #734

Closed xintongxia closed 3 years ago

xintongxia commented 5 years ago

RFC: DrawControl

Background

react-map-gl currently does not support drawing functions. However, we have got a couple of users interested in this capability. Also it is one of P0 features on Kepler.gl 2019 roadmap.

Although Mapbox/mapbox-gl-draw provides quite nice drawing and editing features, because of its manipulating internal states, it cannot work well with React / Redux framework and therefore cannot be integrated with react-map-gl. vis.gl offers another geo editing library Nebula.gl, but it is an overkill while adding heavy dependencies such as deck.gl.

Proposal

react-map-gl can provide a DrawControl, starts from simple functions like the following.

Options

Code Example

import React, { Component } from "react";
import ReactMapGL, {DrawControl} from "react-map-gl";

const MODES = [
  {name: 'Read Only', value: DrawControl.READ_ONLY},
  {name: 'Select FEATURE', value: DrawControl.SELECT_FEATURE},
  {name: 'Select Vertex', value: DrawControl.SELECT_VERTEX},
  {name: 'Draw Path', value: DrawControl.DRAW_PATH}, 
  {name: 'Draw Polygon', value: DrawControl.DRAW_POLYGON}, 
  {name: 'Draw Point', value: DrawControl.DRAW_POINT}
];

class Map extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewport: {
        width: 800,
        height: 600,
        longitude: -122.45,
        latitude: 37.78,
        zoom: 14
      },
      mode: DrawControl.READ_ONLY,
      features: [],
      selectedId: null
    }
  }

  _updateViewport = (viewport) => {
    this.setState({viewport});
  }

  _onSelect = (selectedId) => {
    this.setState({selectedId});
  }

  _onUpdate = (features) => {
    this.setState({
      features,
      selectedId: null
    });
  }

  _renderControlPanel = () => {
    return (
      <div style={{position: absolute, top: 0, right: 0, maxWidth: '320px'}}>
        <select onChange={this._switchMode}>
          <option value="">--Please choose a mode--</option>
          {MODES.map(mode => <option value={mode.value}>{mode.name}</option>)}
        </select>
      </div>
    );
  }

  _switchMode = (evt) => {
    const mode = evt.target.value;
    this.setState({mode});
  }

  render() {
    const {viewport, mode, selectedId, features} = this.state;
    return (
      <ReactMapGL {...viewport} onViewportChange={updateViewport}>
        <DrawControl
          mode={mode}
          features={features}
          styles={}
          selectedId={selectedId}
          onSelect={this._onSelect}
          onUpdate={this._onUpdate}
        />
        {this._renderControlPanel()} 
      </ReactMapGL>
    );
  }
}

Compare with mapbox-gl-draw

Pessimistress commented 5 years ago

@heshan0131 @rolyatmax

macobo commented 5 years ago

Nice! I think this could work, looking forward to playing with this.

Few questions since Pessimistress asked to provice feedback:

rolyatmax commented 5 years ago

This proposal is great! Thanks for putting this together - and especially for including a wonderful example!

A few initial thoughts/questions:

  1. The example has a geometries prop, while the proposal names a features prop. I'm guessing this is just a typo in the example?

  2. I wonder if users might also need custom styling for features the mouse is hovering over?

  3. To make sure I'm clear on the difference between a point and a vertex, I'm assuming a point refers to the GeoJSON feature, while a vertex is a coordinate in the geometry of a path or polygon GeoJSON feature? Does a point feature contain a single vertex?

  4. Will this component support drawing multi-polygons or polygons with holes?

  5. Users might want to apply custom styling to features that are currently being drawn, and in some common cases, these stylings might be relatively complex, taking into account the mouse position. For example, one common polygon-drawing treatment I've noticed is where, after selecting 2 vertices, the application shows a sort of "polygon preview" with the mouse position functioning as a temporary third vertex. I imagine this might be difficult to implement with the proposed API, especially if this preview is meant to be styled differently from other polygons on the map.

  6. Related to number 5, I imagine another common need would be to give different styles to features of the same type. For example, I might want to draw several geofences on a map and have each geofence be a different color.

  7. I'm curious to hear about how the delete functionality would work. Would this component contain a bit of UI (e.g. a "delete" button) for the user to remove a feature?

Thanks again for putting this together! Hope I didn't give too much feedback there. Looking forward to hearing your thoughts!

Pessimistress commented 5 years ago

styles - are these purely overrides?

My understanding of the plan is to provide both JS styling and classname-based styling. The JavaScript styles object will be merged with the default styles.

The example has a geometries prop

Corrected.

I'm assuming a point refers to the GeoJSON feature, while a vertex is a coordinate in the geometry of a path or polygon GeoJSON feature?

Yes.

goldpbear commented 5 years ago

This is fantastic! Thanks for putting this together.

In terms of functionality I think it looks pretty comprehensive, though I'm also wondering about the ability to drag vertices once they're created.

I also wanted to double check my understanding of how geometry drawn on the proposed overlay would relate to other geometry being rendered by the map. In our use case, we want users to draw on the map and then persist their drawn geometry such that it can be rendered by the map once the user leaves draw mode. So, if I'm understanding correctly, a full draw workflow under the current proposal might look like this:

  1. User enters draw mode and creates and styles geometry to their liking
  2. User "saves" their styled geometry, at which point the GeoJSON features stored in the features state would need to be updated with whatever styling the user settled on
  3. The drawn GeoJSON with styling information would then get added as a source to the map's mapStyle prop
  4. Assuming the map also contains layers capable of rendering the new geometry, the user's drawn geometry would now exist alongside all other map geometry
  5. All state associated with the draw overlay would be discarded

Which I think would be a reasonable workflow-- I just wanted to make sure I understand where the responsibilities of the draw overlay end and the underlying map begin.

Thanks again for your work on this, and I'm looking forward to playing around with the overlay!

xintongxia commented 5 years ago
  • Will DRAW_* modes also allow dragging vertices around?

I think it is not possible to support drawing and dragging at the same time, for example while drawing a polygon, when the user interacts with a vertex that is already drawn, DrawControl cannot decide the user's intention is to close the polygon or to drag the vertex.

  • How does selecting vertices work? I don't see a special callback/argument that deals with what vertex is selected.

I am thinking of changing DrawControl.SELECT_VERTEX to DrawControl.EDIT_VERTEX, as most of time when user interacts with an existing vertex, the user intends to drag the vertex.

  • An interesting usability function is an undo button while drawing/editing. Could this be implemented by storing a history of states in onUpdate and using that to move back in history?

We are not going to include this for the initial release. But I agree with you, this is an interesting feature, we probably will leverage it in future.

Pessimistress commented 5 years ago

@goldpbear Your workflow looks about right to me. The only thing I would like to bring attention to is that the proposed DrawControl renders an SVGOverlay, so there are these limitations:

xintongxia commented 5 years ago
  1. Will this component support drawing multi-polygons or polygons with holes?

We do not include multi-polygons or polygons with holes for the initial release. But I will investigate how to implement these features.

  1. I wonder if users might also need custom styling for features the mouse is hovering over?
  2. Users might want to apply custom styling to features that are currently being drawn, and in some common cases, these stylings might be relatively complex, taking into account the mouse position. For example, one common polygon-drawing treatment I've noticed is where, after selecting 2 vertices, the application shows a sort of "polygon preview" with the mouse position functioning as a temporary third vertex. I imagine this might be difficult to implement with the proposed API, especially if this preview is meant to be styled differently from other polygons on the map.
  3. Related to number 5, I imagine another common need would be to give different styles to features of the same type. For example, I might want to draw several geofences on a map and have each geofence be a different color.

I am thinking to change the styles API, allow user to pass in either a map of style objects as mentioned in this proposal, or a function which receives the following parameters.

styles({vertex, feature, selected, ...})

  1. I'm curious to hear about how the delete functionality would work. Would this component contain a bit of UI (e.g. a "delete" button) for the user to remove a feature?

Thanks for pointing out. I am going to remove onDelete callback from this proposal. Since there is already onSelect callback, the users could easily implement their own deleting logic.

heshan0131 commented 5 years ago

LGTM, couple questions.

  1. mode: does it make sense to support draw rectangle? It is common to drag and draw a rectangle then select feature based on whether they intersect with it

  2. Agree that users might want to apply custom styling to features that are currently being drawn. But there seems to be no way to know which feature is currently being edited besides listening on theonUpdate call back.

  3. A path and a polygon is technically the same thing, people start by drawing path, once a path is closed it becomes a polygon. I wonder having 2 separate draw modes is necessary.

xintongxia commented 5 years ago
  1. mode: does it make sense to support draw rectangle? It is common to drag and draw a rectangle then select feature based on whether they intersect with it

Agreed, will also support drawing rectangle in this DrawControl.

  1. Agree that users might want to apply custom styling to features that are currently being drawn. But there seems to be no way to know which feature is currently being edited besides listening on theonUpdate call back.

I am thinking, styles prop could be a callback function, which will receive the selected status of a feature, then user can dynamically decide styles. In the DrawControl, only one feature could be selected and editable at one time.

  1. A path and a polygon is technically the same thing, people start by drawing path, once a path is closed it becomes a polygon. I wonder having 2 separate draw modes is necessary.

Yes, I think you are right. We don't really need differentiate DRAW_PATH and DRAW_POLYGON mode.

mmaclach commented 5 years ago

It might still be useful to differentiate between polygon and path in order to close the polygon automatically when the user is done editing. It seems like it might be difficult for the user to add a vertex at the exact point to close the path, but maybe this is not an issue.

ibgreen commented 5 years ago

Adding some thoughts on the relation to nebula.gl and the fact that we are creating two features with overlapping functionality:

@georgios-uber @supersonicclay Could make sense for the nebula team to keep an eye on this RFC. It would for instance be nice if the API/functionality remains a reasonably clean subset of nebula.gl, facilitating a seamless upgrade path to nebula.gl for users who start with this feature once more advanced features become needed.

@xintongxia I think it would be neat if the docs of this react-map-gl feature linked to nebula.gl and gave some recommendations when to use which version: https://github.com/uber/nebula.gl/blob/master/docs/overview.md

nebula.gl has already done the reciprocal doc update, I paste the current overview text in nebula.gl.


Overview

nebula.gl provides editable and interactive map overlay layers, built using the power of deck.gl.

Design Goals

nebula.gl aspires to be an ultra-performant, fully 3D-enabled GeoJSON editing system primarily focused on geospatial editing use cases.

Why nebula.gl?

You should strongly consider nebula.gl:

You may want to look at alternatives if:

If nebula.gl is more than what you need (e.g. in terms of bundle size), and you may want to look at other solutions, e.g. the simple polygon editor overlay being developed in react-map-gl.

That said, if you are already using deck.gl the additional overhead of nebula.gl is small, and the seamless integration with deck.gl should be valuable.

aptlin commented 5 years ago

It might still be useful to differentiate between polygon and path in order to close the polygon automatically when the user is done editing. It seems like it might be difficult for the user to add a vertex at the exact point to close the path, but maybe this is not an issue.

This was the first impression I got from playing with the mapbox-gl-draw demo. Having the option to skip redundant clicking would be nice.

ctaylor4874 commented 5 years ago

Is there any chance we can add a free-hand drawing mode? The workflow could be

  1. Disable map pan when in free-hand mode.
  2. Mouse down + drag to create a line, recording the location of the mouse at intervals such as 0.05 seconds.
  3. Append the line string with the value recorded.
  4. Release mouse to finish free-hand draw.
ibgreen commented 5 years ago

This module is now a submodule of nebula.gl. The nebula team have an extensible draw mode system and are implementing many draw modes that users can choose from.

The idea is that those editing modes will be made compatible not only with nebula's main module (the editable deck.gl layers) but also with this little React component.

For quickest response, recommend asking this and other related questions in the nebula.gl repo.

supersonicclay commented 5 years ago

We have a feature request for free-hand drawing in nebula.gl. It's here: https://github.com/uber/nebula.gl/issues/172

georgios-uber commented 5 years ago

@xintongxia can you add this rfc to nebula repo and .md file?