nebula.gl icon indicating copy to clipboard operation
nebula.gl copied to clipboard

How to freely resize a rectangle

Open JanVanDerWall opened this issue 3 years ago • 8 comments

First of all I want to emphasize that I think Nebula.gl is a gret pice of open source software and it helped me a lot with my application. But, I encountered a problem. How can I modify a rectangle after it has been drawn with Draw-Rectangl-Mode so that is keeps it rectangular shape. Each point should be able to be draged to any position, not only scaling but also reshaping the rectangle. I know that there is #492. But this answers not the question on how to solve my problem the easiest and best performing way.

JanVanDerWall avatar Jan 30 '22 11:01 JanVanDerWall

Hi @JanVanDerWall I believe this is possible right now if you decide to use modules/react-map-gl-draw. But editable geojson layer does things differently and doesn't differentiate between polygons and other shapes, like rectangles at the moment. I've opened the following PR few days ago to preserve the shape of a rectangle when we edit vertices https://github.com/uber/nebula.gl/pull/691 and https://github.com/uber/nebula.gl/pull/692 to preserve shape during translation. Scale and rotation are wip.

igorDykhta avatar Jan 30 '22 18:01 igorDykhta

Thank you for the reply! Sadly I need to use editable GeoJson layer since some other Deck.Gl layers come in very handy. Therefore I conclude that it is not easily possible right now? And will be possilbe once your PR's are approved, merged and released? Until then, might there be another option to achieve this?

JanVanDerWall avatar Jan 30 '22 18:01 JanVanDerWall

You can try to extend existing modes with custom translation, rotation, and scale calculations. Which modes do you need to use?

igorDykhta avatar Jan 30 '22 18:01 igorDykhta

Right now I am using ModifyMode. Might there be a better choice?

JanVanDerWall avatar Jan 30 '22 19:01 JanVanDerWall

No. I guess you'll need to wait for the next patch.

igorDykhta avatar Jan 30 '22 19:01 igorDykhta

For everybody who is interested: I did find a solution. This is most certainly not elegant nor efficient but it works and it did not take long to implement. I didn't see a reason to implement something prettier (liek adding a custom edit mode) if there will be a nice solution soon.

onEdit: (event) => {
   const { editType, updatedData } = event;
   if (editType == "movePosition") {
      setGeoJSONFeatures(edit_selection(updatedData));
   }

   /*
      ...
   */

}

and the edit_selection function:

const edit_selection = (updatedData) => {
   // is_current_edit and current_edit_corner are declared for the whole file
   let new_coords = updatedData.features[0].geometry.coordinates[0];
   const old_coords = geoJSONFeatures.features[0].geometry.coordinates[0];
  //determin the index of the point which is modified
   let idx;
   old_coords.forEach((value, i) => {
      if (value != new_coords[i]) {
         idx = i;
      }
   });

   let idx_lower = (idx == 0) ? 3 : idx - 1;
   let idx_higher = (idx == 4) ? 1 : idx + 1;
   if (!is_current_edit) {

      is_current_edit = true; //this variable determines if the user is currently modifying a point
      if (
         (old_coords[idx][1] > old_coords[idx_higher][1] &&
            old_coords[idx][0] < old_coords[idx_lower][0]) ||
         (old_coords[idx][0] > old_coords[idx_lower][0] &&
            old_coords[idx][1] < old_coords[idx_higher][1])
      ) {
         //it is either the top left or bottom right corner
         current_edit_corner = true;
      } else if (
         (old_coords[idx][0] < old_coords[idx_higher][0] &&
            old_coords[idx][1] < old_coords[idx_lower][1]) ||
         (old_coords[idx][0] > old_coords[idx_higher][0] &&
            old_coords[idx][1] > old_coords[idx_lower][1])
      ) {
         //it is either the bottom left or top right corner
         current_edit_corner = false;
      } 
   } else {

      if (current_edit_corner) {
         new_coords[idx_higher][0] = new_coords[idx][0];
         new_coords[idx_lower][1] = new_coords[idx][1];
      } else {
         new_coords[idx_higher][1] = new_coords[idx][1];
         new_coords[idx_lower][0] = new_coords[idx][0];
      }
   }


   if (idx === 4 || idx_lower === 4 || idx_higher === 4) {
      new_coords[0] = new_coords[4];
   } else if (idx === 0 || idx_lower === 0 || idx_higher === 0) {
      new_coords[4] = new_coords[0];
   }
   updatedData.features[0].geometry.coordinates[0] = new_coords;
   return updatedData;
};

JanVanDerWall avatar Jan 31 '22 17:01 JanVanDerWall

Here is another solution by creating the custom edit mode. Since "ScaleMode" has already implemented many things and all I want is just resizing freely without fixed aspect ratio. I simply extend that mode and override the getScaleAction method

import bboxPolygon from "@turf/bbox-polygon";
import { getCoord } from "@turf/invariant";
import { ScaleMode } from "@nebula.gl/edit-modes";

class ResizeRectangle extends ScaleMode {
  getScaleAction = (startDragPoint, currentPoint, editType, props) => {
    if (!this._selectedEditHandle) {
      return null;
    }

    const oppositeHandle = this._getOppositeScaleHandle(
      this._selectedEditHandle
    );
    const origin = getCoord(oppositeHandle);

    const boundingBox = bboxPolygon([
      Math.min(origin[0], currentPoint[0]),
      Math.min(origin[1], currentPoint[1]),
      Math.max(origin[0], currentPoint[0]),
      Math.max(origin[1], currentPoint[1]),
    ]);

    return {
      updatedData: this._getUpdatedData(props, {
        type: "FeatureCollection",
        features: [boundingBox],
      }),
      editType,
      editContext: {
        featureIndexes: props.selectedIndexes,
      },
    };
  };
}

export default ResizeRectangle;

This works out perfectly for my own project.

nightspirit avatar May 06 '22 16:05 nightspirit

Here is another solution by creating the custom edit mode. Since "ScaleMode" has already implemented many things and all I want is just resizing freely without fixed aspect ratio. I simply extend that mode and override the getScaleAction method

import bboxPolygon from "@turf/bbox-polygon";
import { getCoord } from "@turf/invariant";
import { ScaleMode } from "@nebula.gl/edit-modes";

class ResizeRectangle extends ScaleMode {
  getScaleAction = (startDragPoint, currentPoint, editType, props) => {
    if (!this._selectedEditHandle) {
      return null;
    }

    const oppositeHandle = this._getOppositeScaleHandle(
      this._selectedEditHandle
    );
    const origin = getCoord(oppositeHandle);

    const boundingBox = bboxPolygon([
      Math.min(origin[0], currentPoint[0]),
      Math.min(origin[1], currentPoint[1]),
      Math.max(origin[0], currentPoint[0]),
      Math.max(origin[1], currentPoint[1]),
    ]);

    return {
      updatedData: this._getUpdatedData(props, {
        type: "FeatureCollection",
        features: [boundingBox],
      }),
      editType,
      editContext: {
        featureIndexes: props.selectedIndexes,
      },
    };
  };
}

export default ResizeRectangle;

This works out perfectly for my own project.

This is the best implementation yet! Thanks for the heads-up!

neelduttahere avatar Aug 08 '23 07:08 neelduttahere