Skip to content

Commit

Permalink
fix: editable vector layer in ol for project creation (#1102)
Browse files Browse the repository at this point in the history
* fix: backend import error fix

* fix (vectorLayer): style - conditionaly apply style on onModify present

* fix (splitTasks): map - edit added to splitted taskLayer

* fix (splitTasks): onModify - edited geojson set to dividedTaskGeojson state

* feat (createNewProject): only enable generate task btn if fgb file fetch is completed

* fix (createNewProject): splitTasks - logic fix

* fix (createNewProject): splitTasks - clear dividedTaskGeojson, splitTasksSelection, and dataExtractGeojson state on previous click

* feat (createNewProject): splitTasks - show loader and message until FGB file is fetching

* fix (createNewProject): taskSplit - display error on taskSplit fail

* fix vectorLayer: on modifyEnd return area of boundary as well

* fix button: loading text added to the button

* fix NewDefineAreaMap: removed data extraction in progress message from mapComponent

* fix (createNewProject): splitTasks - clearing state on step toggle remove

* fix (createNewProject): uploadArea - clear step4 & step5 step on AOI edit

* fix (createNewProject): dataExtract - generateTaskBTN added, disable next until taskGeneration success, state logic changed to track extractWays & featureType state validation

* fix (createNewProject): dataExtract - clear file state on reset click or if generateDataExtract click

* fix (createNewProject): customLine, customPolygon file state clear on AOI edit

* fix (createNewProject): dataExtract - clear previous extractGeojson, customLine, customPolygon on generate extract, btn disable state update
  • Loading branch information
NSUWAL123 authored Jan 17, 2024
1 parent da5c95f commit c03f908
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 51 deletions.
8 changes: 8 additions & 0 deletions src/frontend/src/api/CreateProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,14 @@ const TaskSplittingPreviewService: Function = (
dispatch(CreateProjectActions.SetIsTasksGenerated({ key: 'task_splitting_algorithm', value: true }));
dispatch(CreateProjectActions.GetTaskSplittingPreview(resp));
} catch (error) {
dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'Task generation failed. Please try again',
variant: 'error',
duration: 2000,
}),
);
dispatch(CreateProjectActions.GetTaskSplittingPreviewLoading(false));
} finally {
dispatch(CreateProjectActions.GetTaskSplittingPreviewLoading(false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ const VectorLayer = ({
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857',
});
const geometry = vectorLayer.getSource().getFeatures()?.[0].getGeometry();
const area = formatArea(geometry);

onModify(geoJSONString);
onModify(geoJSONString, area);
});
map.addInteraction(modify);
map.addInteraction(select);
Expand Down Expand Up @@ -191,22 +193,26 @@ const VectorLayer = ({

useEffect(() => {
if (!vectorLayer || !style.visibleOnMap || setStyle) return;
vectorLayer.setStyle((feature, resolution) => [
new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({
color: 'orange',
}),
}),
geometry: function (feature) {
// return the coordinates of the first ring of the polygon
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
}),
getStyles({ style, feature, resolution }),
]);
vectorLayer.setStyle((feature, resolution) => {
return onModify
? [
new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({
color: 'orange',
}),
}),
geometry: function (feature) {
// return the coordinates of the first ring of the polygon
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
}),
getStyles({ style, feature, resolution }),
]
: [getStyles({ style, feature, resolution })];
});
}, [vectorLayer, style, setStyle]);

