Skip to content

Commit

Permalink
Add rectangle mode
Browse files Browse the repository at this point in the history
New draw mode that extends polygon but behaves
like rectangle.

On First click, coordinate is set. On Second click the rectangle
is created as bounding box. This will be used for
drawing bounding box.

Signed-off-by: Vijayan Balasubramanian <balasvij@amazon.com>
  • Loading branch information
VijayanB committed Feb 14, 2023
1 parent 7c7d026 commit bc04699
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 0 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@types/mapbox__mapbox-gl-draw": "^1.3.3",
"@types/wellknown": "^0.5.4",
"cypress-file-upload": "^5.0.8",
"geojson": "^0.5.0",
"install": "^0.13.0",
"maplibre-gl": "^2.4.0",
"prettier": "^2.1.1",
"uuid": "3.3.2",
Expand Down
148 changes: 148 additions & 0 deletions public/components/draw/modes/rectangle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { Feature, GeoJSON, Position } from 'geojson';
import { DrawCustomMode, DrawFeature, DrawPolygon, MapMouseEvent } from '@mapbox/mapbox-gl-draw';

// https://github.com/mapbox/geojson.io/blob/main/src/ui/draw/rectangle.js
const doubleClickZoom = {
enable: (ctx: any) => {
setTimeout(() => {
// First check we've got a map and some context.
if (
!ctx.map ||
!ctx.map.doubleClickZoom ||
!ctx._ctx ||
!ctx._ctx.store ||
!ctx._ctx.store.getInitialConfigValue
)
return;
// Now check initial state wasn't false (we leave it disabled if so)
if (!ctx._ctx.store.getInitialConfigValue('doubleClickZoom')) return;
ctx.map.doubleClickZoom.enable();
}, 0);
},
disable(ctx: { map: { doubleClickZoom: { disable: () => void } } }) {
setTimeout(() => {
if (!ctx.map || !ctx.map.doubleClickZoom) return;
// Always disable here, as it's necessary in some cases.
ctx.map.doubleClickZoom.disable();
}, 0);
},
};

interface DrawRectangleState extends DrawPolygon {
startPoint: Position;
endPoint: Position;
}

// TODO Convert this to class
export const DrawRectangle: DrawCustomMode<DrawRectangleState, {}> = {
onSetup(): any {
const rectangleGeoJSON: GeoJSON = {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: [[]],
},
};
const rectangle: DrawFeature = this.newFeature(rectangleGeoJSON);
// @ts-ignore
this.addFeature(rectangle);
// @ts-ignore
this.clearSelectedFeatures();
doubleClickZoom.disable(this);
// @ts-ignore
this.updateUIClasses({ mouse: 'add' });
// @ts-ignore
this.setActionableState({
trash: true,
});
return rectangle;
},
// Whenever a user clicks on the map, Draw will call `onClick`
onClick(state: DrawRectangleState, e: MapMouseEvent) {
// if state.startPoint exist, means its second click
// change to simple_select mode
if (
state.startPoint &&
state.startPoint[0] !== e.lngLat.lng &&
state.startPoint[1] !== e.lngLat.lat
) {
// @ts-ignore
this.updateUIClasses({ mouse: 'pointer' });
state.endPoint = [e.lngLat.lng, e.lngLat.lat];
// @ts-ignore
this.changeMode('simple_select', { featuresId: state.id });
}
// on first click, save clicked point coords as starting for rectangle
const startPoint = [e.lngLat.lng, e.lngLat.lat];
state.startPoint = startPoint;
},
onMouseMove(state: DrawRectangleState, e: MapMouseEvent) {
// if startPoint, update the feature coordinates, using the bounding box concept
// we are simply using the startingPoint coordinates and the current Mouse Position
// coordinates to calculate the bounding box on the fly, which will be our rectangle
if (state.startPoint) {
state.updateCoordinate('0.0', state.startPoint[0], state.startPoint[1]); // minX, minY - the starting point
state.updateCoordinate('0.1', e.lngLat.lng, state.startPoint[1]); // maxX, minY
state.updateCoordinate('0.2', e.lngLat.lng, e.lngLat.lat); // maxX, maxY
state.updateCoordinate('0.3', state.startPoint[0], e.lngLat.lat); // minX,maxY
state.updateCoordinate('0.4', state.startPoint[0], state.startPoint[1]); // minX,minY - ending point (equals to starting point)
}
},
onKeyUp(state: DrawRectangleState, e: KeyboardEvent) {
if (e.code === 'Escape') {
// change mode to simple select if escape is pressed
// @ts-ignore
this.changeMode('simple_select');
}
},
onStop(state: DrawRectangleState) {
doubleClickZoom.enable(this);
// @ts-ignore
this.updateUIClasses({ mouse: 'none' });
// @ts-ignore
this.activateUIButton();

// check to see if we've deleted this feature
// @ts-ignore
if (this.getFeature(state.id) === undefined) return;

// remove last added coordinate
state.removeCoordinate('0.4');
if (state.isValid()) {
// @ts-ignore
this.map.fire('draw.create', {
features: [state.toGeoJSON()],
});
} else {
// @ts-ignore
this.deleteFeature([state.id], { silent: true });
// @ts-ignore
this.changeMode('simple_select', {}, { silent: true });
}
},
toDisplayFeatures(
state: DrawRectangleState,
geojson: Feature,
display: (geojson: Feature) => void
) {
const isActivePolygon = geojson?.properties?.id === state.id;
geojson.properties!.active = isActivePolygon ? 'true' : 'false';
if (!isActivePolygon) return display(geojson);

// Only render the rectangular polygon if it has the starting point
if (!state.startPoint) return;
return display(geojson);
},
onTrash(state: DrawRectangleState) {
// @ts-ignore
this.deleteFeature([state.id], { silent: true });
// @ts-ignore
this.changeMode('simple_select');
},
};

0 comments on commit bc04699

Please sign in to comment.