Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Map: added Whisp Earth Map button #3563

Merged
merged 3 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/i18n/resources/en/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ Please check also the Spam/Junk mail folder.`,
By confirming, all changes will be lost.
Do you want to proceed?`,
local: 'Local',
loading: 'Loading...',
max: 'Maximum',
med: 'Median',
manage: 'Manage',
Expand Down Expand Up @@ -817,6 +818,8 @@ Merge cannot be performed.`,
},
selectedPeriod: 'Selected period',
whisp: 'Whisp',
whispEarthMap: 'Whisp Earth Map',
whispCsv: 'Whisp CSV',
},

samplingPolygonOptions: {
Expand Down
6 changes: 3 additions & 3 deletions webapp/components/buttons/ButtonMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import MenuItem from '@mui/material/MenuItem'
import { Button } from './Button'

export const ButtonMenu = (props) => {
const { className, closeMenuOnItemClick = true, menuClassName, items, ...otherProps } = props
const { className, closeMenuOnItemClick = true, menuClassName, items, variant = 'text', ...otherProps } = props

const [anchorEl, setAnchorEl] = useState(null)

Expand All @@ -33,10 +33,10 @@ export const ButtonMenu = (props) => {
return (
<>
<Button
{...otherProps}
className={classNames('button-menu__button', className)}
onClick={onButtonClick}
variant="text"
variant={variant}
{...otherProps}
>
{/* show small arrow down icon on the right */}
<span className="icon icon-ctrl button-menu__button-icon" />
Expand Down
4 changes: 1 addition & 3 deletions webapp/components/buttons/ButtonMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
padding: 0 0.4rem;

button {
display: block;
width: 100%;
text-align: left;
padding: 16px 24px;
justify-content: left;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Popup } from 'react-leaflet'
import PropTypes from 'prop-types'
import circleToPolygon from 'circle-to-polygon'
import L from 'leaflet'
import axios from 'axios'

import { Objects, PointFactory, DEFAULT_SRS } from '@openforis/arena-core'

Expand All @@ -19,11 +18,14 @@ import Markdown from '@webapp/components/markdown'
import { ButtonPrevious } from '@webapp/components/buttons/ButtonPrevious'
import { ButtonNext } from '@webapp/components/buttons/ButtonNext'

import { useSurvey, useSurveyPreferredLang, useSurveyInfo, useSurveyId } from '@webapp/store/survey'
import { useSurvey, useSurveyPreferredLang, useSurveyInfo } from '@webapp/store/survey'
import { useUserName } from '@webapp/store/user/hooks'
import { useI18n } from '@webapp/store/system'

import { useAltitude } from '../common/useAltitude'
import { WhispMenuButton } from './WhispMenuButton'

const getEarthMapUrl = (geojson) => `https://earthmap.org/?aoi=global&polygon=${JSON.stringify(geojson)}`

// Builds the path to an attribute like ANCESTOR_ENTITY_LABEL_0 [ANCESTOR_ENTITY_0_KEYS] -> ANCESTOR_ENTITY_LABEL_1 [ANCESTOR_ENTITY_1_KEYS] ...
// E.g. Cluster [123] -> Plot [4].
Expand All @@ -50,9 +52,19 @@ const buildPath = ({ survey, attributeDef, ancestorsKeys, lang }) => {
return pathParts.join(' -> ')
}

const getEarthMapUrl = (geojson) => `https://earthmap.org/?aoi=global&polygon=${JSON.stringify(geojson)}`
const generateContent = ({ i18n, recordOwnerName, point, path, altitude }) => {
const coordinateNumericFieldPrecision = point.srs === DEFAULT_SRS.code ? 6 : NaN
const xFormatted = NumberUtils.roundToPrecision(point.x, coordinateNumericFieldPrecision)
const yFormatted = NumberUtils.roundToPrecision(point.y, coordinateNumericFieldPrecision)

const getWhispDataDownloadUrl = (token) => `https://whisp.openforis.org/api/download-csv/${token}`
const content = `**${path}**
* **X**: ${xFormatted}
* **Y**: ${yFormatted}
* **SRS**: ${point.srs}
* **${i18n.t('mapView.altitude')}**: ${altitude}
* **${i18n.t('common.owner')}**: ${recordOwnerName ?? '...'}`
return content
}

