Skip to content

Commit 07453a3

Browse files
Merge pull request #28 from varun2948/feat-osm-download
Feat osm download
2 parents 6e5809c + 71f1184 commit 07453a3

File tree

8 files changed

+225
-0
lines changed

8 files changed

+225
-0
lines changed

example.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,8 @@ TM_DEFAULT_LOCALE=en
204204
# Sentry.io DSN Config (optional)
205205
# TM_SENTRY_BACKEND_DSN=https://foo.ingest.sentry.io/1234567
206206
# TM_SENTRY_FRONTEND_DSN=https://bar.ingest.sentry.io/8901234
207+
208+
209+
EXPORT TOOL Integration with 0(Disable) and 1(Enable) and S3 URL for Export Tool
210+
#EXPORT_TOOL_S3_URL=https://foorawdataapi.s3.amazonaws.com
211+
#ENABLE_EXPORT_TOOL=0

frontend/.env.expand

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,4 @@ REACT_APP_SENTRY_FRONTEND_DSN=$TM_SENTRY_FRONTEND_DSN
4343
REACT_APP_ENVIRONMENT=$TM_ENVIRONMENT
4444
REACT_APP_TM_DEFAULT_CHANGESET_COMMENT=$TM_DEFAULT_CHANGESET_COMMENT
4545
REACT_APP_RAPID_EDITOR_URL=$RAPID_EDITOR_URL
46+
REACT_APP_EXPORT_TOOL_S3_URL=$EXPORT_TOOL_S3_URL
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React, { useState } from 'react';
2+
import { RoadIcon, HomeIcon, WavesIcon, TaskIcon, AsteriskIcon } from '../svgIcons';
3+
import FileFormatCard from './fileFormatCard';
4+
import Popup from 'reactjs-popup';
5+
import { EXPORT_TOOL_S3_URL } from '../../config';
6+
7+
export const TITLED_ICONS = [
8+
{ Icon: RoadIcon, title: 'roads', value: 'ROADS' },
9+
{ Icon: HomeIcon, title: 'buildings', value: 'BUILDINGS' },
10+
{ Icon: WavesIcon, title: 'waterways', value: 'WATERWAYS' },
11+
{ Icon: TaskIcon, title: 'landUse', value: 'LAND_USE' },
12+
{ Icon: AsteriskIcon, title: 'other', value: 'OTHER' },
13+
];
14+
15+
const fileFormats = [{ format: 'SHP' }, { format: 'GEOJSON' }, { format: 'KML' }];
16+
17+
/**
18+
* Renders a list of download options for OSM data based on the project mapping types.
19+
*
20+
* @param {Array<string>} projectMappingTypes - The mapping types of the project.
21+
* @return {JSX.Element} - The JSX element containing the download options.
22+
*/
23+
24+
export const DownloadOsmData = ({ projectMappingTypes, project }) => {
25+
const [showPopup, setShowPopup] = useState(false);
26+
const [isDownloadingState, setIsDownloadingState] = useState(null);
27+
28+
/**
29+
* Downloads an S3 file from the given URL and saves it as a file.
30+
*
31+
* @param {string} title - The title of the file.
32+
* @param {string} fileFormat - The format of the file.
33+
* @return {Promise<void>} Promise that resolves when the download is complete.
34+
*/
35+
const downloadS3File = async (title, fileFormat) => {
36+
// Create the base URL for the S3 file
37+
const baseUrl = `${EXPORT_TOOL_S3_URL}/TM/${project.projectId}/hotosm_project_${
38+
project.projectId
39+
}_${title}_${fileFormat?.toLowerCase()}.zip`;
40+
41+
// Set the state to indicate that the file download is in progress
42+
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: true });
43+
44+
try {
45+
// Fetch the file from the S3 URL
46+
const response = await fetch(baseUrl);
47+
48+
// Check if the request was successful
49+
if (response.ok) {
50+
// Set the state to indicate that the file download is complete
51+
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: false });
52+
53+
// Get the file data as a blob
54+
const blob = await response.blob();
55+
56+
// Create a download link for the file
57+
const href = window.URL.createObjectURL(blob);
58+
const link = document.createElement('a');
59+
link.href = href;
60+
link.setAttribute(
61+
'download',
62+
`hotosm_project_${project.projectId}_${title}_${fileFormat?.toLowerCase()}.zip`,
63+
);
64+
65+
// Add the link to the document body, click it, and then remove it
66+
document.body.appendChild(link);
67+
link.click();
68+
document.body.removeChild(link);
69+
} else {
70+
// Show a popup and throw an error if the request was not successful
71+
setShowPopup(true);
72+
throw new Error(`Request failed with status: ${response.status}`);
73+
}
74+
} catch (error) {
75+
// Show a popup and log the error if an error occurs during the download
76+
setShowPopup(true);
77+
setIsDownloadingState({ title: title, fileFormat: fileFormat, isDownloading: false });
78+
console.error('Error:', error.message);
79+
}
80+
};
81+
const filteredMappingTypes = TITLED_ICONS?.filter((icon) =>
82+
projectMappingTypes?.includes(icon.value),
83+
);
84+
85+
return (
86+
<div className="mb5 w-100 pa5 ph flex flex-wrap">
87+
<Popup modal open={showPopup} closeOnDocumentClick nested onClose={() => setShowPopup(false)}>
88+
{(close) => (
89+
<div class="blue-dark bg-white pv2 pv4-ns ph2 ph4-ns">
90+
<h3 class="barlow-condensed f3 fw6 mv0">Data Not Available.</h3>
91+
<div class="w-100 pt3 flex justify-end">
92+
<button
93+
aria-pressed="false"
94+
focusindex="0"
95+
class="mr2 bg-red white br1 f5 bn pointer"
96+
style={{ padding: '0.75rem 1.5rem' }}
97+
onClick={() => {
98+
setShowPopup(false);
99+
close();
100+
}}
101+
>
102+
Close
103+
</button>
104+
</div>
105+
</div>
106+
)}
107+
</Popup>
108+
{filteredMappingTypes.map((type) => (
109+
<div
110+
className="osm-card bg-white pa3 mr4 mt4 w-auto-m flex flex-wrap items-center "
111+
style={{
112+
width: '560px',
113+
gap: '16px',
114+
}}
115+
key={type.title}
116+
>
117+
<div
118+
style={{
119+
justifyContent: 'center',
120+
display: 'flex',
121+
alignItems: 'center',
122+
}}
123+
>
124+
<type.Icon
125+
title={type.title}
126+
color="#D73F3F"
127+
className="br1 h2 w2 pa1 ma1 ba b--white bw1 dib h-65 w-65"
128+
style={{ height: '56px' }}
129+
/>
130+
</div>
131+
132+
<div
133+
className="file-list flex barlow-condensed f3"
134+
style={{ display: 'flex', gap: '12px' }}
135+
>
136+
<p className="fw5 ttc">{type.title}</p>
137+
<FileFormatCard
138+
title={type.title}
139+
fileFormats={fileFormats}
140+
downloadS3Data={downloadS3File}
141+
isDownloadingState={isDownloadingState}
142+
/>
143+
</div>
144+
</div>
145+
))}
146+
</div>
147+
);
148+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React from 'react';
2+
import { AnimatedLoadingIcon } from '../button';
3+
4+
/**
5+
* Renders a list of file formats as clickable links.
6+
*
7+
* @param {Object[]} fileFormats - An array of file format objects.
8+
* @param {string} fileFormats[].format - The format of the file.
9+
* @return {JSX.Element} The rendered list of file formats.
10+
*/
11+
12+
function FileFormatCard({ title, fileFormats, downloadS3Data, isDownloadingState }) {
13+
return (
14+
<>
15+
{fileFormats.map((fileFormat, index) => (
16+
<React.Fragment key={index}>
17+
<div
18+
tabIndex={0}
19+
style={{ cursor: 'pointer' }}
20+
role="button"
21+
onClick={() => downloadS3Data(title, fileFormat.format)}
22+
className="link hover-red color-inherit"
23+
>
24+
<p className="underline fw5" style={{ textUnderlineOffset: '5px' }}>
25+
{fileFormat.format}
26+
{isDownloadingState?.isDownloading &&
27+
isDownloadingState?.title === title &&
28+
isDownloadingState?.fileFormat === fileFormat?.format ? (
29+
<AnimatedLoadingIcon />
30+
) : null}
31+
</p>
32+
</div>
33+
{index !== fileFormats.length - 1 && <hr className="file-list-separator" />}
34+
</React.Fragment>
35+
))}
36+
</>
37+
);
38+
}
39+
40+
export default FileFormatCard;

