Skip to content

WIP: Switch form Mapbox Draw to TerraDraw #197

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
196 changes: 196 additions & 0 deletions src/components/MlFeatureDraw/MlFeatureDraw.stories.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<Sidebar open={true}>
<ButtonGroup size="small">
<Tooltip title="Draw">
<Button
variant={isDrawing ? 'contained' : 'outlined'}
onClick={() => startDrawing(args.modeType)}
>
<DrawIcon />
{`Draw ${args.modeType}`}
</Button>
</Tooltip>
<Button variant={!isDrawing ? 'contained' : 'outlined'} onClick={stopDrawing}>
<EditIcon />
</Button>
<Button onClick={clearDrawing}>
<DeleteIcon />
</Button>
</ButtonGroup>
</Sidebar>
</>
);
};

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' };
13 changes: 13 additions & 0 deletions src/components/MlFeatureDraw/MlFeatureDraw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import useFeatureDraw, { useFeatureDrawProps } from '../../hooks/useFeatureDraw';

const MlFeatureDraw: React.FC<useFeatureDrawProps> = (props) => {
useFeatureDraw({
mapId: props.mapId,
mode: props.mode,
});

return <></>;
};

export default MlFeatureDraw;
97 changes: 97 additions & 0 deletions src/hooks/useFeatureDraw.tsx
Original file line number Diff line number Diff line change
@@ -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<any>[];
}

const useFeatureDraw = (props: useFeatureDrawProps) => {
const draw = useRef<TerraDraw | null>(null);
const [isDrawing, setIsDrawing] = useState<boolean>(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;
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -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"
Loading