export const CoordinateAttributePopUp = (props) => {
const { attributeDef, flyToNextPoint, flyToPreviousPoint, onRecordEditClick, pointFeature } = props
Expand All @@ -65,11 +77,9 @@ export const CoordinateAttributePopUp = (props) => {
const surveyInfo = useSurveyInfo()

const survey = useSurvey()
const surveyId = useSurveyId()
const lang = useSurveyPreferredLang()

const [open, setOpen] = useState(false)
const [whispDataLoading, setWhispDataLoading] = useState(false)

const pointLatLong = PointFactory.createInstance({ x: longitude, y: latitude })
// fetch altitude and record owner name only when popup is open
Expand All @@ -96,7 +106,7 @@ export const CoordinateAttributePopUp = (props) => {
[ancestorsKeys, attributeDef, lang, survey]
)

const getGeoJson = useCallback(() => {
const generateGeoJson = useCallback(() => {
if (Survey.isSampleBasedImageInterpretationEnabled(surveyInfo)) {
const isCircle = SamplingPolygon.getIsCircle(surveyInfo)
if (isCircle) {
Expand All @@ -113,32 +123,21 @@ export const CoordinateAttributePopUp = (props) => {
}
}, [latitude, longitude, surveyInfo])

const onEarthMapButtonClick = useCallback(() => {
const geojson = getGeoJson()
Objects.setInPath({ obj: geojson, path: ['properties', 'name'], value: path })
const earthMapUrl = getEarthMapUrl(geojson)
window.open(earthMapUrl, 'EarthMap')
}, [getGeoJson, path])

const onWhispButtonClick = useCallback(async () => {
setWhispDataLoading(true)
const geojson = getGeoJson()
const url = `/api/survey/${surveyId}/geo/whisp/geojson/csv`
axios.post(url, geojson).then(({ data: token }) => {
const csvDownloadUrl = getWhispDataDownloadUrl(token)
setWhispDataLoading(false)
window.open(csvDownloadUrl, 'Whisp')
})
}, [getGeoJson, surveyId])

const coordinateNumericFieldPrecision = point.srs === DEFAULT_SRS.code ? 6 : NaN
const generateGeoJsonWithName = useCallback(() => {
const geojson = generateGeoJson()
return Objects.setInPath({ obj: geojson, path: ['properties', 'name'], value: path })
}, [generateGeoJson, path])

const content = `**${path}**
* **X**: ${NumberUtils.roundToPrecision(point.x, coordinateNumericFieldPrecision)}
* **Y**: ${NumberUtils.roundToPrecision(point.y, coordinateNumericFieldPrecision)}
* **SRS**: ${point.srs}
* **${i18n.t('mapView.altitude')}**: ${altitude}
* **${i18n.t('common.owner')}**: ${recordOwnerName ?? '...'}`
const onEarthMapButtonClick = useCallback(() => {
const geojson = generateGeoJsonWithName()
const url = getEarthMapUrl(geojson)
window.open(url, 'EarthMap')
}, [generateGeoJsonWithName])

const content = useMemo(
() => generateContent({ i18n, recordOwnerName, point, path, altitude }),
[altitude, i18n, path, point, recordOwnerName]
)

return (
<Popup
Expand Down Expand Up @@ -174,18 +173,7 @@ export const CoordinateAttributePopUp = (props) => {
onClick={onEarthMapButtonClick}
variant="outlined"
/>
<Button
className="whisp-btn"
disabled={whispDataLoading}
label="mapView.whisp"
iconAlt="Whisp"
iconHeight={25}
iconSrc="/img/of_whisp_icon.svg"
iconWidth={25}
onClick={onWhispButtonClick}
size="small"
variant="outlined"
/>
<WhispMenuButton geoJsonGenerator={generateGeoJsonWithName} />
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useCallback, useMemo, useState } from 'react'
import axios from 'axios'
import PropTypes from 'prop-types'

import { Button, ButtonMenu } from '@webapp/components'
import { useSurveyId } from '@webapp/store/survey'

const getWhispEarthMapUrl = (geojson) => `https://whisp.earthmap.org/?aoi=global&polygon=${JSON.stringify(geojson)}`

const getWhispDataDownloadUrl = (token) => `https://whisp.openforis.org/api/download-csv/${token}`

export const WhispMenuButton = (props) => {
const { geoJsonGenerator } = props

const surveyId = useSurveyId()
const [whispDataLoading, setWhispDataLoading] = useState(false)

const onWhispCsvButtonClick = useCallback(async () => {
setWhispDataLoading(true)
const geojson = geoJsonGenerator()
const url = `/api/survey/${surveyId}/geo/whisp/geojson/csv`
try {
const { data: token } = await axios.post(url, geojson)
const csvDownloadUrl = getWhispDataDownloadUrl(token)
setWhispDataLoading(false)
window.open(csvDownloadUrl, 'Whisp')
} catch (_error) {
setWhispDataLoading(false)
}
}, [geoJsonGenerator, surveyId])

const onWhispEarthMapButtonClick = useCallback(() => {
const geojson = geoJsonGenerator()
const url = getWhispEarthMapUrl(geojson)
window.open(url, 'WhispEarthMap')
}, [geoJsonGenerator])

const whispButtonDefinitions = useMemo(
() => ({
earthMap: {
label: 'mapView.whispEarthMap',
onClick: onWhispEarthMapButtonClick,
},
csv: {
label: 'mapView.whispCsv',
onClick: onWhispCsvButtonClick,
},
}),
[onWhispCsvButtonClick, onWhispEarthMapButtonClick]
)

const buttons = useMemo(
() =>
Object.entries(whispButtonDefinitions).map(([key, { label, onClick }]) => ({
key,
content: (
<Button
disabled={whispDataLoading}
label={label}
iconHeight={25}
iconSrc="/img/of_whisp_icon.svg"
iconWidth={25}
onClick={onClick}
size="small"
variant="text"
/>
),
})),
[whispButtonDefinitions, whispDataLoading]
)

return (
<ButtonMenu
className="whisp-menu-btn"
disabled={whispDataLoading}
label={whispDataLoading ? 'common.loading' : 'mapView.whisp'}
iconAlt="Whisp"
iconHeight={25}
iconSrc="/img/of_whisp_icon.svg"
items={buttons}
variant="outlined"
/>
)
}

WhispMenuButton.propTypes = {
geoJsonGenerator: PropTypes.func.isRequired,
}
Loading