How to freely resize a rectangle
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.
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.
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?
You can try to extend existing modes with custom translation, rotation, and scale calculations. Which modes do you need to use?
Right now I am using ModifyMode. Might there be a better choice?
No. I guess you'll need to wait for the next patch.
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;
};
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.
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
getScaleActionmethodimport 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!