Skip to content

Commit

Permalink
Merge pull request #449 from hotosm/feat/download-rotated-flight-plan
Browse files Browse the repository at this point in the history
Feat/download rotated flight plan and project detail export
  • Loading branch information
nrjadkry authored Jan 27, 2025
2 parents 85e9901 + 3f44598 commit d74b3ad
Show file tree
Hide file tree
Showing 13 changed files with 470 additions and 127 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
interface IProgressBarProps {
heading?: string;
successCount?: number;
totalCount?: number;
}

const ProgressBar = ({
heading = 'Uploading',
successCount = 0,
totalCount = 100,
}: IProgressBarProps) => {
const fillWidth = (successCount / totalCount) * 100;
return (
<div className="naxatw-flex naxatw-flex-col naxatw-gap-2">
<div className="naxatw-flex naxatw-flex-col naxatw-items-start naxatw-self-stretch">
<p className="naxatw-flex naxatw-items-center naxatw-justify-start naxatw-gap-2 naxatw-text-[1.0625rem] naxatw-font-bold naxatw-leading-normal">
{heading}
</p>
</div>
<div className="naxatw naxatw-flex naxatw-flex-col naxatw-items-start naxatw-gap-1 naxatw-self-stretch">
<p className="naxatw-text-[0.875rem] naxatw-text-[#7A7676]">
{totalCount === successCount && totalCount !== 0 ? (
<></>
) : (
`${successCount} / ${totalCount} Completed`
)}
</p>
<div className="naxatw-h-[0.75rem] naxatw-w-full naxatw-rounded-3xl naxatw-bg-gray-300">
<div
className={`naxatw-h-[0.75rem] naxatw-animate-pulse naxatw-bg-[#D73F3F] ${fillWidth === 100 ? 'naxatw-rounded-3xl' : 'naxatw-rounded-l-3xl'} naxatw-transition-all`}
style={{ width: `${fillWidth}%` }}
/>
</div>
</div>
</div>
);
};

export default ProgressBar;
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import DescriptionBoxComponent from './DescriptionComponent';
import QuestionBox from '../QuestionBox';
import UploadsInformation from '../UploadsInformation';
import UploadsBox from '../UploadsBox';
import ProgressBar from './ProgessBar';

