Skip to content

Commit

Permalink
Merge pull request #143 from hotosm/feat/way-points
Browse files Browse the repository at this point in the history
  • Loading branch information
varun2948 authored Aug 13, 2024
2 parents abfb9e4 + 7069fc2 commit acf93d0
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 44 deletions.
17 changes: 17 additions & 0 deletions src/frontend/src/api/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable import/prefer-default-export */
import { getTaskWaypoint } from '@Services/tasks';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';

export const useGetTaskWaypointQuery = (
projectId: string,
taskId: string,
queryOptions?: Partial<UseQueryOptions>,
) => {
return useQuery({
queryKey: ['task-description'],
enabled: !!(projectId && taskId),
queryFn: () => getTaskWaypoint(projectId, taskId),
select: (res: any) => res.data,
...queryOptions,
});
};
Binary file added src/frontend/src/assets/images/rightArrow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ const TaskLogsTable = ({ data: taskList }: ITaskLogsTableProps) => {
<div
className="naxatw-flex naxatw-h-8 naxatw-w-8 naxatw-cursor-pointer naxatw-items-center naxatw-justify-center naxatw-rounded-lg hover:naxatw-bg-gray-200"
role="presentation"
onClick={() => navigate(`/tasks/${task.task_id}`)}
onClick={() =>
navigate(
`/projects/${task.project_id}/tasks/${task.task_id}`,
)
}
>
<i className="material-icons-outlined">zoom_in</i>
</div>
Expand Down
156 changes: 153 additions & 3 deletions src/frontend/src/components/DroneOperatorTask/MapSection/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,159 @@
const DroneOperatorMapBox = () => {
/* eslint-disable react/no-array-index-key */
import { useEffect } from 'react';
import { LngLatBoundsLike, Map } from 'maplibre-gl';
import { useParams } from 'react-router-dom';
import { FeatureCollection } from 'geojson';
import { useGetTaskWaypointQuery } from '@Api/tasks';
import getBbox from '@turf/bbox';
import { useMapLibreGLMap } from '@Components/common/MapLibreComponents';
import BaseLayerSwitcher from '@Components/common/MapLibreComponents/BaseLayerSwitcher';
import VectorLayer from '@Components/common/MapLibreComponents/Layers/VectorLayer';
import MapContainer from '@Components/common/MapLibreComponents/MapContainer';
import { GeojsonType } from '@Components/common/MapLibreComponents/types';
import right from '@Assets/images/rightArrow.png';

const MapSection = () => {
const { projectId, taskId } = useParams();
const { map, isMapLoaded } = useMapLibreGLMap({
containerId: 'dashboard-map',
mapOptions: {
zoom: 5,
center: [84.124, 28.3949],
maxZoom: 19,
},
disableRotation: true,
});

const { data: taskWayPoints }: any = useGetTaskWaypointQuery(
projectId as string,
taskId as string,
{
select: (data: any) => {
// refine data and return geojson points and single line string
const refinedData = data?.data?.reduce(
(acc: any, curr: any) => {
return {
...acc,
geojsonListOfPoint: [
...acc.geojsonListOfPoint,
{
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {
angle: curr?.angle,
gimbal_angle: curr.gimbal_angle,
},
geometry: {
type: 'Point',
coordinates: curr?.coordinates,
},
},
],
},
],
combinedCoordinates: [
...acc.combinedCoordinates,
curr?.coordinates,
],
};
},
{
combinedCoordinates: [],
geojsonListOfPoint: [],
},
);
const { geojsonListOfPoint, combinedCoordinates } = refinedData;
return {
geojsonListOfPoint,
geojsonAsLineString: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: combinedCoordinates,
},
},
],
},
};
},
},
);

// zoom to task
useEffect(() => {
if (!taskWayPoints?.geojsonAsLineString) return;
const { geojsonAsLineString } = taskWayPoints;
const bbox = getBbox(geojsonAsLineString as FeatureCollection);
map?.fitBounds(bbox as LngLatBoundsLike, { padding: 25 });
}, [map, taskWayPoints]);

return (
<>
<div className="naxatw-h-[70vh] naxatw-w-full naxatw-rounded-xl naxatw-bg-gray-200" />
<div className="naxatw-h-[70vh] naxatw-w-full naxatw-rounded-xl naxatw-bg-gray-200">
<MapContainer
map={map}
isMapLoaded={isMapLoaded}
containerId="dashboard-map"
style={{
width: '100%',
height: '100%',
}}
>
{taskWayPoints && (
<>
<VectorLayer
map={map as Map}
isMapLoaded={isMapLoaded}
id="waypoint-line"
geojson={taskWayPoints?.geojsonAsLineString as GeojsonType}
visibleOnMap={!!taskWayPoints}
layerOptions={{
type: 'line',
paint: {
'line-color': '#000000',
'line-width': 1,
'line-dasharray': [6, 3],
},
}}
hasImage
image={right}
symbolPlacement="line"
/>
{taskWayPoints?.geojsonListOfPoint?.map(
(point: any, index: number) => {
return (
<VectorLayer
key={`waypoint-points-vtLayer-${index}`}
map={map as Map}
isMapLoaded={isMapLoaded}
id={`waypoint-directions-${index}`}
geojson={point as GeojsonType}
visibleOnMap={!!taskWayPoints}
layerOptions={{
type: 'circle',
paint: {
'circle-color': index === 0 ? 'red' : '#176149',
'circle-opacity': 0.8,
'circle-radius': index === 0 ? 9 : 6,
},
}}
/>
);
},
)}
</>
)}
<BaseLayerSwitcher />
</MapContainer>
</div>
</>
);
};

