Skip to content

Commit

Permalink
Merge pull request #6176 from varun2948/feat-osm-download
Browse files Browse the repository at this point in the history
Feat osm download
  • Loading branch information
kshitijrajsharma authored Jan 8, 2024
2 parents c0364c1 + 838eb5b commit 40446ab
Show file tree
Hide file tree
Showing 12 changed files with 447 additions and 2 deletions.
5 changes: 5 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -204,3 +204,8 @@ TM_DEFAULT_LOCALE=en
# Sentry.io DSN Config (optional)
# TM_SENTRY_BACKEND_DSN=https://foo.ingest.sentry.io/1234567
# TM_SENTRY_FRONTEND_DSN=https://bar.ingest.sentry.io/8901234


EXPORT TOOL Integration with 0(Disable) and 1(Enable) and S3 URL for Export Tool
#EXPORT_TOOL_S3_URL=https://foorawdataapi.s3.amazonaws.com
#ENABLE_EXPORT_TOOL=0
2 changes: 2 additions & 0 deletions frontend/.env.expand
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ REACT_APP_SENTRY_FRONTEND_DSN=$TM_SENTRY_FRONTEND_DSN
REACT_APP_ENVIRONMENT=$TM_ENVIRONMENT
REACT_APP_TM_DEFAULT_CHANGESET_COMMENT=$TM_DEFAULT_CHANGESET_COMMENT
REACT_APP_RAPID_EDITOR_URL=$RAPID_EDITOR_URL
REACT_APP_EXPORT_TOOL_S3_URL=$EXPORT_TOOL_S3_URL
REACT_APP_ENABLE_EXPORT_TOOL=$ENABLE_EXPORT_TOOL
14 changes: 14 additions & 0 deletions frontend/src/assets/styles/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,17 @@
@import 'learn';
@import 'notifications';
@import 'contributions';

.fade-in {
opacity: 0;
transition: opacity 0.5s ease-in;
}

.fade-in.active {
opacity: 1;
}

.categorycard:hover > svg *,
.categorycard:hover {
fill: #d73f3f;
}
231 changes: 231 additions & 0 deletions frontend/src/components/projectDetail/downloadOsmData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { RoadIcon, HomeIcon, WavesIcon, TaskIcon, DownloadIcon } from '../svgIcons';
import FileFormatCard from './fileFormatCard';
import Popup from 'reactjs-popup';
import { EXPORT_TOOL_S3_URL } from '../../config';
import messages from './messages';
import { FormattedMessage } from 'react-intl';

export const TITLED_ICONS = [
{
Icon: RoadIcon,
title: 'roads',
value: 'ROADS',
featuretype: ['lines'],
formats: ['geojson', 'shp', 'kml'],
},
{
Icon: HomeIcon,
title: 'buildings',
value: 'BUILDINGS',
featuretype: ['polygons'],
formats: ['geojson', 'shp', 'kml'],
},
{
Icon: WavesIcon,
title: 'waterways',
value: 'WATERWAYS',
featuretype: ['lines', 'polygons'],
formats: ['geojson', 'shp', 'kml'],
},
{
Icon: TaskIcon,
title: 'landuse',
value: 'LAND_USE',
featuretype: ['points', 'polygons'],
formats: ['geojson', 'shp', 'kml'],
},
];

/**
* Renders a list of download options for OSM data based on the project mapping types.
*
* @param {Array<string>} projectMappingTypes - The mapping types of the project.
* @return {JSX.Element} - The JSX element containing the download options.
*/

export const DownloadOsmData = ({ projectMappingTypes, project }) => {
const [showPopup, setShowPopup] = useState(false);
const [isDownloadingState, setIsDownloadingState] = useState(null);
const [selectedCategoryFormat, setSelectedCategoryFormat] = useState(null);

const datasetConfig = {
dataset_prefix: `hotosm_project_${project.projectId}`,
dataset_folder: 'TM',
dataset_title: `Tasking Manger Project ${project.projectId}`,
};
/**
* Downloads an S3 file from the given URL and saves it as a file.
*
* @param {string} title - The title of the file.
* @param {string} fileFormat - The format of the file.
* @param {string} feature_type - The feature type of the ffile.
* @return {Promise<void>} Promise that resolves when the download is complete.
*/
const downloadS3File = async (title, fileFormat, feature_type) => {
// Create the base URL for the S3 file
const baseUrl = `${EXPORT_TOOL_S3_URL}/${datasetConfig.dataset_folder}/${
datasetConfig.dataset_prefix
}/${title}/${feature_type}/${
datasetConfig.dataset_prefix
}_${title}_${feature_type}_${fileFormat.toLowerCase()}.zip`;

// Set the state to indicate that the file download is in progress
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: true });