const DescriptionBox = () => {
const dispatch = useDispatch();
Expand All @@ -34,6 +35,9 @@ const DescriptionBox = () => {
const waypointMode = useTypedSelector(
state => state.droneOperatorTask.waypointMode,
);
const uploadProgress = useTypedSelector(
state => state.droneOperatorTask.uploadProgress,
);

const { data: taskWayPoints }: any = useGetTaskWaypointQuery(
projectId as string,
Expand Down Expand Up @@ -194,6 +198,11 @@ const DescriptionBox = () => {
}
};

const progressDetails = useMemo(
() => uploadProgress?.[taskId || ''],
[taskId, uploadProgress],
);

return (
<>
<div className="naxatw-flex naxatw-flex-col naxatw-gap-5">
Expand Down Expand Up @@ -261,72 +270,83 @@ const DescriptionBox = () => {
</Button>
</div>
)}
{taskAssetsInformation?.state === 'IMAGE_UPLOADED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="replay"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Start Processing
</Button>
</div>
)}
{taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="replay"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Re-run processing
</Button>
</div>
)}
{(taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' ||
// if the state is LOCKED_FOR_MAPPING and has a image count it means all selected images are not uploaded and the status updating api call is interrupted so need to give user to upload the remaining images
taskAssetsInformation?.state === 'LOCKED_FOR_MAPPING' ||
taskAssetsInformation?.state === 'IMAGE_UPLOADED') && (
<div className="naxatw-flex naxatw-flex-col naxatw-gap-1 naxatw-pb-4">
<Label>
<p className="naxatw-text-[0.875rem] naxatw-font-semibold naxatw-leading-normal naxatw-tracking-[0.0175rem] naxatw-text-[#D73F3F]">
Upload Images
</p>
</Label>
<SwitchTab
options={[
{
name: 'image-upload-for',
value: 'add',
label: 'Add to existing',
},
{
name: 'image-upload-for',
value: 'replace',
label: 'Replace existing',
},
]}
valueKey="value"
selectedValue={uploadedImageType}
activeClassName="naxatw-bg-red naxatw-text-white"
onChange={(selected: Record<string, any>) => {
dispatch(setUploadedImagesType(selected.value));
}}
/>
<p className="naxatw-px-1 naxatw-py-1 naxatw-text-xs">
Note:{' '}
{uploadedImageType === 'add'
? 'Uploaded images will be added with the existing images.'
: 'Uploaded images will be replaced with all the existing images and starts processing.'}
</p>
<UploadsBox label="" />
</div>

{progressDetails?.uploadedFiles ? (
<ProgressBar
heading="Uploading Images"
successCount={progressDetails?.uploadedFiles}
totalCount={progressDetails.totalFiles}
/>
) : (
<>
{taskAssetsInformation?.state === 'IMAGE_UPLOADED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="play_arrow"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Start Processing
</Button>
</div>
)}
{taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="replay"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Re-run processing
</Button>
</div>
)}
{(taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' ||
// if the state is LOCKED_FOR_MAPPING and has a image count it means all selected images are not uploaded and the status updating api call is interrupted so need to give user to upload the remaining images
taskAssetsInformation?.state === 'LOCKED_FOR_MAPPING' ||
taskAssetsInformation?.state === 'IMAGE_UPLOADED') && (
<div className="naxatw-flex naxatw-flex-col naxatw-gap-1 naxatw-pb-4">
<Label>
<p className="naxatw-text-[0.875rem] naxatw-font-semibold naxatw-leading-normal naxatw-tracking-[0.0175rem] naxatw-text-[#D73F3F]">
Upload Images
</p>
</Label>
<SwitchTab
options={[
{
name: 'image-upload-for',
value: 'add',
label: 'Add to existing',
},
{
name: 'image-upload-for',
value: 'replace',
label: 'Replace existing',
},
]}
valueKey="value"
selectedValue={uploadedImageType}
activeClassName="naxatw-bg-red naxatw-text-white"
onChange={(selected: Record<string, any>) => {
dispatch(setUploadedImagesType(selected.value));
}}
/>
<p className="naxatw-px-1 naxatw-py-1 naxatw-text-xs">
Note:{' '}
{uploadedImageType === 'add'
? 'Uploaded images will be added with the existing images.'
: 'Uploaded images will be replaced with all the existing images and starts processing.'}
</p>
<UploadsBox label="" />
</div>
)}
</>
)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Button } from '@Components/RadixComponents/Button';
import {
resetFilesExifData,
setFilesExifData,
setUploadProgress,
} from '@Store/actions/droneOperatorTask';
import { useTypedDispatch, useTypedSelector } from '@Store/hooks';
import convertExifDataToGeoJson from '@Utils/exifDataToGeoJson';
Expand Down Expand Up @@ -48,6 +49,9 @@ const ImageMapBox = () => {
const filesExifData = useTypedSelector(
state => state.droneOperatorTask.filesExifData,
);
const uploadProgress = useTypedSelector(
state => state.droneOperatorTask.uploadProgress,
);
const modalState = useTypedSelector(state => state.common.showModal);

useEffect(() => {
Expand Down Expand Up @@ -154,12 +158,27 @@ const ImageMapBox = () => {
uploadedFilesNumber.current += urlChunk.length;
const width = widthCalulator(uploadedFilesNumber.current, files.length);
setLoadingWidth(width);
// maintain progress state for individual task
dispatch(
setUploadProgress({
...uploadProgress,
[taskId]: {
totalFiles: files.length,
uploadedFiles: uploadedFilesNumber.current,
},
}),
);
}
updateStatus({
projectId,
taskId,
data: { event: 'image_upload', updated_at: new Date().toISOString() },
});

// clear progress state on success
const currentUploadProgress = { ...uploadProgress };
delete currentUploadProgress?.[taskId];
dispatch(setUploadProgress(currentUploadProgress));
},
onSuccess: () => {
resetFilesExifData();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const DroneOperatorDescriptionBox = () => {
const waypointMode = useTypedSelector(
state => state.droneOperatorTask.waypointMode,
);
const rotationAngle = useTypedSelector(
state => state.droneOperatorTask.rotationAngle,
);

const { data: taskDescription }: Record<string, any> =
useGetIndividualTaskQuery(taskId as string);
const rotatedFlightPlanData = useTypedSelector(
Expand All @@ -29,7 +33,7 @@ const DroneOperatorDescriptionBox = () => {

const downloadFlightPlanKmz = () => {
fetch(
`${BASE_URL}/waypoint/task/${taskId}/?project_id=${projectId}&download=true&mode=${waypointMode}`,
`${BASE_URL}/waypoint/task/${taskId}/?project_id=${projectId}&download=true&mode=${waypointMode}&rotation_angle=${rotationAngle}`,
{ method: 'POST' },
)
.then(response => {
Expand All @@ -42,7 +46,7 @@ const DroneOperatorDescriptionBox = () => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'flight_plan.kmz';
link.download = `flight_plan-${projectId}-${taskId}-${waypointMode}.kmz`;
document.body.appendChild(link);
link.click();
link.remove();
Expand All @@ -64,7 +68,7 @@ const DroneOperatorDescriptionBox = () => {
const url = window.URL.createObjectURL(fileBlob);
const link = document.createElement('a');
link.href = url;
link.download = `${waypointMode}.geojson`;
link.download = `flight_plan-${projectId}-${taskId}-${waypointMode}.geojson`;
document.body.appendChild(link);
link.click();
link.remove();
Expand All @@ -86,7 +90,7 @@ const DroneOperatorDescriptionBox = () => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'task_area.kml';
link.download = `task_area-${projectId}-${taskId}.kml`;
document.body.appendChild(link);
link.click();
link.remove();
Expand All @@ -113,7 +117,7 @@ const DroneOperatorDescriptionBox = () => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'task_area.geojson';
link.download = `task_area-${projectId}-${taskId}.geojson`;
document.body.appendChild(link);
link.click();
link.remove();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { GeojsonType } from '@Components/common/MapLibreComponents/types';
import BaseLayerSwitcherUI from '@Components/common/BaseLayerSwitcher';
import { waypointModeOptions } from '@Constants/taskDescription';
import {
setRotationAngle as setFinalRotationAngle,
setRotatedFlightPlan,
setSelectedTakeOffPoint,
setSelectedTakeOffPointOption,
Expand Down Expand Up @@ -61,7 +62,6 @@ const MapSection = ({ className }: { className?: string }) => {
const [dragging, setDragging] = useState(false);
const [isRotationEnabled, setIsRotationEnabled] = useState(false);
const [rotationAngle, setRotationAngle] = useState(0);
const [finalRotationAngle, setFinalRotationAngle] = useState(0);
const [initialWaypointData, setInitialWaypointData] = useState<Record<
string,
any
Expand All @@ -82,6 +82,9 @@ const MapSection = ({ className }: { className?: string }) => {
const rotatedFlightPlanData = useTypedSelector(
state => state.droneOperatorTask.rotatedFlightPlan,
);
const finalRotationAngle = useTypedSelector(
state => state.droneOperatorTask.rotationAngle,
);

const { map, isMapLoaded }: any = useMapLibreGLMap({
containerId: 'dashboard-map',
Expand Down Expand Up @@ -388,7 +391,7 @@ const MapSection = ({ className }: { className?: string }) => {
if (!dragging) {
setVisibilityOfLayers(mapLayerIDs, 'visible');
if (rotationAngle !== finalRotationAngle)
setFinalRotationAngle(rotationAngle);
dispatch(setFinalRotationAngle(rotationAngle));
return;
}
setVisibilityOfLayers(mapLayerIDs, 'none');
Expand Down Expand Up @@ -497,6 +500,7 @@ const MapSection = ({ className }: { className?: string }) => {
() => () => {
dispatch(setSelectedTakeOffPoint(null));
dispatch(setSelectedTakeOffPointOption('current_location'));
dispatch(setFinalRotationAngle(0));
dispatch(
setTaskAssetsInformation({
total_image_uploaded: 0,
Expand Down
Loading

0 comments on commit d74b3ad

Please sign in to comment.