export default DroneOperatorMapBox;
export default MapSection;
21 changes: 0 additions & 21 deletions src/frontend/src/components/DroneOperatorTask/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { FeatureCollection } from 'geojson';
import { LngLatBoundsLike, Map } from 'maplibre-gl';
import { setProjectState } from '@Store/actions/project';
import { useGetTaskStatesQuery } from '@Api/projects';
import DTMLogo from '@Assets/images/lock.png';
import lock from '@Assets/images/lock.png';
import { postTaskStatus } from '@Services/project';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'react-toastify';
Expand Down Expand Up @@ -178,7 +178,7 @@ export default function MapSection() {
hasImage={
taskStatusObj?.[`${task?.id}`] === 'LOCKED_FOR_MAPPING' || false
}
image={DTMLogo}
image={lock}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* eslint-disable no-param-reassign */
/* eslint-disable consistent-return */
/* eslint-disable no-unused-expressions */
import { useEffect } from 'react';

export default function VectorLayerWithCluster({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default function VectorLayer({
visibleOnMap = true,
hasImage = false,
image,
symbolPlacement = 'point',
}: IVectorLayer) {
const sourceId = useMemo(() => id.toString(), [id]);
const hasInteractions = useRef(false);
Expand Down Expand Up @@ -58,6 +59,7 @@ export default function VectorLayer({
type: 'symbol',
source: sourceId,
layout: {
'symbol-placement': symbolPlacement,
'icon-image': imageId,
'icon-size': 1,
'icon-overlap': 'always',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface IVectorLayer extends ILayer {
onFeatureSelect?: (properties: Record<string, any>) => void;
hasImage?: boolean;
image?: any;
symbolPlacement?: 'point' | 'line' | 'line-center';
}

type InteractionsType = 'hover' | 'select';
Expand Down
8 changes: 4 additions & 4 deletions src/frontend/src/routes/appRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import GoogleAuth from '@Components/GoogleAuth';
import userRoutes from '@UserModule/routes';
import LandingPage from '@Views/LandingPage';
import IndividualProject from '@Views/IndividualProject';
import DroneOperatorTaskPage from '@Views/DroneOperatorTask';
import TaskDescription from '@Views/TaskDescription';
import { IRoute } from './types';

const appRoutes: IRoute[] = [
Expand Down Expand Up @@ -54,9 +54,9 @@ const appRoutes: IRoute[] = [
authenticated: true,
},
{
path: 'dashboard/drone-operator-task',
name: 'Drone Operator Task',
component: DroneOperatorTaskPage,
path: 'projects/:projectId/tasks/:taskId',
name: 'Task description',
component: TaskDescription,
authenticated: true,
},
];
Expand Down
7 changes: 7 additions & 0 deletions src/frontend/src/services/tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable import/prefer-default-export */
import { api, authenticated } from '.';

export const getTaskWaypoint = (projectId: string, taskId: string) =>
authenticated(api).get(
`/waypoint/task/${taskId}/?project_id=${projectId}&download=false`,
);
13 changes: 0 additions & 13 deletions src/frontend/src/views/DroneOperatorTask/index.tsx

This file was deleted.

23 changes: 23 additions & 0 deletions src/frontend/src/views/TaskDescription/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import DroneOperatorDescriptionBox from '@Components/DroneOperatorTask/DescriptionSection';
import DroneOperatorTaskHeader from '@Components/DroneOperatorTask/Header';
import MapSection from '@Components/DroneOperatorTask/MapSection';

const TaskDescription = () => {
return (
<>
<div className="naxatw-min-h-[calc(100vh-99px)]">
<div className="naxatw-mx-auto naxatw-w-11/12 naxatw-max-w-[90rem] naxatw-px-8 naxatw-pb-[2.9375rem] naxatw-pt-3">
<div className="naxatw-flex naxatw-flex-col naxatw-gap-3">
<DroneOperatorTaskHeader />
<div className="naxatw-grid naxatw-grid-cols-[30%_70%] naxatw-gap-5">
<DroneOperatorDescriptionBox />
<MapSection />
</div>
</div>
</div>
</div>
</>
);
};

export default TaskDescription;

0 comments on commit acf93d0

Please sign in to comment.