try {
// Fetch the file from the S3 URL
const response = await fetch(baseUrl);

// Check if the request was successful
if (response.ok) {
// Get the file data as a blob
const blob = await response.blob();

// Create a download link for the file
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.setAttribute(
'download',
`hotosm_project_${
project.projectId
}_${title}_${feature_type}_${fileFormat?.toLowerCase()}.zip`,
);

// Add the link to the document body, click it, and then remove it
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Set the state to indicate that the file download is complete
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: false });
} else {
// Show a popup and throw an error if the request was not successful
setShowPopup(true);
throw new Error(`Request failed with status: ${response.status}`);
}
} catch (error) {
// Show a popup and log the error if an error occurs during the download
setShowPopup(true);
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: false });
console.error('Error:', error.message);
}
};
const filteredMappingTypes = TITLED_ICONS?.filter((icon) =>
projectMappingTypes?.includes(icon.value),
);
return (
<div className="mb5 w-100 pb5 ph4 flex flex-wrap">
<Popup modal open={showPopup} closeOnDocumentClick nested onClose={() => setShowPopup(false)}>
{(close) => (
<div className="blue-dark bg-white pv2 pv4-ns ph2 ph4-ns">
<h3 className="barlow-condensed f3 fw6 mv0">
<FormattedMessage {...messages.errorDownloadOsmData} />
</h3>
<p className="mt4">
<FormattedMessage {...messages.errorDownloadOsmDataDescription} />
</p>
<div className="w-100 pt3 flex justify-end">
<button
aria-pressed="false"
tabIndex={0}
className="mr2 bg-red white br1 f5 bn pointer"
style={{ padding: '0.75rem 1.5rem' }}
onClick={() => {
setShowPopup(false);
close();
}}
>
Close
</button>
</div>
</div>
)}
</Popup>
{filteredMappingTypes.map((type) => (
<div
className="osm-card bg-white pa3 mr4 mt4 w-auto-m flex flex-wrap items-center "
style={{
width: '560px',
gap: '16px',
}}
key={type.title}
>
<div
style={{
justifyContent: 'center',
display: 'flex',
alignItems: 'center',
}}
>
<type.Icon
title={type.title}
color="#D73F3F"
className="br1 h2 w2 pa1 ma1 ba b--white bw1 dib h-65 w-65"
style={{ height: '56px' }}
/>
</div>
<div className="flex-column">
<div
className="file-list flex barlow-condensed f3"
style={{ display: 'flex', gap: '12px' }}
>
<p className="fw5 ttc">{type.title}</p>
<FileFormatCard
title={type.title}
fileFormats={type.formats}
downloadS3Data={downloadS3File}
isDownloadingState={isDownloadingState}
selectedCategoryFormat={selectedCategoryFormat}
setSelectedCategoryFormat={setSelectedCategoryFormat}
/>
</div>
<div
className={`flex flex-row ${
selectedCategoryFormat && selectedCategoryFormat.title === type.title
? 'fade-in active'
: 'fade-in'
} `}
style={{ gap: '20px' }}
>
{selectedCategoryFormat &&
selectedCategoryFormat.title === type.title &&
type?.featuretype?.map((typ) => (
<span
key={`${typ}_${selectedCategoryFormat.title}`}
onClick={() =>
downloadS3File(
selectedCategoryFormat.title,
selectedCategoryFormat.format,
typ,
)
}
onKeyUp={() =>
downloadS3File(
selectedCategoryFormat.title,
selectedCategoryFormat.format,
typ,
)
}
className="flex flex-row items-center pointer link hover-red color-inherit categorycard"
style={{ gap: '10px' }}
>
<DownloadIcon style={{ height: '28px' }} color="#D73F3F" />
<p className="ttc">
{typ} {selectedCategoryFormat.format}
</p>
</span>
))}
</div>
</div>
</div>
))}
</div>
);
};

DownloadOsmData.propTypes = {
projectMappingTypes: PropTypes.arrayOf(PropTypes.string).isRequired,
project: PropTypes.objectOf(PropTypes.any).isRequired,
};
73 changes: 73 additions & 0 deletions frontend/src/components/projectDetail/fileFormatCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';
import { AnimatedLoadingIcon } from '../button';
import PropTypes from 'prop-types';

/**
* Renders a list of file formats as clickable links.
*
* @param {string} props.title - The title of the card.
* @param {Object[]} fileFormats - An array of file format objects.
* @param {Object} isDownloadingState - The downloading state object.
* @param {function} setSelectedCategoryFormat - The function to set the selected category format.
* @param {Object} selectedCategoryFormat - The selected category format object.
* @return {JSX.Element} The rendered list of file formats.
*/

function FileFormatCard({
title,
fileFormats,
isDownloadingState,
setSelectedCategoryFormat,
selectedCategoryFormat,
}) {
return (
<>
{fileFormats.map((fileFormat, index) => {
const loadingState =
isDownloadingState?.isDownloading &&
isDownloadingState?.title === title &&
isDownloadingState?.fileFormat === fileFormat;

return (
<React.Fragment key={fileFormat}>
<span
role="button"
tabIndex={0}
style={
loadingState
? { cursor: 'not-allowed', pointerEvents: 'none' }
: { cursor: 'pointer' }
}
onClick={() => setSelectedCategoryFormat({ title, format: fileFormat })}
onKeyUp={() => setSelectedCategoryFormat({ title, format: fileFormat })}
className={`link ${
selectedCategoryFormat === fileFormat && selectedCategoryFormat.title === title
? 'red'
: ''
} hover-red color-inherit`}
>
<p className="underline fw5 ttu " style={{ textUnderlineOffset: '5px' }}>
{fileFormat}
{loadingState ? <AnimatedLoadingIcon /> : null}
</p>
</span>
{index !== fileFormats.length - 1 && <hr className="file-list-separator" />}
</React.Fragment>
);
})}
</>
);
}

export default FileFormatCard;

FileFormatCard.propTypes = {
title: PropTypes.string,
fileFormats: PropTypes.arrayOf(PropTypes.object),
isDownloadingState: PropTypes.bool,
setSelectedCategoryFormat: PropTypes.func,
selectedCategoryFormat: PropTypes.objectOf({
title: PropTypes.string,
format: PropTypes.PropTypes.string,
}),
};
4 changes: 4 additions & 0 deletions frontend/src/components/projectDetail/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const menuItems = [
href: '#contributions',
label: <FormattedMessage {...messages.contributions} />,
},
{
href: '#downloadOsmData',
label: <FormattedMessage {...messages.downloadOsmData} />,
},
{
href: '#similarProjects',
label: <FormattedMessage {...messages.similarProjects} />,
Expand Down
Loading

0 comments on commit 40446ab

Please sign in to comment.