diff --git a/package.json b/package.json
index 8f73721e..6f52932b 100644
--- a/package.json
+++ b/package.json
@@ -50,6 +50,8 @@
"react-redux": "^9.1.2",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
+ "terra-draw": "^1.0.0",
+ "terra-draw-maplibre-gl-adapter": "^1.0.1",
"three": "^0.161.0",
"topojson-client": "^3.1.0",
"uuid": "^9.0.1",
diff --git a/src/components/MlFeatureDraw/MlFeatureDraw.stories.tsx b/src/components/MlFeatureDraw/MlFeatureDraw.stories.tsx
new file mode 100644
index 00000000..aeaf2274
--- /dev/null
+++ b/src/components/MlFeatureDraw/MlFeatureDraw.stories.tsx
@@ -0,0 +1,196 @@
+import React from 'react';
+import mapContextDecorator from '../../decorators/MapContextDecorator';
+import MlFeatureDraw from './MlFeatureDraw';
+import Sidebar from '../../ui_components/Sidebar';
+import useFeatureDraw from '../../hooks/useFeatureDraw';
+import {
+ TerraDrawPointMode,
+ TerraDrawLineStringMode,
+ TerraDrawPolygonMode,
+ TerraDrawRectangleMode,
+ TerraDrawFreehandMode,
+ TerraDrawCircleMode,
+ TerraDrawSelectMode,
+} from 'terra-draw';
+import DeleteIcon from '@mui/icons-material/Delete';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import EditIcon from '@mui/icons-material/Edit';
+import { Button, Tooltip } from '@mui/material';
+import DrawIcon from '@mui/icons-material/Draw';
+
+const storyoptions = {
+ title: 'MapComponents/MlFeatureDraw',
+ component: MlFeatureDraw,
+ argTypes: {
+ modeType: {
+ control: {
+ type: 'select',
+ options: ['point', 'linestring', 'polygon', 'rectangle', 'freehand', 'circle'],
+ },
+ defaultValue: 'polygon',
+ },
+ },
+ decorators: mapContextDecorator,
+};
+
+export default storyoptions;
+
+const Template = (args: { modeType: string }) => {
+ const modes = React.useMemo(() => {
+ let baseMode;
+ let selectConfig;
+
+ switch (args.modeType) {
+ case 'point':
+ baseMode = new TerraDrawPointMode();
+ selectConfig = {
+ flags: {
+ point: {
+ feature: { draggable: true },
+ },
+ },
+ };
+ break;
+
+ case 'linestring':
+ baseMode = new TerraDrawLineStringMode();
+ selectConfig = {
+ flags: {
+ linestring: {
+ feature: {
+ draggable: true,
+ coordinates: {
+ midpoints: true,
+ draggable: true,
+ deletable: true,
+ },
+ },
+ },
+ },
+ };
+ break;
+
+ case 'polygon':
+ baseMode = new TerraDrawPolygonMode();
+ selectConfig = {
+ flags: {
+ polygon: {
+ feature: {
+ draggable: true,
+ rotateable: true,
+ scaleable: true,
+ coordinates: {
+ midpoints: true,
+ draggable: true,
+ deletable: true,
+ },
+ },
+ },
+ },
+ };
+ break;
+
+ case 'rectangle':
+ baseMode = new TerraDrawRectangleMode();
+ selectConfig = {
+ flags: {
+ rectangle: {
+ feature: {
+ draggable: true,
+ rotateable: true,
+ scaleable: true,
+ coordinates: {
+ midpoints: true,
+ draggable: true,
+ deletable: true,
+ },
+ },
+ },
+ },
+ };
+ break;
+
+ case 'freehand':
+ baseMode = new TerraDrawFreehandMode();
+ selectConfig = {
+ flags: {
+ freehand: {
+ feature: {
+ draggable: true,
+ coordinates: {
+ midpoints: true,
+ draggable: true,
+ deletable: true,
+ },
+ },
+ },
+ },
+ };
+ break;
+
+ case 'circle':
+ baseMode = new TerraDrawCircleMode();
+ selectConfig = {
+ flags: {
+ circle: {
+ feature: {
+ draggable: true,
+ },
+ },
+ },
+ };
+ break;
+
+ default:
+ throw new Error(`Unknown mode type: ${args.modeType}`);
+ }
+
+ return [baseMode, new TerraDrawSelectMode(selectConfig)];
+ }, [args.modeType]);
+
+ const { startDrawing, stopDrawing, clearDrawing, isDrawing } = useFeatureDraw({
+ mapId: 'map_1',
+ mode: modes,
+ });
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export const DrawPoint = Template.bind({});
+DrawPoint.args = { modeType: 'point' };
+
+export const DrawLine = Template.bind({});
+DrawLine.args = { modeType: 'linestring' };
+
+export const DrawPolygon = Template.bind({});
+DrawPolygon.args = { modeType: 'polygon' };
+
+export const DrawRectangle = Template.bind({});
+DrawRectangle.args = { modeType: 'rectangle' };
+
+export const DrawFreehand = Template.bind({});
+DrawFreehand.args = { modeType: 'freehand' };
+
+export const DrawCircle = Template.bind({});
+DrawCircle.args = { modeType: 'circle' };
diff --git a/src/components/MlFeatureDraw/MlFeatureDraw.tsx b/src/components/MlFeatureDraw/MlFeatureDraw.tsx
new file mode 100644
index 00000000..c5b627e0
--- /dev/null
+++ b/src/components/MlFeatureDraw/MlFeatureDraw.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import useFeatureDraw, { useFeatureDrawProps } from '../../hooks/useFeatureDraw';
+
+const MlFeatureDraw: React.FC = (props) => {
+ useFeatureDraw({
+ mapId: props.mapId,
+ mode: props.mode,
+ });
+
+ return <>>;
+};
+
+export default MlFeatureDraw;
diff --git a/src/hooks/useFeatureDraw.tsx b/src/hooks/useFeatureDraw.tsx
new file mode 100644
index 00000000..19eebd80
--- /dev/null
+++ b/src/hooks/useFeatureDraw.tsx
@@ -0,0 +1,97 @@
+import useMap from './useMap';
+import { useEffect, useRef, useState } from 'react';
+import { TerraDraw } from 'terra-draw';
+import { TerraDrawMapLibreGLAdapter } from 'terra-draw-maplibre-gl-adapter';
+import { TerraDrawBaseDrawMode } from 'terra-draw/dist/modes/base.mode';
+
+export interface useFeatureDrawProps {
+ /**
+ * Id of the target MapLibre instance in mapContext
+ */
+ mapId?: string;
+ /**
+ * Id of an existing layer in the mapLibre instance to help specify the layer order
+ * This layer will be visually beneath the layer with the "insertBeforeLayer" id.
+ */
+ insertBeforeLayer?: string;
+ /**
+ * drawing mode
+ */
+ mode: TerraDrawBaseDrawMode[];
+}
+
+const useFeatureDraw = (props: useFeatureDrawProps) => {
+ const draw = useRef(null);
+ const [isDrawing, setIsDrawing] = useState(false);
+ const mapHook = useMap({
+ mapId: props.mapId,
+ waitForLayer: props.insertBeforeLayer,
+ });
+
+ const cleanup = () => {
+ if (draw.current) {
+ draw.current.stop();
+ draw.current = null;
+ }
+ setIsDrawing(false);
+ };
+
+ const initializeDraw = () => {
+ if (!mapHook.map) return;
+ cleanup();
+ draw.current = new TerraDraw({
+ adapter: new TerraDrawMapLibreGLAdapter(mapHook.map),
+ modes: props.mode,
+ });
+ };
+
+ useEffect(() => {
+ initializeDraw();
+ return () => {
+ cleanup();
+ };
+ }, [mapHook.map, props.mode]);
+
+ const setMode = (mode: string): void => {
+ if (!draw.current) return;
+
+ try {
+ draw.current.setMode(mode);
+ setIsDrawing(mode !== 'select');
+ } catch (error) {
+ console.error('Error setting mode:', error);
+ setIsDrawing(false);
+ }
+ };
+
+ const startDrawing = (mode: string) => {
+ if (!draw.current) initializeDraw();
+ if (!draw.current) return;
+
+ try {
+ if (!draw.current.enabled) {
+ draw.current.start();
+ }
+ setMode(mode);
+ } catch (error) {
+ console.error('Error starting drawing:', error);
+ cleanup();
+ }
+ };
+
+ const stopDrawing = (): void => {
+ if (draw.current) {
+ console.log('select mode');
+ setMode('select');
+ }
+ };
+
+ const clearDrawing = (): void => {
+ if (draw.current) {
+ draw.current.clear();
+ }
+ };
+
+ return { startDrawing, stopDrawing, clearDrawing, isDrawing };
+};
+export default useFeatureDraw;
diff --git a/yarn.lock b/yarn.lock
index f3f81b3a..4205dd38 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15239,6 +15239,16 @@ tempy@^1.0.1:
type-fest "^0.16.0"
unique-string "^2.0.0"
+terra-draw-maplibre-gl-adapter@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/terra-draw-maplibre-gl-adapter/-/terra-draw-maplibre-gl-adapter-1.0.1.tgz#1b85fbe3c50836e8e8b0bae796cc1ce018ebcca4"
+ integrity sha512-B5zM6OhOEwcYegNLP2HybOc+V0ZaQNYuSkOiAcGABV6ZVd5zZCX51GIXcAgOL07okcs1D+7a+4zCgqtD/fLgTg==
+
+terra-draw@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/terra-draw/-/terra-draw-1.0.0.tgz#ffd6339a8644ed66e589457bc22a670381ea95cd"
+ integrity sha512-LMD5wLHHSfkXOX0eGjhUV/Pnxedn5MvsKBvGOP6txCY1D1LAIVnIx1g+trTXfBHUjogDJj/vmswsGMD/5LtNKQ==
+
terser-webpack-plugin@^5.3.1, terser-webpack-plugin@^5.3.10:
version "5.3.10"
resolved "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz"