uber-apireact-map-gl

react-map-gl-draw addFeatures creating duplicates


Describe the Issue When feature is deleted and new feature is added for vertices to update. The new feature is created twice. Every time this cycle is repeated. Function deleteVertex have the the code of this problem.

Actual Result new duplicate feature is added.

Expected Result Only 1 new feature should be created.

Reproduce Steps Add a feature. Remove the Feature any 1 index of coordinates. Remove the old Feature. Add the this new modified version of Feature using addFeatures.

Question Also I want to know if there is any easy way to add or remove the vertices.

Code

const MapOverLay = ({
  type,
  viewport,
  setViewport,
  save,
  close,
  previousFeatures,
  defaultViewPort,
}) => {

  const editorRef = useRef(null);
  const featureRef = useRef(null);
  const locateIconRef = useRef(false);
  const [previousFeature, setPreviousFeature] = useState(previousFeatures);
  const [mapData, setMapData] = useState({
    editor: {
      type: previousFeatures.length ? 'Editing' : '',
      mode: previousFeatures.length ? new EditingMode() : null,
      selectedFeatureIndex: null,
    },
  });



  const onSelect = map => {
    if (map.selectedEditHandleIndex !== null) {
      featureRef.current = {
        type: 'selectedEditHandleIndex',
        index: map.selectedEditHandleIndex,
        featureIndex: map.selectedFeatureIndex,
      };
    }
    else {
      featureRef.current = {
        type: 'selectedFeatureIndex',
        index: map.selectedFeatureIndex,
      };
    }
  };

  const onUpdate = ({ editType, data }) => {
    if (editType === 'addFeature') {
      const temp_data = makeClone(mapData);
      temp_data.editor.type = 'Editing';
      temp_data.editor.mode = new EditingMode();
      setMapData(temp_data);
    }
    if (previousFeatures.length) {
      setPreviousFeature(data);
    }
  };

  const deleteVertex = () => {
    const feature = editorRef.current.getFeatures()[0];
    if (feature.geometry.coordinates[0].length !== 2) {
      feature.geometry.coordinates[0].splice(featureRef.current.index, 1);
      editorRef.current.deleteFeatures(featureRef.current.featureIndex);
      editorRef.current.addFeatures(feature);
      console.log('Delete');
    }
    else {
      editorRef.current.deleteFeatures(featureRef.current.featureIndex);
    }
    featureRef.current = null;
  };

  // console.log(editorRef.current);

  const deleteFeature = () => {
    if (previousFeature.length) {
      setPreviousFeature([]);
    }
    else {
      editorRef.current.deleteFeatures(featureRef.current.index);
    }
    save([]);
  };

  const onDelete = () => {
    if (featureRef.current !== null) {
      switch (featureRef.current.type) {
        case 'selectedEditHandleIndex':
          deleteVertex();
          return;
        case 'selectedFeatureIndex':
          deleteFeature();
          return;
        default:
          break;
      }
    }
  };


  const onSave = map => {
    save(map.getFeatures());
  };

  const getDrawModeStyle = () => {
    switch (type) {
      case 'Polygon':
        return style.polygon;

      case 'Point':
        return style.point;

    }
  };

  const getDrawModePressedStyle = () => {
    switch (type) {
      case 'Polygon':
        return style.pressed_polygon;

      case 'Point':
        return style.pressed_point;

    }
  };

  const getDrawMode = () => {
    switch (type) {
      case 'Polygon':
        return new DrawPolygonMode();

      case 'Point':
        return new DrawPointMode();

    }
  };

  const onToolClick = () => {
    if (editorRef.current.getFeatures().length === 0) {
      const temp_data = makeClone(mapData);
      if (type === temp_data.editor.type) {
        temp_data.editor.type = '';
        temp_data.editor.mode = null;
        setMapData(temp_data);
      }
      else {
        temp_data.editor.type = type;
        temp_data.editor.mode = getDrawMode();
        setMapData(temp_data);
      }
    }
  };

  const locate = map => {
    locateIconRef.current = true;
    const features = map.getFeatures();
    if (features.length) {
      const center = centerOfMass(features[0]);
      setViewport(prevState => ({
        ...prevState,
        zoom: 10,
        longitude: center.geometry.coordinates[0],
        latitude: center.geometry.coordinates[1],
      }));
    }
    else {
      defaultViewPort();
    }
  };

  const viewPortChangeFromMap = nextViewport => {
    locateIconRef.current = false;
    setViewport(nextViewport);
  };

  const _renderDrawTools = () => {
    // copy from mapbox
    return (
      <>
        <div className='mapboxgl-ctrl-top-left'>
          <div className={['mapboxgl-ctrl-group mapboxgl-ctrl', style.navigation_group_top_left].join(' ')}>
            <button
              className="mapbox-gl-draw_ctrl-draw-btn"
              title="Save"
              onClick={() => onSave(editorRef.current)}
            />
            <button
              id={getDrawModeStyle()}
              className={
                [
                  'mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_polygon',
                  mapData.editor.type === type ? getDrawModePressedStyle() : '',
                ].join(' ')
              }
              title="Select"
              onClick={() => onToolClick()}
            />
            <button
              className="mapbox-gl-draw_ctrl-draw-btn mapbox-gl-draw_trash"
              title="Remove"
              onClick={onDelete}
            />
          </div>
        </div>
        <div className='mapboxgl-ctrl-top-right'>
          <div className={['mapboxgl-ctrl-group mapboxgl-ctrl', style.navigation_group_top_right].join(' ')}>
            <button
              className="mapbox-gl-draw_ctrl-draw-btn"
              title="Close"
              onClick={close}
            />
          </div>
        </div>
        <div className='mapboxgl-ctrl-bottom-right'>
          <div className={['mapboxgl-ctrl-group mapboxgl-ctrl', style.navigation_group_bottom_right].join(' ')}>
            <button
              className="mapbox-gl-draw_ctrl-draw-btn"
              title="Focus"
              onClick={() => locate(editorRef.current)}
            />
          </div>
        </div>
      </>
    );
  };

  return (
    <ReactMapGL
      {...viewport}
      mapStyle="mapbox://styles/giddyops/ckips5wdw61xb17qs3eorsoj9"
      mapboxApiAccessToken={MAPBOX_TOKEN}
      onViewportChange={viewPortChangeFromMap}
      attributionControl={false}
      transitionDuration={1000}
      transitionInterpolator={locateIconRef.current ? new FlyToInterpolator() : null}
    >
      <Editor
        ref={editorRef}
        mode={mapData.editor.mode}
        clickRadius={12}
        features={previousFeature.length ? previousFeature : null}
        onSelect={onSelect}
        onUpdate={onUpdate}
        editHandleShape={'circle'}
        featureStyle={getFeatureStyle}
        editHandleStyle={getEditHandleStyle}
        onClick={e => console.log(e)}
      />
      {_renderDrawTools()}
    </ReactMapGL>
  );
};

Solution

  • I have reviewed your code and found out that it could be issue that delete features was finished deleting way after than adding feature. you can solve this issue by making the following changes:

    1. make deleteVertex function async.
    2. cloning the feature in the first line.
    3. await the editorRef.current.deleteFeatures(featureRef.current.featureIndex);