useEffect(() => {
Expand Down Expand Up @@ -254,7 +260,6 @@ const VectorLayer = ({
});
function pointerMovefn(event) {
vectorLayer.getFeatures(event.pixel).then((features) => {
console.log(selection, 'selection');
if (!features.length) {
selection = {};
hoverEffect(undefined, vectorLayer);
Expand Down
19 changes: 16 additions & 3 deletions src/frontend/src/components/common/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface IButton {
icon?: React.ReactNode;
isLoading?: boolean;
disabled?: boolean;
loadingText?: string;
}

const btnStyle = (btnType, className) => {
Expand All @@ -24,13 +25,25 @@ const btnStyle = (btnType, className) => {
case 'other':
return `fmtm-py-1 fmtm-px-5 fmtm-bg-red-500 fmtm-text-white fmtm-rounded-lg hover:fmtm-bg-red-600`;
case 'disabled':
return `fmtm-py-1 fmtm-px-4 fmtm-text-white fmtm-rounded-lg fmtm-bg-gray-400 fmtm-cursor-not-allowed`;
return `fmtm-py-1 fmtm-px-4 fmtm-text-white fmtm-rounded-lg fmtm-bg-gray-400 fmtm-cursor-not-allowed ${className}`;

default:
return 'fmtm-primary';
}
};
const Button = ({ btnText, btnType, type, onClick, disabled, className, count, dataTip, icon, isLoading }: IButton) => (
const Button = ({
btnText,
btnType,
type,
onClick,
disabled,
className,
count,
dataTip,
icon,
isLoading,
loadingText,
}: IButton) => (
<div className="fmtm-w-fit">
<button
type={type === 'submit' ? 'submit' : 'button'}
Expand All @@ -44,7 +57,7 @@ const Button = ({ btnText, btnType, type, onClick, disabled, className, count, d
>
{isLoading ? (
<>
{type === 'submit' ? 'Submitting...' : 'Loading...'}
{type === 'submit' ? 'Submitting...' : loadingText ? loadingText : 'Loading...'}
<Loader2 className="fmtm-mr-2 fmtm-h-6 fmtm-w-6 fmtm-animate-spin" />
</>
) : (
Expand Down
153 changes: 131 additions & 22 deletions src/frontend/src/components/createnewproject/DataExtract.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios';
import { geojson as fgbGeojson } from 'flatgeobuf';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import Button from '../../components/common/Button';
import { useDispatch } from 'react-redux';
import { CommonActions } from '../../store/slices/CommonSlice';
Expand All @@ -25,27 +25,66 @@ const osmFeatureTypeOptions = [
{ name: 'osm_feature_type', value: 'polygon', label: 'Polygon' },
];

enum FeatureTypeName {
point_centroid = 'Point/Centroid',
line = 'Line',
polygon = 'Polygon',
}

const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygonUpload, setCustomPolygonUpload }) => {
const dispatch = useDispatch();
const navigate = useNavigate();

const [extractWays, setExtractWays] = useState('');
const [featureType, setFeatureType] = useState('');
const projectDetails: any = useAppSelector((state) => state.createproject.projectDetails);
const drawnGeojson = useAppSelector((state) => state.createproject.drawnGeojson);
const dataExtractGeojson = useAppSelector((state) => state.createproject.dataExtractGeojson);
const isFgbFetching = useAppSelector((state) => state.createproject.isFgbFetching);

const submission = () => {
if (featureType !== formValues?.dataExtractFeatureType && formValues.dataExtractWays === 'osm_data_extract') {
dispatch(
CommonActions.SetSnackBar({
open: true,
message: `Please generate data extract for ${FeatureTypeName[featureType]}`,
variant: 'warning',
duration: 2000,
}),
);
return;
}

const submission = async () => {
dispatch(CreateProjectActions.SetIndividualProjectDetailsData(formValues));
dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: 5 }));

// First go to next page, to not block UX
navigate('/split-tasks');
};

const resetFile = (setDataExtractToState) => {
setDataExtractToState(null);
};

const {
handleSubmit,
handleCustomChange,
values: formValues,
errors,
}: any = useForm(projectDetails, submission, DataExtractValidation);

// Generate OSM data extract
const generateDataExtract = async () => {
// Get OSM data extract if required
if (formValues.dataExtractWays === 'osm_data_extract') {
if (extractWays === 'osm_data_extract') {
// Remove current data extract
dispatch(CreateProjectActions.setDataExtractGeojson(null));

// Create a file object from the project area Blob
const projectAreaBlob = new Blob([JSON.stringify(drawnGeojson)], { type: 'application/json' });
const drawnGeojsonFile = new File([projectAreaBlob], 'outline.json', { type: 'application/json' });

dispatch(CreateProjectActions.SetFgbFetchingStatus(true));
// Create form and POST endpoint
const dataExtractRequestFormData = new FormData();
dataExtractRequestFormData.append('geojson_file', drawnGeojsonFile);
Expand All @@ -56,9 +95,16 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
);

const fgbUrl = response.data.url;
// Append url to project data
// Append url to project data & remove custom files
dispatch(
CreateProjectActions.SetIndividualProjectDetailsData({ ...projectDetails, data_extract_type: fgbUrl }),
CreateProjectActions.SetIndividualProjectDetailsData({
...formValues,
data_extract_type: fgbUrl,
dataExtractWays: extractWays,
dataExtractFeatureType: featureType,
customLineUpload: null,
customPolygonUpload: null,
}),
);

// Extract fgb and set geojson to map
Expand All @@ -67,21 +113,43 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
const uint8ArrayData = new Uint8Array(binaryData);
// Deserialize the binary data
const geojsonExtract = await fgbGeojson.deserialize(uint8ArrayData);
dispatch(CreateProjectActions.SetFgbFetchingStatus(false));
await dispatch(CreateProjectActions.setDataExtractGeojson(geojsonExtract));
} catch (error) {
dispatch(
CommonActions.SetSnackBar({
open: true,
message: 'Error to generate FGB file.',
variant: 'error',
duration: 2000,
}),
);
dispatch(CreateProjectActions.SetFgbFetchingStatus(false));
// TODO add error message for user
console.error('Error getting data extract:', error);
}
}
};
const {
handleSubmit,
handleCustomChange,
values: formValues,
errors,
}: any = useForm(projectDetails, submission, DataExtractValidation);

useEffect(() => {
if (formValues?.dataExtractWays) {
setExtractWays(formValues?.dataExtractWays);
}
if (formValues?.dataExtractFeatureType) {
setFeatureType(formValues?.dataExtractFeatureType);
}
}, [formValues?.dataExtractWays, formValues?.dataExtractFeatureType]);

const toggleStep = (step, url) => {
if (url === '/select-form') {
dispatch(
CreateProjectActions.SetIndividualProjectDetailsData({
...formValues,
dataExtractWays: extractWays,
dataExtractFeatureType: featureType,
}),
);
}
dispatch(CommonActions.SetCurrentStepFormStep({ flag: flag, step: step }));
navigate(url);
};
Expand Down Expand Up @@ -137,10 +205,6 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
await dispatch(CreateProjectActions.setDataExtractGeojson(extractFeatCol));
};

const resetFile = (setDataExtractToState) => {
setDataExtractToState(null);
};

useEffect(() => {
dispatch(FormCategoryService(`${import.meta.env.VITE_API_URL}/central/list-forms`));
}, []);
Expand Down Expand Up @@ -179,31 +243,59 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
value={formValues.dataExtractWays}
onChangeData={(value) => {
handleCustomChange('dataExtractWays', value);
setExtractWays(value);
}}
errorMsg={errors.dataExtractWays}
/>
{formValues.dataExtractWays === 'osm_data_extract' && (
{extractWays === 'osm_data_extract' && (
<div className="fmtm-mt-6">
<RadioButton
topic="Select OSM feature type"
options={osmFeatureTypeOptions}
direction="column"
value={formValues.dataExtractFeatureType}
value={featureType}
onChangeData={(value) => {
handleCustomChange('dataExtractFeatureType', value);
setFeatureType(value);
}}
errorMsg={errors.dataExtractFeatureType}
/>
</div>
)}
{formValues.dataExtractWays === 'custom_data_extract' && (
{extractWays === 'osm_data_extract' && featureType && (
<Button
btnText="Generate Data Extract"
btnType="primary"
onClick={() => {
resetFile(setCustomPolygonUpload);
resetFile(setCustomLineUpload);
generateDataExtract();
}}
className="fmtm-mt-6"
isLoading={isFgbFetching}
loadingText="Data extracting..."
disabled={
featureType === formValues?.dataExtractFeatureType &&
dataExtractGeojson &&
!customPolygonUpload &&
!customLineUpload
? true
: false
}
/>
)}
{extractWays === 'custom_data_extract' && (
<>
<FileInputComponent
onChange={(e) => {
changeFileHandler(e, setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', e.target.files[0]);
handleCustomChange('dataExtractFeatureType', '');
setFeatureType('');
}}
onResetFile={() => {
resetFile(setCustomPolygonUpload);
handleCustomChange('customPolygonUpload', null);
}}
onResetFile={() => resetFile(setCustomPolygonUpload)}
customFile={customPolygonUpload}
btnText="Upload Polygons"
accept=".geojson,.json,.fgb"
Expand All @@ -214,8 +306,12 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
onChange={(e) => {
changeFileHandler(e, setCustomLineUpload);
handleCustomChange('customLineUpload', e.target.files[0]);
handleCustomChange('dataExtractFeatureType', null);
}}
onResetFile={() => {
resetFile(setCustomLineUpload);
handleCustomChange('customLineUpload', null);
}}
onResetFile={() => resetFile(setCustomLineUpload)}
customFile={customLineUpload}
btnText="Upload Lines"
accept=".geojson,.json,.fgb"
Expand All @@ -233,7 +329,20 @@ const DataExtract = ({ flag, customLineUpload, setCustomLineUpload, customPolygo
onClick={() => toggleStep(3, '/select-form')}
className="fmtm-font-bold"
/>
<Button btnText="NEXT" btnType="primary" type="submit" className="fmtm-font-bold" />
<Button
btnText="NEXT"
btnType="primary"
type="submit"
className="fmtm-font-bold"
dataTip={`${!dataExtractGeojson ? 'Please Generate Data Extract First.' : ''}`}
disabled={
!dataExtractGeojson ||
(extractWays === 'osm_data_extract' && !formValues?.dataExtractFeatureType) ||
isFgbFetching
? true
: false
}
/>
</div>
</form>
<div className="fmtm-w-full lg:fmtm-w-[60%] fmtm-flex fmtm-flex-col fmtm-gap-6 fmtm-bg-gray-300 fmtm-h-[60vh] lg:fmtm-h-full">
Expand Down
Loading

0 comments on commit c03f908

Please sign in to comment.