mapbox-gl-draw icon indicating copy to clipboard operation
mapbox-gl-draw copied to clipboard

Expose extra drawing mode events (e.g. onDragVertex, clickOnVertex, onMouseMove, clickAnywhere etc.)

Open jacknkandy opened this issue 7 years ago • 7 comments

Hi There,

I have been working on extending the Mapbox GL Draw library to include some measuring tools (line and polygon) and have been building my own custom modes in order to enable this functionality.

Rather than having to build a custom mode, I initially explored trying to obtain the data that is currently being drawn through some of the callback events that are available with Mapbox GL Draw. Discovering that this is impossible, I had to go down the path of building my own custom modes.

I have built my custom measuring tools by extending the existing "draw_line_string" and "draw_polygon" modes that comes with Mapbox GL Draw. I wanted the measurements to be calculated 'live' as in while the user is actually drawing the line/polygon (not after the entire line/polygon has been drawn).

However now I am stuck with the issue of not being able to properly update the measurements when the user goes to re-edit the line. I have determined that this is due to the fact that when a geometry is being edited, Mapbox GL Draw is actually in "direct_select" mode, so in order to keep this functionality consistent I am now required to modify and extend the "direct_select" mode as well.

It seems that it would be simpler instead to include additional events which correspond to some of the events that internally available within the mode interface. In my particular case, I didn't actually need anything different than what was provided out-of-the-box for drawing lines and polygons. The only reason why I had to create a custom mode was to be able to access the geometry while the user is drawing it.

I believe that most of the events (if not all) that are found within the mode interface should be exposed and accessible in the same way as the other draw events, i.e.

map.on('draw.#event_name#', (e) => {
   ...
});

jacknkandy avatar Aug 10 '18 06:08 jacknkandy

Jamie,

Have you tried doing this by adding a draw.update event handler? I could see this being too slow for your use case and a bit awkward, but it would be my first guess for how to do this without making new modes.

I believe that most of the events (if not all) that are found within the mode interface should be exposed and accessible in the same way as the other draw events

This should be pretty easy to prototype by having all of the default modes emit invocations emit an event at the start. I'd be interesting in seeing what that kind of functionality unlocks.

Mapbox GL Draw is actually in "direct_select" mode, so in order to keep this functionality consistent I am now required to modify and extend the "direct_select" mode as well.

I wonder if we could drop direct_select mode and move all feature type editing to the feature modes?

mcwhittemore avatar Aug 10 '18 18:08 mcwhittemore

Hey @mcwhittemore,

I am using draw.update to some extent, but this event only fires once the editing has finished, it does not fire while the geometry is being placed/edited. In order to provide a measurement to users while they are drawing, I have had to make use of the other events that are only available in the mode interface.

This should be pretty easy to prototype by having all of the default modes emit invocations emit an event at the start. I'd be interesting in seeing what that kind of functionality unlocks.

I believe there are other useful situations where having direct access to these events would be useful.

I wonder if we could drop direct_select mode and move all feature type editing to the feature modes?

When I was exploring the custom modes, my originally assumption was that this was the case. In my opinion, it is a little strange that the re-editing a piece of geometry is not related to its original drawing mode. If you have custom functionality that you have built into a custom mode, you are then always required to duplicate this functionality in the direct_select mode (and in some cases even the simple_select mode) in order to maintain consistency.

jacknkandy avatar Aug 13 '18 00:08 jacknkandy

In my opinion, it is a little strange that the re-editing a piece of geometry is not related to its original drawing mode.

Yea. This is a wort from not being custom modes from the start and would be a great project to take on. Its a break API change, but I think a worth while one.

mcwhittemore avatar Aug 16 '18 19:08 mcwhittemore

Any update on this? @jacknkandy I am working on the same thing now, would it be possible to share how you did it?

RonanFa avatar Jan 27 '19 15:01 RonanFa

Hi @RonanFa,

I was able to achieve this functionality by extending and modifying the draw mode classes that I needed these extra events for. Rather than copying the entire file and re-writing the parts that I needed, I extended the necessary event functions in the mode classes as required.

This example is for the "Direct Select" mode:

// direct_select.js

import * as MapboxDraw from '@mapbox/mapbox-gl-draw';

const DirectSelectMode = MapboxDraw.modes.direct_select;

const _dragFeature = DirectSelectMode.dragFeature;
const _dragVertex = DirectSelectMode.dragVertex;

DirectSelectMode.dragFeature = function (state, e, delta) {
  const result = _dragFeature.apply(this, [state, e, delta]);

  const feature = state.feature.toGeoJSON();

  this.map.fire('draw.liveUpdate', {
    features: [feature]
  });

  return result;
};

DirectSelectMode.dragVertex = function (state, e, delta) {
  const result = _dragVertex.apply(this, [state, e, delta]);

  const feature = state.feature.toGeoJSON();

  this.map.fire('draw.liveUpdate', {
    features: [feature]
  });

  return result;
};

export default DirectSelectMode;
// In my mapbox service class

import DirectSelectMode from './custom-modes/direct-select';

// ...

// When creating the mapbox draw object you will need to override the existing draw modes with your new class
const options = {
  displayControlsDefault: false,
  controls: {},
  userProperties: true,
  modes: Object.assign({
    measure_line: MeasureLineMode,
    measure_polygon: MeasurePolygonMode,
    measure_radius: MeasureRadiusMode,
    measure_travel: MeasureTravelMode,
    direct_select: DirectSelectMode
  }, MapboxDraw.modes)
};

const draw = new MapboxDraw(options);

// ..

// Now the new event is available, e.g.
map.on('draw.liveUpdate', (e) => {
  // do whatever you want here
  // The event object will include the GeoJSON for the updated feature
});

Depending on which draw modes you wish to add this functionality to, and what you are trying to achieve, you may need to extend the other event functions, e.g. clickAnywehere, onMouseMove, and etc. Obviously in this example I have routed all these events through a custom event I've named draw.liveUpdate. However you can call it whatever you like and you can route these events separately if you prefer.

Hope this helps!

jacknkandy avatar Jan 28 '19 23:01 jacknkandy

It appears the modes API is not exposed any longer. Can someone please verify? Instead, the options are ENUMS MapboxDraw.modes.DIRECT_SELECT which points to a type. MapboxDraw.DrawModes.DIRECT_SELECT: "direct_select"

IsaacTrevino avatar Apr 26 '22 14:04 IsaacTrevino

@IsaacTrevino You are correct. I recently implemented some new custom modes for a new project and as of v1.4.1 of @mapbox/mapbox-gl-draw, the API exposing these values has changed, examples below:

MapboxDraw.constants.cursors.POINTER MapboxDraw.constants.modes.SIMPLE_SELECT MapboxDraw.lib.createVertex(geojson.properties.id, geojson.properties.user_center, 0.0, false)

jacknkandy avatar Apr 05 '23 05:04 jacknkandy