frontend/src/components/projectDetail/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { Alert } from '../alert';
2626

2727
import './styles.scss';
2828
import { useWindowSize } from '../../hooks/UseWindowSize';
29+
import { DownloadOsmData } from './downloadOsmData.js';
2930

3031
/* lazy imports must be last import */
3132
const ProjectTimeline = React.lazy(() => import('./timeline' /* webpackChunkName: "timeline" */));
@@ -285,6 +286,24 @@ export const ProjectDetail = (props) => {
285286
/>
286287
)}
287288
</div>
289+
290+
{/* Download OSM Data section Start */}
291+
292+
<div className="bg-tan-dim">
293+
<a href="#downloadOsmData" name="downloadOsmData" style={{ visibility: 'hidden' }}>
294+
<FormattedMessage {...messages.downloadOsmData} />
295+
</a>
296+
<h3 className={`${h2Classes}`}>
297+
<FormattedMessage {...messages.downloadOsmData} />
298+
</h3>
299+
<DownloadOsmData
300+
projectMappingTypes={props?.project?.mappingTypes}
301+
project={props.project}
302+
/>
303+
</div>
304+
305+
{/* Download OSM Data section End */}
306+
288307
<a href="#contributionTimeline" style={{ visibility: 'hidden' }} name="contributionTimeline">
289308
<FormattedMessage {...messages.contributionsTimeline} />
290309
</a>

frontend/src/components/projectDetail/messages.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ export default defineMessages({
223223
id: 'project.detail.sections.contributionsTimeline',
224224
defaultMessage: 'Contributions timeline',
225225
},
226+
downloadOsmData: {
227+
id: 'project.detail.sections.downloadOsmData',
228+
defaultMessage: 'Download OSM Data',
229+
},
226230
viewInOsmcha: {
227231
id: 'project.detail.sections.contributions.osmcha',
228232
defaultMessage: 'Changesets in OSMCha',

frontend/src/components/projectDetail/styles.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,10 @@
2626
.menu-items-container {
2727
scrollbar-width: none;
2828
}
29+
30+
.file-list-separator {
31+
top: 13px;
32+
height: 27px;
33+
position: relative;
34+
border: 1px solid #d73f3f;
35+
}

frontend/src/config/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const POTLATCH2_EDITOR_URL =
5959
'https://www.openstreetmap.org/edit?editor=potlatch2';
6060
export const RAPID_EDITOR_URL =
6161
process.env.REACT_APP_RAPID_EDITOR_URL || 'https://mapwith.ai/rapid';
62+
export const EXPORT_TOOL_S3_URL = process.env.REACT_APP_EXPORT_TOOL_S3_URL || '';
6263

6364
export const TASK_COLOURS = {
6465
READY: '#fff',

0 commit comments

Comments
 (0)