From 4c52c5819a854711b401a50bd5bfa23e5276ede1 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 17:19:05 +0100 Subject: [PATCH 01/10] Made sure listFiles return the count of the response --- modules/user-files/src/filesystem.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/user-files/src/filesystem.js b/modules/user-files/src/filesystem.js index 680c57c44..8c6eb88c8 100644 --- a/modules/user-files/src/filesystem.js +++ b/modules/user-files/src/filesystem.js @@ -410,7 +410,11 @@ const listFiles = async (homeDir, ctx) => { ? a.name.localeCompare(b.name) : (a.type === 'directory' ? -1 : 1)) - ctx.body = {path: Path.relative(userHomeDir, absolutePath), files} + ctx.body = { + path: Path.relative(userHomeDir, absolutePath), + files, + count: files.length + } } catch (error) { log.error(() => `Error listing directory: ${error.message}`) ctx.response.status = 500 From 63bcc1c80ecc8de2b7d5a72b3568de5172775dea Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 17:21:46 +0100 Subject: [PATCH 02/10] Created endpoint to retrieve recipe data --- modules/gui/src/api/tasks.js | 3 +++ .../task/endpoint/TaskEndpoint.groovy | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/modules/gui/src/api/tasks.js b/modules/gui/src/api/tasks.js index 9cdd319c2..4b29895b1 100644 --- a/modules/gui/src/api/tasks.js +++ b/modules/gui/src/api/tasks.js @@ -6,6 +6,9 @@ export default { retry }), + loadDetails$: taskId => + get$(`/api/tasks/task/${taskId}/details`), + submit$: task => postJson$('/api/tasks', { body: task diff --git a/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy b/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy index 77d73a734..503da7f80 100644 --- a/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy +++ b/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy @@ -59,6 +59,26 @@ class TaskEndpoint { send toJson(task) } + get('/tasks/task/{id}/details') { + def task = component.submit( + new GetTask( + taskId: params.required('id', String), + username: currentUser.username + ) + ) + def taskDetails = [ + id: task.id, + recipeId: task.recipeId, + name: task.title, + status: task.state, + statusDescription: task.statusDescription, + creationTime: task.creationTime, + updateTime: task.updateTime, + params: task.params + ] + send toJson(taskDetails) + } + post('/tasks/task/{id}/cancel') { submit(new CancelTask(taskId: params.required('id', String), username: currentUser.username)) response.status = 204 From 266a92cd6c5120b192eb24baa2ab853a90d84d34 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 18:44:15 +0100 Subject: [PATCH 03/10] Extracted drivePath function to driveUtils module for reuse across export handlers --- modules/task/src/jobs/export/driveUtils.js | 4 ++++ modules/task/src/jobs/export/toDrive.js | 4 +--- modules/task/src/jobs/export/toSepal.js | 4 +--- 3 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 modules/task/src/jobs/export/driveUtils.js diff --git a/modules/task/src/jobs/export/driveUtils.js b/modules/task/src/jobs/export/driveUtils.js new file mode 100644 index 000000000..5ced6464b --- /dev/null +++ b/modules/task/src/jobs/export/driveUtils.js @@ -0,0 +1,4 @@ +const drivePath = folder => + `SEPAL/exports/${folder}` + +module.exports = {drivePath} diff --git a/modules/task/src/jobs/export/toDrive.js b/modules/task/src/jobs/export/toDrive.js index 37dae0eba..5055c8e00 100644 --- a/modules/task/src/jobs/export/toDrive.js +++ b/modules/task/src/jobs/export/toDrive.js @@ -7,9 +7,7 @@ const {getCurrentContext$} = require('#task/jobs/service/context') const {exportLimiter$} = require('#task/jobs/service/exportLimiter') const {driveSerializer$} = require('#task/jobs/service/driveSerializer') const {task$} = require('#task/ee/task') - -const drivePath = folder => - `SEPAL/exports/${folder}` +const {drivePath} = require('./driveUtils') const createDriveFolder$ = folder => defer(() => driveSerializer$( diff --git a/modules/task/src/jobs/export/toSepal.js b/modules/task/src/jobs/export/toSepal.js index 5c9e2faae..a27fe42c3 100644 --- a/modules/task/src/jobs/export/toSepal.js +++ b/modules/task/src/jobs/export/toSepal.js @@ -10,12 +10,10 @@ const {exportLimiter$} = require('#task/jobs/service/exportLimiter') const {driveSerializer$} = require('#task/jobs/service/driveSerializer') const {gcsSerializer$} = require('#task/jobs/service/gcsSerializer') const {task$} = require('#task/ee/task') +const {drivePath} = require('./driveUtils') const CONCURRENT_FILE_DOWNLOAD = 3 -const drivePath = folder => - `SEPAL/exports/${folder}` - const createDriveFolder$ = folder => defer(() => driveSerializer$( drive.getFolderByPath$({path: drivePath(folder), create: true}) From 074f50a96efcfa3e31ab2104f4d0f696483a3afc Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 18:46:18 +0100 Subject: [PATCH 04/10] Created task details modal to show more task information --- .../src/app/home/body/tasks/taskDetails.jsx | 268 ++++++++++++++++++ .../home/body/tasks/taskDetails.module.css | 53 ++++ modules/gui/src/app/home/body/tasks/tasks.jsx | 63 +++- modules/gui/src/locale/en/translations.json | 84 +++++- 4 files changed, 453 insertions(+), 15 deletions(-) create mode 100644 modules/gui/src/app/home/body/tasks/taskDetails.jsx create mode 100644 modules/gui/src/app/home/body/tasks/taskDetails.module.css diff --git a/modules/gui/src/app/home/body/tasks/taskDetails.jsx b/modules/gui/src/app/home/body/tasks/taskDetails.jsx new file mode 100644 index 000000000..520785769 --- /dev/null +++ b/modules/gui/src/app/home/body/tasks/taskDetails.jsx @@ -0,0 +1,268 @@ +import PropTypes from 'prop-types' +import React from 'react' + +import api from '~/apiRegistry' +import {copyToClipboard} from '~/clipboard' +import format from '~/format' +import {msg} from '~/translate' +import {Button} from '~/widget/button' +import {Label} from '~/widget/label' +import {Layout} from '~/widget/layout' +import {Panel} from '~/widget/panel/panel' +import {Widget} from '~/widget/widget' + +import styles from './taskDetails.module.css' + +export class TaskDetails extends React.Component { + constructor(props) { + super(props) + this.state = { + task: null, + duration: null + } + this.intervalId = null + } + + componentDidMount() { + this.loadTaskDetails() + } + + loadTaskDetails() { + const {taskId} = this.props + + api.tasks.loadDetails$(taskId).subscribe({ + next: task => { + this.setState({ + task, + duration: this.calculateDuration(task) + }) + + // Set up interval only if task is still running + if (task.status === 'ACTIVE') { + this.intervalId = setInterval(() => { + this.setState({duration: this.calculateDuration(task)}) + }, 1000) + } + }, + error: error => { + // Error is logged but not displayed in UI + console.error('Failed to load task details:', error) + } + }) + } + + componentWillUnmount() { + if (this.intervalId) { + clearInterval(this.intervalId) + } + } + + calculateDuration(task) { + const taskData = task || this.state.task + if (!taskData || !taskData.creationTime) { + return '--' + } + + const start = new Date(taskData.creationTime) + const end = taskData.status === 'ACTIVE' + ? new Date() + : (taskData.updateTime ? new Date(taskData.updateTime) : new Date()) + + const durationMs = end - start + + // Format duration + if (durationMs < 0) { + return '--' + } + + const seconds = Math.floor(durationMs / 1000) % 60 + const minutes = Math.floor(durationMs / (1000 * 60)) % 60 + const hours = Math.floor(durationMs / (1000 * 60 * 60)) + + if (hours > 0) { + return `${hours}h ${minutes}m ${seconds}s` + } else if (minutes > 0) { + return `${minutes}m ${seconds}s` + } else { + return `${seconds}s` + } + } + + render() { + const {onClose} = this.props + const {task} = this.state + + if (!task) { + return null + } + + return ( + + + + + {this.renderStatus()} + {this.renderConfiguration()} + {this.renderLocation()} + {this.renderProgress()} + + + + + + + + + ) + } + + renderStatus() { + const {task, duration} = this.state + + return ( + +
+
+ +
+
+ +
+
+
+ ) + } + + renderConfiguration() { + const {task} = this.state + const taskInfo = task.params?.taskInfo + const image = task.params?.image + const recipe = image?.recipe + + if (!recipe?.type && !taskInfo?.recipeType) { + return null + } + + const recipeType = taskInfo?.recipeType || recipe?.type + + return ( + +
+
+
+ ) + } + + renderLocation() { + const {task} = this.state + const taskInfo = task.params?.taskInfo + + if (task.status === 'FAILED') { + return null + } + + if (!taskInfo?.destination && !taskInfo?.outputPath) { + return null + } + + return ( + + {taskInfo?.destination && ( +
+
+ )} + + {taskInfo?.destination === 'SEPAL' && taskInfo?.filenamePrefix && ( +
+
+ )} + + {taskInfo?.outputPath && ( +
+
+ )} + + {taskInfo?.destination === 'GEE' && taskInfo?.sharing && ( +
+
+ )} +
+ ) + } + + getStatusColorClass() { + const {task} = this.state + switch (task?.status) { + case 'FAILED': + return styles.statusError + case 'COMPLETED': + return styles.statusSuccess + default: + return '' + } + } + + renderProgress() { + const {description} = this.props + + if (!description) { + return null + } + + return ( + +
+
+
+ ) + } +} + +TaskDetails.propTypes = { + taskId: PropTypes.string.isRequired, + onClose: PropTypes.func.isRequired, + description: PropTypes.string +} diff --git a/modules/gui/src/app/home/body/tasks/taskDetails.module.css b/modules/gui/src/app/home/body/tasks/taskDetails.module.css new file mode 100644 index 000000000..e503aeac4 --- /dev/null +++ b/modules/gui/src/app/home/body/tasks/taskDetails.module.css @@ -0,0 +1,53 @@ +.panel { + width: 40rem; +} + +.row { + display: grid; + grid-template-columns: 2fr 4fr; /* Changed from 3fr 2fr to give more space to values */ + grid-template-rows: auto; + column-gap: 1rem; + width: 100%; + align-items: center; + justify-items: right; + margin-bottom: 0.25rem; + grid-template-areas: 'label value'; +} + +.row .fieldLabel { + grid-area: label; + justify-self: left; +} + +.row .fieldValue { + grid-area: value; + word-break: break-all; + font-weight: normal; + + /* taken from widget.crudItem.module.css.description */ + font-size: .9rem; + line-height: 1.5rem; + white-space: normal; + word-wrap: break-word; + text-align: left; +} + +.row .fieldValueWithButton { + grid-area: value; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.row .fieldValueWithButton .fieldValue { + grid-area: unset; + flex: 1; +} + +.statusError { + color: var(--error-color); +} + +.statusSuccess { + color: var(--success-color); +} diff --git a/modules/gui/src/app/home/body/tasks/tasks.jsx b/modules/gui/src/app/home/body/tasks/tasks.jsx index b76d85cb8..d9cffebb3 100644 --- a/modules/gui/src/app/home/body/tasks/tasks.jsx +++ b/modules/gui/src/app/home/body/tasks/tasks.jsx @@ -14,6 +14,7 @@ import {Scrollable} from '~/widget/scrollable' import {Content, SectionLayout, TopBar} from '~/widget/sectionLayout' import {Shape} from '~/widget/shape' +import {TaskDetails} from './taskDetails' import styles from './tasks.module.css' const mapStateToProps = state => ({ @@ -24,7 +25,15 @@ class _Tasks extends React.Component { constructor(props) { super(props) this.renderTask = this.renderTask.bind(this) - this.state = {tasks: props.tasks || []} + this.showInfo = this.showInfo.bind(this) + this.closeTaskDetails = this.closeTaskDetails.bind(this) + this.removeTask = this.removeTask.bind(this) + this.stopTask = this.stopTask.bind(this) + this.copyToClipboard = this.copyToClipboard.bind(this) + this.state = { + tasks: props.tasks || [], + selectedTask: null + } } isRunning(task) { @@ -42,7 +51,7 @@ class _Tasks extends React.Component { chromeless shape='circle' icon='times' - onConfirm={() => this.stopTask(task)} + onConfirm={e => this.stopTask(task, e)} tooltip={msg('tasks.stop.tooltip')} tooltipPlacement='left' /> @@ -56,12 +65,25 @@ class _Tasks extends React.Component { chromeless shape='circle' icon='copy' - onClick={() => this.copyToClipboard(task)} + onClick={e => { + e.stopPropagation() + this.copyToClipboard(task) + }} tooltip={msg('tasks.copyToClipboard.tooltip')} tooltipPlacement='left' /> ) : null } + + // Info button removed as clicking on the CrudItem now shows the task details + + showInfo(task) { + this.setState({selectedTask: task}) + } + + closeTaskDetails() { + this.setState({selectedTask: null}) + } getStatusIcon(task) { const iconMap = { @@ -89,7 +111,9 @@ class _Tasks extends React.Component { renderTask(task) { const {icon, iconVariant} = this.getStatusIcon(task) return ( - + this.showInfo(task)}> this.removeTask(task)} + onRemove={e => { + e.stopPropagation() + this.removeTask(task) + }} /> ) @@ -157,6 +184,21 @@ class _Tasks extends React.Component { ) } + renderTaskDetails() { + const {selectedTask} = this.state + if (!selectedTask) { + return null + } + + return ( + + ) + } + render() { return ( @@ -168,6 +210,7 @@ class _Tasks extends React.Component { {this.renderTasks()} + {this.renderTaskDetails()} ) } @@ -205,7 +248,10 @@ class _Tasks extends React.Component { ) } - removeTask(task) { + removeTask(task, e) { + if (e) { + e.stopPropagation() + } const {stream} = this.props const {tasks} = this.state this.setState({ @@ -234,7 +280,10 @@ class _Tasks extends React.Component { return tasks.filter(({status}) => !['PENDING', 'ACTIVE'].includes(status)) } - stopTask(task) { + stopTask(task, e) { + if (e) { + e.stopPropagation() + } const {stream} = this.props this.updateTaskInState(task, () => ({ ...task, diff --git a/modules/gui/src/locale/en/translations.json b/modules/gui/src/locale/en/translations.json index 00b2c849a..77c3b8940 100644 --- a/modules/gui/src/locale/en/translations.json +++ b/modules/gui/src/locale/en/translations.json @@ -1321,9 +1321,9 @@ } }, "task": { - "GEE": "Export {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve {name} to SEPAL workspace", - "DRIVE": "Retrieve {name} to Google Drive" + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", + "DRIVE": "Retrieve recipe: {name} to Google Drive" }, "title": "Retrieve", "tooltip": "Retrieve time-series" @@ -1593,8 +1593,8 @@ } }, "task": { - "GEE": "Export {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve {name} to SEPAL workspace" + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace" }, "title": "Retrieve", "tooltip": "Retrieve time-series" @@ -4610,9 +4610,9 @@ } }, "task": { - "GEE": "Export {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve {name} to SEPAL workspace", - "DRIVE": "Retrieve {name} to Google Drive" + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", + "DRIVE": "Retrieve recipe:{name} to Google Drive" }, "tileSize": { "label": "Tile size", @@ -4624,6 +4624,11 @@ "label": "Workspace path", "placeholder": "Enter workspace path...", "tooltip": "Relative path in your workspace home directory where the image should be retrieved to." + }, + "filenamePrefix" : { + "label" : "Filename prefix", + "placeholder" : "Enter filename prefix...", + "tooltip" : "Custom prefix that will be added to exported files" } }, "title": "Retrieve", @@ -4819,6 +4824,64 @@ "tooltip": "Copy to clipboard", "success": "Copied to clipboard" }, + "info": { + "tooltip": "View task details" + }, + "details": { + "id": "Task ID", + "status": "Status", + "recipeName": "Recipe Name", + "recipeType": "Recipe Type", + "recipeId": "Recipe ID", + "creationTime": "Start Time", + "updateTime": "Last Update", + "duration": "Duration", + "description": "Description", + "workspacePath" : "Output path", + "filenamePrefix" : "Filename prefix", + "actualWorkspacePath" : "Actual workspace path", + "section" :{ + "status" : "Status", + "conf" : "Recipe configuration", + "location" : "Location", + "progress" : "Progress" + }, + "sharing" : { + "label" : "Access", + "PRIVATE" : "Private", + "PUBLIC" : "Public" + }, + "destination" : { + "label" : "Destination", + "SEPAL" : "SEPAL workspace", + "GEE" : "Google Earth Engine asset", + "DRIVE" : "Google Drive" + }, + "recipeTypeNames" : { + "MOSAIC": "Optical Mosaic", + "RADAR_MOSAIC": "Radar Mosaic", + "PLANET_MOSAIC": "Planet Mosaic", + "TIME_SERIES": "Time Series", + "CCDC": "CCDC", + "CCDC_SLICE": "CCDC Slice", + "CHANGE_ALERTS": "Change Alerts", + "BAYTS_HISTORICAL": "BAYTS Historical", + "BAYTS_ALERTS": "BAYTS Alerts", + "CLASSIFICATION": "Classification", + "UNSUPERVISED_CLASSIFICATION": "Unsupervised Classification", + "REGRESSION": "Regression", + "CLASS_CHANGE": "Class Change", + "INDEX_CHANGE": "Index Change", + "STACK": "Stack", + "BAND_MATH": "Band Math", + "REMAPPING": "Remapping", + "PHENOLOGY": "Phenology", + "MASKING": "Masking", + "ASSET_MOSAIC": "Asset Mosaic", + "RECIPE_REF": "Recipe Reference", + "ASSET": "Asset" + } + }, "remove": { "tooltip": "Remove task" }, @@ -5233,6 +5296,11 @@ "label": "Strategy" } }, + "workspaceDestination" : { + "taskPending": "A task is already pending in this folder. Please choose a different folder.", + "notEmpty": "This folder must be completely empty. Please choose a different folder or remove its contents.", + "loadError": "Unable to load the workspace. Please try again." + }, "assetInput": { "loadError": "Unable to load asset" }, From 76550635615fc95dff42a22bf4bd285eea627e9a Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 18:47:04 +0100 Subject: [PATCH 05/10] Standarized submitTask strategy --- .../body/process/recipe/asset/assetRecipe.js | 47 +------ .../process/recipe/bandMath/bandMathRecipe.js | 45 +------ .../recipe/baytsAlerts/baytsAlertsRecipe.js | 48 +------ .../baytsHistorical/baytsHistoricalRecipe.js | 45 +------ .../body/process/recipe/ccdc/ccdcRecipe.js | 24 +++- .../recipe/ccdcSlice/ccdcSliceRecipe.js | 46 ++----- .../recipe/changeAlerts/changeAlertsRecipe.js | 47 +------ .../recipe/classChange/classChangeRecipe.js | 49 +------ .../classification/classificationRecipe.js | 46 +------ .../recipe/indexChange/indexChangeRecipe.js | 50 +------ .../process/recipe/masking/maskingRecipe.js | 52 +------- .../opticalMosaic/opticalMosaicRecipe.js | 49 +------ .../recipe/phenology/phenologyRecipe.js | 48 +------ .../recipe/planetMosaic/planetMosaicRecipe.js | 48 +------ .../recipe/radarMosaic/radarMosaicRecipe.js | 48 +------ .../body/process/recipe/recipeOutputPath.js | 29 ++++ .../process/recipe/recipeTaskSubmitter.js | 126 ++++++++++++++++++ .../recipe/regression/regressionRecipe.js | 47 ++----- .../recipe/remapping/remappingRecipe.js | 44 +----- .../body/process/recipe/stack/stackRecipe.js | 45 +------ .../recipe/timeSeries/timeSeriesRecipe.js | 23 +++- .../unsupervisedClassificationRecipe.js | 47 ++----- modules/task/src/tasks/ccdcAssetExport.js | 4 +- modules/task/src/tasks/imageDriveExport.js | 9 +- modules/task/src/tasks/imageSepalExport.js | 31 +++-- .../task/src/tasks/timeSeriesSepalExport.js | 25 +++- 26 files changed, 323 insertions(+), 799 deletions(-) create mode 100644 modules/gui/src/app/home/body/process/recipe/recipeOutputPath.js create mode 100644 modules/gui/src/app/home/body/process/recipe/recipeTaskSubmitter.js diff --git a/modules/gui/src/app/home/body/process/recipe/asset/assetRecipe.js b/modules/gui/src/app/home/body/process/recipe/asset/assetRecipe.js index b3f544a4d..0b2899abd 100644 --- a/modules/gui/src/app/home/body/process/recipe/asset/assetRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/asset/assetRecipe.js @@ -1,12 +1,8 @@ import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' const DATE_FORMAT = 'YYYY-MM-DD' @@ -77,42 +73,7 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'RADAR' +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + dataSetType: 'RADAR' }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/bandMath/bandMathRecipe.js b/modules/gui/src/app/home/body/process/recipe/bandMath/bandMathRecipe.js index ecfb09d1f..6444048ad 100644 --- a/modules/gui/src/app/home/body/process/recipe/bandMath/bandMathRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/bandMath/bandMathRecipe.js @@ -1,10 +1,5 @@ -import _ from 'lodash' - -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' export const getDefaultModel = () => ({ inputImagery: {images: []}, @@ -32,40 +27,8 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = recipe.ui.retrieveOptions.bands - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - ...recipe.ui.retrieveOptions, - recipe: _.omit(recipe, ['ui']), - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - properties: recipeProperties, - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + includeTimeRange: false }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/baytsAlerts/baytsAlertsRecipe.js b/modules/gui/src/app/home/body/process/recipe/baytsAlerts/baytsAlertsRecipe.js index 2aa4e97f5..bab1794da 100644 --- a/modules/gui/src/app/home/body/process/recipe/baytsAlerts/baytsAlertsRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/baytsAlerts/baytsAlertsRecipe.js @@ -1,13 +1,9 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {defaultModel as defaultHistoricalModel} from '~/app/home/body/process/recipe/baytsHistorical/baytsHistoricalRecipe' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {selectFrom} from '~/stateUtils' -import {msg} from '~/translate' import {visualizationOptions} from './visualizations' @@ -81,43 +77,7 @@ export const getAllVisualizations = recipe => { : [] } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const pyramidingPolicy = {'.default': 'sample'} - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - pyramidingPolicy, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.sample }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/baytsHistorical/baytsHistoricalRecipe.js b/modules/gui/src/app/home/body/process/recipe/baytsHistorical/baytsHistoricalRecipe.js index 001d96f13..62762a64a 100644 --- a/modules/gui/src/app/home/body/process/recipe/baytsHistorical/baytsHistoricalRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/baytsHistorical/baytsHistoricalRecipe.js @@ -1,12 +1,7 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {getAvailableBands} from './bands' @@ -65,41 +60,9 @@ export const RecipeActions = id => { } const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) const bands = Object.keys(getAvailableBands(recipe)) - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'RADAR' + return submitTask(recipe, { + dataSetType: 'RADAR', + customizeImage: image => ({...image, bands: {selection: bands}}) }) - return api.tasks.submit$(task).subscribe() } diff --git a/modules/gui/src/app/home/body/process/recipe/ccdc/ccdcRecipe.js b/modules/gui/src/app/home/body/process/recipe/ccdc/ccdcRecipe.js index 4dc81b634..46a8a78fe 100644 --- a/modules/gui/src/app/home/body/process/recipe/ccdc/ccdcRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/ccdc/ccdcRecipe.js @@ -6,6 +6,7 @@ import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {defaultModel as defaultOpticalModel} from '~/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe' import {defaultModel as defaultPlanetModel} from '~/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe' import {defaultModel as defaultRadarModel} from '~/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe' +import {getTaskInfo} from '~/app/home/body/process/recipe/recipeOutputPath' import {getAllVisualizations as recipeVisualizations} from '~/app/home/body/process/recipe/visualizations' import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' import {publishEvent} from '~/eventPublisher' @@ -192,7 +193,9 @@ export const loadCCDCObservations$ = ({recipe, latLng, bands}) => const submitRetrieveRecipeTask = recipe => { const name = recipe.title || recipe.placeholder - const title = msg(['process.retrieve.form.task.GEE'], {name}) + const bands = recipe.ui.retrieveOptions.bands + const destination = 'GEE' + const taskTitle = msg(['process.retrieve.form.task.GEE'], {name}) const visualizations = getAllVisualizations(recipe) const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) const operation = 'ccdc.GEE' @@ -209,13 +212,20 @@ const submitRetrieveRecipeTask = recipe => { const task = { operation, params: { - title, + title: taskTitle, description: name, - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: recipe.ui.retrieveOptions.bands, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} + image: { + ...recipe.ui.retrieveOptions, + recipe: _.omit(recipe, ['ui']), + bands, + visualizations, + properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} + }, + taskInfo: getTaskInfo({ + recipe, + destination, + retrieveOptions: recipe.ui.retrieveOptions + }) } } publishEvent('submit_task', { diff --git a/modules/gui/src/app/home/body/process/recipe/ccdcSlice/ccdcSliceRecipe.js b/modules/gui/src/app/home/body/process/recipe/ccdcSlice/ccdcSliceRecipe.js index 33423032f..1f9fa5c90 100644 --- a/modules/gui/src/app/home/body/process/recipe/ccdcSlice/ccdcSliceRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/ccdcSlice/ccdcSliceRecipe.js @@ -4,11 +4,9 @@ import moment from 'moment' import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {toT} from '~/app/home/body/process/recipe/ccdc/t' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {normalize} from '~/app/home/map/visParams/visParams' -import {publishEvent} from '~/eventPublisher' import {selectFrom} from '~/stateUtils' -import {msg} from '~/translate' export const defaultModel = { date: { @@ -105,9 +103,6 @@ export const additionalVisualizations = recipe => { } const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.ccdcSlice.panel.retrieve.task', destination], {name}) const {baseBands, bandTypes, segmentBands} = recipe.ui.retrieveOptions const bandTypeSuffixes = { value: '', @@ -140,37 +135,12 @@ const submitRetrieveRecipeTask = recipe => { ...baseBands.map(({name}) => name), ...segmentBands ].filter(band => allBands.includes(band)) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const visualizations = getAllVisualizations(recipe) - .filter(({bands: visBands}) => visBands.every(band => bands.includes(band))) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands, baseBands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination + + return submitTask(recipe, { + filterVisualizations: true, + customizeImage: image => ({ + ...image, + bands: {selection: bands, baseBands} + }) }) - return api.tasks.submit$(task).subscribe() } diff --git a/modules/gui/src/app/home/body/process/recipe/changeAlerts/changeAlertsRecipe.js b/modules/gui/src/app/home/body/process/recipe/changeAlerts/changeAlertsRecipe.js index f316aabe5..68c79cbc4 100644 --- a/modules/gui/src/app/home/body/process/recipe/changeAlerts/changeAlertsRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/changeAlerts/changeAlertsRecipe.js @@ -1,4 +1,3 @@ -import _ from 'lodash' import moment from 'moment' import api from '~/apiRegistry' @@ -6,10 +5,8 @@ import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {defaultModel as defaultOpticalModel} from '~/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe' import {defaultModel as defaultPlanetModel} from '~/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe' import {defaultModel as defaultRadarModel} from '~/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {selectFrom} from '~/stateUtils' -import {msg} from '~/translate' import {visualizationOptions} from './visualizations' @@ -125,43 +122,7 @@ export const getAllVisualizations = recipe => { : [] } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const pyramidingPolicy = {'.default': 'sample'} - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - pyramidingPolicy, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.sample }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/classChange/classChangeRecipe.js b/modules/gui/src/app/home/body/process/recipe/classChange/classChangeRecipe.js index ecdb8b599..d92c87619 100644 --- a/modules/gui/src/app/home/body/process/recipe/classChange/classChangeRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/classChange/classChangeRecipe.js @@ -1,12 +1,8 @@ import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' const DATE_FORMAT = 'YYYY-MM-DD' @@ -67,44 +63,7 @@ export const hasConfidence = recipe => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const pyramidingPolicy = {} - bands.forEach(band => pyramidingPolicy[band] = band === 'transition' ? 'mode' : 'mean') - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - pyramidingPolicy, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.changeBased('transition') }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/classification/classificationRecipe.js b/modules/gui/src/app/home/body/process/recipe/classification/classificationRecipe.js index dd7596917..e682cd63c 100644 --- a/modules/gui/src/app/home/body/process/recipe/classification/classificationRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/classification/classificationRecipe.js @@ -1,10 +1,8 @@ import _ from 'lodash' -import api from '~/apiRegistry' import {removeImageLayerSource} from '~/app/home/body/process/mapLayout/imageLayerSources' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {publishEvent} from '~/eventPublisher' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {msg} from '~/translate' import {uuid} from '~/uuid' @@ -164,45 +162,11 @@ export const supportRegression = classifierType => export const supportProbability = classifierType => ['RANDOM_FOREST', 'GRADIENT_TREE_BOOST', 'CART', 'SVM', 'NAIVE_BAYES'].includes(classifierType) -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = recipe.ui.retrieveOptions.bands - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const pyramidingPolicy = {} - bands.forEach(band => pyramidingPolicy[band] = band === 'class' ? 'mode' : 'mean') - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - ...recipe.ui.retrieveOptions, - recipe: _.omit(recipe, ['ui']), - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - pyramidingPolicy, - properties: recipeProperties, - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.classBased, + includeTimeRange: false }) - return api.tasks.submit$(task).subscribe() -} export const hasTrainingData = recipe => { const hasRecipeDataType = recipe.model.trainingData.dataSets.find(({type}) => type === 'RECIPE') diff --git a/modules/gui/src/app/home/body/process/recipe/indexChange/indexChangeRecipe.js b/modules/gui/src/app/home/body/process/recipe/indexChange/indexChangeRecipe.js index e8e0ecaef..190a39910 100644 --- a/modules/gui/src/app/home/body/process/recipe/indexChange/indexChangeRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/indexChange/indexChangeRecipe.js @@ -1,12 +1,7 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {uuid} from '~/uuid' const DATE_FORMAT = 'YYYY-MM-DD' @@ -102,44 +97,7 @@ export const hasError = recipe => { return fromImage && fromImage.errorBand && toImage && toImage.errorBand } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const pyramidingPolicy = {} - bands.forEach(band => pyramidingPolicy[band] = band === 'change' ? 'mode' : 'mean') - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - pyramidingPolicy, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.changeBased('change') }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/masking/maskingRecipe.js b/modules/gui/src/app/home/body/process/recipe/masking/maskingRecipe.js index cc6eb6aaf..47e2cba63 100644 --- a/modules/gui/src/app/home/body/process/recipe/masking/maskingRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/masking/maskingRecipe.js @@ -1,11 +1,5 @@ -import _ from 'lodash' - -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' export const defaultModel = {} @@ -41,45 +35,7 @@ export const hasError = recipe => { return imageToMask && imageToMask.errorBand && imageMask && imageMask.errorBand } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const type = getRecipeType(recipe.type) - const [timeStart, timeEnd] = ((type.getDateRange && type.getDateRange(recipe)) || []).map(date => date.valueOf()) - const pyramidingPolicy = {} - bands.forEach(band => pyramidingPolicy[band] = band === 'change' ? 'mode' : 'mean') - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - pyramidingPolicy, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.changeBased('change') }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe.js b/modules/gui/src/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe.js index c1d244566..cc2a9619b 100644 --- a/modules/gui/src/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe.js @@ -1,13 +1,9 @@ import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {selectFrom} from '~/stateUtils' -import {msg} from '~/translate' const DATE_FORMAT = 'YYYY-MM-DD' @@ -121,46 +117,11 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - .filter(({bands: visBands}) => visBands.every(band => bands.includes(band))) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'OPTICAL' +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + dataSetType: 'OPTICAL', + filterVisualizations: true }) - return api.tasks.submit$(task).subscribe() -} export const inDateRange = (date, dates) => { date = moment(date, DATE_FORMAT) diff --git a/modules/gui/src/app/home/body/process/recipe/phenology/phenologyRecipe.js b/modules/gui/src/app/home/body/process/recipe/phenology/phenologyRecipe.js index c4f741b49..13487d387 100644 --- a/modules/gui/src/app/home/body/process/recipe/phenology/phenologyRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/phenology/phenologyRecipe.js @@ -1,15 +1,10 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {defaultModel as defaultOpticalModel} from '~/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe' import {defaultModel as defaultPlanetModel} from '~/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe' import {defaultModel as defaultRadarModel} from '~/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' export const defaultModel = { dates: { @@ -64,42 +59,7 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'OPTICAL' +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + dataSetType: 'OPTICAL' }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe.js b/modules/gui/src/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe.js index 85d683b3e..674790685 100644 --- a/modules/gui/src/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe.js @@ -1,12 +1,7 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' const DATE_FORMAT = 'YYYY-MM-DD' @@ -62,42 +57,7 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'PLANET' +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + dataSetType: 'PLANET' }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe.js b/modules/gui/src/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe.js index b8323e848..6fbad0111 100644 --- a/modules/gui/src/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe.js @@ -1,12 +1,7 @@ -import _ from 'lodash' import moment from 'moment' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' const DATE_FORMAT = 'YYYY-MM-DD' @@ -62,42 +57,7 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const bands = recipe.ui.retrieveOptions.bands - const visualizations = getAllVisualizations(recipe) - const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []).map(date => date.valueOf()) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations, - properties: {...recipeProperties, 'system:time_start': timeStart, 'system:time_end': timeEnd} - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination, - data_set_type: 'RADAR' +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + dataSetType: 'RADAR' }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/recipeOutputPath.js b/modules/gui/src/app/home/body/process/recipe/recipeOutputPath.js new file mode 100644 index 000000000..40c1def70 --- /dev/null +++ b/modules/gui/src/app/home/body/process/recipe/recipeOutputPath.js @@ -0,0 +1,29 @@ +import moment from 'moment' + +export const getOutputPath = ({destination, retrieveOptions, recipe}) => { + switch (destination) { + case 'GEE': + return retrieveOptions.assetId + case 'SEPAL': + return retrieveOptions.workspacePath + case 'DRIVE': { + const description = recipe.title || recipe.placeholder + return `${description}_${moment().format('YYYY-MM-DD_HH:mm:ss.SSS')}` + + } + default: + return null + } +} + +export const getTaskInfo = ({recipe, destination, retrieveOptions}) => { + const outputPath = getOutputPath({destination, retrieveOptions, recipe}) + + return { + recipeType: recipe.type, + destination, + outputPath, + sharing: retrieveOptions.sharing, + filenamePrefix: retrieveOptions.filenamePrefix + } +} diff --git a/modules/gui/src/app/home/body/process/recipe/recipeTaskSubmitter.js b/modules/gui/src/app/home/body/process/recipe/recipeTaskSubmitter.js new file mode 100644 index 000000000..0df652082 --- /dev/null +++ b/modules/gui/src/app/home/body/process/recipe/recipeTaskSubmitter.js @@ -0,0 +1,126 @@ +import _ from 'lodash' + +import api from '~/apiRegistry' +import {getTaskInfo} from '~/app/home/body/process/recipe/recipeOutputPath' +import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' +import {getRecipeType} from '~/app/home/body/process/recipeTypeRegistry' +import {publishEvent} from '~/eventPublisher' +import {msg} from '~/translate' + +export const pyramidingPolicies = { + + // For classification recipe - 'class' band uses 'mode', others use 'mean' + classBased: bands => { + const policy = {} + bands.forEach(band => policy[band] = band === 'class' ? 'mode' : 'mean') + return policy + }, + + // For change detection recipes - specified band uses 'mode', others use 'mean' + changeBased: bandName => bands => { + const policy = {} + bands.forEach(band => policy[band] = band === bandName ? 'mode' : 'mean') + return policy + }, + + // For alert recipes - use sample for all bands + sample: {'.default': 'sample'} +} + +export const submitRetrieveRecipeTask = (recipe, config = {}) => { + const { + dataSetType, + pyramidingPolicy, + includeTimeRange = true, + filterVisualizations = false, + customizeImage + } = config + + const name = recipe.title || recipe.placeholder + const destination = recipe.ui.retrieveOptions.destination + const taskTitle = msg(['process.retrieve.form.task', destination], {name}) + const bands = recipe.ui.retrieveOptions.bands + const operation = `image.${destination}` + + let visualizations = getAllVisualizations(recipe) + if (filterVisualizations) { + visualizations = visualizations.filter(({bands: visBands}) => + visBands.every(band => bands.includes(band)) + ) + } + + // Build recipe properties + const recipeProperties = { + recipe_id: recipe.id, + recipe_projectId: recipe.projectId, + recipe_type: recipe.type, + recipe_title: recipe.title || recipe.placeholder, + ..._(recipe.model) + .mapValues(value => JSON.stringify(value)) + .mapKeys((_value, key) => `recipe_${key}`) + .value() + } + + // Add time range if needed + if (includeTimeRange) { + const [timeStart, timeEnd] = (getRecipeType(recipe.type).getDateRange(recipe) || []) + .map(date => date.valueOf()) + if (timeStart !== undefined && timeEnd !== undefined) { + recipeProperties['system:time_start'] = timeStart + recipeProperties['system:time_end'] = timeEnd + } + } + + const taskInfo = getTaskInfo({ + recipe, + destination, + retrieveOptions: recipe.ui.retrieveOptions + }) + + // Build base image object + let image = { + recipe: _.omit(recipe, ['ui']), + ...recipe.ui.retrieveOptions, + bands: {selection: bands}, + visualizations, + properties: recipeProperties + } + + // Add pyramiding policy if specified + if (pyramidingPolicy) { + if (typeof pyramidingPolicy === 'function') { + image.pyramidingPolicy = pyramidingPolicy(bands) + } else { + image.pyramidingPolicy = pyramidingPolicy + } + } + + // Allow custom modifications to the image object + if (customizeImage) { + image = customizeImage(image, taskInfo, recipe) + } + + if (destination === 'DRIVE') { + image = {...image, driveFolder: taskInfo.outputPath} + } + + // Build task + const task = { + operation, + params: { + title: taskTitle, + description: name, + image, + taskInfo + } + } + + // Publish analytics event + publishEvent('submit_task', { + recipe_type: recipe.type, + destination, + ...(dataSetType && {data_set_type: dataSetType}) + }) + + return api.tasks.submit$(task).subscribe() +} diff --git a/modules/gui/src/app/home/body/process/recipe/regression/regressionRecipe.js b/modules/gui/src/app/home/body/process/recipe/regression/regressionRecipe.js index 27913e572..32b21fcc0 100644 --- a/modules/gui/src/app/home/body/process/recipe/regression/regressionRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/regression/regressionRecipe.js @@ -1,10 +1,7 @@ import _ from 'lodash' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' export const getDefaultModel = () => ({ trainingData: { @@ -44,42 +41,14 @@ export const supportRegression = classifierType => export const supportProbability = classifierType => ['RANDOM_FOREST', 'GRADIENT_TREE_BOOST', 'CART', 'SVM', 'NAIVE_BAYES'].includes(classifierType) -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = ['regression'] - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - ...recipe.ui.retrieveOptions, - recipe: _.omit(recipe, ['ui']), - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - properties: recipeProperties, - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + includeTimeRange: false, + customizeImage: image => ({ + ...image, + bands: {selection: ['regression']} + }) }) - return api.tasks.submit$(task).subscribe() -} export const hasTrainingData = recipe => { return !!recipe.model.trainingData.dataSets.length diff --git a/modules/gui/src/app/home/body/process/recipe/remapping/remappingRecipe.js b/modules/gui/src/app/home/body/process/recipe/remapping/remappingRecipe.js index e43fc1eb0..0f2eb25c1 100644 --- a/modules/gui/src/app/home/body/process/recipe/remapping/remappingRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/remapping/remappingRecipe.js @@ -1,10 +1,8 @@ import _ from 'lodash' -import api from '~/apiRegistry' import {removeImageLayerSource} from '~/app/home/body/process/mapLayout/imageLayerSources' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {msg} from '~/translate' +import {pyramidingPolicies, submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {uuid} from '~/uuid' export const getDefaultModel = () => ({ @@ -33,41 +31,11 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = recipe.ui.retrieveOptions.bands - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const pyramidingPolicy = {} - bands.forEach(band => pyramidingPolicy[band] = band === 'class' ? 'mode' : 'mean') - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - recipe: _.omit(recipe, ['ui']), - ...recipe.ui.retrieveOptions, - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - pyramidingPolicy, - properties: recipeProperties - } - } - } - return api.tasks.submit$(task).subscribe() -} +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + pyramidingPolicy: pyramidingPolicies.classBased, + includeTimeRange: false + }) export const bandsAvailableToAdd = (bands, includedBands) => (Object.keys(bands || {})) diff --git a/modules/gui/src/app/home/body/process/recipe/stack/stackRecipe.js b/modules/gui/src/app/home/body/process/recipe/stack/stackRecipe.js index 5e785e47c..c74e33b58 100644 --- a/modules/gui/src/app/home/body/process/recipe/stack/stackRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/stack/stackRecipe.js @@ -1,10 +1,5 @@ -import _ from 'lodash' - -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {publishEvent} from '~/eventPublisher' -import {msg} from '~/translate' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' export const getDefaultModel = () => ({ inputImagery: {images: []}, @@ -31,40 +26,8 @@ export const RecipeActions = id => { } } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = recipe.ui.retrieveOptions.bands - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - ...recipe.ui.retrieveOptions, - recipe: _.omit(recipe, ['ui']), - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - properties: recipeProperties, - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + includeTimeRange: false }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js b/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js index 34c345f6e..e66b5877d 100644 --- a/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js @@ -1,3 +1,4 @@ +import _ from 'lodash' import moment from 'moment' import api from '~/apiRegistry' @@ -5,6 +6,7 @@ import {recipeActionBuilder} from '~/app/home/body/process/recipe' import {defaultModel as defaultOpticalModel} from '~/app/home/body/process/recipe/opticalMosaic/opticalMosaicRecipe' import {defaultModel as defaultPlanetModel} from '~/app/home/body/process/recipe/planetMosaic/planetMosaicRecipe' import {defaultModel as defaultRadarModel} from '~/app/home/body/process/recipe/radarMosaic/radarMosaicRecipe' +import {getTaskInfo} from '~/app/home/body/process/recipe/recipeOutputPath' import {publishEvent} from '~/eventPublisher' import {msg} from '~/translate' @@ -63,21 +65,30 @@ export const RecipeActions = id => { const submitRetrieveRecipeTask = recipe => { const name = recipe.title || recipe.placeholder - const title = msg(['process.retrieve.form.task.SEPAL'], {name}) + const destination = 'SEPAL' + const taskTitle = msg(['process.retrieve.form.task.SEPAL'], {name}) const operation = 'timeseries.download' + const task = { operation, params: { - title, + title: taskTitle, description: name, - recipe, - ...recipe.ui.retrieveOptions, - indicator: recipe.ui.retrieveOptions.bands + image: { + ...recipe.ui.retrieveOptions, + recipe, + indicator: recipe.ui.retrieveOptions.bands + }, + taskInfo: getTaskInfo({ + recipe, + destination, + retrieveOptions: recipe.ui.retrieveOptions + }) } } publishEvent('submit_task', { recipe_type: recipe.type, - destination: 'SEPAL', + destination, data_set_type: recipe.model.dataSetType }) return api.tasks.submit$(task).subscribe() diff --git a/modules/gui/src/app/home/body/process/recipe/unsupervisedClassification/unsupervisedClassificationRecipe.js b/modules/gui/src/app/home/body/process/recipe/unsupervisedClassification/unsupervisedClassificationRecipe.js index 471c75733..5e4af2d6b 100644 --- a/modules/gui/src/app/home/body/process/recipe/unsupervisedClassification/unsupervisedClassificationRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/unsupervisedClassification/unsupervisedClassificationRecipe.js @@ -1,11 +1,8 @@ import _ from 'lodash' -import api from '~/apiRegistry' import {recipeActionBuilder} from '~/app/home/body/process/recipe' -import {getAllVisualizations} from '~/app/home/body/process/recipe/visualizations' -import {publishEvent} from '~/eventPublisher' +import {submitRetrieveRecipeTask as submitTask} from '~/app/home/body/process/recipe/recipeTaskSubmitter' import {selectFrom} from '~/stateUtils' -import {msg} from '~/translate' export const getDefaultModel = () => ({ sampling: { @@ -42,39 +39,11 @@ export const getMaxNumberofClusters = recipe => { : maxNumberOfClusters } -const submitRetrieveRecipeTask = recipe => { - const name = recipe.title || recipe.placeholder - const bands = ['class'] - const destination = recipe.ui.retrieveOptions.destination - const taskTitle = msg(['process.retrieve.form.task', destination], {name}) - const operation = `image.${destination}` - const recipeProperties = { - recipe_id: recipe.id, - recipe_projectId: recipe.projectId, - recipe_type: recipe.type, - recipe_title: recipe.title || recipe.placeholder, - ..._(recipe.model) - .mapValues(value => JSON.stringify(value)) - .mapKeys((_value, key) => `recipe_${key}`) - .value() - } - const task = { - operation, - params: { - title: taskTitle, - description: name, - image: { - ...recipe.ui.retrieveOptions, - recipe: _.omit(recipe, ['ui']), - bands: {selection: bands}, - visualizations: getAllVisualizations(recipe), - properties: recipeProperties, - } - } - } - publishEvent('submit_task', { - recipe_type: recipe.type, - destination +const submitRetrieveRecipeTask = recipe => + submitTask(recipe, { + includeTimeRange: false, + customizeImage: image => ({ + ...image, + bands: {selection: ['class']} + }) }) - return api.tasks.submit$(task).subscribe() -} diff --git a/modules/task/src/tasks/ccdcAssetExport.js b/modules/task/src/tasks/ccdcAssetExport.js index 283ad57ee..97e4dfd24 100644 --- a/modules/task/src/tasks/ccdcAssetExport.js +++ b/modules/task/src/tasks/ccdcAssetExport.js @@ -7,7 +7,8 @@ const {formatProperties} = require('./formatProperties') const {setWorkloadTag} = require('./workloadTag') module.exports = { - submit$: (taskId, {recipe, bands, scale, visualizations, properties, ...other}) => { + submit$: (taskId, {image, description}) => { + const {recipe, bands, scale, visualizations, properties, ...other} = image setWorkloadTag(recipe) const segments = ccdc(recipe, {selection: bands}) return forkJoin({ @@ -19,6 +20,7 @@ module.exports = { const allBands = getAllBands(bands) return exportImageToAsset$(taskId, { ...other, + description, image: segments, region: geometry.bounds(), scale, diff --git a/modules/task/src/tasks/imageDriveExport.js b/modules/task/src/tasks/imageDriveExport.js index 62d7d997d..fd495d683 100644 --- a/modules/task/src/tasks/imageDriveExport.js +++ b/modules/task/src/tasks/imageDriveExport.js @@ -1,23 +1,22 @@ const {forkJoin, switchMap} = require('rxjs') -const moment = require('moment') const {exportImageToDrive$} = require('../jobs/export/toDrive') const ImageFactory = require('#sepal/ee/imageFactory') const {getCurrentContext$} = require('#task/jobs/service/context') const {setWorkloadTag} = require('./workloadTag') module.exports = { - submit$: (taskId, {image: {recipe, bands, ...retrieveOptions}}) => { + submit$: (taskId, {image: {recipe, bands, driveFolder: folder, ...retrieveOptions}}) => { setWorkloadTag(recipe) return getCurrentContext$().pipe( switchMap(() => { const description = recipe.title || recipe.placeholder - return export$(taskId, {description, recipe, bands, ...retrieveOptions}) + return export$(taskId, {description, recipe, bands, folder, ...retrieveOptions}) }) ) } } -const export$ = (taskId, {description, recipe, bands, scale, ...retrieveOptions}) => { +const export$ = (taskId, {description, recipe, bands, scale, folder, ...retrieveOptions}) => { const factory = ImageFactory(recipe, bands) return forkJoin({ image: factory.getImage$(), @@ -26,7 +25,7 @@ const export$ = (taskId, {description, recipe, bands, scale, ...retrieveOptions} switchMap(({image, geometry}) => exportImageToDrive$(taskId, { image, - folder: `${description}_${moment().format('YYYY-MM-DD_HH:mm:ss.SSS')}`, + folder, ...retrieveOptions, description, region: geometry.bounds(scale), diff --git a/modules/task/src/tasks/imageSepalExport.js b/modules/task/src/tasks/imageSepalExport.js index de7824067..2f2d38175 100644 --- a/modules/task/src/tasks/imageSepalExport.js +++ b/modules/task/src/tasks/imageSepalExport.js @@ -1,6 +1,6 @@ -const {concat, forkJoin, switchMap} = require('rxjs') +const {concat, forkJoin, switchMap, finalize} = require('rxjs') const moment = require('moment') -const {mkdirSafe$} = require('#task/rxjs/fileSystem') +const {mkdir$, createLock$, releaseLock$} = require('#task/rxjs/fileSystem') const {createVrt$, setBandNames$} = require('#sepal/gdal') const {exportImageToSepal$} = require('../jobs/export/toSepal') const ImageFactory = require('#sepal/ee/imageFactory') @@ -8,27 +8,36 @@ const {getCurrentContext$} = require('#task/jobs/service/context') const {setWorkloadTag} = require('./workloadTag') module.exports = { - submit$: (taskId, {image: {recipe, workspacePath, bands, ...retrieveOptions}}) => { + submit$: (taskId, {image: {recipe, workspacePath, bands, filenamePrefix, ...retrieveOptions}}) => { setWorkloadTag(recipe) return getCurrentContext$().pipe( switchMap(({config}) => { const description = recipe.title || recipe.placeholder + const exportPrefix = filenamePrefix || description const preferredDownloadDir = workspacePath ? `${config.homeDir}/${workspacePath}/` : `${config.homeDir}/downloads/${description}/` - return mkdirSafe$(preferredDownloadDir, {recursive: true}).pipe( - switchMap(downloadDir => concat( - export$(taskId, {description, recipe, downloadDir, bands, ...retrieveOptions}), - postProcess$({description, downloadDir, bands}) - ) - ) + // the UI already validated the path here, no need to have mkdirsafe here + return mkdir$(preferredDownloadDir, {recursive: true}).pipe( + switchMap(downloadDir => { + return createLock$(downloadDir).pipe( + switchMap(() => { + return concat( + export$(taskId, {description, exportPrefix, recipe, downloadDir, bands, ...retrieveOptions}), + postProcess$({description: exportPrefix, downloadDir, bands}) + ).pipe( + finalize(() => releaseLock$(downloadDir).subscribe()) + ) + }) + ) + }) ) }) ) } } -const export$ = (taskId, {description, recipe, bands, scale, ...retrieveOptions}) => { +const export$ = (taskId, {description, exportPrefix, recipe, bands, scale, ...retrieveOptions}) => { const factory = ImageFactory(recipe, bands) return forkJoin({ image: factory.getImage$(), @@ -39,7 +48,7 @@ const export$ = (taskId, {description, recipe, bands, scale, ...retrieveOptions} image, folder: `${description}_${moment().format('YYYY-MM-DD_HH:mm:ss.SSS')}`, ...retrieveOptions, - description, + description: exportPrefix, region: geometry.bounds(scale), scale }) diff --git a/modules/task/src/tasks/timeSeriesSepalExport.js b/modules/task/src/tasks/timeSeriesSepalExport.js index caab64b40..849243460 100644 --- a/modules/task/src/tasks/timeSeriesSepalExport.js +++ b/modules/task/src/tasks/timeSeriesSepalExport.js @@ -4,8 +4,8 @@ const {hasImagery: hasRadarImagery} = require('#sepal/ee/radar/collection') const {hasImagery: hasPlanetImagery} = require('#sepal/ee/planet/collection') const tile = require('#sepal/ee/tile') const {exportImageToSepal$} = require('../jobs/export/toSepal') -const {mkdirSafe$} = require('#task/rxjs/fileSystem') -const {concat, forkJoin, from, of, map, mergeMap, scan, switchMap, tap} = require('rxjs') +const {mkdir$, createLock$, releaseLock$} = require('#task/rxjs/fileSystem') +const {concat, forkJoin, from, of, map, mergeMap, scan, switchMap, tap, finalize} = require('rxjs') const {swallow} = require('#sepal/rxjs') const Path = require('path') const {terminal$} = require('#sepal/terminal') @@ -22,16 +22,29 @@ const DATE_DELTA = 3 const DATE_DELTA_UNIT = 'months' module.exports = { - submit$: (taskId, {workspacePath, description, ...retrieveOptions}) => { + submit$: (taskId, {description, image: {workspacePath, filenamePrefix, ...retrieveOptions}}) => { setWorkloadTag(retrieveOptions.recipe) return getCurrentContext$().pipe( switchMap(({config}) => { + const exportPrefix = filenamePrefix || description const preferredDownloadDir = workspacePath ? `${config.homeDir}/${workspacePath}/` : `${config.homeDir}/downloads/${description}/` - return mkdirSafe$(preferredDownloadDir, {recursive: true}).pipe( - switchMap(downloadDir => export$(taskId, {description, downloadDir, ...retrieveOptions}) - ) + // the UI already validated the path here, no need to have mkdirsafe here + return mkdir$(preferredDownloadDir, {recursive: true}).pipe( + switchMap(downloadDir => { + return createLock$(downloadDir).pipe( + switchMap(lockPath => { + log.debug('Created lock for time series export', {lockPath}) + return export$(taskId, {description: exportPrefix, downloadDir, ...retrieveOptions}).pipe( + finalize(() => { + log.debug('Releasing lock for time series export', {lockPath}) + releaseLock$(downloadDir).subscribe() + }) + ) + }) + ) + }) ) }) ) From 71e406cc773e648882352ef6bc86e2515d06f8d3 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 18:49:15 +0100 Subject: [PATCH 06/10] Added fileNamePrefix to sepal exportations --- .../mosaic/panels/retrieve/retrievePanel.jsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/modules/gui/src/app/home/body/process/recipe/mosaic/panels/retrieve/retrievePanel.jsx b/modules/gui/src/app/home/body/process/recipe/mosaic/panels/retrieve/retrievePanel.jsx index 895746335..e09dceeec 100644 --- a/modules/gui/src/app/home/body/process/recipe/mosaic/panels/retrieve/retrievePanel.jsx +++ b/modules/gui/src/app/home/body/process/recipe/mosaic/panels/retrieve/retrievePanel.jsx @@ -56,6 +56,8 @@ const fields = { .skip((v, {destination}) => destination !== 'GEE') .number() .notBlank(), + filenamePrefix: new Form.Field() + .skip((v, {destination}) => destination !== 'SEPAL'), crs: new Form.Field() .notBlank(), crsTransform: new Form.Field() @@ -74,7 +76,9 @@ const mapStateToProps = state => ({ }) const mapRecipeToProps = recipe => ({ - projectId: recipe.projectId + projectId: recipe.projectId, + recipeTitle: recipe.title, + recipePlaceholder: recipe.placeholder }) class _MosaicRetrievePanel extends React.Component { @@ -118,6 +122,7 @@ class _MosaicRetrievePanel extends React.Component { {this.renderScale()} {toEE && toSepal && this.renderDestination()} {destination.value === 'SEPAL' ? this.renderWorkspaceDestination() : null} + {destination.value === 'SEPAL' ? this.renderFilenamePrefix() : null} {destination.value === 'GEE' ? this.renderAssetType() : null} {destination.value === 'GEE' ? this.renderAssetDestination() : null} {destination.value === 'GEE' ? this.renderSharing() : null} @@ -239,6 +244,18 @@ class _MosaicRetrievePanel extends React.Component { /> ) } + + renderFilenamePrefix() { + const {inputs: {filenamePrefix}} = this.props + return ( + + ) + } renderAssetDestination() { const {inputs: {assetId, assetType, strategy}} = this.props @@ -335,7 +352,7 @@ class _MosaicRetrievePanel extends React.Component { componentDidMount() { const {allBands, defaultAssetType, defaultCrs, defaultScale, defaultShardSize, defaultFileDimensionsMultiple, defaultTileSize, - inputs: {assetType, sharing, crs, crsTransform, scale, shardSize, fileDimensionsMultiple, tileSize, useAllBands} + inputs: {assetType, sharing, crs, crsTransform, scale, shardSize, fileDimensionsMultiple, tileSize, useAllBands, filenamePrefix} } = this.props const more = (crs.value && crs.value !== defaultCrs) || (crsTransform.value) @@ -367,6 +384,10 @@ class _MosaicRetrievePanel extends React.Component { if (allBands) { useAllBands.set(true) } + if (!filenamePrefix.value) { + const recipeName = this.getRecipeName() + filenamePrefix.set(recipeName) + } this.update() } @@ -407,6 +428,11 @@ class _MosaicRetrievePanel extends React.Component { const {projects, projectId} = this.props return projects.find(({id}) => id === projectId) } + + getRecipeName() { + const {recipeTitle, recipePlaceholder} = this.props + return recipeTitle || recipePlaceholder || 'sepal_export' + } } export const MosaicRetrievePanel = compose( From 46c2fc466c5b220489ca6178d15a7047525819f0 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Tue, 10 Mar 2026 18:49:38 +0100 Subject: [PATCH 07/10] Validated the destination path in the UI before creating directories in imageSepalExport.js and timeSeriesSepalExport.js --- modules/gui/src/api/userFiles.js | 8 +- .../gui/src/widget/workspaceDestination.jsx | 175 ++++++++---------- modules/task/src/rxjs/fileSystem.js | 21 ++- 3 files changed, 107 insertions(+), 97 deletions(-) diff --git a/modules/gui/src/api/userFiles.js b/modules/gui/src/api/userFiles.js index 56cea14d5..833f89bc6 100644 --- a/modules/gui/src/api/userFiles.js +++ b/modules/gui/src/api/userFiles.js @@ -1,6 +1,12 @@ +import {get$} from '~/http-client' + import {moduleWebSocket$} from './ws.js' export default { ws: () => moduleWebSocket$('user-files'), - downloadUrl: path => `/api/user-files/download?path=${encodeURIComponent(path)}` + downloadUrl: path => `/api/user-files/download?path=${encodeURIComponent(path)}`, + listFiles$: (path, options = {}) => + get$('/api/user-files/listFiles', { + query: {path, ...options}, + }), } diff --git a/modules/gui/src/widget/workspaceDestination.jsx b/modules/gui/src/widget/workspaceDestination.jsx index 5bbc4fe14..f3fce8433 100644 --- a/modules/gui/src/widget/workspaceDestination.jsx +++ b/modules/gui/src/widget/workspaceDestination.jsx @@ -1,14 +1,20 @@ import PropTypes from 'prop-types' import React from 'react' +import {catchError, debounceTime, EMPTY, map, Subject, switchMap, tap} from 'rxjs' +import api from '~/apiRegistry' import {withRecipe} from '~/app/home/body/process/recipeContext' import {compose} from '~/compose' import {connect} from '~/connect' import {selectFrom} from '~/stateUtils' import {toSafeString} from '~/string' +import {withSubscriptions} from '~/subscription' +import {msg} from '~/translate' import {currentUser} from '~/user' import {Form} from '~/widget/form' +const DEBOUNCE_TIME_MS = 750 + const mapStateToProps = state => ({ projects: selectFrom(state, 'process.projects') }) @@ -20,92 +26,77 @@ const mapRecipeToProps = recipe => ({ }) class _WorkspaceDestination extends React.Component { + workspacePath$ = new Subject() + state = { - currentType: undefined + currentType: undefined, + loading: null } constructor(props) { super(props) - // this.onLoading = this.onLoading.bind(this) - // this.onError = this.onError.bind(this) + this.onError = this.onError.bind(this) + this.onWorkspaceChecked = this.onWorkspaceChecked.bind(this) } render() { - return this.renderInput() - // const {currentType} = this.state - // const showStrategy = ['Image', 'ImageCollection'].includes(currentType) - // return showStrategy - // ? ( - // - // {this.renderInput()} - // {this.renderStrategy()} - // - // ) - // : this.renderInput() - } - - renderInput() { const {workspacePathInput, label, placeholder, autoFocus} = this.props + const {loading} = this.state return ( this.onLoaded(metadata?.type)} - // onError={this.onError} + busyMessage={loading && msg('widget.loading')} /> ) } - // renderStrategy() { - // const {strategyInput, type} = this.props - // const {currentType} = this.state - // const options = [ - // { - // value: 'resume', - // label: msg('widget.workspaceDestination.resume.label'), - // tooltip: msg('widget.workspaceDestination.resume.tooltip'), - // disabled: !currentType || type !== 'ImageCollection' - // }, - // { - // value: 'replace', - // label: msg('widget.workspaceDestination.replace.label'), - // tooltip: msg('widget.workspaceDestination.replace.tooltip') - // } - // ].filter(({value}) => value !== 'resume' || type === 'ImageCollection') - // return ( - // - // ) - // } - componentDidMount() { const {workspacePathInput} = this.props + this.loadWorkspaceContent() if (!workspacePathInput.value) { workspacePathInput.set(this.defaultWorkspacePath() || null) + } else { + this.checkWorkspacePath(workspacePathInput.value) } } - // componentDidUpdate(prevProps) { - // const {workspacePathInput, strategyInput, type} = this.props - // const {currentType} = this.state - // if (currentType && strategyInput.value && workspacePathInput.error) { - // workspacePathInput.setInvalid(null) - // } - // if (prevProps.type !== type && strategyInput.value === 'resume') { - // // Switching type and resume strategy, we have to reset it prevent invalid strategy - // strategyInput.set(null) - // this.onLoaded(currentType) - // } - // } + componentDidUpdate(prevProps) { + const {workspacePathInput} = this.props + const {workspacePathInput: prevWorkspacePathInput} = prevProps + if (workspacePathInput?.value !== prevWorkspacePathInput?.value) { + this.checkWorkspacePath(workspacePathInput.value) + } + } + + loadWorkspaceContent() { + const {addSubscription} = this.props + addSubscription( + this.workspacePath$.pipe( + debounceTime(DEBOUNCE_TIME_MS), + tap(() => { + this.setState({loading: true}) + }), + switchMap(path => + api.userFiles.listFiles$(path, {includeHidden: true}).pipe( + tap(() => { + this.setState({loading: null}) + }), + catchError(error => { + this.setState({loading: null}) + this.onError(error) + return EMPTY + }), + map(response => response) + ) + ) + ).subscribe( + response => this.onWorkspaceChecked(response) + ) + ) + } defaultWorkspacePath() { const {recipeName} = this.props @@ -127,51 +118,45 @@ class _WorkspaceDestination extends React.Component { return projects.find(({id}) => id === projectId) } - // onLoading() { - // const {strategyInput} = this.props - // strategyInput.set(null) - // this.setState({currentType: null}) - // } - - // onLoaded(currentType) { - // const {workspacePathInput, strategyInput} = this.props - // this.setState({currentType}) - // if (currentType) { - // workspacePathInput.setInvalid(msg( - // ['Image', 'ImageCollection'].includes(currentType) - // ? 'widget.workspaceDestination.exists.replaceable' - // : 'widget.workspaceDestination.exists.notReplaceable' - // )) - // } else { - // strategyInput.set('new') - // } - // } - - // onError(error) { - // const {workspacePathInput, onError} = this.props - // if (error.status === 404) { - // this.onLoaded() - // } else { - // onError && onError(error) - // workspacePathInput.setInvalid( - // error.response && error.response.messageKey - // ? msg(error.response.messageKey, error.response.messageArgs, error.response.defaultMessage) - // : msg('widget.workspacePathInput.loadError') - // ) - // } - // } + onWorkspaceChecked(response) { + const {workspacePathInput} = this.props + if (response && response.count > 0) { + const hasPendingTask = response.files?.some(file => file.name === '.task_pending') + if (hasPendingTask) { + workspacePathInput.setInvalid(msg('widget.workspaceDestination.taskPending')) + } else { + workspacePathInput.setInvalid(msg('widget.workspaceDestination.notEmpty')) + } + } else { + workspacePathInput.setInvalid(null) + } + } + + onError(error) { + const {workspacePathInput} = this.props + if (error.status === 404) { + workspacePathInput.setInvalid(null) + } else { + workspacePathInput.setInvalid(msg('widget.workspaceDestination.loadError')) + } + } + + checkWorkspacePath(path) { + if (path) { + this.workspacePath$.next(path) + } + } } export const WorkspaceDestination = compose( _WorkspaceDestination, + withSubscriptions(), connect(mapStateToProps), withRecipe(mapRecipeToProps) ) WorkspaceDestination.propTypes = { workspacePathInput: PropTypes.object.isRequired, - // strategyInput: PropTypes.object.isRequired, - // type: PropTypes.any.isRequired, label: PropTypes.any, placeholder: PropTypes.string, tooltip: PropTypes.any diff --git a/modules/task/src/rxjs/fileSystem.js b/modules/task/src/rxjs/fileSystem.js index 46bed0e97..38e7648fe 100644 --- a/modules/task/src/rxjs/fileSystem.js +++ b/modules/task/src/rxjs/fileSystem.js @@ -47,4 +47,23 @@ const mkdirSafe$ = (preferredPath, options = {}) => defer(() => { ) }) -module.exports = {exists$, ls$, mkdir$, mkdirSafe$} +const createLock$ = dir => { + const lockPath = Path.join(dir, '.task_pending') + return defer(() => + fromPromise(fs.promises.writeFile(lockPath, '')).pipe( + map(() => lockPath) + ) + ) +} + +const releaseLock$ = dir => { + const lockPath = Path.join(dir, '.task_pending') + return defer(() => + fromPromise(fs.promises.unlink(lockPath)).pipe( + map(() => lockPath), + catchError(() => of(null)) + ) + ) +} + +module.exports = {exists$, ls$, mkdir$, mkdirSafe$, createLock$, releaseLock$} From f8f0fd99bae32581d14e1a34d97ea1274c21a9e4 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Wed, 11 Mar 2026 10:29:39 +0100 Subject: [PATCH 08/10] Fixed spaces in export locale --- modules/gui/src/locale/en/translations.json | 10440 +++++++++--------- 1 file changed, 5220 insertions(+), 5220 deletions(-) diff --git a/modules/gui/src/locale/en/translations.json b/modules/gui/src/locale/en/translations.json index 77c3b8940..0584a5316 100644 --- a/modules/gui/src/locale/en/translations.json +++ b/modules/gui/src/locale/en/translations.json @@ -1,5467 +1,5467 @@ { - "action": { - "error": { - "title": "Failed: {action}" - }, - "type": { - "AUTO_SELECT_SCENES": "Auto-select scenes", - "DUPLICATE_RECIPE": "Duplicate recipe", - "INIT_GOOGLE_MAPS_API": "Initialize Google Maps", - "LOAD_APPS": "Load applications", - "LOAD_COUNTRIES": "Load countries", - "LOAD_COUNTRY_AREAS": "Load areas of country", - "LOAD_CURRENT_USER": "Load current user", - "LOAD_DIRECTORY": "Load user directory", - "LOAD_FUSION_TABLE_COLUMNS": "Load Fusion Table columns", - "LOAD_FUSION_TABLE_ROWS": "Load Fusion Table rows", - "LOAD_RECIPE": "Load recipe", - "LOAD_RECIPES": "Load recipes", - "LOAD_SCENE_AREAS": "Load scene areas", - "LOAD_SCENES": "Load scenes", - "LOGIN": "Login", - "REMOVE_DIRECTORY": "Remove directory", - "REMOVE_FILE": "Remove file", - "REMOVE_TASK": "Remove task", - "REQUEST_PASSWORD_RESET": "Request password reset", - "RESET_PASSWORD": "Reset password", - "RESTART_TASK": "Restart task", - "RUN_APP": "Open application", - "SCHEDULE_UPDATE_TASKS": "Update background tasks", - "SCHEDULE_USER_INFO_REFRESH": "Schedule user info request", - "STOP_TASK": "Stop task", - "VALIDATE_TOKEN": "Validate link" + "action": { + "error": { + "title": "Failed: {action}" + }, + "type": { + "AUTO_SELECT_SCENES": "Auto-select scenes", + "DUPLICATE_RECIPE": "Duplicate recipe", + "INIT_GOOGLE_MAPS_API": "Initialize Google Maps", + "LOAD_APPS": "Load applications", + "LOAD_COUNTRIES": "Load countries", + "LOAD_COUNTRY_AREAS": "Load areas of country", + "LOAD_CURRENT_USER": "Load current user", + "LOAD_DIRECTORY": "Load user directory", + "LOAD_FUSION_TABLE_COLUMNS": "Load Fusion Table columns", + "LOAD_FUSION_TABLE_ROWS": "Load Fusion Table rows", + "LOAD_RECIPE": "Load recipe", + "LOAD_RECIPES": "Load recipes", + "LOAD_SCENE_AREAS": "Load scene areas", + "LOAD_SCENES": "Load scenes", + "LOGIN": "Login", + "REMOVE_DIRECTORY": "Remove directory", + "REMOVE_FILE": "Remove file", + "REMOVE_TASK": "Remove task", + "REQUEST_PASSWORD_RESET": "Request password reset", + "RESET_PASSWORD": "Reset password", + "RESTART_TASK": "Restart task", + "RUN_APP": "Open application", + "SCHEDULE_UPDATE_TASKS": "Update background tasks", + "SCHEDULE_USER_INFO_REFRESH": "Schedule user info request", + "STOP_TASK": "Stop task", + "VALIDATE_TOKEN": "Validate link" + } + }, + "apps": { + "count": "{count} {count, plural, one {app} other {apps}}", + "developedBy": "Developed by {author}", + "filter": { + "search": { + "placeholder": "Search apps" + }, + "tag": { + "ignore.label": "All" + } + }, + "googleAccountRequired": "Some apps are unavailable. To enable them, connect your SEPAL account to your Google account.", + "info": "Show information about this app", + "initializing": "Starting session...", + "loading": { + "error": "Failed to load apps. Please reload page to try again.", + "progress": "Loading..." + }, + "notRunning": "App is not running", + "run": { + "error": "Failed to run {label}" + }, + "running": "App is running", + "noInstanceRequired": "No instance required", + "admin": { + "title": "{app} management panel", + "logs": { + "title": "Logs", + "empty": "No logs available" + }, + "container": { + "title": "Container info", + "notFound": "Container not found", + "status": "Status", + "containerStatus": "Container status", + "mainProcess": "Main process", + "memoryUsage": "Memory usage", + "cpuUsage": "CPU usage", + "connections": "Active connections" + }, + "repo": { + "title": "Repository", + "url": "Repository origin", + "lastCommit": "Last commit", + "lastClone": "Repository last cloned", + "error": "Error", + "branch": "Branch" + }, + "health": { + "healthy": "Healthy", + "starting": "Starting", + "unhealthy": "Unhealthy" + }, + "restart": { + "error": "Failed to restart", + "success": "Restarted" + }, + "buildRestart": { + "error": "Failed to build and restart", + "success": "Built and restarted successfully" + }, + "button": { + "restart": "Restart container", + "buildRestart": "Build and restart container", + "reloadStatus": "Reload status", + "reloadLogs": "Reload logs" + }, + "status": { + "running": "Running", + "idle": "Idle", + "error": "Error", + "checking": "Checking", + "notAvailable": "Not available", + "loading": "Loading...", + "exited": "Exited" + }, + "timestamp": { + "never": "Never" + }, + "update": { + "available": "Update available", + "checkForUpdates": "Check for updates", + "error": "Failed to update", + "success": "Updated successfully", + "noChanges": "No updates available" + }, + "tooltip": "Manage app" + } + }, + "asset": { + "failedToLoad": "Failed to load EE asset", + "failedToLoadSome": "Failed to load some EE assets", + "notFound": "Asset not found", + "unauthorized": "You don't have access to this asset", + "folder": "Folder", + "image": "Image", + "imageCollection": "Image collection", + "table": "Table", + "classifier": "Classifier", + "new": "New asset", + "recentAssets": "Recent assets", + "userAssets": "User assets", + "otherAssets": "Other assets", + "settings": "Asset settings", + "datasets": { + "community": { + "label": "Awesome GEE Community Datasets ({matchingResults} matches)", + "tooltip": "The awesome-gee-community-datasets are community-sourced geospatial datasets made available for use by the larger Google Earth Engine community and shared publicly as Earth Engine assets." + }, + "gee": { + "label": "GEE Catalog ({matchingResults} matches)", + "tooltip": "Earth Engine's public data archive includes more than forty years of historical imagery and scientific datasets, updated and expanded daily." + }, + "failedToLoad": "Failed to load Awesome GEE Community Datasets" + }, + "copyId": { + "tooltip": "Copy asset id to clipboard", + "success": "Asset id copied to clipboard" + }, + "reload": { + "tooltip": "Reload assets", + "progress": "Reloading... {count}" + }, + "browser": { + "tooltip": "Open asset browser", + "title": "Asset browser", + "assetName": "Name", + "asset": "Asset", + "filter.placeholder": "Filter by id", + "quota": "Quota: {quota}" + } + }, + "bands": { + "aerosol": "Aerosol", + "blue": "Blue", + "brightness": "Brightness", + "cirrus": "Cirrus", + "dayOfYear": "Day of year", + "daysFromTarget": "Days from target", + "fifth": "Fifth", + "fourth": "Fourth", + "green": "Green", + "greenness": "Greenness", + "nir": "NIR", + "pan": "Pan", + "red": "Red", + "redEdge1": "Red edge 1", + "redEdge2": "Red edge 2", + "redEdge3": "Red edge 3", + "redEdge4": "Red edge 4", + "sixth": "Sixth", + "swir1": "SWIR 1", + "swir2": "SWIR 2", + "thermal": "Thermal", + "thermal2": "Thermal 2", + "unixTimeDays": "Date", + "waterVapor": "Water vapor", + "wetness": "Wetness", + "ndvi": "Normalized Difference Vegetation Index: (nir - red) / (nir + red)", + "ndmi": "Normalized Difference Moisture Index: (nir - swir1) / (nir + swir1)", + "ndwi": "Normalized Difference Water Index: (green - nir) / (green + nir)", + "mndwi": "Modified Normalized Difference Water Index: (green - swir1) / (green + swir1)", + "ndfi": "Normalized Difference Fraction Index", + "evi": "Enhanced Vegetation Index: 2.5 * (nir - red) / (nir + 6 * red - 7.5 * blue + 1)", + "evi2": "Enhanced Vegetation Index 2: 2.5 * (nir - red) / (nir + 2.4 * red + 1)", + "savi": "Soil-Adjusted Vegetation Index: (1.5 * (nir - red) / (nir + red + 0.5)", + "nbr": "Normalized Burn Ratio: (nir - swir2) / (nir + swir2)", + "mvi": "Mangrove Vegetation Index: 0.1 * (nir - green) / abs(swir1 - green)", + "ui": "Urban Index: (swir2 - nir) / (swir2 + nir)", + "ndbi": "Normalized Difference Built-up Index: (swir1 - nir) / (swir1 + nir)", + "ibi": "Index-based Built-up Index: (ndbi - (savi + mndwi) / 2) / (ndbi + (savi + mndwi) / 2)", + "nbi": "New Built-up Index: red * swir1 / nir", + "ebbi": "Enhanced Built-Up and Bareness Index: (swir1 - nir) / (10 * sqrt(swir1 + thermal))", + "bui": "Built-Up Index: (red - swir1) / (red + swir1) + (swir2 - swir1) / (swir2 + swir1)", + "kndvi": "Kernel Normalized Difference Vegetation Index", + "red, green, blue": "Natural color: red, green, blue", + "nir, red, green": "False color: nir, red, green", + "nir, swir1, red": "False color: nir, swir1, red", + "swir2, nir, red": "False color: swir2, nir, red", + "swir2, swir1', red": "False color: swir2, swir1', red", + "swir2, nir, green": "False color: swir2, nir, green", + "brightness, greenness, wetness": "Tasseled cap: brightness, greenness, wetness", + "fifth, sixth, fourth": "Tasseled cap: fifth, sixth, fourth" + }, + "body": { + "loading-apps": "Loading apps...", + "loading-initializing-google-maps": "Initializing map...", + "starting-sepal": "Starting SEPAL...", + "starting-sepal-failed": "Something has gone wrong. Please reload the page and try again." + }, + "browse": { + "addBrowser": { + "files": "SEPAL workspace browser", + "assets": "Earth Engine asset browser" + }, + "title": { + "files": "SEPAL workspace", + "assets": "Earth Engine assets" + }, + "controls": { + "clearSelection": { + "tooltip": "Clear selection" + }, + "download": { + "tooltip": "Download selected item" + }, + "createFolder": { + "tooltip": "Create folder" + }, + "remove": { + "tooltip": "Remove selected items" + }, + "dotFiles": { + "label": "dotfiles", + "hide.tooltip": "Hide files beginning with a dot", + "show.tooltip": "Show files beginning with a dot" + }, + "collapseDirs": { + "label": "collapse", + "tooltip": "Collapse all folders" + }, + "expandDirs": { + "label": "expand", + "tooltip": "Expand all folders" + }, + "splitDirs": { + "label": "split", + "split.tooltip": "Split directories and files", + "mix.tooltip": "Mix directories and files" + }, + "sorting": { + "date.label": "Date", + "name.label": "Name" + } + }, + "info": { + "directory": "{itemCount} {itemCount, plural, one {item} other {items}}", + "empty": "empty" + }, + "loading": { + "error": "Could not retrieve file information." + }, + "createFolder": { + "existing.error": "Cannot create a folder with the same name as an existing asset or folder", + "tooDeep.error": "Cannot create a folder more than 10 levels deep" + }, + "removeConfirmation": "Remove {directories} {directories, plural, one {directory} other {directories}} and {files} {files, plural, one {file} other {files}}?", + "removing": { + "error": "Could not remove files." + }, + "selected": "{directories} {directories, plural, one {dir} other {dirs}} and {files} {files, plural, one {file} other {files}} selected" + }, + "button": { + "add": "Add", + "apply": "Apply", + "back": "Back", + "cancel": "Cancel", + "close": "Close", + "confirm": "Confirm", + "copyToClipboard": "Copy to clipboard", + "discard": "Don't save", + "done": "Done", + "disabled": "Disabled", + "enabled": "Enabled", + "less": "Less", + "more": "More", + "next": "Next", + "ok": "OK", + "remove": "Remove", + "restart": "Restart", + "retry": "Retry", + "save": "Save", + "select": "Select", + "stop": "Stop" + }, + "clipboard": { + "copy": { + "success": "Copied to clipboard", + "failure": "Could not copy to clipboard" + } + }, + "connectionStatus": { + "disconnected": "Disconnected", + "connected": "Connected", + "partiallyConnected": "Connected, some services unavailable" + }, + "eeTable": { + "failedToLoad": "Failed to load EE Table", + "notFound": "EE Table not found", + "unauthorized": "You don't have access to this EE Table" + }, + "error": { + "badRequest": "Bad request", + "internal": "Internal error", + "notFound": "Not found" + }, + "fieldValidation": { + "date": "Must be a date", + "email": "Must be an email", + "greaterThan": "Must be greater than {minValue}", + "int": "Must be an integer", + "lessThan": "Must be less than {maxValue}", + "match": "Must match {regex}", + "max": "Max value is {maxValue}", + "min": "Min value is {minValue}", + "notBlank": "Cannot be blank", + "notEmpty": "Cannot be empty", + "number": "Must be a number" + }, + "footer": { + "buildNumber": "Build number", + "buildNumberCopied": "Build number copied to clipboard", + "gitCommit": "Git commit", + "gitCommitCopied": "Git commit copied to clipboard" + }, + "format": { + "dollars": "${value}", + "dollarsPerHour": "${value}/hr", + "GB": "{value}GB", + "hour": "{value}hr", + "percent": "{value}%" + }, + "fusionTable": { + "failedToLoad": "Failed to load Fusion Table", + "notFound": "Fusion Table not found", + "unauthorized": "You don't have access to this Fusion Table" + }, + "gee": { + "asset": { + "error": { + "wrongType": "Asset is of type {actualType} while the only allowed types are: {allowedTypes}." + } + }, + "classification": { + "error": { + "noTrainingData": "There is no training data in selected area of interest." + } + }, + "error": { + "earthEngineException": "{earthEngineMessage}", + "failedToLoadRecipe": "Failed to load recipe", + "title": "Google Earth Engine error" + }, + "image": { + "error": { + "notAnImage": "Not an image", + "notFound": "Asset not found" + } + }, + "table": { + "error": { + "notATable": "Not a Table", + "notFound": "Table not found" + } + } + }, + "home": { + "connectivityError": "You are currently unable to connect to the SEPAL server.", + "versionMismatch": { + "title": "SEPAL has been updated!", + "message": "Please reload your browser as soon as possible to make sure everything works as expected.", + "reloadNow": "Reload now" + }, + "sections": { + "account": "Account settings", + "app-launch-pad": "Apps", + "browse": "Files", + "collapse": "Automatically hide and show menu", + "expand": "Always show menu", + "help": "Open SEPAL documentation in a new tab/window", + "logout": "Logout from SEPAL", + "process": "Process", + "tasks": "Tasks", + "terminal": "Terminal", + "user": { + "messages": "Show user messages", + "profile": "Manage user profile", + "report": { + "budgetExceeded": "Exceeded", + "tooltip": "Show user report" } + }, + "users": "Users" + } + }, + "landing": { + "features": { + "apps": { + "description": "Extend the features in SEPAL with custom data processing applications", + "title": "Extensible with apps" + }, + "browse": { + "description": "Preview and download your products", + "title": "Browse your data" + }, + "process": { + "description": "Fast and easy processing of geo data", + "title": "Process geo data" + }, + "terminal": { + "description": "Powerful command-line tools for data processing", + "title": "Terminal" + } }, - "apps": { - "count": "{count} {count, plural, one {app} other {apps}}", - "developedBy": "Developed by {author}", - "filter": { - "search": { - "placeholder": "Search apps" - }, - "tag": { - "ignore.label": "All" - } + "forgot-password": { + "button": "Reset password", + "cancel-link": "Cancel", + "instructions": "Provide us with the email address of your SEPAL account and we'll send you an email with instructions on how to reset your password.", + "invalid": "Not a valid email", + "label": "Forgot your password ?", + "email.placeholder": "Enter your email", + "required": "Email is required", + "success": "An email with password reset instructions has been sent to {email}.", + "error": "Cannot reset password." + }, + "intro": { + "about": { + "description": "SEPAL is an opensource project by the Open Foris team in Forestry Department of the United Nations Food and Agriculture Organization (FAO), funded by the Government of Norway.", + "title": "About" + }, + "computingPower": { + "description": "Harness high performance cloud-based computing and modern geospatial data infrastructures.", + "title": "Computing power" + }, + "easeOfUse": { + "description": "Allows users to query and process satellite data quickly and efficiently, tailor their products for local needs, and produce sophisticated and relevant geospatial analyses quickly.", + "title": "Ease of use" + }, + "googleEarthEngine": { + "description": "Get access to Google Earth Engine's multi-petabyte catalog of satellite imagery and use their planetary-scale analysis capabilities. All without writing a single line of code. Just connect your Google account to SEPAL.", + "title": "Earth Engine" + }, + "integrations": { + "description": "SEPAL doesn't want to reinvent the wheel. We rather use and integrate with existing solutions, such as Open Foris Collect Earth Online, for visual interpretation of satellite imagery.", + "title": "Integrations" + }, + "jupyterNotebook": { + "description": "Run any of the geospatial processing notebooks in SEPAL's catalogue, or develop your own. The hosted Jupyter server comes with Python 3, R, and JavaScript kernels.", + "title": "Jupyter Notebook" + }, + "partners": { + "title": "Partners" + }, + "powerUsers": { + "description": "Get access to dedicated Linux instances, with up to 128 CPU cores, 2TB of RAM and a host of development and geospatial tools installed. Access it directly from within the browser, or through an SSH client. Transfer files to and from the instance with rsync, scp, or your favorite FTP client.", + "title": "Power Users" + }, + "rstudio": { + "description": "Develop your R scripts with RStudio, directly inside SEPAL. Use any of the many useful R packages already installed, and install your own when you need to.", + "title": "RStudio Server" + }, + "shiny": { + "description": "Perform stratified area estimation, time-series analysis with BFAST, and other geospatial processing through the R Shiny apps hosted in SEPAL.", + "title": "Shiny Server" + } + }, + "launch": "Launch", + "documentation": "Documentation", + "loadCurrentUser": { + "error": "Failed to connect to SEPAL" + }, + "signup": { + "button": "Sign up", + "username": { + "label": "Username", + "placeholder": "Choose your username", + "required": "Username is required", + "format": "Username must start with a letter and can only contain letters and digits", + "duplicate": "This username is not available, please choose another one", + "cannotValidate": "Cannot validate username" + }, + "name": { + "label": "Name", + "placeholder": "Enter your full name", + "required": "Name is required", + "invalid": "" + }, + "email": { + "label": "Email", + "placeholder": "Enter your email address", + "required": "Email is required", + "format": "Please enter a valid email address", + "duplicate": "This email is not available, please choose another one", + "cannotValidate": "Cannot validate email" + }, + "organization": { + "label": "Organization", + "placeholder": "Enter your organization", + "required": "Organization is required", + "invalid": "" + }, + "intendedUse": { + "label": "Intended use", + "placeholder": "What do you plan to do with SEPAL?", + "required": "Intended use is required", + "invalid": "" + }, + "success": "You've successfully signed up to SEPAL. An activation email has been sent to {email}." + }, + "login": { + "button": "Login", + "error": "An error occurred during authentication.", + "forgot-password-link": "Forgot password?", + "password": { + "invalid": "Invalid username/password", + "label": "Password", + "placeholder": "Enter your password", + "required": "Password is required" + }, + "sign-up": "Sign up", + "username": { + "label": "Username", + "placeholder": "Enter your username", + "required": "Username is required" + } + }, + "privacyPolicy": "Privacy policy", + "reset-password": { + "button": "Set password", + "password": { + "invalid": "Password must be at least 12 characters long", + "label": "Password", + "placeholder": "Enter a new password", + "required": "Password is required" + }, + "password2": { + "label": "Confirm password", + "not-matching": "Passwords are not the same", + "placeholder": "Enter the password again", + "required": "Password must be confirmed" + }, + "success": "Password has been reset", + "error": "Password could not be reset", + "username": { + "label": "Username" + }, + "validating-link": "Validating link..." + }, + "tagline": "System for earth observations, data access, processing & analysis for land monitoring.", + "title": "SEPAL", + "validate-token": { + "error": "This link is not valid anymore. Make sure you use the link in the latest email you received from SEPAL." + } + }, + "map": { + "info": { + "tooltip": "Show coordinates", + "center": "Center", + "bounds": "Bounds", + "copy": "Copy to clipboard", + "coordinatesCopied": "Coordinates copied to the clipboard" + }, + "legendBuilder": { + "addEntry": { + "tooltip": "Add legend entry" + }, + "colors": { + "edit": { + "tooltip": "Change color" }, - "googleAccountRequired": "Some apps are unavailable. To enable them, connect your SEPAL account to your Google account.", - "info": "Show information about this app", - "initializing": "Starting session...", - "loading": { - "error": "Failed to load apps. Please reload page to try again.", - "progress": "Loading..." + "swap": { + "tooltip": "Swap this color with another in the legend" + } + }, + "entry": { + "error": { + "duplicateColor": "There are more than one entry with this color", + "duplicateLabel": "There are more than one entry with this label", + "duplicateValue": "There are more than one entry with this value", + "invalidColor": "Another entry has the same color" }, - "notRunning": "App is not running", - "run": { - "error": "Failed to run {label}" + "remove": { + "tooltip": "Remove this legend entry" }, - "running": "App is running", - "noInstanceRequired" : "No instance required", - "admin": { - "title": "{app} management panel", - "logs": { - "title": "Logs", - "empty": "No logs available" - }, - "container": { - "title": "Container info", - "notFound": "Container not found", - "status": "Status", - "containerStatus": "Container status", - "mainProcess": "Main process", - "memoryUsage": "Memory usage", - "cpuUsage": "CPU usage", - "connections" : "Active connections" - }, - "repo": { - "title": "Repository", - "url": "Repository origin", - "lastCommit": "Last commit", - "lastClone": "Repository last cloned", - "error": "Error", - "branch" : "Branch" - }, - "health": { - "healthy": "Healthy", - "starting": "Starting", - "unhealthy": "Unhealthy" - }, - "restart": { - "error": "Failed to restart", - "success": "Restarted" - }, - "buildRestart": { - "error": "Failed to build and restart", - "success": "Built and restarted successfully" - }, - "button": { - "restart": "Restart container", - "buildRestart": "Build and restart container", - "reloadStatus": "Reload status", - "reloadLogs": "Reload logs" - }, - "status": { - "running": "Running", - "idle": "Idle", - "error": "Error", - "checking": "Checking", - "notAvailable": "Not available", - "loading": "Loading...", - "exited": "Exited" - }, - "timestamp": { - "never": "Never" - }, - "update": { - "available": "Update available", - "checkForUpdates": "Check for updates", - "error": "Failed to update", - "success": "Updated successfully", - "noChanges": "No updates available" - }, - "tooltip": "Manage app" + "classLabel": { + "placeholder": "Label..." } - }, - "asset": { - "failedToLoad": "Failed to load EE asset", - "failedToLoadSome": "Failed to load some EE assets", - "notFound": "Asset not found", - "unauthorized": "You don't have access to this asset", - "folder": "Folder", - "image": "Image", - "imageCollection": "Image collection", - "table": "Table", - "classifier": "Classifier", - "new": "New asset", - "recentAssets": "Recent assets", - "userAssets": "User assets", - "otherAssets": "Other assets", - "settings": "Asset settings", - "datasets": { - "community": { - "label": "Awesome GEE Community Datasets ({matchingResults} matches)", - "tooltip": "The awesome-gee-community-datasets are community-sourced geospatial datasets made available for use by the larger Google Earth Engine community and shared publicly as Earth Engine assets." - }, - "gee": { - "label": "GEE Catalog ({matchingResults} matches)", - "tooltip": "Earth Engine's public data archive includes more than forty years of historical imagery and scientific datasets, updated and expanded daily." - }, - "failedToLoad": "Failed to load Awesome GEE Community Datasets" + }, + "import": { + "title": "Import", + "file": { + "label": "CSV file" }, - "copyId": { - "tooltip": "Copy asset id to clipboard", - "success": "Asset id copied to clipboard" + "clipboard": { + "tooltip": "Read legend from the clipboard", + "disabledTooltip": "This browser doesn't allow reading from the clipboard" }, - "reload": { - "tooltip": "Reload assets", - "progress": "Reloading... {count}" + "colorColumnType": { + "label": "Color column format", + "tooltip": "Specify if the color is in a single column, or if red, green, blue are in separate column.", + "single": "Single column", + "multiple": "Multiple columns" }, - "browser": { - "tooltip": "Open asset browser", - "title": "Asset browser", - "assetName": "Name", - "asset": "Asset", - "filter.placeholder": "Filter by id", - "quota": "Quota: {quota}" + "column": { + "valueColumn": { + "label": "Value", + "placeholder": "Select...", + "tooltip": "Column with class values" + }, + "labelColumn": { + "label": "Label", + "placeholder": "Select...", + "tooltip": "Column with class labels" + }, + "colorColumn": { + "label": "Color", + "placeholder": "Select...", + "tooltip": "Column with class colors" + }, + "redColumn": { + "label": "Red", + "placeholder": "Select...", + "tooltip": "Column with red color channel values" + }, + "greenColumn": { + "label": "Green", + "placeholder": "Select...", + "tooltip": "Column with green color channel values" + }, + "blueColumn": { + "label": "Blue", + "placeholder": "Select...", + "tooltip": "Column with blue color channel values" + } + } + }, + "label": "Legend", + "load": { + "label": "Load...", + "options": { + "exportToCsv": { + "label": "Export as CSV" + }, + "importFromCsv": { + "label": "Import from CSV..." + }, + "imageValues": { + "label": "Distinct values from image band", + "loadError": "Failed to load distinct values from image band" + } } + }, + "noEntries": "No entries in the legend. Add at least one." }, - "bands": { - "aerosol": "Aerosol", - "blue": "Blue", - "brightness": "Brightness", - "cirrus": "Cirrus", - "dayOfYear": "Day of year", - "daysFromTarget": "Days from target", - "fifth": "Fifth", - "fourth": "Fourth", - "green": "Green", - "greenness": "Greenness", - "nir": "NIR", - "pan": "Pan", - "red": "Red", - "redEdge1": "Red edge 1", - "redEdge2": "Red edge 2", - "redEdge3": "Red edge 3", - "redEdge4": "Red edge 4", - "sixth": "Sixth", - "swir1": "SWIR 1", - "swir2": "SWIR 2", - "thermal": "Thermal", - "thermal2": "Thermal 2", - "unixTimeDays": "Date", - "waterVapor": "Water vapor", - "wetness": "Wetness", - "ndvi": "Normalized Difference Vegetation Index: (nir - red) / (nir + red)", - "ndmi": "Normalized Difference Moisture Index: (nir - swir1) / (nir + swir1)", - "ndwi": "Normalized Difference Water Index: (green - nir) / (green + nir)", - "mndwi": "Modified Normalized Difference Water Index: (green - swir1) / (green + swir1)", - "ndfi": "Normalized Difference Fraction Index", - "evi": "Enhanced Vegetation Index: 2.5 * (nir - red) / (nir + 6 * red - 7.5 * blue + 1)", - "evi2": "Enhanced Vegetation Index 2: 2.5 * (nir - red) / (nir + 2.4 * red + 1)", - "savi": "Soil-Adjusted Vegetation Index: (1.5 * (nir - red) / (nir + red + 0.5)", - "nbr": "Normalized Burn Ratio: (nir - swir2) / (nir + swir2)", - "mvi": "Mangrove Vegetation Index: 0.1 * (nir - green) / abs(swir1 - green)", - "ui": "Urban Index: (swir2 - nir) / (swir2 + nir)", - "ndbi": "Normalized Difference Built-up Index: (swir1 - nir) / (swir1 + nir)", - "ibi": "Index-based Built-up Index: (ndbi - (savi + mndwi) / 2) / (ndbi + (savi + mndwi) / 2)", - "nbi": "New Built-up Index: red * swir1 / nir", - "ebbi": "Enhanced Built-Up and Bareness Index: (swir1 - nir) / (10 * sqrt(swir1 + thermal))", - "bui": "Built-Up Index: (red - swir1) / (red + swir1) + (swir2 - swir1) / (swir2 + swir1)", - "kndvi": "Kernel Normalized Difference Vegetation Index", - "red, green, blue": "Natural color: red, green, blue", - "nir, red, green": "False color: nir, red, green", - "nir, swir1, red": "False color: nir, swir1, red", - "swir2, nir, red": "False color: swir2, nir, red", - "swir2, swir1', red": "False color: swir2, swir1', red", - "swir2, nir, green": "False color: swir2, nir, green", - "brightness, greenness, wetness": "Tasseled cap: brightness, greenness, wetness", - "fifth, sixth, fourth": "Tasseled cap: fifth, sixth, fourth" + "layer": { + "error": "Failed to load layer" }, - "body": { - "loading-apps": "Loading apps...", - "loading-initializing-google-maps": "Initializing map...", - "starting-sepal": "Starting SEPAL...", - "starting-sepal-failed": "Something has gone wrong. Please reload the page and try again." + "layout": { + "addImageLayerSource": { + "title": "Add Layer", + "types": { + "Asset": { + "description": "Add an Earth Engine asset", + "form": { + "asset.label": "Earth Engine Asset", + "loadError": "Unable to read asset" + }, + "presets": "Pre-sets" + }, + "Planet": { + "description": "Add a Planet account", + "form": { + "description.label": "Description", + "apiKey.label": "Planet API Key", + "invalidApiKey": "Invalid Planet API Key" + } + }, + "Recipe": { + "description": "Add a SEPAL recipe" + } + } + }, + "area": { + "edit.tooltip": "Edit area properties", + "remove": { + "message": "Please confirm removal of this area", + "tooltip": "Remove area" + } + }, + "layer": { + "remove": { + "tooltip": "Remove layer" + } + }, + "mode": { + "grid": { + "label": "Grid" + }, + "stack": { + "label": "Stack" + } + }, + "title": "Layers" }, - "browse": { - "addBrowser": { - "files": "SEPAL workspace browser", - "assets": "Earth Engine asset browser" + "visParams": { + "title": "Visualization", + "bands": { + "loading": "Loading bands...", + "loadError": "Failed to load image bands" + }, + "form": { + "band": { + "band.label": "Band", + "blueChannel.label": "Blue Channel", + "greenChannel.label": "Green Channel", + "hue.label": "Hue", + "redChannel.label": "Red Channel", + "reverse.tooltip": "Reverse the band", + "saturation.label": "Saturation", + "select.placeholder": "Select band...", + "value.label": "Value" }, - "title": { - "files": "SEPAL workspace", - "assets": "Earth Engine assets" + "gamma": { + "label": "Gamma", + "info": "Value: {gamma}" }, - "controls": { - "clearSelection": { - "tooltip": "Clear selection" - }, - "download": { - "tooltip": "Download selected item" - }, - "createFolder": { - "tooltip": "Create folder" - }, - "remove": { - "tooltip": "Remove selected items" - }, - "dotFiles": { - "label": "dotfiles", - "hide.tooltip": "Hide files beginning with a dot", - "show.tooltip": "Show files beginning with a dot" - }, - "collapseDirs": { - "label": "collapse", - "tooltip": "Collapse all folders" - }, - "expandDirs": { - "label": "expand", - "tooltip": "Expand all folders" - }, - "splitDirs": { - "label": "split", - "split.tooltip": "Split directories and files", - "mix.tooltip": "Mix directories and files" - }, - "sorting": { - "date.label": "Date", - "name.label": "Name" - } + "histogram": { + "error": "Failed to create histogram.", + "noData": "The band contain no data." + }, + "inverted": { + "label": "Inverted" }, - "info": { - "directory": "{itemCount} {itemCount, plural, one {item} other {items}}", - "empty": "empty" + "palette": { + "label": "Palette", + "tooltip": "Load a preset palette or create a new one. Use drag and drop for changing the order or removing colors. Warning: loading a palette will overwrite the current one.", + "empty": "Empty palette, using gray scale.", + "add": { + "tooltip": "Add a color to the palette." + }, + "preset": { + "placeholder": "Load a preset palette...", + "attribution": "Presets from {url}." + }, + "text": { + "tooltip": "Show and edit palette as text.", + "placeholder": "Enter comma-separated list of colors..." + } }, - "loading": { - "error": "Could not retrieve file information." + "min": { + "label": "min", + "notSmallerThanMax": "Must be less than max" }, - "createFolder": { - "existing.error": "Cannot create a folder with the same name as an existing asset or folder", - "tooDeep.error": "Cannot create a folder more than 10 levels deep" + "max": { + "label": "max" + } + }, + "stretch": { + "label": "Stretch" + }, + "type": { + "categorical": { + "label": "Categorical", + "tooltip": "Single band with categorical values" + }, + "continuous": { + "label": "Continuous", + "tooltip": "Single band with continuous values" }, - "removeConfirmation": "Remove {directories} {directories, plural, one {directory} other {directories}} and {files} {files, plural, one {file} other {files}}?", - "removing": { - "error": "Could not remove files." + "rgb": { + "label": "RGB", + "tooltip": "Three bands mapping to red, green, and blue color channels" }, - "selected": "{directories} {directories, plural, one {dir} other {dirs}} and {files} {files, plural, one {file} other {files}} selected" + "hsv": { + "label": "HSV", + "tooltip": "Three bands mapping to hue, saturation, and value" + } + } }, - "button": { - "add": "Add", - "apply": "Apply", - "back": "Back", - "cancel": "Cancel", - "close": "Close", - "confirm": "Confirm", - "copyToClipboard": "Copy to clipboard", - "discard": "Don't save", - "done": "Done", - "disabled": "Disabled", - "enabled": "Enabled", - "less": "Less", - "more": "More", - "next": "Next", - "ok": "OK", - "remove": "Remove", - "restart": "Restart", - "retry": "Retry", - "save": "Save", - "select": "Select", - "stop": "Stop" + "visualizationSelector": { + "label": "Bands", + "userDefined.label": "User defined", + "add.tooltip": "Add visualization", + "edit.tooltip": "Edit selected visualization", + "clone.tooltip": "Duplicate selected pre-set visualization", + "remove.tooltip": "Remove selected visualization" }, - "clipboard": { - "copy": { - "success": "Copied to clipboard", - "failure": "Could not copy to clipboard" - } + "drawingMode": { + "zoomarea": "Drawing mode: rectangle", + "polygon": "Drawing mode: polygon" + } + }, + "featureLayerSources": { + "Aoi": { + "type": "AOI", + "description": "Area of Interest" }, - "connectionStatus": { - "disconnected": "Disconnected", - "connected": "Connected", - "partiallyConnected": "Connected, some services unavailable" + "Labels": { + "type": "Labels", + "description": "Map labels, roads and points of interest" }, - "eeTable": { - "failedToLoad": "Failed to load EE Table", - "notFound": "EE Table not found", - "unauthorized": "You don't have access to this EE Table" + "Legend": { + "type": "Legend", + "description": "Map legend" }, - "error": { - "badRequest": "Bad request", - "internal": "Internal error", - "notFound": "Not found" + "Classification": { + "type": "Classification", + "description": "Map legend. It highlights the class of the pixel under the mouse cursor." + }, + "Palette": { + "type": "Palette", + "description": "Palette applied to image. It displays an approximate value for the pixel under the mouse cursor." }, - "fieldValidation": { - "date": "Must be a date", - "email": "Must be an email", - "greaterThan": "Must be greater than {minValue}", - "int": "Must be an integer", - "lessThan": "Must be less than {maxValue}", - "match": "Must match {regex}", - "max": "Max value is {maxValue}", - "min": "Min value is {minValue}", - "notBlank": "Cannot be blank", - "notEmpty": "Cannot be empty", - "number": "Must be a number" + "ReferenceData": { + "type": "Reference data", + "description": "Show reference data markers" }, - "footer": { - "buildNumber": "Build number", - "buildNumberCopied": "Build number copied to clipboard", - "gitCommit": "Git commit", - "gitCommitCopied": "Git commit copied to clipboard" + "Values": { + "type": "Values", + "description": "Display approximate band values for the pixel under the mouse cursor." }, - "format": { - "dollars": "${value}", - "dollarsPerHour": "${value}/hr", - "GB": "{value}GB", - "hour": "{value}hr", - "percent": "{value}%" + "SceneAreas": { + "type": "Scene Areas", + "description": "Select scenes to use in mosaic" + } + }, + "imageLayerSources": { + "Asset": { + "label": "EE Asset", + "description": "Earth Engine Asset" }, - "fusionTable": { - "failedToLoad": "Failed to load Fusion Table", - "notFound": "Fusion Table not found", - "unauthorized": "You don't have access to this Fusion Table" + "Recipe": { + "label": "Recipe", + "thisRecipeDescription": "This recipe", + "loadError": "Failed to load recipe: {error}" }, - "gee": { + "Planet": { + "label": "Planet", + "nicfiDescription": "NICFI Planet composites", + "bands": { + "tooltip": "Description of false-color indices." + } + }, + "GoogleSatellite": { + "label": "Google Satellite", + "description": "Google hi-res satellite imagery" + } + }, + "notifications": { + "error": { + "connectionError": "Failed to connect to server. Either your internet connection failed, or server is unavailable at the moment.", + "generic": "SEPAL encountered an error. Please try again or contact support if the problem persists." + } + }, + "pagination": { + "info": "{start} - {stop - 1} / {count}" + }, + "process": { + "panels": { + "inputImagery": { "asset": { - "error": { - "wrongType": "Asset is of type {actualType} while the only allowed types are: {allowedTypes}." - } + "title": "Earth Engine asset" }, - "classification": { - "error": { - "noTrainingData": "There is no training data in selected area of interest." - } + "button": "IMG", + "form": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID..." + }, + "band": { + "label": "Band" + }, + "legend": { + "label": "Legend" + }, + "noImagery": "Please add at least one image.", + "recipe": { + "label": "Recipe", + "placeholder": "Select a recipe..." + }, + "remove": { + "tooltip": "Remove image from selection" + }, + "section": { + "required": "An image must be selected" + }, + "type": { + "categorical": "Categorical", + "continuous": "Continuous", + "ASSET": "Earth Engine asset", + "RECIPE_REF": "SEPAL recipe", + "label": "Type" + } }, - "error": { - "earthEngineException": "{earthEngineMessage}", - "failedToLoadRecipe": "Failed to load recipe", - "title": "Google Earth Engine error" + "recipe": { + "title": "Saved SEPAL recipe" }, - "image": { - "error": { - "notAnImage": "Not an image", - "notFound": "Asset not found" - } + "sections": { + "title": "Image to include" }, - "table": { - "error": { - "notATable": "Not a Table", - "notFound": "Table not found" - } - } + "title": "Input imagery" + } }, - "home": { - "connectivityError": "You are currently unable to connect to the SEPAL server.", - "versionMismatch": { - "title": "SEPAL has been updated!", - "message": "Please reload your browser as soon as possible to make sure everything works as expected.", - "reloadNow": "Reload now" + "mapZoom": { + "fit": { + "tooltip": "Zoom to area of interest" + }, + "zoomArea": { + "tooltip": "Zoom to area" + }, + "zoomIn": { + "tooltip": "Zoom in" + }, + "zoomOut": { + "tooltip": "Zoom out" + }, + "search": { + "placeholder": "geo search" + }, + "scrollwheel": { + "enabled": { + "tooltip": "Disable scroll wheel zooming" }, - "sections": { - "account": "Account settings", - "app-launch-pad": "Apps", - "browse": "Files", - "collapse": "Automatically hide and show menu", - "expand": "Always show menu", - "help": "Open SEPAL documentation in a new tab/window", - "logout": "Logout from SEPAL", - "process": "Process", - "tasks": "Tasks", - "terminal": "Terminal", - "user": { - "messages": "Show user messages", - "profile": "Manage user profile", - "report": { - "budgetExceeded": "Exceeded", - "tooltip": "Show user report" - } - }, - "users": "Users" + "disabled": { + "tooltip": "Enable scroll wheel zooming" } + } }, - "landing": { - "features": { - "apps": { - "description": "Extend the features in SEPAL with custom data processing applications", - "title": "Extensible with apps" - }, - "browse": { - "description": "Preview and download your products", - "title": "Browse your data" - }, - "process": { - "description": "Fast and easy processing of geo data", - "title": "Process geo data" - }, - "terminal": { - "description": "Powerful command-line tools for data processing", - "title": "Terminal" - } - }, - "forgot-password": { - "button": "Reset password", - "cancel-link": "Cancel", - "instructions": "Provide us with the email address of your SEPAL account and we'll send you an email with instructions on how to reset your password.", - "invalid": "Not a valid email", - "label": "Forgot your password ?", - "email.placeholder": "Enter your email", - "required": "Email is required", - "success": "An email with password reset instructions has been sent to {email}.", - "error": "Cannot reset password." - }, - "intro": { - "about": { - "description": "SEPAL is an opensource project by the Open Foris team in Forestry Department of the United Nations Food and Agriculture Organization (FAO), funded by the Government of Norway.", - "title": "About" - }, - "computingPower": { - "description": "Harness high performance cloud-based computing and modern geospatial data infrastructures.", - "title": "Computing power" - }, - "easeOfUse": { - "description": "Allows users to query and process satellite data quickly and efficiently, tailor their products for local needs, and produce sophisticated and relevant geospatial analyses quickly.", - "title": "Ease of use" - }, - "googleEarthEngine": { - "description": "Get access to Google Earth Engine's multi-petabyte catalog of satellite imagery and use their planetary-scale analysis capabilities. All without writing a single line of code. Just connect your Google account to SEPAL.", - "title": "Earth Engine" - }, - "integrations": { - "description": "SEPAL doesn't want to reinvent the wheel. We rather use and integrate with existing solutions, such as Open Foris Collect Earth Online, for visual interpretation of satellite imagery.", - "title": "Integrations" - }, - "jupyterNotebook": { - "description": "Run any of the geospatial processing notebooks in SEPAL's catalogue, or develop your own. The hosted Jupyter server comes with Python 3, R, and JavaScript kernels.", - "title": "Jupyter Notebook" - }, - "partners": { - "title": "Partners" - }, - "powerUsers": { - "description": "Get access to dedicated Linux instances, with up to 128 CPU cores, 2TB of RAM and a host of development and geospatial tools installed. Access it directly from within the browser, or through an SSH client. Transfer files to and from the instance with rsync, scp, or your favorite FTP client.", - "title": "Power Users" - }, - "rstudio": { - "description": "Develop your R scripts with RStudio, directly inside SEPAL. Use any of the many useful R packages already installed, and install your own when you need to.", - "title": "RStudio Server" - }, - "shiny": { - "description": "Perform stratified area estimation, time-series analysis with BFAST, and other geospatial processing through the R Shiny apps hosted in SEPAL.", - "title": "Shiny Server" + "ccdc": { + "bands": { + "count": "Count" + }, + "chartPixel": { + "cancel.tooltip": "Cancel time-series charting", + "loadFailed": "Failed to load chart.", + "loadObservations.error": "Failed to load observations.", + "loadSegments.error": "Failed to load segments.", + "start.tooltip": "Chart time-series for pixel" + }, + "create": "CCDC", + "description": "Create a CCDC asset from a time-series.", + "mapToolbar": { + "ccdcGraph": { + "date.label": "Date", + "endDate.label": "End date", + "magnitude.label": "Break magnitude", + "model.label": "Model value", + "observation.label": "Observation", + "observationCount.label": "Obs. count", + "rmse.label": "RMSE", + "startDate.label": "Start date" + } + }, + "panel": { + "dates": { + "button": "DAT", + "form": { + "dateFormat": { + "fractionalYears": { + "label": "Fractional years" + }, + "jDays": { + "label": "Julian days" + }, + "label": "Date format", + "required": "Date format must be specified", + "unixTimeMillis": { + "label": "Unix time millis" + } + }, + "endDate": { + "label": "End date", + "malformed": "Invalid date", + "required": "Date is required", + "tooltip": "The last date of the time-series, exclusive." + }, + "startDate": { + "beforeEnd": "Must be before end", + "label": "Start date", + "malformed": "Invalid date", + "required": "Date is required", + "tooltip": "The first date of the time-series, inclusive." } + }, + "title": "Dates", + "tooltip": "Specify date range" }, - "launch": "Launch", - "documentation": "Documentation", - "loadCurrentUser": { - "error": "Failed to connect to SEPAL" - }, - "signup": { - "button": "Sign up", - "username": { - "label": "Username", - "placeholder": "Choose your username", - "required": "Username is required", - "format": "Username must start with a letter and can only contain letters and digits", - "duplicate": "This username is not available, please choose another one", - "cannotValidate": "Cannot validate username" - }, - "name": { - "label": "Name", - "placeholder": "Enter your full name", - "required": "Name is required", - "invalid": "" - }, - "email": { - "label": "Email", - "placeholder": "Enter your email address", - "required": "Email is required", - "format": "Please enter a valid email address", - "duplicate": "This email is not available, please choose another one", - "cannotValidate": "Cannot validate email" - }, - "organization": { - "label": "Organization", - "placeholder": "Enter your organization", - "required": "Organization is required", - "invalid": "" - }, - "intendedUse": { - "label": "Intended use", - "placeholder": "What do you plan to do with SEPAL?", - "required": "Intended use is required", - "invalid": "" - }, - "success": "You've successfully signed up to SEPAL. An activation email has been sent to {email}." - }, - "login": { - "button": "Login", - "error": "An error occurred during authentication.", - "forgot-password-link": "Forgot password?", - "password": { - "invalid": "Invalid username/password", - "label": "Password", - "placeholder": "Enter your password", - "required": "Password is required" - }, - "sign-up": "Sign up", - "username": { - "label": "Username", - "placeholder": "Enter your username", - "required": "Username is required" + "options": { + "button": "OPT", + "form": { + "breakDetection": { + "aggressive": { + "label": "Aggressive", + "tooltip": "Be aggressive in detecting breaks in the time-series. It might detect breaks where there are none." + }, + "conservative": { + "label": "Conservative", + "tooltip": "Be conservative in detecting breaks in the time-series. It might miss more subtle breaks or breaks where there is bad data availability" + }, + "label": "Break detection", + "moderate": { + "label": "Moderate", + "tooltip": "Be moderate in detecting breaks in the time-series." + } + }, + "chiSquareProbability": "Chi square probability", + "lambda": "Lambda", + "maxIterations": "Max iterations", + "minNumOfYearsScaler": "min number of years scaler", + "minObservations": "Min observations", + "tmaskBands": { + "label": "TMask bands", + "noneOrTwo": "Exactly two bands must be selected or none" } + }, + "title": "CCDC options", + "tooltip": "Configure CCDC options" }, - "privacyPolicy": "Privacy policy", - "reset-password": { - "button": "Set password", - "password": { - "invalid": "Password must be at least 12 characters long", - "label": "Password", - "placeholder": "Enter a new password", - "required": "Password is required" - }, - "password2": { - "label": "Confirm password", - "not-matching": "Passwords are not the same", - "placeholder": "Enter the password again", - "required": "Password must be confirmed" - }, - "success": "Password has been reset", - "error": "Password could not be reset", - "username": { - "label": "Username" - }, - "validating-link": "Validating link..." + "preprocess": { + "button": "PRC", + "title": "Pre-process", + "tooltip": "Configure imagery pre-processing" }, - "tagline": "System for earth observations, data access, processing & analysis for land monitoring.", - "title": "SEPAL", - "validate-token": { - "error": "This link is not valid anymore. Make sure you use the link in the latest email you received from SEPAL." + "sources": { + "button": "SRC", + "classificationLoadError": "Failed to load classification: {error}", + "form": { + "breakpointBands": { + "atLeastOne": "Select at least one band", + "label": "Breakpoint bands", + "probability": "{label} %", + "regression": "Regression" + }, + "classification": { + "label": "Classification", + "placeholder": "Select a classification...", + "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." + }, + "cloudPercentageThreshold": { + "label": "Max cloud cover %", + "value": "{value}%", + "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" + }, + "dataSets": { + "label": "Data set" + }, + "dataSetType": { + "label": "Type" + }, + "dataSetTypes": { + "OPTICAL": "Optical", + "PLANET": "Planet", + "RADAR": "Radar" + }, + "required": "A data source must be selected" + }, + "title": "Sources", + "tooltip": "Select image source" } + }, + "tabPlaceholder": "CCDC" }, - "map": { - "info": { - "tooltip": "Show coordinates", - "center": "Center", - "bounds": "Bounds", - "copy": "Copy to clipboard", - "coordinatesCopied": "Coordinates copied to the clipboard" + "ccdcSlice": { + "chartPixel": { + "cancel.tooltip": "Cancel time-series charting", + "loadFailed": "Failed to load chart: {error}", + "loadObservations.error": "Failed to load observations: {error}", + "loadSegments.error": "Failed to load segments: {error}", + "start.tooltip": "Chart time-series for pixel" + }, + "create": "CCDC slice", + "description": "Create a slice of a CCDC asset, for a specific date.", + "panel": { + "date": { + "button": "DAT", + "form": { + "date": { + "label": "Date of slice", + "range": "The CCDC segments were created with data from {segmentsStartDate} to {segmentsEndDate}", + "tooltip": "The date of the slice" + }, + "dateType": { + "label": "Date type", + "RANGE": "Date range", + "SINGLE": "Single date" + }, + "endDate": { + "label": "End date", + "range": "The CCDC segments were created with data from {startDate} to {endDate}", + "tooltip": "The end date of the slice" + }, + "startDate": { + "label": "Start date", + "tooltip": "The start date of the slice" + } + }, + "title": "Date", + "tooltip": "Specify date of the slice" }, - "legendBuilder": { - "addEntry": { - "tooltip": "Add legend entry" - }, - "colors": { - "edit": { - "tooltip": "Change color" - }, - "swap": { - "tooltip": "Swap this color with another in the legend" - } - }, - "entry": { - "error": { - "duplicateColor": "There are more than one entry with this color", - "duplicateLabel": "There are more than one entry with this label", - "duplicateValue": "There are more than one entry with this value", - "invalidColor": "Another entry has the same color" + "options": { + "button": "OPT", + "form": { + "breakAnalysisBand": { + "label": "Break analysis band", + "placeholder": "Select band...", + "tooltip": "The band to use when analyzing which breakpoint to provide data for." + }, + "breakMagnitudeDirection": { + "options": { + "ANY": { + "label": "Any", + "tooltip": "Include all breakpoints independent on their change magnitude direction." }, - "remove": { - "tooltip": "Remove this legend entry" + "DECREASE": { + "label": "Decrease", + "tooltip": "Only include breakpoints with a negative break magnitude in the break analysis band." }, - "classLabel": { - "placeholder": "Label..." + "INCREASE": { + "label": "Increase", + "tooltip": "Only include breakpoints with an positive break magnitude in the break analysis band." } - }, - "import": { - "title": "Import", - "file": { - "label": "CSV file" + }, + "label": "Break magnitude direction", + "tooltip": "Specify which breakpoints to filter out, based on weather the break magnitude for the break analysis band decrease or increase." + }, + "breakSelection": { + "options": { + "CONFIDENCE": { + "label": "Confidence", + "tooltip": "Select the breakpoint with the highest confidence." }, - "clipboard": { - "tooltip": "Read legend from the clipboard", - "disabledTooltip": "This browser doesn't allow reading from the clipboard" + "FIRST": { + "label": "First", + "tooltip": "Select the first breakpoint in the date range." }, - "colorColumnType": { - "label": "Color column format", - "tooltip": "Specify if the color is in a single column, or if red, green, blue are in separate column.", - "single": "Single column", - "multiple": "Multiple columns" + "LAST": { + "label": "Last", + "tooltip": "Select the last breakpoint in the date range." }, - "column": { - "valueColumn": { - "label": "Value", - "placeholder": "Select...", - "tooltip": "Column with class values" - }, - "labelColumn": { - "label": "Label", - "placeholder": "Select...", - "tooltip": "Column with class labels" - }, - "colorColumn": { - "label": "Color", - "placeholder": "Select...", - "tooltip": "Column with class colors" - }, - "redColumn": { - "label": "Red", - "placeholder": "Select...", - "tooltip": "Column with red color channel values" - }, - "greenColumn": { - "label": "Green", - "placeholder": "Select...", - "tooltip": "Column with green color channel values" - }, - "blueColumn": { - "label": "Blue", - "placeholder": "Select...", - "tooltip": "Column with blue color channel values" - } - } - }, - "label": "Legend", - "load": { - "label": "Load...", - "options": { - "exportToCsv": { - "label": "Export as CSV" - }, - "importFromCsv": { - "label": "Import from CSV..." - }, - "imageValues": { - "label": "Distinct values from image band", - "loadError": "Failed to load distinct values from image band" - } + "MAGNITUDE": { + "label": "Magnitude", + "tooltip": "Select the breakpoint with the highest break magnitude." } - }, - "noEntries": "No entries in the legend. Add at least one." + }, + "label": "Break selection", + "tooltip": "Specify which of the break points to provide data for." + }, + "extrapolateMaxDays": { + "label": "Max days to extrapolate", + "unlimited": "all" + }, + "extrapolateSegment": { + "closest": { + "label": "Closest", + "tooltip": "Extrapolate the segment closest to the date." + }, + "label": "Segment to extrapolate", + "next": { + "label": "Next", + "tooltip": "Back-extrapolate the segment after the date." + }, + "previous": { + "label": "Previous", + "tooltip": "Extrapolate the segment before the date." + } + }, + "gapStrategy": { + "extrapolate": { + "label": "Extrapolate", + "tooltip": "Generate a new model based on the segment before or after the gap." + }, + "interpolate": { + "label": "Interpolate", + "tooltip": "Generate a new model based on surrounding segments. If there isn't any segment before or after, the pixel will be masked." + }, + "label": "Gap strategy", + "mask": { + "label": "Mask", + "tooltip": "Mask all pixels that fall on a gap." + }, + "tooltip": "Decide which strategy to use for pixels where there is no CCDC segment. There will be gaps during detected breaks or for dates before and after the CCDC date range." + }, + "harmonics": { + "label": "Harmonics", + "tooltip": "Specify how many harmonics from the CCDC regression model to use when creating the slice." + }, + "minBreakConfidence": { + "label": "Min break confidence", + "tooltip": "The minimum confidence of a breakpoint to be included. This is calculated by the break magnitude divided by the RMSE.", + "value": "{value} (magnitude/rmse)" + }, + "skipBreakInLastSegment": { + "label": "Breaks at the end", + "options": { + "EXCLUDE": "Exclude", + "INCLUDE": "Include" + }, + "tooltip": "CCDC sometimes detect breaks in the last segment. These can at times be unreliable." + } + }, + "title": "CCDC slice options", + "tooltip": "Configure how the CCDC slice is made" + }, + "retrieve": { + "apply": "Retrieve", + "form": { + "bandTypes": { + "amplitude1": { + "label": "Amplitude 1", + "tooltip": "The amplitude of the model's first order harmonic" + }, + "amplitude2": { + "label": "Amplitude 2", + "tooltip": "The amplitude of the model's second order harmonic" + }, + "amplitude3": { + "label": "Amplitude 3", + "tooltip": "The amplitude of the model's third order harmonic" + }, + "atLeastOne": "At least one band type must be selected", + "intercept": { + "label": "Intercept", + "tooltip": "The intercept of the model's linear component" + }, + "breakConfidence": { + "label": "Break confidence", + "tooltip": "Confidence of break, defined as magnitude/rmse" + }, + "label": "Band types", + "magnitude": { + "label": "Magnitude", + "tooltip": "Break magnitude of segment" + }, + "phase1": { + "label": "Phase 1", + "tooltip": "The phase of the model's first order harmonic" + }, + "phase2": { + "label": "Phase 2", + "tooltip": "The phase of the model's second order harmonic" + }, + "phase3": { + "label": "Phase 3", + "tooltip": "The phase of the model's third order harmonic" + }, + "rmse": { + "label": "RMSE", + "tooltip": "RMSE of the harmonic models" + }, + "slope": { + "label": "Slope", + "tooltip": "The slope of the model's linear component" + }, + "value": { + "label": "Value", + "tooltip": "The evaluated value of the harmonic models for the specified date" + } + }, + "baseBands": { + "atLeastOne": "At least one base band must be selected", + "label": "Base bands" + }, + "destination": { + "GEE": "Google Earth Engine asset", + "label": "Destination", + "required": "A destination must be selected", + "SEPAL": "SEPAL workspace", + "DRIVE": "Google Drive" + }, + "scale": { + "label": "Scale", + "placeholder": "Scale..." + }, + "segmentBands": { + "changeProb": { + "label": "Probability", + "tooltip": "Probability of detected break being accurate" + }, + "label": "Segment bands", + "numObs": { + "label": "Observations", + "tooltip": "Number of observations in segment" + }, + "tBreak": { + "label": "Break", + "tooltip": "Date of detected break" + }, + "tEnd": { + "label": "End", + "tooltip": "End date of segment" + }, + "tooltip": "Bands with information about the segment the slice falls in. If a pixel falls in a gap, the previous segment will be used", + "tStart": { + "label": "Start", + "tooltip": "Start date of segment" + } + } + }, + "task": { + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", + "DRIVE": "Retrieve recipe: {name} to Google Drive" + }, + "title": "Retrieve", + "tooltip": "Retrieve time-series" }, - "layer": { - "error": "Failed to load layer" + "source": { + "asset": { + "label": "Earth Engine CCDC Asset", + "title": "Earth Engine CCDC Asset", + "notCcdc": "Asset does not contain CCDC segments" + }, + "button": "SRC", + "form": { + "asset": { + "invalid": "Asset must be an exported CCDC asset", + "label": "CCDC asset", + "placeholder": "Enter CCDC asset ID", + "required": "An asset ID must be specified" + } + }, + "recipe": { + "label": "Saved SEPAL CCDC recipe", + "title": "Saved SEPAL CCDC recipe" + }, + "title": "Source", + "tooltip": "Select EE asset with CCDC segments to slice" + } + }, + "preview": { + "description": "Preview of a CCDC slice, generated on the fly by EE.", + "error": "Failed to generate preview", + "initializing": "Loading preview...", + "label": "CCDC slice", + "loading": "Loading tiles: {pending}", + "tilesFailed": "{failed} failed" + }, + "source": { + "asset": { + "loadError": "Failed to load asset" }, - "layout": { - "addImageLayerSource": { - "title": "Add Layer", - "types": { - "Asset": { - "description": "Add an Earth Engine asset", - "form": { - "asset.label": "Earth Engine Asset", - "loadError": "Unable to read asset" - }, - "presets": "Pre-sets" - }, - "Planet": { - "description": "Add a Planet account", - "form": { - "description.label": "Description", - "apiKey.label": "Planet API Key", - "invalidApiKey": "Invalid Planet API Key" - } - }, - "Recipe": { - "description": "Add a SEPAL recipe" - } - } - }, - "area": { - "edit.tooltip": "Edit area properties", - "remove": { - "message": "Please confirm removal of this area", - "tooltip": "Remove area" - } - }, - "layer": { - "remove": { - "tooltip": "Remove layer" - } - }, - "mode": { - "grid": { - "label": "Grid" - }, - "stack": { - "label": "Stack" - } - }, - "title": "Layers" + "recipe": { + "loadError": "Failed to load recipe" + } + }, + "tabPlaceholder": "CCDC_slice" + }, + "changeAlerts": { + "create": "Change Alerts", + "description": "Generate change alerts using a CCDC as baseline.", + "imageLayerForm": { + "mosaicType": { + "label": "Mosaic", + "latest": { + "label": "Latest", + "tooltip": "Render a composite of the latest available imagery in the period" + }, + "median": { + "label": "Median", + "tooltip": "Render a median composite of the period" + } }, - "visParams": { - "title": "Visualization", - "bands": { - "loading": "Loading bands...", - "loadError": "Failed to load image bands" - }, - "form": { - "band": { - "band.label": "Band", - "blueChannel.label": "Blue Channel", - "greenChannel.label": "Green Channel", - "hue.label": "Hue", - "redChannel.label": "Red Channel", - "reverse.tooltip": "Reverse the band", - "saturation.label": "Saturation", - "select.placeholder": "Select band...", - "value.label": "Value" + "visualizationType": { + "calibration": { + "label": "Calibration", + "tooltip": "Render a composite of the calibration period" + }, + "changes": { + "label": "Changes", + "tooltip": "Render change in the monitoring period" + }, + "label": "Type", + "monitoring": { + "label": "Monitoring", + "tooltip": "Render a composite of the monitoring period" + } + } + }, + "layers": { + "imageLayer": { + "changes": "Changes", + "dates": "Dates", + "observations": "Observations" + } + }, + "panel": { + "date": { + "button": "DAT", + "form": { + "calibration": { + "duration": { + "label": "Calibration duration", + "placeholder": "Duration..." + } + }, + "durationUnit": { + "placeholder": "Unit...", + "DAYS": "Days", + "MONTHS": "Months", + "WEEKS": "Weeks" + }, + "monitoring": { + "duration": { + "label": "Monitoring duration", + "placeholder": "Duration..." + } + }, + "monitoringEnd": { + "label": "Monitoring end", + "tooltip": "The date when monitoring ends. You typically set this to today's date" + }, + "range": "The CCDC segments were created with data from {segmentsStartDate} to {segmentsEndDate}.", + "noRange": "The CCDC segments doesn't specify any date range." + }, + "title": "Date range", + "tooltip": "Specify date range to get alerts for" + }, + "options": { + "button": "OPT", + "form": { + "minConfidence": { + "label": "Min confidence", + "tooltip": "Minimum number of errors from reference model to consider observation a change.", + "value": "{value} (difference/rmse)" + }, + "numberOfObservations": { + "label": "Number of observations", + "tooltip": "Number of observations in window when looking at consecutive changes" + }, + "minNumberOfChanges": { + "label": "Min number of changes", + "tooltip": "Minimum number of change observations in window to consider the window a change, or minimum number of stable observations to consider the window stable" + }, + "mustBeConfirmedInMonitoring": { + "label": "Confirmed in monitoring", + "tooltip": "A change must be confirmed in the monitoring period. If it was confirmed already in the calibration period, the change is discarded. This prevents changes occuring in the calibration period from being detected." + }, + "mustBeStableBeforeChange": { + "label": "Stable before change", + "tooltip": "A change must be stable before it's detected. Otherwise, the change is discarded. This prevents changes that happened outside of the monitoring period in the last year from being detected." + }, + "mustStayChanged": { + "label": "Must stay change", + "tooltip": "A change must stay change, and not be classified as stable after the change was detected. Otherwise, the change is discarded. This lowers the risk of multiple consecutive outliers to be classified as change." + } + }, + "title": "Change alert options", + "tooltip": "Configure sensitivity in detecting changes" + }, + "preprocess": { + "button": "PRC", + "title": "Pre-process", + "tooltip": "Configure imagery pre-processing" + }, + "reference": { + "asset": { + "label": "Earth Engine CCDC Asset", + "title": "Earth Engine CCDC Asset", + "notCcdc": "Asset does not contain CCDC segments" + }, + "button": "REF", + "form": { + "asset": { + "invalid": "Asset must be an exported CCDC asset", + "label": "CCDC asset", + "placeholder": "Enter CCDC asset ID", + "required": "An asset ID must be specified" + } + }, + "recipe": { + "label": "Saved SEPAL CCDC recipe", + "title": "Saved SEPAL CCDC recipe" + }, + "title": "Source", + "tooltip": "Select CCDC asset or recipe to use as reference for alerts" + }, + "retrieve": { + "apply": "Retrieve", + "form": { + "bandTypes": { + "amplitude1": { + "label": "Amplitude 1", + "tooltip": "The amplitude of the model's first order harmonic" + }, + "amplitude2": { + "label": "Amplitude 2", + "tooltip": "The amplitude of the model's second order harmonic" + }, + "amplitude3": { + "label": "Amplitude 3", + "tooltip": "The amplitude of the model's third order harmonic" + }, + "atLeastOne": "At least one band type must be selected", + "intercept": { + "label": "Intercept", + "tooltip": "The intercept of the model's linear component" + }, + "breakConfidence": { + "label": "Break confidence", + "tooltip": "Confidence of break, defined as magnitude/rmse" + }, + "label": "Band types", + "magnitude": { + "label": "Magnitude", + "tooltip": "Break magnitude of segment" + }, + "phase1": { + "label": "Phase 1", + "tooltip": "The phase of the model's first order harmonic" + }, + "phase2": { + "label": "Phase 2", + "tooltip": "The phase of the model's second order harmonic" + }, + "phase3": { + "label": "Phase 3", + "tooltip": "The phase of the model's third order harmonic" + }, + "rmse": { + "label": "RMSE", + "tooltip": "RMSE of the harmonic models" + }, + "slope": { + "label": "Slope", + "tooltip": "The slope of the model's linear component" + }, + "value": { + "label": "Value", + "tooltip": "The evaluated value of the harmonic models for the specified date" + } + }, + "baseBands": { + "atLeastOne": "At least one base band must be selected", + "label": "Base bands" + }, + "destination": { + "GEE": "Google Earth Engine asset", + "label": "Destination", + "required": "A destination must be selected", + "SEPAL": "SEPAL workspace", + "DRIVE": "Google Drive" + }, + "scale": { + "label": "Scale" + }, + "segmentBands": { + "changeProb": { + "label": "Probability", + "tooltip": "Probability of detected break being accurate" + }, + "label": "Segment bands", + "numObs": { + "label": "Observations", + "tooltip": "Number of observations in segment" + }, + "tBreak": { + "label": "Break", + "tooltip": "Date of detected break" + }, + "tEnd": { + "label": "End", + "tooltip": "End date of segment" + }, + "tooltip": "Bands with information about the segment the slice falls in. If a pixel falls in a gap, the previous segment will be used", + "tStart": { + "label": "Start", + "tooltip": "Start date of segment" + } + } + }, + "task": { + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace" + }, + "title": "Retrieve", + "tooltip": "Retrieve time-series" + }, + "sources": { + "button": "SRC", + "form": { + "band": { + "label": "Analysis band" + }, + "cloudPercentageThreshold": { + "label": "Max cloud cover %", + "value": "{value}%", + "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" + }, + "dataSets": { + "label": "Data sets", + "optical": { + "label": "Optical" + }, + "options": { + "LANDSAT_7": { + "label": "L7", + "tooltip": "Landsat 7" }, - "gamma": { - "label": "Gamma", - "info": "Value: {gamma}" + "LANDSAT_7_T2": { + "label": "L7 T2", + "tooltip": "Landsat 7 Tier 2" }, - "histogram": { - "error": "Failed to create histogram.", - "noData": "The band contain no data." + "LANDSAT_8": { + "label": "L8", + "tooltip": "Landsat 8" }, - "inverted": { - "label": "Inverted" + "LANDSAT_8_T2": { + "label": "L8 T2", + "tooltip": "Landsat 8 Tier 2" }, - "palette": { - "label": "Palette", - "tooltip": "Load a preset palette or create a new one. Use drag and drop for changing the order or removing colors. Warning: loading a palette will overwrite the current one.", - "empty": "Empty palette, using gray scale.", - "add": { - "tooltip": "Add a color to the palette." - }, - "preset": { - "placeholder": "Load a preset palette...", - "attribution": "Presets from {url}." - }, - "text": { - "tooltip": "Show and edit palette as text.", - "placeholder": "Enter comma-separated list of colors..." - } + "LANDSAT_9": { + "label": "L9", + "tooltip": "Landsat 9" }, - "min": { - "label": "min", - "notSmallerThanMax": "Must be less than max" + "LANDSAT_9_T2": { + "label": "L9 T2", + "tooltip": "Landsat 9 Tier 2" }, - "max": { - "label": "max" - } - }, - "stretch": { - "label": "Stretch" - }, - "type": { - "categorical": { - "label": "Categorical", - "tooltip": "Single band with categorical values" + "LANDSAT_TM": { + "label": "L4-5", + "tooltip": "Landsat 4-5" }, - "continuous": { - "label": "Continuous", - "tooltip": "Single band with continuous values" + "LANDSAT_TM_T2": { + "label": "L4-5 T2", + "tooltip": "Landsat 4-5 Tier 2" }, - "rgb": { - "label": "RGB", - "tooltip": "Three bands mapping to red, green, and blue color channels" + "SENTINEL_1": { + "label": "S1", + "tooltip": "Sentinel 1 A+B" }, - "hsv": { - "label": "HSV", - "tooltip": "Three bands mapping to hue, saturation, and value" + "SENTINEL_2": { + "label": "S2", + "tooltip": "Sentinel 2 A+B" } - } - }, - "visualizationSelector": { - "label": "Bands", - "userDefined.label": "User defined", - "add.tooltip": "Add visualization", - "edit.tooltip": "Edit selected visualization", - "clone.tooltip": "Duplicate selected pre-set visualization", - "remove.tooltip": "Remove selected visualization" - }, - "drawingMode": { - "zoomarea": "Drawing mode: rectangle", - "polygon": "Drawing mode: polygon" + }, + "radar": { + "label": "Radar" + } + }, + "dataSetType": { + "label": "Type" + }, + "dataSetTypes": { + "OPTICAL": "Optical", + "PLANET": "Planet", + "RADAR": "Radar" + }, + "required": "A data source must be selected" + }, + "title": "Sources", + "tooltip": "Select image source" } - }, - "featureLayerSources": { - "Aoi": { - "type": "AOI", - "description": "Area of Interest" - }, - "Labels": { - "type": "Labels", - "description": "Map labels, roads and points of interest" - }, - "Legend": { - "type": "Legend", - "description": "Map legend" - }, - "Classification": { - "type": "Classification", - "description": "Map legend. It highlights the class of the pixel under the mouse cursor." - }, - "Palette": { - "type": "Palette", - "description": "Palette applied to image. It displays an approximate value for the pixel under the mouse cursor." - }, - "ReferenceData": { - "type": "Reference data", - "description": "Show reference data markers" - }, - "Values": { - "type": "Values", - "description": "Display approximate band values for the pixel under the mouse cursor." + }, + "preview": { + "description": "Preview of change alerts, generated on the fly by EE.", + "error": "Failed to generate preview", + "initializing": "Loading preview...", + "label": "Change alerts", + "loading": "Loading tiles: {pending}", + "tilesFailed": "{failed} failed" + }, + "reference": { + "asset": { + "loadError": "Failed to load asset" }, - "SceneAreas": { - "type": "Scene Areas", - "description": "Select scenes to use in mosaic" + "recipe": { + "loadError": "Failed to load recipe" } + }, + "tabPlaceholder": "Change_alerts" }, - "imageLayerSources": { - "Asset": { - "label": "EE Asset", - "description": "Earth Engine Asset" - }, - "Recipe": { - "label": "Recipe", - "thisRecipeDescription": "This recipe", - "loadError": "Failed to load recipe: {error}" + "classification": { + "bands": { + "class": "Class", + "classProbability": "Class probability", + "probability": "{class} %", + "regression": "Regression" + }, + "collect": { + "disable.tooltip": "Disable reference data collection", + "enable.tooltip": "Enable reference data collection", + "findingNextPoint": { + "error": "Failed to find points: {error}", + "message": "Finding next point...", + "notFound": "Unable to find any points for you. Try to select some yourself.", + "title": "Finding next point..." + } + }, + "create": "Classification", + "description": "Classify imagery from SEPAL or Google Earth Engine. Include imagery from multiple points in time to do change detection.", + "layers": { + "imageLayer": { + "preSets": "Pre-sets" }, - "Planet": { - "label": "Planet", - "nicfiDescription": "NICFI Planet composites", - "bands": { - "tooltip": "Description of false-color indices." + "referenceData": { + "description": "Reference data points used to create classification", + "label": "Reference data" + } + }, + "panel": { + "auxiliaryImagery": { + "button": "AUX", + "form": { + "options": { + "label": "Sources", + "latitude": { + "label": "Latitude", + "tooltip": "Include the latitude of each pixel." + }, + "terrain": { + "label": "Terrain", + "tooltip": "Include SRTM data." + }, + "water": { + "label": "Water", + "tooltip": "Include JRC Global Surface Water Mapping Layers." + } } + }, + "title": "Auxiliary sources", + "tooltip": "Include Auxiliary image sources in classification" }, - "GoogleSatellite": { - "label": "Google Satellite", - "description": "Google hi-res satellite imagery" - } - }, - "notifications": { - "error": { - "connectionError": "Failed to connect to server. Either your internet connection failed, or server is unavailable at the moment.", - "generic": "SEPAL encountered an error. Please try again or contact support if the problem persists." - } - }, - "pagination": { - "info": "{start} - {stop - 1} / {count}" - }, - "process": { - "panels": { - "inputImagery": { - "asset": { - "title": "Earth Engine asset" + "classifier": { + "button": "CLS", + "form": { + "cart": { + "label": "Cart" + }, + "decisionTree": { + "label": "Decision tree", + "placeholder": "Paste or drop file with the decision tree...", + "tooltip": "Paste or drop file with the decision tree" + }, + "gradientTreeBoost": { + "config": { + "loss": { + "label": "Loss function", + "options": { + "Huber.label": "Huber", + "LeastAbsoluteDeviation.label": "Least abs. deviation", + "LeastSquares.label": "Least squares" + }, + "placeholder": "Loss function...", + "tooltip": "Loss function for regression" }, - "button": "IMG", - "form": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID..." - }, - "band": { - "label": "Band" - }, - "legend": { - "label": "Legend" - }, - "noImagery": "Please add at least one image.", - "recipe": { - "label": "Recipe", - "placeholder": "Select a recipe..." - }, - "remove": { - "tooltip": "Remove image from selection" + "samplingRate": { + "label": "Sampling rate", + "placeholder": "Sampling rate...", + "tooltip": "The sampling rate for stochastic tree boosting" + }, + "shrinkage": { + "label": "Shrinkage", + "placeholder": "Shrinkage...", + "tooltip": "The shrinkage parameter in [0, 1] controls the learning rate of procedure" + } + }, + "label": "Gradient tree boost" + }, + "minimumDistance": { + "config": { + "metric": { + "label": "Metric", + "options": { + "cosine": { + "label": "Spectral angle", + "tooltip": "Spectral angle from the un-normalized class mean" }, - "section": { - "required": "An image must be selected" + "euclidean": { + "label": "Euclidean", + "tooltip": "Euclidean distance from the un-normalized class mean" }, - "type": { - "categorical": "Categorical", - "continuous": "Continuous", - "ASSET": "Earth Engine asset", - "RECIPE_REF": "SEPAL recipe", - "label": "Type" + "mahalanobis": { + "label": "Mahalanobis", + "tooltip": "Mahalanobis distance from the class mean" } + } + } + }, + "label": "Min distance" + }, + "naiveBayes": { + "config": { + "lambda": { + "label": "Lambda", + "placeholder": "Lambda...", + "tooltip": "A smoothing lambda. Used to avoid assigning zero probability to classes not seen during training, instead using lambda / (lambda * nFeatures)" + } + }, + "label": "Naive bayes" + }, + "randomForest": { + "config": { + "bagFraction": { + "label": "Bag fraction", + "placeholder": "Bag fraction...", + "tooltip": "The fraction of input to bag per tree" }, - "recipe": { - "title": "Saved SEPAL recipe" + "maxNodes": { + "label": "Max nodes", + "placeholder": "Max nodes...", + "tooltip": "The maximum number of leaf nodes in each tree. If unspecified, defaults to no limit" }, - "sections": { - "title": "Image to include" + "minLeafPopulation": { + "label": "Min leaf population", + "placeholder": "Min leaf population...", + "tooltip": "Only create nodes whose training set contains at least this many points" }, - "title": "Input imagery" - } - }, - "mapZoom": { - "fit": { - "tooltip": "Zoom to area of interest" - }, - "zoomArea": { - "tooltip": "Zoom to area" - }, - "zoomIn": { - "tooltip": "Zoom in" - }, - "zoomOut": { - "tooltip": "Zoom out" - }, - "search": { - "placeholder": "geo search" - }, - "scrollwheel": { - "enabled": { - "tooltip": "Disable scroll wheel zooming" + "numberOfTrees": { + "label": "Number of trees", + "placeholder": "Number of trees...", + "tooltip": "The number of decision trees to create" }, - "disabled": { - "tooltip": "Enable scroll wheel zooming" - } - } - }, - "ccdc": { - "bands": { - "count": "Count" - }, - "chartPixel": { - "cancel.tooltip": "Cancel time-series charting", - "loadFailed": "Failed to load chart.", - "loadObservations.error": "Failed to load observations.", - "loadSegments.error": "Failed to load segments.", - "start.tooltip": "Chart time-series for pixel" - }, - "create": "CCDC", - "description": "Create a CCDC asset from a time-series.", - "mapToolbar": { - "ccdcGraph": { - "date.label": "Date", - "endDate.label": "End date", - "magnitude.label": "Break magnitude", - "model.label": "Model value", - "observation.label": "Observation", - "observationCount.label": "Obs. count", - "rmse.label": "RMSE", - "startDate.label": "Start date" - } - }, - "panel": { - "dates": { - "button": "DAT", - "form": { - "dateFormat": { - "fractionalYears": { - "label": "Fractional years" - }, - "jDays": { - "label": "Julian days" - }, - "label": "Date format", - "required": "Date format must be specified", - "unixTimeMillis": { - "label": "Unix time millis" - } - }, - "endDate": { - "label": "End date", - "malformed": "Invalid date", - "required": "Date is required", - "tooltip": "The last date of the time-series, exclusive." - }, - "startDate": { - "beforeEnd": "Must be before end", - "label": "Start date", - "malformed": "Invalid date", - "required": "Date is required", - "tooltip": "The first date of the time-series, inclusive." - } - }, - "title": "Dates", - "tooltip": "Specify date range" + "seed": { + "label": "Seed", + "placeholder": "Seed...", + "tooltip": "The randomization seed" }, - "options": { - "button": "OPT", - "form": { - "breakDetection": { - "aggressive": { - "label": "Aggressive", - "tooltip": "Be aggressive in detecting breaks in the time-series. It might detect breaks where there are none." - }, - "conservative": { - "label": "Conservative", - "tooltip": "Be conservative in detecting breaks in the time-series. It might miss more subtle breaks or breaks where there is bad data availability" - }, - "label": "Break detection", - "moderate": { - "label": "Moderate", - "tooltip": "Be moderate in detecting breaks in the time-series." - } - }, - "chiSquareProbability": "Chi square probability", - "lambda": "Lambda", - "maxIterations": "Max iterations", - "minNumOfYearsScaler": "min number of years scaler", - "minObservations": "Min observations", - "tmaskBands": { - "label": "TMask bands", - "noneOrTwo": "Exactly two bands must be selected or none" - } - }, - "title": "CCDC options", - "tooltip": "Configure CCDC options" + "variablesPerSplit": { + "label": "Variables per split", + "placeholder": "Variables per split...", + "tooltip": "The number of variables per split. If unspecified, uses the square root of the number of variables." + } + }, + "label": "Random Forest" + }, + "svm": { + "config": { + "coef0": { + "label": "coef₀", + "placeholder": "coef₀...", + "tooltip": "The coef₀ value in the kernel function" }, - "preprocess": { - "button": "PRC", - "title": "Pre-process", - "tooltip": "Configure imagery pre-processing" + "cost": { + "label": "Cost", + "placeholder": "Cost...", + "tooltip": "The cost (C) parameter" }, - "sources": { - "button": "SRC", - "classificationLoadError": "Failed to load classification: {error}", - "form": { - "breakpointBands": { - "atLeastOne": "Select at least one band", - "label": "Breakpoint bands", - "probability": "{label} %", - "regression": "Regression" - }, - "classification": { - "label": "Classification", - "placeholder": "Select a classification...", - "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." - }, - "cloudPercentageThreshold": { - "label": "Max cloud cover %", - "value": "{value}%", - "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" - }, - "dataSets": { - "label": "Data set" - }, - "dataSetType": { - "label": "Type" - }, - "dataSetTypes": { - "OPTICAL": "Optical", - "PLANET": "Planet", - "RADAR": "Radar" - }, - "required": "A data source must be selected" - }, - "title": "Sources", - "tooltip": "Select image source" - } - }, - "tabPlaceholder": "CCDC" - }, - "ccdcSlice": { - "chartPixel": { - "cancel.tooltip": "Cancel time-series charting", - "loadFailed": "Failed to load chart: {error}", - "loadObservations.error": "Failed to load observations: {error}", - "loadSegments.error": "Failed to load segments: {error}", - "start.tooltip": "Chart time-series for pixel" - }, - "create": "CCDC slice", - "description": "Create a slice of a CCDC asset, for a specific date.", - "panel": { - "date": { - "button": "DAT", - "form": { - "date": { - "label": "Date of slice", - "range": "The CCDC segments were created with data from {segmentsStartDate} to {segmentsEndDate}", - "tooltip": "The date of the slice" - }, - "dateType": { - "label": "Date type", - "RANGE": "Date range", - "SINGLE": "Single date" - }, - "endDate": { - "label": "End date", - "range": "The CCDC segments were created with data from {startDate} to {endDate}", - "tooltip": "The end date of the slice" - }, - "startDate": { - "label": "Start date", - "tooltip": "The start date of the slice" - } - }, - "title": "Date", - "tooltip": "Specify date of the slice" + "decisionProcedure": { + "label": "Decision procedure", + "options": { + "Margin.label": "Margin", + "Voting.label": "Voting" + } }, - "options": { - "button": "OPT", - "form": { - "breakAnalysisBand": { - "label": "Break analysis band", - "placeholder": "Select band...", - "tooltip": "The band to use when analyzing which breakpoint to provide data for." - }, - "breakMagnitudeDirection": { - "options": { - "ANY": { - "label": "Any", - "tooltip": "Include all breakpoints independent on their change magnitude direction." - }, - "DECREASE": { - "label": "Decrease", - "tooltip": "Only include breakpoints with a negative break magnitude in the break analysis band." - }, - "INCREASE": { - "label": "Increase", - "tooltip": "Only include breakpoints with an positive break magnitude in the break analysis band." - } - }, - "label": "Break magnitude direction", - "tooltip": "Specify which breakpoints to filter out, based on weather the break magnitude for the break analysis band decrease or increase." - }, - "breakSelection": { - "options": { - "CONFIDENCE": { - "label": "Confidence", - "tooltip": "Select the breakpoint with the highest confidence." - }, - "FIRST": { - "label": "First", - "tooltip": "Select the first breakpoint in the date range." - }, - "LAST": { - "label": "Last", - "tooltip": "Select the last breakpoint in the date range." - }, - "MAGNITUDE": { - "label": "Magnitude", - "tooltip": "Select the breakpoint with the highest break magnitude." - } - }, - "label": "Break selection", - "tooltip": "Specify which of the break points to provide data for." - }, - "extrapolateMaxDays": { - "label": "Max days to extrapolate", - "unlimited": "all" - }, - "extrapolateSegment": { - "closest": { - "label": "Closest", - "tooltip": "Extrapolate the segment closest to the date." - }, - "label": "Segment to extrapolate", - "next": { - "label": "Next", - "tooltip": "Back-extrapolate the segment after the date." - }, - "previous": { - "label": "Previous", - "tooltip": "Extrapolate the segment before the date." - } - }, - "gapStrategy": { - "extrapolate": { - "label": "Extrapolate", - "tooltip": "Generate a new model based on the segment before or after the gap." - }, - "interpolate": { - "label": "Interpolate", - "tooltip": "Generate a new model based on surrounding segments. If there isn't any segment before or after, the pixel will be masked." - }, - "label": "Gap strategy", - "mask": { - "label": "Mask", - "tooltip": "Mask all pixels that fall on a gap." - }, - "tooltip": "Decide which strategy to use for pixels where there is no CCDC segment. There will be gaps during detected breaks or for dates before and after the CCDC date range." - }, - "harmonics": { - "label": "Harmonics", - "tooltip": "Specify how many harmonics from the CCDC regression model to use when creating the slice." - }, - "minBreakConfidence": { - "label": "Min break confidence", - "tooltip": "The minimum confidence of a breakpoint to be included. This is calculated by the break magnitude divided by the RMSE.", - "value": "{value} (magnitude/rmse)" - }, - "skipBreakInLastSegment": { - "label": "Breaks at the end", - "options": { - "EXCLUDE": "Exclude", - "INCLUDE": "Include" - }, - "tooltip": "CCDC sometimes detect breaks in the last segment. These can at times be unreliable." - } - }, - "title": "CCDC slice options", - "tooltip": "Configure how the CCDC slice is made" + "degree": { + "label": "Degree of polynomial", + "placeholder": "Degree..." }, - "retrieve": { - "apply": "Retrieve", - "form": { - "bandTypes": { - "amplitude1": { - "label": "Amplitude 1", - "tooltip": "The amplitude of the model's first order harmonic" - }, - "amplitude2": { - "label": "Amplitude 2", - "tooltip": "The amplitude of the model's second order harmonic" - }, - "amplitude3": { - "label": "Amplitude 3", - "tooltip": "The amplitude of the model's third order harmonic" - }, - "atLeastOne": "At least one band type must be selected", - "intercept": { - "label": "Intercept", - "tooltip": "The intercept of the model's linear component" - }, - "breakConfidence": { - "label": "Break confidence", - "tooltip": "Confidence of break, defined as magnitude/rmse" - }, - "label": "Band types", - "magnitude": { - "label": "Magnitude", - "tooltip": "Break magnitude of segment" - }, - "phase1": { - "label": "Phase 1", - "tooltip": "The phase of the model's first order harmonic" - }, - "phase2": { - "label": "Phase 2", - "tooltip": "The phase of the model's second order harmonic" - }, - "phase3": { - "label": "Phase 3", - "tooltip": "The phase of the model's third order harmonic" - }, - "rmse": { - "label": "RMSE", - "tooltip": "RMSE of the harmonic models" - }, - "slope": { - "label": "Slope", - "tooltip": "The slope of the model's linear component" - }, - "value": { - "label": "Value", - "tooltip": "The evaluated value of the harmonic models for the specified date" - } - }, - "baseBands": { - "atLeastOne": "At least one base band must be selected", - "label": "Base bands" - }, - "destination": { - "GEE": "Google Earth Engine asset", - "label": "Destination", - "required": "A destination must be selected", - "SEPAL": "SEPAL workspace", - "DRIVE": "Google Drive" - }, - "scale": { - "label": "Scale", - "placeholder": "Scale..." - }, - "segmentBands": { - "changeProb": { - "label": "Probability", - "tooltip": "Probability of detected break being accurate" - }, - "label": "Segment bands", - "numObs": { - "label": "Observations", - "tooltip": "Number of observations in segment" - }, - "tBreak": { - "label": "Break", - "tooltip": "Date of detected break" - }, - "tEnd": { - "label": "End", - "tooltip": "End date of segment" - }, - "tooltip": "Bands with information about the segment the slice falls in. If a pixel falls in a gap, the previous segment will be used", - "tStart": { - "label": "Start", - "tooltip": "Start date of segment" - } - } - }, - "task": { - "GEE": "Export recipe: {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", - "DRIVE": "Retrieve recipe: {name} to Google Drive" - }, - "title": "Retrieve", - "tooltip": "Retrieve time-series" + "gamma": { + "label": "Gamma", + "placeholder": "Gamma...", + "tooltip": "The gamma value in the kernel function. Defaults to the reciprocal of the number of features" }, - "source": { - "asset": { - "label": "Earth Engine CCDC Asset", - "title": "Earth Engine CCDC Asset", - "notCcdc": "Asset does not contain CCDC segments" + "kernelType": { + "label": "Kernel type", + "options": { + "LINEAR": { + "label": "Linear", + "tooltip": "u′×v (work well with large training data sets)" }, - "button": "SRC", - "form": { - "asset": { - "invalid": "Asset must be an exported CCDC asset", - "label": "CCDC asset", - "placeholder": "Enter CCDC asset ID", - "required": "An asset ID must be specified" - } + "POLY": { + "label": "Polynomial", + "tooltip": "(γ×u′×v + coef₀)ᵈᵉᵍʳᵉᵉ" }, - "recipe": { - "label": "Saved SEPAL CCDC recipe", - "title": "Saved SEPAL CCDC recipe" - }, - "title": "Source", - "tooltip": "Select EE asset with CCDC segments to slice" - } - }, - "preview": { - "description": "Preview of a CCDC slice, generated on the fly by EE.", - "error": "Failed to generate preview", - "initializing": "Loading preview...", - "label": "CCDC slice", - "loading": "Loading tiles: {pending}", - "tilesFailed": "{failed} failed" - }, - "source": { - "asset": { - "loadError": "Failed to load asset" - }, - "recipe": { - "loadError": "Failed to load recipe" - } - }, - "tabPlaceholder": "CCDC_slice" - }, - "changeAlerts": { - "create": "Change Alerts", - "description": "Generate change alerts using a CCDC as baseline.", - "imageLayerForm": { - "mosaicType": { - "label": "Mosaic", - "latest": { - "label": "Latest", - "tooltip": "Render a composite of the latest available imagery in the period" + "RBF": { + "label": "Gaussian RBD", + "tooltip": "exp(-γ×|u-v|²) (Handles non-linear problems well)" }, - "median": { - "label": "Median", - "tooltip": "Render a median composite of the period" + "SIGMOID": { + "label": "Sigmoid", + "tooltip": "tanh(γ×u′×v + coef₀)" } + }, + "tooltip": "The type of kernel function" + }, + "nu": { + "label": "nu", + "placeholder": "nu parameter...", + "tooltip": "The nu parameter" }, - "visualizationType": { - "calibration": { - "label": "Calibration", - "tooltip": "Render a composite of the calibration period" + "oneClass": { + "label": "Class to train on", + "placeholder": "Class to train on..." + }, + "shrinking": { + "label": "Shrinking", + "options": { + "no.label": "No", + "yes.label": "Yes" + }, + "tooltip": "Whether to use shrinking heuristics" + }, + "svmType": { + "label": "SVM type", + "options": { + "C_SVC": { + "label": "C-SVC", + "tooltip": "Default type" }, - "changes": { - "label": "Changes", - "tooltip": "Render change in the monitoring period" + "NU_SVC": { + "label": "NU-SVC", + "tooltip": "More flexible type" }, - "label": "Type", - "monitoring": { - "label": "Monitoring", - "tooltip": "Render a composite of the monitoring period" + "ONE_CLASS": { + "label": "ONE-CLASS", + "tooltip": "Train on a single class, using outliers as negative examples" } + }, + "tooltip": "The decision procedure to use for classification" } + }, + "label": "SVM" }, - "layers": { - "imageLayer": { - "changes": "Changes", - "dates": "Dates", - "observations": "Observations" - } - }, - "panel": { - "date": { - "button": "DAT", - "form": { - "calibration": { - "duration": { - "label": "Calibration duration", - "placeholder": "Duration..." - } - }, - "durationUnit": { - "placeholder": "Unit...", - "DAYS": "Days", - "MONTHS": "Months", - "WEEKS": "Weeks" - }, - "monitoring": { - "duration": { - "label": "Monitoring duration", - "placeholder": "Duration..." - } - }, - "monitoringEnd": { - "label": "Monitoring end", - "tooltip": "The date when monitoring ends. You typically set this to today's date" - }, - "range": "The CCDC segments were created with data from {segmentsStartDate} to {segmentsEndDate}.", - "noRange": "The CCDC segments doesn't specify any date range." - }, - "title": "Date range", - "tooltip": "Specify date range to get alerts for" + "type": { + "label": "Type" + }, + "normalize": { + "label": "Normalize", + "tooltip": "Determine whether the input bands should automatically be scaled to a comparable numeric range before classification", + "YES": { + "label": "Yes", + "tooltip": "Automatically scales input bands to a comparable numeric range before classification" + }, + "NO": { + "label": "No", + "tooltip": "Assume all input bands are already scaled appropriately for the selected classifier. This avoids redundant processing and improves performance" + } + }, + "tileScale": { + "label": "Tile scale", + "tooltip": "(Range: 0.1 to 16) Controls the tile size used during training data sampling and normalization. Larger values use smaller processing tiles, which reduce memory usage and can prevent out-of-memory errors, but may be slower.", + "placeholder": "Tile scale..." + } + }, + "title": "Classifier configuration", + "tooltip": "Configure the classifier" + }, + "inputImagery": { + "asset": { + "title": "Earth Engine asset" + }, + "bandSetSpec": { + "addBands": { + "placeholder": "Select bands...", + "tooltip": "Add bands" + }, + "imageBands": { + "label": "Image bands" + }, + "indexes": { + "label": "Indexes" + }, + "pairWiseExpression": { + "ANGLE": { + "label": "Angle" + }, + "DIFFERENCE": { + "label": "Difference" + }, + "DISTANCE": { + "label": "Distance" + }, + "NORMALIZED_DIFFERENCE": { + "label": "Normalized difference" + }, + "RATIO": { + "label": "Ratio" + } + } + }, + "button": "IMG", + "derivedBands": { + "bandCombinations": { + "label": "Band combinations", + "options": { + "ANGLE": { + "label": "Angle", + "tooltip": "Include bands with the angle evaluated pair-wise between all selected bands" }, - "options": { - "button": "OPT", - "form": { - "minConfidence": { - "label": "Min confidence", - "tooltip": "Minimum number of errors from reference model to consider observation a change.", - "value": "{value} (difference/rmse)" - }, - "numberOfObservations": { - "label": "Number of observations", - "tooltip": "Number of observations in window when looking at consecutive changes" - }, - "minNumberOfChanges": { - "label": "Min number of changes", - "tooltip": "Minimum number of change observations in window to consider the window a change, or minimum number of stable observations to consider the window stable" - }, - "mustBeConfirmedInMonitoring": { - "label": "Confirmed in monitoring", - "tooltip": "A change must be confirmed in the monitoring period. If it was confirmed already in the calibration period, the change is discarded. This prevents changes occuring in the calibration period from being detected." - }, - "mustBeStableBeforeChange": { - "label": "Stable before change", - "tooltip": "A change must be stable before it's detected. Otherwise, the change is discarded. This prevents changes that happened outside of the monitoring period in the last year from being detected." - }, - "mustStayChanged": { - "label": "Must stay change", - "tooltip": "A change must stay change, and not be classified as stable after the change was detected. Otherwise, the change is discarded. This lowers the risk of multiple consecutive outliers to be classified as change." - } - }, - "title": "Change alert options", - "tooltip": "Configure sensitivity in detecting changes" + "DIFFERENCE": { + "label": "Difference", + "tooltip": "Include bands with the difference evaluated pair-wise between all selected bands" }, - "preprocess": { - "button": "PRC", - "title": "Pre-process", - "tooltip": "Configure imagery pre-processing" + "DISTANCE": { + "label": "Distance", + "tooltip": "Include bands with the distance evaluated pair-wise between all selected bands" }, - "reference": { - "asset": { - "label": "Earth Engine CCDC Asset", - "title": "Earth Engine CCDC Asset", - "notCcdc": "Asset does not contain CCDC segments" - }, - "button": "REF", - "form": { - "asset": { - "invalid": "Asset must be an exported CCDC asset", - "label": "CCDC asset", - "placeholder": "Enter CCDC asset ID", - "required": "An asset ID must be specified" - } - }, - "recipe": { - "label": "Saved SEPAL CCDC recipe", - "title": "Saved SEPAL CCDC recipe" - }, - "title": "Source", - "tooltip": "Select CCDC asset or recipe to use as reference for alerts" + "INDEXES": { + "label": "Indexes", + "tooltip": "Include selected indexes" }, - "retrieve": { - "apply": "Retrieve", - "form": { - "bandTypes": { - "amplitude1": { - "label": "Amplitude 1", - "tooltip": "The amplitude of the model's first order harmonic" - }, - "amplitude2": { - "label": "Amplitude 2", - "tooltip": "The amplitude of the model's second order harmonic" - }, - "amplitude3": { - "label": "Amplitude 3", - "tooltip": "The amplitude of the model's third order harmonic" - }, - "atLeastOne": "At least one band type must be selected", - "intercept": { - "label": "Intercept", - "tooltip": "The intercept of the model's linear component" - }, - "breakConfidence": { - "label": "Break confidence", - "tooltip": "Confidence of break, defined as magnitude/rmse" - }, - "label": "Band types", - "magnitude": { - "label": "Magnitude", - "tooltip": "Break magnitude of segment" - }, - "phase1": { - "label": "Phase 1", - "tooltip": "The phase of the model's first order harmonic" - }, - "phase2": { - "label": "Phase 2", - "tooltip": "The phase of the model's second order harmonic" - }, - "phase3": { - "label": "Phase 3", - "tooltip": "The phase of the model's third order harmonic" - }, - "rmse": { - "label": "RMSE", - "tooltip": "RMSE of the harmonic models" - }, - "slope": { - "label": "Slope", - "tooltip": "The slope of the model's linear component" - }, - "value": { - "label": "Value", - "tooltip": "The evaluated value of the harmonic models for the specified date" - } - }, - "baseBands": { - "atLeastOne": "At least one base band must be selected", - "label": "Base bands" - }, - "destination": { - "GEE": "Google Earth Engine asset", - "label": "Destination", - "required": "A destination must be selected", - "SEPAL": "SEPAL workspace", - "DRIVE": "Google Drive" - }, - "scale": { - "label": "Scale" - }, - "segmentBands": { - "changeProb": { - "label": "Probability", - "tooltip": "Probability of detected break being accurate" - }, - "label": "Segment bands", - "numObs": { - "label": "Observations", - "tooltip": "Number of observations in segment" - }, - "tBreak": { - "label": "Break", - "tooltip": "Date of detected break" - }, - "tEnd": { - "label": "End", - "tooltip": "End date of segment" - }, - "tooltip": "Bands with information about the segment the slice falls in. If a pixel falls in a gap, the previous segment will be used", - "tStart": { - "label": "Start", - "tooltip": "Start date of segment" - } - } - }, - "task": { - "GEE": "Export recipe: {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve recipe: {name} to SEPAL workspace" - }, - "title": "Retrieve", - "tooltip": "Retrieve time-series" + "NORMALIZED_DIFFERENCE": { + "label": "Normalized difference", + "tooltip": "Include bands with the normalized difference evaluated pair-wise between all selected bands" }, - "sources": { - "button": "SRC", - "form": { - "band": { - "label": "Analysis band" - }, - "cloudPercentageThreshold": { - "label": "Max cloud cover %", - "value": "{value}%", - "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" - }, - "dataSets": { - "label": "Data sets", - "optical": { - "label": "Optical" - }, - "options": { - "LANDSAT_7": { - "label": "L7", - "tooltip": "Landsat 7" - }, - "LANDSAT_7_T2": { - "label": "L7 T2", - "tooltip": "Landsat 7 Tier 2" - }, - "LANDSAT_8": { - "label": "L8", - "tooltip": "Landsat 8" - }, - "LANDSAT_8_T2": { - "label": "L8 T2", - "tooltip": "Landsat 8 Tier 2" - }, - "LANDSAT_9": { - "label": "L9", - "tooltip": "Landsat 9" - }, - "LANDSAT_9_T2": { - "label": "L9 T2", - "tooltip": "Landsat 9 Tier 2" - }, - "LANDSAT_TM": { - "label": "L4-5", - "tooltip": "Landsat 4-5" - }, - "LANDSAT_TM_T2": { - "label": "L4-5 T2", - "tooltip": "Landsat 4-5 Tier 2" - }, - "SENTINEL_1": { - "label": "S1", - "tooltip": "Sentinel 1 A+B" - }, - "SENTINEL_2": { - "label": "S2", - "tooltip": "Sentinel 2 A+B" - } - }, - "radar": { - "label": "Radar" - } - }, - "dataSetType": { - "label": "Type" - }, - "dataSetTypes": { - "OPTICAL": "Optical", - "PLANET": "Planet", - "RADAR": "Radar" - }, - "required": "A data source must be selected" - }, - "title": "Sources", - "tooltip": "Select image source" + "RATIO": { + "label": "Ratio", + "tooltip": "Include bands with the ratio evaluated pair-wise between all selected bands" } - }, - "preview": { - "description": "Preview of change alerts, generated on the fly by EE.", - "error": "Failed to generate preview", - "initializing": "Loading preview...", - "label": "Change alerts", - "loading": "Loading tiles: {pending}", - "tilesFailed": "{failed} failed" - }, - "reference": { - "asset": { - "loadError": "Failed to load asset" + } + }, + "label": "Derived bands", + "profiles": { + "label": "Profiles", + "options": { + "RLCMS": { + "label": "RLCMS", + "tooltip": "Include bands used by the Regional Land Cover Monitoring System" }, - "recipe": { - "loadError": "Failed to load recipe" + "SIMPLE": { + "label": "Simple", + "tooltip": "Include red, nir, swir1, swir2 and ratios between them." } - }, - "tabPlaceholder": "Change_alerts" + } + }, + "tooltip": "Add additional derived bands to use in the classification." + }, + "form": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID...", + "required": "You must enter an asset ID" + }, + "includedBands": { + "label": "Included bands" + }, + "recipe": { + "label": "Recipe", + "placeholder": "Select a recipe...", + "required": "You must select a recipe" + }, + "section": { + "required": "An image must be selected" + } + }, + "loadingBands": "Loading available bands", + "loadingBandsError": "Failed to load from Google Earth Engine", + "noImagery": "Please add at least one image to classify.", + "recipe": { + "title": "Saved SEPAL recipe" + }, + "remove": { + "confirmationMessage": "Are you sure you want to remove {name} from your classification?", + "tooltip": "Remove image from classification" + }, + "sections": { + "title": "Image to classify" + }, + "title": "Images to classify", + "tooltip": "Select images to classify", + "type": { + "ASSET": "Earth Engine asset", + "RECIPE_REF": "SEPAL recipe" + } }, - "classification": { - "bands": { - "class": "Class", - "classProbability": "Class probability", - "probability": "{class} %", - "regression": "Regression" - }, - "collect": { - "disable.tooltip": "Disable reference data collection", - "enable.tooltip": "Enable reference data collection", - "findingNextPoint": { - "error": "Failed to find points: {error}", - "message": "Finding next point...", - "notFound": "Unable to find any points for you. Try to select some yourself.", - "title": "Finding next point..." + "legend": { + "button": "LEG", + "title": "Legend", + "tooltip": "Setup legend for classification", + "import": "Import..." + }, + "trainingData": { + "button": "TRN", + "classMapping": { + "addColumns": { + "placeholder": "Add columns...", + "tooltip": "Add columns to map to this value" + }, + "columnValues": "Column value(s)", + "enterExpression": "Enter expression", + "noColumValuesSelected": "No column value selected for this class", + "removeColumnValue": "Remove column value" + }, + "clearCollected": { + "confirmation": { + "label": "Clear", + "message": "Are you sure you want to clear all collected reference data?", + "title": "Clear reference data" + }, + "label": "Clear collected reference data" + }, + "export": { + "label": "Export reference data to CSV" + }, + "form": { + "class": { + "classColumnFormat": { + "label": "Class format", + "options": { + "MULTIPLE_COLUMNS": { + "label": "Column per class", + "tooltip": "The classes are in separate columns" + }, + "OTHER_FORMAT": { + "label": "Other format", + "tooltip": "Reference data has a different format" + }, + "SINGLE_COLUMN": { + "label": "Single column", + "tooltip": "The classes are in a single column" + } } - }, - "create": "Classification", - "description": "Classify imagery from SEPAL or Google Earth Engine. Include imagery from multiple points in time to do change detection.", - "layers": { - "imageLayer": { - "preSets": "Pre-sets" + }, + "valueColumn": { + "label": "Class column", + "placeholder": "Select column containing class values...", + "required": "A column must be selected", + "tooltip": "Select column containing class values" + } + }, + "csvUpload": { + "file": { + "label": "CSV file" + } + }, + "eeTable": { + "label": "EE table asset ID", + "placeholder": "Enter an asset ID...", + "required": "You must enter an asset ID" + }, + "location": { + "geoJson": { + "label": "GeoJSON column", + "placeholder": "Select column" + }, + "locationType": { + "GEO_JSON": { + "label": "GeoJSON column", + "tooltip": "Location is in a single GeoJSON column" }, - "referenceData": { - "description": "Reference data points used to create classification", - "label": "Reference data" + "label": "Location type", + "tooltip": "Select how location of reference data is represented", + "XY_COLUMNS": { + "label": "X/Y coordinate columns", + "tooltip": "Location is in two X/Y coordinate columns" } - }, - "panel": { - "auxiliaryImagery": { - "button": "AUX", - "form": { - "options": { - "label": "Sources", - "latitude": { - "label": "Latitude", - "tooltip": "Include the latitude of each pixel." - }, - "terrain": { - "label": "Terrain", - "tooltip": "Include SRTM data." - }, - "water": { - "label": "Water", - "tooltip": "Include JRC Global Surface Water Mapping Layers." - } - } - }, - "title": "Auxiliary sources", - "tooltip": "Include Auxiliary image sources in classification" + }, + "xColumn": { + "label": "X coordinate column", + "placeholder": "Select column" + }, + "yColumn": { + "label": "Y coordinate column", + "placeholder": "Select column" + } + }, + "sampleClassification": { + "typeToSample": { + "label": "Image source", + "ASSET": "EE Asset", + "RECIPE": "Recipe" + }, + "assetToSample": { + "label": "EE asset ID of classification", + "placeholder": "Enter an asset ID...", + "required": "You must enter an asset ID" + }, + "recipeToSample": { + "label": "Recipe to sample" + }, + "sampleScale": { + "label": "Scale to sample in", + "placeholder": "Scale...", + "required": "You must specify the scale to sample in", + "suffix": "meters", + "tooltip": "The scale to sample the asset in" + }, + "samplesPerClass": { + "label": "Samples per class", + "placeholder": "Count...", + "required": "You must specify the number of samples to collect for each class", + "suffix": "samples", + "tooltip": "The number of samples to extract from the image" + }, + "valueColumn": { + "label": "Class band", + "placeholder": "Select band containing class...", + "tooltip": "Select band containing class" + } + }, + "ceo": { + "login": { + "title": "Collect Earth Online Authentication", + "tooltip": "Use training data from CEO", + "email": { + "label": "CEO Email", + "placeholder": "Enter your email address", + "required": "You must enter your email address", + "invalid": "Invalid email format", + "tooltip": "Email of the CEO account you wish to connect to" }, - "classifier": { - "button": "CLS", - "form": { - "cart": { - "label": "Cart" - }, - "decisionTree": { - "label": "Decision tree", - "placeholder": "Paste or drop file with the decision tree...", - "tooltip": "Paste or drop file with the decision tree" - }, - "gradientTreeBoost": { - "config": { - "loss": { - "label": "Loss function", - "options": { - "Huber.label": "Huber", - "LeastAbsoluteDeviation.label": "Least abs. deviation", - "LeastSquares.label": "Least squares" - }, - "placeholder": "Loss function...", - "tooltip": "Loss function for regression" - }, - "samplingRate": { - "label": "Sampling rate", - "placeholder": "Sampling rate...", - "tooltip": "The sampling rate for stochastic tree boosting" - }, - "shrinkage": { - "label": "Shrinkage", - "placeholder": "Shrinkage...", - "tooltip": "The shrinkage parameter in [0, 1] controls the learning rate of procedure" - } - }, - "label": "Gradient tree boost" - }, - "minimumDistance": { - "config": { - "metric": { - "label": "Metric", - "options": { - "cosine": { - "label": "Spectral angle", - "tooltip": "Spectral angle from the un-normalized class mean" - }, - "euclidean": { - "label": "Euclidean", - "tooltip": "Euclidean distance from the un-normalized class mean" - }, - "mahalanobis": { - "label": "Mahalanobis", - "tooltip": "Mahalanobis distance from the class mean" - } - } - } - }, - "label": "Min distance" - }, - "naiveBayes": { - "config": { - "lambda": { - "label": "Lambda", - "placeholder": "Lambda...", - "tooltip": "A smoothing lambda. Used to avoid assigning zero probability to classes not seen during training, instead using lambda / (lambda * nFeatures)" - } - }, - "label": "Naive bayes" - }, - "randomForest": { - "config": { - "bagFraction": { - "label": "Bag fraction", - "placeholder": "Bag fraction...", - "tooltip": "The fraction of input to bag per tree" - }, - "maxNodes": { - "label": "Max nodes", - "placeholder": "Max nodes...", - "tooltip": "The maximum number of leaf nodes in each tree. If unspecified, defaults to no limit" - }, - "minLeafPopulation": { - "label": "Min leaf population", - "placeholder": "Min leaf population...", - "tooltip": "Only create nodes whose training set contains at least this many points" - }, - "numberOfTrees": { - "label": "Number of trees", - "placeholder": "Number of trees...", - "tooltip": "The number of decision trees to create" - }, - "seed": { - "label": "Seed", - "placeholder": "Seed...", - "tooltip": "The randomization seed" - }, - "variablesPerSplit": { - "label": "Variables per split", - "placeholder": "Variables per split...", - "tooltip": "The number of variables per split. If unspecified, uses the square root of the number of variables." - } - }, - "label": "Random Forest" - }, - "svm": { - "config": { - "coef0": { - "label": "coef₀", - "placeholder": "coef₀...", - "tooltip": "The coef₀ value in the kernel function" - }, - "cost": { - "label": "Cost", - "placeholder": "Cost...", - "tooltip": "The cost (C) parameter" - }, - "decisionProcedure": { - "label": "Decision procedure", - "options": { - "Margin.label": "Margin", - "Voting.label": "Voting" - } - }, - "degree": { - "label": "Degree of polynomial", - "placeholder": "Degree..." - }, - "gamma": { - "label": "Gamma", - "placeholder": "Gamma...", - "tooltip": "The gamma value in the kernel function. Defaults to the reciprocal of the number of features" - }, - "kernelType": { - "label": "Kernel type", - "options": { - "LINEAR": { - "label": "Linear", - "tooltip": "u′×v (work well with large training data sets)" - }, - "POLY": { - "label": "Polynomial", - "tooltip": "(γ×u′×v + coef₀)ᵈᵉᵍʳᵉᵉ" - }, - "RBF": { - "label": "Gaussian RBD", - "tooltip": "exp(-γ×|u-v|²) (Handles non-linear problems well)" - }, - "SIGMOID": { - "label": "Sigmoid", - "tooltip": "tanh(γ×u′×v + coef₀)" - } - }, - "tooltip": "The type of kernel function" - }, - "nu": { - "label": "nu", - "placeholder": "nu parameter...", - "tooltip": "The nu parameter" - }, - "oneClass": { - "label": "Class to train on", - "placeholder": "Class to train on..." - }, - "shrinking": { - "label": "Shrinking", - "options": { - "no.label": "No", - "yes.label": "Yes" - }, - "tooltip": "Whether to use shrinking heuristics" - }, - "svmType": { - "label": "SVM type", - "options": { - "C_SVC": { - "label": "C-SVC", - "tooltip": "Default type" - }, - "NU_SVC": { - "label": "NU-SVC", - "tooltip": "More flexible type" - }, - "ONE_CLASS": { - "label": "ONE-CLASS", - "tooltip": "Train on a single class, using outliers as negative examples" - } - }, - "tooltip": "The decision procedure to use for classification" - } - }, - "label": "SVM" - }, - "type": { - "label": "Type" - }, - "normalize": { - "label": "Normalize", - "tooltip": "Determine whether the input bands should automatically be scaled to a comparable numeric range before classification", - "YES": { - "label": "Yes", - "tooltip": "Automatically scales input bands to a comparable numeric range before classification" - }, - "NO": { - "label": "No", - "tooltip": "Assume all input bands are already scaled appropriately for the selected classifier. This avoids redundant processing and improves performance" - } - }, - "tileScale": { - "label": "Tile scale", - "tooltip": "(Range: 0.1 to 16) Controls the tile size used during training data sampling and normalization. Larger values use smaller processing tiles, which reduce memory usage and can prevent out-of-memory errors, but may be slower.", - "placeholder": "Tile scale..." - } - }, - "title": "Classifier configuration", - "tooltip": "Configure the classifier" + "password": { + "label": "CEO Password", + "placeholder": "Enter your CEO password", + "required": "You must enter your CEO password" }, - "inputImagery": { - "asset": { - "title": "Earth Engine asset" - }, - "bandSetSpec": { - "addBands": { - "placeholder": "Select bands...", - "tooltip": "Add bands" - }, - "imageBands": { - "label": "Image bands" - }, - "indexes": { - "label": "Indexes" - }, - "pairWiseExpression": { - "ANGLE": { - "label": "Angle" - }, - "DIFFERENCE": { - "label": "Difference" - }, - "DISTANCE": { - "label": "Distance" - }, - "NORMALIZED_DIFFERENCE": { - "label": "Normalized difference" - }, - "RATIO": { - "label": "Ratio" - } - } - }, - "button": "IMG", - "derivedBands": { - "bandCombinations": { - "label": "Band combinations", - "options": { - "ANGLE": { - "label": "Angle", - "tooltip": "Include bands with the angle evaluated pair-wise between all selected bands" - }, - "DIFFERENCE": { - "label": "Difference", - "tooltip": "Include bands with the difference evaluated pair-wise between all selected bands" - }, - "DISTANCE": { - "label": "Distance", - "tooltip": "Include bands with the distance evaluated pair-wise between all selected bands" - }, - "INDEXES": { - "label": "Indexes", - "tooltip": "Include selected indexes" - }, - "NORMALIZED_DIFFERENCE": { - "label": "Normalized difference", - "tooltip": "Include bands with the normalized difference evaluated pair-wise between all selected bands" - }, - "RATIO": { - "label": "Ratio", - "tooltip": "Include bands with the ratio evaluated pair-wise between all selected bands" - } - } - }, - "label": "Derived bands", - "profiles": { - "label": "Profiles", - "options": { - "RLCMS": { - "label": "RLCMS", - "tooltip": "Include bands used by the Regional Land Cover Monitoring System" - }, - "SIMPLE": { - "label": "Simple", - "tooltip": "Include red, nir, swir1, swir2 and ratios between them." - } - } - }, - "tooltip": "Add additional derived bands to use in the classification." - }, - "form": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID...", - "required": "You must enter an asset ID" - }, - "includedBands": { - "label": "Included bands" - }, - "recipe": { - "label": "Recipe", - "placeholder": "Select a recipe...", - "required": "You must select a recipe" - }, - "section": { - "required": "An image must be selected" - } - }, - "loadingBands": "Loading available bands", - "loadingBandsError": "Failed to load from Google Earth Engine", - "noImagery": "Please add at least one image to classify.", - "recipe": { - "title": "Saved SEPAL recipe" - }, - "remove": { - "confirmationMessage": "Are you sure you want to remove {name} from your classification?", - "tooltip": "Remove image from classification" - }, - "sections": { - "title": "Image to classify" - }, - "title": "Images to classify", - "tooltip": "Select images to classify", - "type": { - "ASSET": "Earth Engine asset", - "RECIPE_REF": "SEPAL recipe" - } + "connect.label": "Connect", + "disconnect.label": "Disconnect", + "connected": { + "title": "You are connected to CEO", + "tooltip": "You are connected to CEO" }, - "legend": { - "button": "LEG", - "title": "Legend", - "tooltip": "Setup legend for classification", - "import": "Import..." + "disconnected": { + "title": "Disconnected from CEO", + "tooltip": "You are disconnected from CEO" }, - "trainingData": { - "button": "TRN", - "classMapping": { - "addColumns": { - "placeholder": "Add columns...", - "tooltip": "Add columns to map to this value" - }, - "columnValues": "Column value(s)", - "enterExpression": "Enter expression", - "noColumValuesSelected": "No column value selected for this class", - "removeColumnValue": "Remove column value" - }, - "clearCollected": { - "confirmation": { - "label": "Clear", - "message": "Are you sure you want to clear all collected reference data?", - "title": "Clear reference data" - }, - "label": "Clear collected reference data" - }, - "export": { - "label": "Export reference data to CSV" - }, - "form": { - "class": { - "classColumnFormat": { - "label": "Class format", - "options": { - "MULTIPLE_COLUMNS": { - "label": "Column per class", - "tooltip": "The classes are in separate columns" - }, - "OTHER_FORMAT": { - "label": "Other format", - "tooltip": "Reference data has a different format" - }, - "SINGLE_COLUMN": { - "label": "Single column", - "tooltip": "The classes are in a single column" - } - } - }, - "valueColumn": { - "label": "Class column", - "placeholder": "Select column containing class values...", - "required": "A column must be selected", - "tooltip": "Select column containing class values" - } - }, - "csvUpload": { - "file": { - "label": "CSV file" - } - }, - "eeTable": { - "label": "EE table asset ID", - "placeholder": "Enter an asset ID...", - "required": "You must enter an asset ID" - }, - "location": { - "geoJson": { - "label": "GeoJSON column", - "placeholder": "Select column" - }, - "locationType": { - "GEO_JSON": { - "label": "GeoJSON column", - "tooltip": "Location is in a single GeoJSON column" - }, - "label": "Location type", - "tooltip": "Select how location of reference data is represented", - "XY_COLUMNS": { - "label": "X/Y coordinate columns", - "tooltip": "Location is in two X/Y coordinate columns" - } - }, - "xColumn": { - "label": "X coordinate column", - "placeholder": "Select column" - }, - "yColumn": { - "label": "Y coordinate column", - "placeholder": "Select column" - } - }, - "sampleClassification": { - "typeToSample": { - "label": "Image source", - "ASSET": "EE Asset", - "RECIPE": "Recipe" - }, - "assetToSample": { - "label": "EE asset ID of classification", - "placeholder": "Enter an asset ID...", - "required": "You must enter an asset ID" - }, - "recipeToSample": { - "label": "Recipe to sample" - }, - "sampleScale": { - "label": "Scale to sample in", - "placeholder": "Scale...", - "required": "You must specify the scale to sample in", - "suffix": "meters", - "tooltip": "The scale to sample the asset in" - }, - "samplesPerClass": { - "label": "Samples per class", - "placeholder": "Count...", - "required": "You must specify the number of samples to collect for each class", - "suffix": "samples", - "tooltip": "The number of samples to extract from the image" - }, - "valueColumn": { - "label": "Class band", - "placeholder": "Select band containing class...", - "tooltip": "Select band containing class" - } - }, - "ceo": { - "login": { - "title": "Collect Earth Online Authentication", - "tooltip": "Use training data from CEO", - "email": { - "label": "CEO Email", - "placeholder": "Enter your email address", - "required": "You must enter your email address", - "invalid": "Invalid email format", - "tooltip": "Email of the CEO account you wish to connect to" - }, - "password": { - "label": "CEO Password", - "placeholder": "Enter your CEO password", - "required": "You must enter your CEO password" - }, - "connect.label": "Connect", - "disconnect.label": "Disconnect", - "connected": { - "title": "You are connected to CEO", - "tooltip": "You are connected to CEO" - }, - "disconnected": { - "title": "Disconnected from CEO", - "tooltip": "You are disconnected from CEO" - }, - "error": { - "invalidCredentials": "Invalid CEO email or password", - "serverTitle": "Failed to connect to CEO", - "serverMessage": "SEPAL unfortunately failed to connect to Collect Earth Online. Please try again later." - } - }, - "token": { - "invalid": "CEO token is invalid or expired. Please try to reconnect.", - "timedOut": "Your connection to CEO has expired. Please try to reconnect." - }, - "disconnected": { - "description": "Connect your Collect Earth Online (CEO) account to SEPAL to seamlessly access your projects and extract data for training your classification models. Rest assured, your credentials are never stored on SEPAL servers. Once you end your SEPAL session, your CEO connection is automatically terminated." - }, - "dataType": { - "title": "Data type", - "tooltip": "Use the sample or aggregated plot data", - "sample": { - "label": "Sample", - "tooltip": "Use the sample data from CEO" - }, - "plot": { - "label": "Plot", - "tooltip": "Use the plot data from CEO" - } - }, - "institution": { - "label": "Institution", - "placeholder": "Select an institution", - "required": "You must select an institution", - "tooltip": "You can only see institutions where you are both a member and an admin" - }, - "project": { - "label": "Project", - "placeholder": "Select a project", - "required": "You must select a project", - "tooltip": "Select the project that you want to use the training data from" - }, - "loadCsv": { - "failed": "Failed to get the training data from CEO" - }, - "loadProjects": { - "failed": "Failed to get the projects for the selected institution" - }, - "loadInstitutions": { - "failed": "Failed to get the institutions" - } - } - }, - "noDataSet": "No training data set added", - "remove": { - "confirmationMessage": "Are you sure you want to remove {name} from your training data?", - "tooltip": "Remove training data set" - }, - "sectionSelection": { - "title": "Training data set" - }, - "title": "Training data", - "tooltip": "Configure training data source", - "type": { - "COLLECTED": { - "label": "Collected reference data", - "title": "COLLECTED" - }, - "CSV_UPLOAD": { - "label": "CSV file", - "title": "CSV UPLOAD", - "tooltip": "Upload a CSV file." - }, - "EE_TABLE": { - "label": "Earth Engine Table", - "title": "EE TABLE", - "tooltip": "Load reference data from an EE table." - }, - "RECIPE": { - "label": "Saved SEPAL recipe", - "title": "SEPAL RECIPE", - "tooltip": "Use training data from a saved SEPAL recipe." - }, - "SAMPLE_CLASSIFICATION": { - "label": "Sample classification", - "title": "SAMPLE CLASSIFICATION", - "tooltip": "Sample a pre-existing classification." - }, - "CEO": { - "label": "Collect Earth Online", - "title": "Collect Earth Online", - "tooltip": "Use training data from CEO" - } - } - } - }, - "preview": { - "description": "Preview of classification, generated on the fly by EE.", - "error": "Failed to create preview", - "initializing": "Loading preview...", - "label": "Classification", - "loading": "Loading tiles: {pending}", - "tilesFailed": "{failed} failed" - }, - "tabPlaceholder": "Classification" - }, - "regression": { - "bands": { - "regression": "Regression" - }, - "create": "Regression", - "description": "Create a regression from SEPAL or Google Earth Engine imagery.", - "panel": { - "trainingData": { - "form": { - "eeTable": { - "valueColumn": { - "label": "Value column", - "placeholder": "Select value column...", - "tooltip": "Select column containing the values to train the regression classifier with." - } - }, - "sampleImage": { - "sampleCount": { - "label": "Sample count", - "placeholder": "Count...", - "required": "You must specify the number of samples to collect", - "suffix": "samples", - "tooltip": "The number of samples to extract from the image" - }, - "valueBand": { - "label": "Value band", - "placeholder": "Select band with value to sample...", - "tooltip": "The band to sample and use to train the regression classifier." - }, - "noIntersection": "The image to sample does not intersect the recipe", - "unableToSampleAllPixels": "Unable to sample {numPixels} from the image. It's either too few pixels or too large areas of the image is masked." - } - }, - "type": { - "EE_TABLE": { - "label": "Earth Engine Table", - "tooltip": "Load reference data from an EE table", - "title": "EE Table" - }, - "SAMPLE_IMAGE": { - "label": "Sample Image", - "tooltip": "Sample a pre-existing image", - "title": "Sample Image" - }, - "RECIPE": { - "label": "Saved SEPAL recipe", - "tooltip": "Use training data from a saved SEPAL recipe", - "title": "SEPAL Recipe" - } - } + "error": { + "invalidCredentials": "Invalid CEO email or password", + "serverTitle": "Failed to connect to CEO", + "serverMessage": "SEPAL unfortunately failed to connect to Collect Earth Online. Please try again later." } - }, - "tabPlaceholder": "Regression" - }, - "unsupervisedClassification": { - "bands": { - "class": "Class" - }, - "create": "Unsupervised Classification", - "description": "Create an unsupervised classification from SEPAL or Google Earth Engine imagery.", - "panel": { - "sampling": { - "button": "SMP", - "tooltip": "Configure the sampling", - "title": "Sampling configuration", - "form": { - "numberOfSamples": { - "label": "Number of samples", - "placeholder": "Count...", - "tooltip": "The number of samples to use to train the clusterer.", - "suffix": "samples" - }, - "sampleScale": { - "label": "Scale to sample in", - "placeholder": "Scale...", - "suffix": "meters", - "tooltip": "The scale to sample the asset in" - } - } + }, + "token": { + "invalid": "CEO token is invalid or expired. Please try to reconnect.", + "timedOut": "Your connection to CEO has expired. Please try to reconnect." + }, + "disconnected": { + "description": "Connect your Collect Earth Online (CEO) account to SEPAL to seamlessly access your projects and extract data for training your classification models. Rest assured, your credentials are never stored on SEPAL servers. Once you end your SEPAL session, your CEO connection is automatically terminated." + }, + "dataType": { + "title": "Data type", + "tooltip": "Use the sample or aggregated plot data", + "sample": { + "label": "Sample", + "tooltip": "Use the sample data from CEO" }, - "clusterer": { - "button": "CLS", - "tooltip": "Configure the clusterer", - "title": "Clusterer configuration", - "form": { - "type": { - "label": "Type" - }, - "kmeans": { - "label": "KMeans", - "tooltip": "Cluster data using the k-means algorithm." - }, - "cascadeKMeans": { - "label": "Cascade KMeans", - "tooltip": "Cascade simple k-means selects the best k according to the Calinski-Harabasz criterion." - }, - "xmeans": { - "label": "XMeans", - "tooltip": "X-Means is K-Means with an efficient estimation of the number of clusters." - }, - "lvq": { - "label": "LVQ", - "tooltip": "A Clusterer that implements the Learning Vector Quantization algorithm." - }, - "config": { - "numberOfClusters": { - "label": "Number of clusters", - "placeholder": "Number of clusters...", - "tooltip": "The number of clusters to generate" - }, - "minNumberOfClusters": { - "label": "Min number of clusters", - "placeholder": "Min number of clusters...", - "tooltip": "The minimum number of clusters to generate" - }, - "maxNumberOfClusters": { - "label": "Max number of clusters", - "placeholder": "Max number of clusters...", - "tooltip": "The maximum number of clusters to generate" - }, - "restarts": { - "label": "Restarts", - "placeholder": "Restarts...", - "tooltip": "Number of restarts." - }, - "init": { - "label": "Initialization method", - "options": { - "random.label": "Random", - "kmeans.label": "K-means++", - "canopy.label": "Canopy", - "farthestFirst.label": "farthest first" - } - }, - "distanceFunction": { - "label": "Distance function", - "options": { - "euclidean.label": "Euclidean", - "manhattan.label": "Manhattan", - "chebyshev.label": "Chebyshev" - } - }, - "maxIterations": { - "label": "Max iterations", - "placeholder": "Max iterations...", - "tooltip": "Maximum number of iterations." - }, - "canopies": { - "label": "Canopies", - "tooltip": "Specify whether to use canopies to reduce the number of distance calculations or not.", - "options": { - "true.label": "Canopies", - "false.label": "No Canopies" - } - }, - "maxCandidates": { - "label": "Max candidates", - "placeholder": "Max candidates...", - "tooltip": "Maximum number of candidate canopies to retain in memory at any one time when using canopy clustering. T2 distance plus, data characteristics, will determine how many candidate canopies are formed before periodic and final pruning are performed, which might result in exceess memory consumption. This setting avoids large numbers of candidate canopies consuming memory." - }, - "periodicPruning": { - "label": "Periodic pruning", - "placeholder": "Periodic pruning...", - "tooltip": "How often to prune low density canopies when using canopy clustering." - }, - "minDensity": { - "label": "Min density", - "placeholder": "Min density...", - "tooltip": "Minimum canopy density, when using canopy clustering, below which a canopy will be pruned during periodic pruning." - }, - "t1": { - "label": "T1", - "placeholder": "T1...", - "tooltip": "The T1 distance to use when using canopy clustering. A value < 0 is taken as a positive multiplier for T2." - }, - "t2": { - "label": "T2", - "placeholder": "T2...", - "tooltip": "The T2 distance to use when using canopy clustering. Values < 0 cause a heuristic based on attribute std. deviation to be used." - }, - "preserveOrder": { - "label": "Preserve order", - "tooltip": "Specify whether order of instances should be preserved or not.", - "options": { - "true.label": "Preserve", - "false.label": "Don't preserve" - } - }, - "fast": { - "label": "Fast", - "tooltip": "Enables faster distance calculations, using cut-off values. Disables the calculation/output of squared errors/distances.", - "options": { - "true.label": "Enable", - "false.label": "Disable" - } - }, - "seed": { - "label": "Seed", - "placeholder": "Seed...", - "tooltip": "The randomization seed." - }, - "learningRate": { - "label": "Learning rate", - "placeholder": "Learning rate...", - "tooltip": "The learning rate for the training algorithm. Value should be greater than 0 and less or equal to 1." - }, - "epochs": { - "label": "Epochs", - "placeholder": "Epochs...", - "tooltip": "Number of training epochs. Value should be greater than or equal to 1." - }, - "maxIterationsOverall": { - "label": "Max iterations", - "placeholder": "Max iterations...", - "tooltip": "Maximum number of overall iterations." - }, - "maxKMeans": { - "label": "Max K-means", - "placeholder": "Max K-means...", - "tooltip": "The maximum number of iterations to perform in K-means." - }, - "maxForChildren": { - "label": "Max for children", - "placeholder": "Max for children...", - "tooltip": "The maximum number of iterations in KMeans that is performed on the child centers." - }, - "useKD": { - "label": "Use K-D tree", - "tooltip": "Specifies whether a K-D tree should be used or not.", - "options": { - "true.label": "Use", - "false.label": "Don't use" - } - }, - "cutoffFactor": { - "label": "Cut-off factor", - "placeholder": "Cut-off factor...", - "tooltip": "Takes the given percentage of the split centroids if none of the children win." - } - } - } + "plot": { + "label": "Plot", + "tooltip": "Use the plot data from CEO" } - }, - "tabPlaceholder": "Unsupervised_classification" + }, + "institution": { + "label": "Institution", + "placeholder": "Select an institution", + "required": "You must select an institution", + "tooltip": "You can only see institutions where you are both a member and an admin" + }, + "project": { + "label": "Project", + "placeholder": "Select a project", + "required": "You must select a project", + "tooltip": "Select the project that you want to use the training data from" + }, + "loadCsv": { + "failed": "Failed to get the training data from CEO" + }, + "loadProjects": { + "failed": "Failed to get the projects for the selected institution" + }, + "loadInstitutions": { + "failed": "Failed to get the institutions" + } + } + }, + "noDataSet": "No training data set added", + "remove": { + "confirmationMessage": "Are you sure you want to remove {name} from your training data?", + "tooltip": "Remove training data set" + }, + "sectionSelection": { + "title": "Training data set" + }, + "title": "Training data", + "tooltip": "Configure training data source", + "type": { + "COLLECTED": { + "label": "Collected reference data", + "title": "COLLECTED" + }, + "CSV_UPLOAD": { + "label": "CSV file", + "title": "CSV UPLOAD", + "tooltip": "Upload a CSV file." + }, + "EE_TABLE": { + "label": "Earth Engine Table", + "title": "EE TABLE", + "tooltip": "Load reference data from an EE table." + }, + "RECIPE": { + "label": "Saved SEPAL recipe", + "title": "SEPAL RECIPE", + "tooltip": "Use training data from a saved SEPAL recipe." + }, + "SAMPLE_CLASSIFICATION": { + "label": "Sample classification", + "title": "SAMPLE CLASSIFICATION", + "tooltip": "Sample a pre-existing classification." + }, + "CEO": { + "label": "Collect Earth Online", + "title": "Collect Earth Online", + "tooltip": "Use training data from CEO" + } + } + } + }, + "preview": { + "description": "Preview of classification, generated on the fly by EE.", + "error": "Failed to create preview", + "initializing": "Loading preview...", + "label": "Classification", + "loading": "Loading tiles: {pending}", + "tilesFailed": "{failed} failed" + }, + "tabPlaceholder": "Classification" + }, + "regression": { + "bands": { + "regression": "Regression" + }, + "create": "Regression", + "description": "Create a regression from SEPAL or Google Earth Engine imagery.", + "panel": { + "trainingData": { + "form": { + "eeTable": { + "valueColumn": { + "label": "Value column", + "placeholder": "Select value column...", + "tooltip": "Select column containing the values to train the regression classifier with." + } + }, + "sampleImage": { + "sampleCount": { + "label": "Sample count", + "placeholder": "Count...", + "required": "You must specify the number of samples to collect", + "suffix": "samples", + "tooltip": "The number of samples to extract from the image" + }, + "valueBand": { + "label": "Value band", + "placeholder": "Select band with value to sample...", + "tooltip": "The band to sample and use to train the regression classifier." + }, + "noIntersection": "The image to sample does not intersect the recipe", + "unableToSampleAllPixels": "Unable to sample {numPixels} from the image. It's either too few pixels or too large areas of the image is masked." + } + }, + "type": { + "EE_TABLE": { + "label": "Earth Engine Table", + "tooltip": "Load reference data from an EE table", + "title": "EE Table" + }, + "SAMPLE_IMAGE": { + "label": "Sample Image", + "tooltip": "Sample a pre-existing image", + "title": "Sample Image" + }, + "RECIPE": { + "label": "Saved SEPAL recipe", + "tooltip": "Use training data from a saved SEPAL recipe", + "title": "SEPAL Recipe" + } + } + } + }, + "tabPlaceholder": "Regression" + }, + "unsupervisedClassification": { + "bands": { + "class": "Class" + }, + "create": "Unsupervised Classification", + "description": "Create an unsupervised classification from SEPAL or Google Earth Engine imagery.", + "panel": { + "sampling": { + "button": "SMP", + "tooltip": "Configure the sampling", + "title": "Sampling configuration", + "form": { + "numberOfSamples": { + "label": "Number of samples", + "placeholder": "Count...", + "tooltip": "The number of samples to use to train the clusterer.", + "suffix": "samples" + }, + "sampleScale": { + "label": "Scale to sample in", + "placeholder": "Scale...", + "suffix": "meters", + "tooltip": "The scale to sample the asset in" + } + } }, - "stack": { - "create": "Stack", - "description": "Create an image stack from SEPAL and/or Google Earth Engine image assets.", - "panel": { - "bandNames": { - "button": "NAM", - "title": "Band names", - "tooltip": "Set the band names of the stack.", - "duplicateBand": "Duplicate band name", - "invalidFormat": "Must start with a letter or underscore and then be followed by only numbers, letters and underscores. Cannot be longer than 30 characters" + "clusterer": { + "button": "CLS", + "tooltip": "Configure the clusterer", + "title": "Clusterer configuration", + "form": { + "type": { + "label": "Type" + }, + "kmeans": { + "label": "KMeans", + "tooltip": "Cluster data using the k-means algorithm." + }, + "cascadeKMeans": { + "label": "Cascade KMeans", + "tooltip": "Cascade simple k-means selects the best k according to the Calinski-Harabasz criterion." + }, + "xmeans": { + "label": "XMeans", + "tooltip": "X-Means is K-Means with an efficient estimation of the number of clusters." + }, + "lvq": { + "label": "LVQ", + "tooltip": "A Clusterer that implements the Learning Vector Quantization algorithm." + }, + "config": { + "numberOfClusters": { + "label": "Number of clusters", + "placeholder": "Number of clusters...", + "tooltip": "The number of clusters to generate" + }, + "minNumberOfClusters": { + "label": "Min number of clusters", + "placeholder": "Min number of clusters...", + "tooltip": "The minimum number of clusters to generate" + }, + "maxNumberOfClusters": { + "label": "Max number of clusters", + "placeholder": "Max number of clusters...", + "tooltip": "The maximum number of clusters to generate" + }, + "restarts": { + "label": "Restarts", + "placeholder": "Restarts...", + "tooltip": "Number of restarts." + }, + "init": { + "label": "Initialization method", + "options": { + "random.label": "Random", + "kmeans.label": "K-means++", + "canopy.label": "Canopy", + "farthestFirst.label": "farthest first" } - }, - "tabPlaceholder": "Stack" - }, - "bandMath": { - "create": "Band Math", - "description": "Perform band math on imagery from SEPAL and/or Google Earth Engine image assets.", - "duplicateName": "Duplicate name. There are other images or calculations with this name.", - "invalidName": "Cannot use names that are math functions or constants.", - "panel": { - "inputImagery": { - "name": { - "label": "Image name", - "tooltip": "The name of this image to use when referring to it within expressions." - } - }, - "calculations": { - "button": "CAL", - "title": "Calculations", - "tooltip": "Generate calculated bands.", - "form": { - "noCalculations": "No calculations provided", - "usedBands": { - "label": "Bands", - "tooltip": "The bands to apply the function to" - }, - "function": { - "label": "Function", - "placeholder": "Select Function...", - "tooltip": "Reduce the selected bands to a single band value using the selected function." - }, - "bandName": { - "label": "Band name", - "placeholder": "Band name...", - "tooltip": "The name of the resulting band after applying the function." - }, - "calculationName": { - "label": "Calculation name", - "placeholder": "Calculation name...", - "tooltip": "The name of this calculation to use when referring to it within expressions." - }, - "dataType": { - "label": "Data type", - "placeholder": "Data type...", - "tooltip": "The data type to cast the output band(s) to.", - "auto": "[auto]", - "int8": "int8", - "int16": "int16", - "int32": "int32", - "int64": "int64", - "uint8": "uint8", - "uint16": "uint16", - "uint32": "uint32", - "byte": "byte", - "short": "short", - "int": "int", - "long": "long", - "float": "float", - "double": "double" - }, - "expression": { - "label": "Expression", - "placeholder": "Expression...", - "tooltip": "The expression to evaluate." - }, - "type": { - "FUNCTION": "Function", - "EXPRESSION": "Expression" - }, - "bandRenameStrategy": { - "label": "Band rename strategy", - "tooltip": "The expression generates an image with multiple bands. Select which strategy to use for naming them.", - "PREFIX": { - "label": "Prefix" - }, - "SUFFIX": { - "label": "Suffix" - }, - "REGEX": { - "label": "Regular expression", - "tooltip": "Renames the bands outputted by the calculation by applying a regular expression replacement." - } - }, - "regex": { - "label": "Regular expression", - "placeholder": "Regular expression...", - "tooltip": "The regular expression used to match bands to rename. Any bands not matched by the regex will included without renaming." - }, - "bandRename": { - "PREFIX": { - "label": "Prefix", - "placeholder": "Prefix...", - "tooltip": "Apply this prefix to all bands outputted by expression" - }, - "SUFFIX": { - "label": "Suffix", - "placeholder": "Suffix...", - "tooltip": "Apply this suffix to all bands outputted by expression" - }, - "REGEX": { - "label": "Replacement", - "placeholder": "Replacement...", - "tooltip": "The text with which to replace each match. Supports $n syntax for captured values." - } - } - }, - "sections": { - "title": "Calculation" - }, - "expression": { - "title": "Expression" - }, - "function": { - "title": "Function" - } - }, - "outputBands": { - "button": "OUT", - "title": "Output band names", - "tooltip": "Select bands to output and their corresponding names.", - "duplicateBand": "Duplicate band name", - "invalidFormat": "Must start with a letter or underscore and then be followed by only numbers, letters and underscores. Cannot be longer than 30 characters", - "noImages": "No images/calculations added to output", - "noBands": "No bands added to output", - "addImage": { - "label": "Add", - "tooltip": "Include bands from image or calculation to output", - "calculations": "Calculations", - "images": "Images" - }, - "addBands": { - "placeholder": "Add band...", - "tooltip": "Add band to include in recipe output.", - "all": { - "label": "[All bands]" - } - }, - "type": { - "ASSET": "Earth Engine asset", - "RECIPE_REF": "SEPAL recipe", - "EXPRESSION": "Expression", - "FUNCTION": "Function" - } + }, + "distanceFunction": { + "label": "Distance function", + "options": { + "euclidean.label": "Euclidean", + "manhattan.label": "Manhattan", + "chebyshev.label": "Chebyshev" } - }, - "tabPlaceholder": "Band_math" - }, - "classChange": { - "create": "Class change", - "description": "Create a class change map from two categorical images, either SEPAL recipes or EE assets.", - "layers": { - "imageLayer": { - "preSets": "Pre-sets" + }, + "maxIterations": { + "label": "Max iterations", + "placeholder": "Max iterations...", + "tooltip": "Maximum number of iterations." + }, + "canopies": { + "label": "Canopies", + "tooltip": "Specify whether to use canopies to reduce the number of distance calculations or not.", + "options": { + "true.label": "Canopies", + "false.label": "No Canopies" } - }, - "panel": { - "inputImage": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID...", - "title": "Earth Engine Asset" - }, - "changeBand": { - "label": "Change band" - }, - "from": { - "button": { - "label": "FRM", - "tooltip": "Select from image" - }, - "title": "From image" - }, - "legend": { - "label": "Legend", - "tooLong": "The legend is too long. It cannot contain more than {max} entries." - }, - "recipe": { - "title": "Saved SEPAL Recipe" - }, - "to": { - "button": { - "label": "TO", - "tooltip": "Select to image" - }, - "title": "To image" - } - }, - "legend": { - "button": { - "label": "LEG", - "tooltip": "Configure class change legend" - }, - "import": "Import..." - }, + }, + "maxCandidates": { + "label": "Max candidates", + "placeholder": "Max candidates...", + "tooltip": "Maximum number of candidate canopies to retain in memory at any one time when using canopy clustering. T2 distance plus, data characteristics, will determine how many candidate canopies are formed before periodic and final pruning are performed, which might result in exceess memory consumption. This setting avoids large numbers of candidate canopies consuming memory." + }, + "periodicPruning": { + "label": "Periodic pruning", + "placeholder": "Periodic pruning...", + "tooltip": "How often to prune low density canopies when using canopy clustering." + }, + "minDensity": { + "label": "Min density", + "placeholder": "Min density...", + "tooltip": "Minimum canopy density, when using canopy clustering, below which a canopy will be pruned during periodic pruning." + }, + "t1": { + "label": "T1", + "placeholder": "T1...", + "tooltip": "The T1 distance to use when using canopy clustering. A value < 0 is taken as a positive multiplier for T2." + }, + "t2": { + "label": "T2", + "placeholder": "T2...", + "tooltip": "The T2 distance to use when using canopy clustering. Values < 0 cause a heuristic based on attribute std. deviation to be used." + }, + "preserveOrder": { + "label": "Preserve order", + "tooltip": "Specify whether order of instances should be preserved or not.", "options": { - "button": { - "label": "OPT", - "tooltip": "Configure how class change is created" - }, - "minConfidence": { - "label": "Min confidence", - "tooltip": "The minimum change in classification probability required to consider a change.", - "value": "{value}%" - }, - "title": "Options" + "true.label": "Preserve", + "false.label": "Don't preserve" } - }, - "tabPlaceholder": "Class_change" - }, - "closeRecipe": { - "message": "Do you want to save this recipe?" - }, - "indexChange": { - "create": "Index change", - "description": "Create an index change map from two images, either SEPAL recipes or EE assets.", - "layers": { - "imageLayer": { - "preSets": "Pre-sets" + }, + "fast": { + "label": "Fast", + "tooltip": "Enables faster distance calculations, using cut-off values. Disables the calculation/output of squared errors/distances.", + "options": { + "true.label": "Enable", + "false.label": "Disable" } - }, - "panel": { - "inputImage": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID...", - "title": "Earth Engine Asset" - }, - "changeBand": { - "label": "Change band" - }, - "errorBand": { - "label": "Error band" - }, - "from": { - "button": { - "label": "FRM", - "tooltip": "Select from image" - }, - "title": "From image" - }, - "recipe": { - "label": "Recipe", - "placeholder": "Select a recipe...", - "title": "Saved SEPAL Recipe" - }, - "to": { - "button": { - "label": "TO", - "tooltip": "Select to image" - }, - "title": "To image" - } - }, - "legend": { - "button": { - "label": "LEG", - "tooltip": "Configure change category legend" - }, - "import": "Import...", - "title": "Legend" - }, - "mapping": { - "button": { - "label": "MAP", - "tooltip": "Map index difference to change category" - }, - "title": "Mapping" - }, + }, + "seed": { + "label": "Seed", + "placeholder": "Seed...", + "tooltip": "The randomization seed." + }, + "learningRate": { + "label": "Learning rate", + "placeholder": "Learning rate...", + "tooltip": "The learning rate for the training algorithm. Value should be greater than 0 and less or equal to 1." + }, + "epochs": { + "label": "Epochs", + "placeholder": "Epochs...", + "tooltip": "Number of training epochs. Value should be greater than or equal to 1." + }, + "maxIterationsOverall": { + "label": "Max iterations", + "placeholder": "Max iterations...", + "tooltip": "Maximum number of overall iterations." + }, + "maxKMeans": { + "label": "Max K-means", + "placeholder": "Max K-means...", + "tooltip": "The maximum number of iterations to perform in K-means." + }, + "maxForChildren": { + "label": "Max for children", + "placeholder": "Max for children...", + "tooltip": "The maximum number of iterations in KMeans that is performed on the child centers." + }, + "useKD": { + "label": "Use K-D tree", + "tooltip": "Specifies whether a K-D tree should be used or not.", "options": { - "button": { - "label": "OPT", - "tooltip": "Configure how index change is created" - }, - "minConfidence": { - "label": "Min confidence", - "tooltip": "Determines how large the difference must be to be considered significant. If difference/error is smaller than this value, the difference is considered 0 when creating the categorical change band.", - "value": "{value} x error" - }, - "title": "Options" + "true.label": "Use", + "false.label": "Don't use" } + }, + "cutoffFactor": { + "label": "Cut-off factor", + "placeholder": "Cut-off factor...", + "tooltip": "Takes the given percentage of the split centroids if none of the children win." + } + } + } + } + }, + "tabPlaceholder": "Unsupervised_classification" + }, + "stack": { + "create": "Stack", + "description": "Create an image stack from SEPAL and/or Google Earth Engine image assets.", + "panel": { + "bandNames": { + "button": "NAM", + "title": "Band names", + "tooltip": "Set the band names of the stack.", + "duplicateBand": "Duplicate band name", + "invalidFormat": "Must start with a letter or underscore and then be followed by only numbers, letters and underscores. Cannot be longer than 30 characters" + } + }, + "tabPlaceholder": "Stack" + }, + "bandMath": { + "create": "Band Math", + "description": "Perform band math on imagery from SEPAL and/or Google Earth Engine image assets.", + "duplicateName": "Duplicate name. There are other images or calculations with this name.", + "invalidName": "Cannot use names that are math functions or constants.", + "panel": { + "inputImagery": { + "name": { + "label": "Image name", + "tooltip": "The name of this image to use when referring to it within expressions." + } + }, + "calculations": { + "button": "CAL", + "title": "Calculations", + "tooltip": "Generate calculated bands.", + "form": { + "noCalculations": "No calculations provided", + "usedBands": { + "label": "Bands", + "tooltip": "The bands to apply the function to" + }, + "function": { + "label": "Function", + "placeholder": "Select Function...", + "tooltip": "Reduce the selected bands to a single band value using the selected function." + }, + "bandName": { + "label": "Band name", + "placeholder": "Band name...", + "tooltip": "The name of the resulting band after applying the function." + }, + "calculationName": { + "label": "Calculation name", + "placeholder": "Calculation name...", + "tooltip": "The name of this calculation to use when referring to it within expressions." + }, + "dataType": { + "label": "Data type", + "placeholder": "Data type...", + "tooltip": "The data type to cast the output band(s) to.", + "auto": "[auto]", + "int8": "int8", + "int16": "int16", + "int32": "int32", + "int64": "int64", + "uint8": "uint8", + "uint16": "uint16", + "uint32": "uint32", + "byte": "byte", + "short": "short", + "int": "int", + "long": "long", + "float": "float", + "double": "double" + }, + "expression": { + "label": "Expression", + "placeholder": "Expression...", + "tooltip": "The expression to evaluate." }, - "tabPlaceholder": "Index_change" + "type": { + "FUNCTION": "Function", + "EXPRESSION": "Expression" + }, + "bandRenameStrategy": { + "label": "Band rename strategy", + "tooltip": "The expression generates an image with multiple bands. Select which strategy to use for naming them.", + "PREFIX": { + "label": "Prefix" + }, + "SUFFIX": { + "label": "Suffix" + }, + "REGEX": { + "label": "Regular expression", + "tooltip": "Renames the bands outputted by the calculation by applying a regular expression replacement." + } + }, + "regex": { + "label": "Regular expression", + "placeholder": "Regular expression...", + "tooltip": "The regular expression used to match bands to rename. Any bands not matched by the regex will included without renaming." + }, + "bandRename": { + "PREFIX": { + "label": "Prefix", + "placeholder": "Prefix...", + "tooltip": "Apply this prefix to all bands outputted by expression" + }, + "SUFFIX": { + "label": "Suffix", + "placeholder": "Suffix...", + "tooltip": "Apply this suffix to all bands outputted by expression" + }, + "REGEX": { + "label": "Replacement", + "placeholder": "Replacement...", + "tooltip": "The text with which to replace each match. Supports $n syntax for captured values." + } + } + }, + "sections": { + "title": "Calculation" + }, + "expression": { + "title": "Expression" + }, + "function": { + "title": "Function" + } }, - "asset": { - "create": "EE Asset", - "description": "Create and configure an image from an EE image or image collection asset.", - "panel": { - "assetDetails": { - "button": "SRC", - "form": { - "assetId": { - "label": "EE Asset", - "placeholder": "Select EE Asset..." - } - }, - "title": "EE Asset", - "tooltip": "Select EE asset" - }, - "dates": { - "button": "DAT", - "form": { - "fromDate": { - "label": "From date", - "malformed": "Invalid date" - }, - "targetDate": { - "label": "Target date", - "malformed": "Invalid date" - }, - "toDate": { - "label": "To date", - "malformed": "Invalid date" - }, - "type": { - "label": "Type", - "ALL_DATES": { - "label": "All dates", - "tooltip": "Include images from all dates." - }, - "CUSTOM_DATE_RANGE": { - "label": "Date range", - "tooltip": "Include images from a custom date range." - }, - "YEAR": { - "label": "Year", - "tooltip": "Include images from a specified calendar year." - } - }, - "year": { - "label": "Year", - "malformed": "Not a year" - } - }, - "title": "Dates", - "tooltip": "Specify dates" - }, - "filter": { - "button": "FLT", - "filters": { - "title": "Image filters" - }, - "noFilters": "No filters added.", - "emptyFilters": "Empty", - "remove": { - "tooltip": "Remove filters." - }, - "title": "Collection Filter", - "tooltip": "Filter down collection to images of interest" - }, - "mask": { - "button": "MSK", - "constraints": { - "title": "Image mask constraints" - }, - "noConstraints": "No constraints added.", - "emptyConstraints": "Empty", - "remove": { - "tooltip": "Remove masking constraints." - }, - "title": "Mask imagery", - "tooltip": "Mask out unwanted areas of imagery" - }, - "composite": { - "button": "CMP", - "form": { - "type": { - "label": "Type", - "MOSAIC": { - "label": "Mosaic", - "tooltip": "Simple spatial composition" - }, - "MEDIAN": { - "label": "Median", - "tooltip": "Take the median of each band for the images in the collection" - }, - "MEAN": { - "label": "Mean", - "tooltip": "Take the mean of each band for the images in the collection" - }, - "MIN": { - "label": "Min", - "tooltip": "Use the minimum value of each band for the images in the collection" - }, - "MAX": { - "label": "Max", - "tooltip": "Use the maximum value of each band for the images in the collection" - }, - "SD": { - "label": "SD", - "tooltip": "Use the standard deviation of each band for the images in the collection" - }, - "MODE": { - "label": "Mode", - "tooltip": "Use the most common value of each band for the images in the collection. This only make sense for categorical bands" - } - } - }, - "title": "Composite", - "tooltip": "Combine images in image collection into a single image" - } + "outputBands": { + "button": "OUT", + "title": "Output band names", + "tooltip": "Select bands to output and their corresponding names.", + "duplicateBand": "Duplicate band name", + "invalidFormat": "Must start with a letter or underscore and then be followed by only numbers, letters and underscores. Cannot be longer than 30 characters", + "noImages": "No images/calculations added to output", + "noBands": "No bands added to output", + "addImage": { + "label": "Add", + "tooltip": "Include bands from image or calculation to output", + "calculations": "Calculations", + "images": "Images" + }, + "addBands": { + "placeholder": "Add band...", + "tooltip": "Add band to include in recipe output.", + "all": { + "label": "[All bands]" + } + }, + "type": { + "ASSET": "Earth Engine asset", + "RECIPE_REF": "SEPAL recipe", + "EXPRESSION": "Expression", + "FUNCTION": "Function" + } + } + }, + "tabPlaceholder": "Band_math" + }, + "classChange": { + "create": "Class change", + "description": "Create a class change map from two categorical images, either SEPAL recipes or EE assets.", + "layers": { + "imageLayer": { + "preSets": "Pre-sets" + } + }, + "panel": { + "inputImage": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID...", + "title": "Earth Engine Asset" + }, + "changeBand": { + "label": "Change band" + }, + "from": { + "button": { + "label": "FRM", + "tooltip": "Select from image" }, - "layers": { - "imageLayer": { - "preSets": "Pre-sets" - } + "title": "From image" + }, + "legend": { + "label": "Legend", + "tooLong": "The legend is too long. It cannot contain more than {max} entries." + }, + "recipe": { + "title": "Saved SEPAL Recipe" + }, + "to": { + "button": { + "label": "TO", + "tooltip": "Select to image" }, - "tabPlaceholder": "EE_Asset" + "title": "To image" + } }, - "masking": { - "create": "Masking", - "description": "Apply a mask to an image.", - "layers": { - "imageLayer": { - "preSets": "Pre-sets" - } + "legend": { + "button": { + "label": "LEG", + "tooltip": "Configure class change legend" + }, + "import": "Import..." + }, + "options": { + "button": { + "label": "OPT", + "tooltip": "Configure how class change is created" + }, + "minConfidence": { + "label": "Min confidence", + "tooltip": "The minimum change in classification probability required to consider a change.", + "value": "{value}%" + }, + "title": "Options" + } + }, + "tabPlaceholder": "Class_change" + }, + "closeRecipe": { + "message": "Do you want to save this recipe?" + }, + "indexChange": { + "create": "Index change", + "description": "Create an index change map from two images, either SEPAL recipes or EE assets.", + "layers": { + "imageLayer": { + "preSets": "Pre-sets" + } + }, + "panel": { + "inputImage": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID...", + "title": "Earth Engine Asset" + }, + "changeBand": { + "label": "Change band" + }, + "errorBand": { + "label": "Error band" + }, + "from": { + "button": { + "label": "FRM", + "tooltip": "Select from image" }, - "panel": { - "inputImage": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID...", - "title": "Earth Engine Asset" - }, - "imageMask": { - "button": { - "label": "MSK", - "tooltip": "Select mask to use" - }, - "title": "Mask to use" - }, - "imageToMask": { - "button": { - "label": "IMG", - "tooltip": "Select image to mask" - }, - "title": "Image to mask" - }, - "recipe": { - "label": "Recipe", - "placeholder": "Select a recipe...", - "title": "Saved SEPAL Recipe" - } - } + "title": "From image" + }, + "recipe": { + "label": "Recipe", + "placeholder": "Select a recipe...", + "title": "Saved SEPAL Recipe" + }, + "to": { + "button": { + "label": "TO", + "tooltip": "Select to image" }, - "tabPlaceholder": "Masking" + "title": "To image" + } }, - "menu": { - "duplicateRecipe": { - "label": "Duplicate recipe", - "tooltip": "Duplicate this recipe." - }, - "exportRecipe": "Export recipe", - "removeRecipe.tooltip": "Remove recipe", - "selectRecipe.tooltip": "Select recipe", - "saveRecipe": "Save recipe...", - "searchRecipes": "Search recipes" + "legend": { + "button": { + "label": "LEG", + "tooltip": "Configure change category legend" + }, + "import": "Import...", + "title": "Legend" }, - "mosaic": { - "bands": { - "combinations": "Combinations", - "indexes": "Indexes", - "label": "Bands", - "metadata": "Metadata", - "months": "Months", - "panSharpen": "Pan sharpen", - "placeholder": "Select preview bands" - }, - "create": "Optical mosaic", - "error": { - "noImages": "All images have been filtered out. Update the recipe to ensure at least one image is included." - }, - "description": "Create a mosaic using Landsat or Sentinel 2.", - "mapToolbar": { - "mapRendering": { - "tooltip": "Start/stop map rendering", - "processing": "Processing", - "tiles": "{tiles} {tiles, plural, one {tile} other {tiles}}" - }, - "zoom": { - "tooltip": "Zoom" - }, - "layers": { - "tooltip": "Select layers to show" - }, - "options": { - "tooltip": "Map options" - }, - "unlink": { - "tooltip": "Unlink this map" - } + "mapping": { + "button": { + "label": "MAP", + "tooltip": "Map index difference to change category" + }, + "title": "Mapping" + }, + "options": { + "button": { + "label": "OPT", + "tooltip": "Configure how index change is created" + }, + "minConfidence": { + "label": "Min confidence", + "tooltip": "Determines how large the difference must be to be considered significant. If difference/error is smaller than this value, the difference is considered 0 when creating the categorical change band.", + "value": "{value} x error" + }, + "title": "Options" + } + }, + "tabPlaceholder": "Index_change" + }, + "asset": { + "create": "EE Asset", + "description": "Create and configure an image from an EE image or image collection asset.", + "panel": { + "assetDetails": { + "button": "SRC", + "form": { + "assetId": { + "label": "EE Asset", + "placeholder": "Select EE Asset..." + } + }, + "title": "EE Asset", + "tooltip": "Select EE asset" + }, + "dates": { + "button": "DAT", + "form": { + "fromDate": { + "label": "From date", + "malformed": "Invalid date" }, - "mapOptions": { - "location": { - "label": "Map location", - "tooltip": "Synchronize map location (center and zoom) with other maps or keep it independent", - "independent": "Independent", - "synchronized": "Synchronized", - "getOther": "Get location from other maps", - "useCurrent": "Use current location" - }, - "retile": { - "label": "Re-tile", - "tooltip": "Split Earth Engine processing of each tile into smaller sub-tiles. Please use this control sparingly, and only if really necessary, as it would cause a much higher Earth Engine resource utilization, and in most cases it would just slow down tile rendering. It should only be used when one or more tiles fail with \"User memory limit exceeded\" errors, possibly increasing the re-tile level one step at a time." - } + "targetDate": { + "label": "Target date", + "malformed": "Invalid date" }, - "panel": { - "areaOfInterest": { - "button": "AOI", - "form": { - "bounds": { - "required": "Bounds are not loaded" - }, - "buffer": { - "info": "{buffer} km buffer", - "label": "Buffer", - "tooltip": "Area of interest and preview will take longer to show up when buffering is enabled." - }, - "assetBounds": { - "description": "Use bounds of selected EE asset.", - "title": "EE asset bounds" - }, - "country": { - "area": { - "label": "Province/area", - "placeholder": { - "loaded": "Select a province", - "loading": "Select a province", - "noAreas": "No provinces configured", - "noCountry": "Select a country first" - }, - "title": "Select area" - }, - "country": { - "label": "Country", - "loadFailed": "Cannot load countries", - "placeholder": { - "loaded": "Select a country...", - "loading": "Loading countries..." - } - }, - "required": "Country is required", - "title": "Select country/province" - }, - "eeTable": { - "column": { - "label": "Column", - "placeholder": { - "loaded": "Select a column...", - "loading": "Loading columns...", - "noEETable": "Enter a EE Table ID first" - }, - "required": "A column must be selected" - }, - "eeTable": { - "label": "EE Table", - "placeholder": "Enter a EE Table ID", - "required": "EE Table must be specified" - }, - "eeTableRowSelection": { - "FILTER": "Filter", - "INCLUDE_ALL": "Include all", - "label": "Rows" - }, - "row": { - "label": "Value", - "placeholder": { - "loaded": "Select a value...", - "loading": "Loading rows...", - "noColumn": "Select a column first", - "noEETable": "Enter a EE Table ID first", - "noRows": "No rows in EE Table" - }, - "required": "A row must be selected" - }, - "title": "Select from EE Table" - }, - "fusionTable": { - "column": { - "label": "Column", - "placeholder": { - "loaded": "Select a column...", - "loading": "Loading columns...", - "noFusionTable": "Enter a Fusion Table ID first" - }, - "required": "A column must be selected" - }, - "fusionTable": { - "label": "Fusion Table", - "placeholder": "Enter a Fusion Table ID", - "required": "Fusion Table must be specified" - }, - "fusionTableRowSelection": { - "FILTER": "Filter", - "INCLUDE_ALL": "Include all", - "label": "Rows" - }, - "row": { - "label": "Value", - "placeholder": { - "loaded": "Select a value...", - "loading": "Loading rows...", - "noColumn": "Select a column first", - "noFusionTable": "Enter a Fusion Table ID first", - "noRows": "No rows in Fusion Table" - }, - "required": "A row must be selected" - }, - "title": "Select from Fusion Table" - }, - "polygon": { - "description": "Draw a polygon on the map.", - "title": "Draw a polygon" - }, - "section": { - "required": "A section must be selected" - } - }, - "title": "Area of interest", - "tooltip": "Specify area of interest" - }, - "autoSelectScenes": { - "form": { - "max": { - "label": "Maximum number of scenes" - }, - "min": { - "label": "Minimum number of scenes" - }, - "selectScenes": "Select scenes" - }, - "selecting": "Selecting scenes...", - "title": "Auto-select scenes", - "tooltip": "Auto-select scenes" - }, - "clearSelectedScenes": { - "apply": "Clear scenes", - "message": "You are about to clear all selected scenes.", - "title": "Clear selected scenes", - "tooltip": "Clear selected scenes" - }, - "composite": { - "button": "CMP", - "form": { - "cloudBuffer": { - "aggressive": { - "label": "Aggressive", - "tooltip": "Mask an additional 600m around each larger cloud. This helps prevent hazy pixels at the border of clouds to be included in the mosaic." - }, - "label": "Cloud buffering", - "moderate": { - "label": "Moderate", - "tooltip": "Mask an additional 120m around each larger cloud. This helps prevent hazy pixels at the border of clouds to be included in the mosaic." - }, - "none": { - "label": "None", - "tooltip": "Only mask the clouds. It might leave hazy pixels around masked clouds, but will minimize the amount of maxed pixels in the mosaic." - } - }, - "cloudDetection": { - "label": "Cloud detection", - "cloudScore": { - "label": "Cloud score", - "tooltip": "Use cloud scoring algorithm" - }, - "pino26": { - "label": "PINO 26", - "tooltip": "Use Pan-Tropical S2 cloud detection algorithm developed by Dario Simonetti" - }, - "qa": { - "label": "QA bands", - "tooltip": "Use pre-created QA bands" - } - }, - "cloudMasking": { - "aggressive": { - "label": "Aggressive", - "tooltip": "Rely on image source QA bands and a cloud scoring algorithm for cloud masking. This will probably mask out some built-up areas and other bright features." - }, - "label": "Cloud masking", - "moderate": { - "label": "Moderate", - "tooltip": "Rely only on image source QA bands for cloud masking" - }, - "custom": { - "label": "Custom" - }, - "noData": "No cloud masking applied", - "none": { - "label": "Off", - "tooltip": "Use cloud-free pixels if possible, but don't mask areas without cloud-free pixels" - }, - "sentinel2CloudProbability": { - "label": "S2 Cloud Probability", - "maxCloudProbability": { - "label": "Max cloud probability", - "info": "Max {percent}% cloud probability" - } - }, - "sentinel2CloudScorePlus": { - "label": "S2 Cloud Score+", - "band": { - "label": "Band", - "cs": { - "tooltip": "The cs band scores QA based on a spectral distance between the observed pixel and a (theoretical) clear reference observation. It can be thought of as a more instantaneous atmospheric similarity score (i.e., how similar is this pixel to what we'd expect to see in a perfectly a clear reference)." - }, - "cs_cdf": { - "tooltip": "The cs_cdf band represents the likelihood an observed pixel is clear based on an estimated cumulative distribution of scores for a given location through time. It captures an expectation of the estimated score through time (i.e., if we had all the scores for this pixel through time, how would this score rank?)." - } - }, - "maxCloudProbability": { - "label": "Max cloud probability", - "tooltip": "Cloud Score+ provides likelihood of clear pixels. To harmonize the input with other cloud scoring algorithms, in SEPAL, we have inverted it to represent likelihood of cloudy pixels.", - "info": "Max {percent}% cloud probability" - } - }, - "landsatCFMask": { - "label": "Landsat CFMask", - "level": { - "off": "Off", - "moderate": "Moderate", - "aggressive": "Aggressive" - }, - "cloudMasking": { - "label": "Cloud Masking" - }, - "cloudShadowMasking": { - "label": "Cloud Shadow Masking" - }, - "cirrusMasking": { - "label": "Cirrus Masking" - }, - "dilatedCloud": { - "label": "Dilated clouds", - "tooltip": "Cloud dilation fills any “clear” pixels that are surrounded by clouds, reducing uncertainty concerning thin cloud or adjacent-to-cloud pixels. Dilation reduces ambiguity of a cloud-contaminated pixel at the cost of potentially high commission error; this is ideal for land use/land cover change assessments analyzing large spatiotemporal stacks of data, where cloud cover can adversely alter trending analysis.", - "keep": "Keep", - "remove": "Remove" - } - }, - "sepalCloudScore": { - "label": "SEPAL Cloud Score", - "maxCloudProbability": { - "label": "Max cloud probability", - "info": "Max {percent}% cloud probability" - } - }, - "pino26": { - "label": "Pino 26" - } - }, - "composingMethod": { - "label": "Composing method", - "median": { - "label": "Median", - "tooltip": "Use the median of remaining pixels. Pixel value will be artificial, and metadata for mosaic will be unavailable." - }, - "medoid": { - "label": "Medoid", - "tooltip": "Use the medoid of remaining pixels." - } - }, - "corrections": { - "brdf": { - "label": "BRDF", - "tooltip": "Correct for bidirectional reflectance distribution function (BRDF) effects." - }, - "calibrate": { - "label": "Calibrate", - "tooltip": "Calibrate bands to improve cross-sensor mosaic." - }, - "label": "Corrections", - "surfaceReflectance": { - "label": "SR", - "tooltip": "Use scenes atmospherically corrected surface reflectance." - } - }, - "brdfMultiplier": { - "label": "BRDF Multiplier", - "placeholder": "Multiplier...", - "tooltip": "Multiplier to increase the amount of BRDF correction to apply. Normally, values between 3-4 works well. If the correction is overcompensating the BRDF effect, lower the multiplier, and increase it when the effect is not compensated for enough." - }, - "filters": { - "dayOfYearPercentile": { - "label": "Date", - "max": "Select pixels closest to target day", - "off": "Don't filter based on day of year", - "percentile": "Exclude {percentile}% pixels (far from target)" - }, - "hazePercentile": { - "label": "Haze", - "max": "Select pixels with least haze", - "off": "Don't filter based on haze", - "percentile": "Exclude {percentile}% pixels (with haze)" - }, - "label": "Filters", - "noData": "No filters applied", - "ndviPercentile": { - "label": "NDVI", - "max": "Select pixels with highest NDVI", - "off": "Don't filter based on NDVI", - "percentile": "Exclude {percentile}% pixels (low NDVI)" - }, - "shadowPercentile": { - "label": "Shadow", - "max": "Select pixels with least shadow", - "off": "Don't filter based on shadow", - "percentile": "Exclude {percentile}% pixels (with shadow)" - }, - "tooltip": "Filter out pixels based on on percentile of selected properties" - }, - "holes": { - "label": "Masked out pixels", - "tooltips": "Control if some pixels can be completely masked out due to cloud and/or snow masking or not.", - "prevent": { - "label": "Prevent", - "tooltip": "Prevent pixels from being completely masked out due to cloud and/or snow masking. Pixels that would be completely masked out will retrain cloudy and/or snowy pixels." - }, - "allow": { - "label": "ALLOW", - "tooltip": "Allow pixels to be completely masked out if every acquisition are cloudy and/or snowy." - } - }, - "orbitOverlap": { - "label": "Orbit Overlap", - "keep": { - "label": "Keep", - "tooltip": "Keep overlap between Sentinel 2orbits. This additional data will result in better models in the overlap." - }, - "remove": { - "label": "Remove", - "tooltip": "Removes overlap between Sentinel 2 orbits. This adds an extra step in the preprocessing. This tends to make the BRDF effect more noticable, and it might be worth while trying to apply BRDF correction in conjuction with this feature." - } - }, - "tileOverlap": { - "label": "Tile Overlap", - "keep": { - "label": "Keep", - "tooltip": "Keep overlap between Sentinel 2 tiles. Areas with overlap will have twice or quadruple number of observations. All of these additional observations are all have the same value. Keeping them uses more memory than necessary and can lead to out of memory errors." - }, - "quickRemove": { - "label": "Quick remove", - "tooltip": "Makes an effort to remove most of the overlap between Sentinel 2 tiles. By removing the duplicate data, the memory needed in processing is reduced." - }, - "remove": { - "label": "Remove", - "tooltip": "Removes all overlap between Sentinel 2 tiles. This adds an extra step in the preprocessing. By removing the duplicate data, the memory needed in processing is reduced. There is no significant difference in this memory reduction with this option compared to the quick remove option." - } - }, - "snowMasking": { - "label": "Snow/ice masking", - "off": { - "label": "Off", - "tooltip": "Don't mask snow. Note that some clouds might get misclassified as snow, and because of this, disabling snow masking might lead to cloud artifacts." - }, - "on": { - "label": "On", - "tooltip": "Mask snow. This tend to leave some pixels with shadowy snow" - } - } - }, - "title": "Composite", - "tooltip": "Configure composite creation" - }, - "dates": { - "button": "DAT", - "form": { - "futureSeasons": { - "info": { - "none": "Don't include seasons from the future", - "number": "Include {futureSeasons} {futureSeasons, plural, one {season} other {seasons}} from the future" - }, - "label": "Future seasons" - }, - "pastSeasons": { - "info": { - "none": "Don't include seasons from the past", - "number": "Include {pastSeasons} {pastSeasons, plural, one {season} other {seasons}} from the past" - }, - "label": "Past seasons" - }, - "season": { - "label": "Season", - "malformed": "Invalid date", - "tooEarly": "Must not be before {min}", - "tooLate": "Must not be after {max}", - "tooltip": "Restrict the time of year to include imagery for." - }, - "targetDate": { - "label": "Target date", - "malformed": "Invalid date", - "tooltip": "The date in which pixels in the mosaic should ideally come from." - }, - "targetYear": { - "label": "Year", - "malformed": "Not a year", - "tooltip": "The year which pixels in the mosaic should come from." - } - }, - "title": "Dates", - "tooltip": "Specify dates" - }, - "layers": { - "title": "Layers" - }, - "scenes": { - "button": "SCN", - "form": { - "required": "Scene selection must be configured", - "targetDateWeight": { - "balanced": { - "label": "Balanced", - "tooltip": "Try to take images no too cloudy and not too far from the target date" - }, - "cloudFree": { - "label": "Cloud free", - "tooltip": "Prefer imagery as cloud-free as possible, ignoring the date" - }, - "label": "Priority", - "targetDate": { - "label": "Target date", - "tooltip": "Prefer imagery as close as possible to the target day" - } - }, - "type": { - "all": { - "label": "Use all scenes" - }, - "label": "Scene selection", - "select": { - "label": "Select scenes" - } - } - }, - "title": "Scenes", - "tooltip": "Configure scenes selection" - }, - "sceneSelection": { - "availableScenes": "Available scenes", - "loadingScenes": "Loading scenes...", - "noScenes": "No scenes are available for the current combination of area of interest, dates and data sources. Please revise the settings in AOI, DAT and/or SRC.", - "preview": { - "afterTarget": "{daysFromTarget} after", - "beforeTarget": "{daysFromTarget} before", - "cloudCover": "Cloud cover", - "dataSet": "Data set", - "date": "Acquisition date", - "daysFromTarget": "Days from target", - "label": "Preview", - "onTarget": "On target day" - }, - "selectedScenes": "Selected scenes" - }, - "sources": { - "button": "SRC", - "form": { - "dataSets": { - "label": "Datasets", - "oneDataSet": "Selected satellite family only have a single dataset.", - "options": { - "LANDSAT_7": { - "label": "L7", - "tooltip": "Landsat 7" - }, - "LANDSAT_7_T2": { - "label": "L7 T2", - "tooltip": "Landsat 7 Tier 2" - }, - "LANDSAT_8": { - "label": "L8", - "tooltip": "Landsat 8" - }, - "LANDSAT_8_T2": { - "label": "L8 T2", - "tooltip": "Landsat 8 Tier 2" - }, - "LANDSAT_9": { - "label": "L9", - "tooltip": "Landsat 9" - }, - "LANDSAT_9_T2": { - "label": "L9 T2", - "tooltip": "Landsat 9 Tier 2" - }, - "LANDSAT_TM": { - "label": "L4-5", - "tooltip": "Landsat 4-5" - }, - "LANDSAT_TM_T2": { - "label": "L4-5 T2", - "tooltip": "Landsat 4-5 Tier 2" - }, - "SENTINEL_1": { - "label": "Sentinel 1", - "tooltip": "Sentinel 1A and Sentinel 1B" - }, - "SENTINEL_2": { - "label": "A + B", - "tooltip": "Sentinel 2A and Sentinel 2B" - } - } - }, - "required": "A data source must be selected", - "source": { - "label": "Satellite family", - "options": { - "LANDSAT": "Landsat", - "SENTINEL_2": "Sentinel 2" - } - } - }, - "title": "Sources", - "tooltip": "Select image source" - } + "toDate": { + "label": "To date", + "malformed": "Invalid date" }, - "preview": { - "description": "Preview of optical mosaic, generated on the fly by EE.", - "error": "Failed to create preview", - "initializing": "Loading preview...", - "label": "Optical mosaic", - "loading": "Loading tiles: {pending}", - "tilesFailed": "{failed} failed", - "addScene": "Add scene", - "removeScene": "Remove scene" - }, - "sceneAreas": { - "description": "Manually select individual scenes to include in mosaic", - "error": "Failed to load scene areas", - "label": "Scene areas", - "loading": "Loading scene areas..." - }, - "tabPlaceholder": "Optical_mosaic" + "type": { + "label": "Type", + "ALL_DATES": { + "label": "All dates", + "tooltip": "Include images from all dates." + }, + "CUSTOM_DATE_RANGE": { + "label": "Date range", + "tooltip": "Include images from a custom date range." + }, + "YEAR": { + "label": "Year", + "tooltip": "Include images from a specified calendar year." + } + }, + "year": { + "label": "Year", + "malformed": "Not a year" + } + }, + "title": "Dates", + "tooltip": "Specify dates" }, - "planetMosaic": { - "create": "Planet mosaic", - "description": "Create a mosaic using Planet composites.", - "panel": { - "dates": { - "button": "DAT", - "form": { - "type": { - "DATE_RANGE": { - "label": "Date range", - "tooltip": "" - }, - "SINGLE_DATE": { - "label": "Single date", - "tooltip": "" - } - }, - "fromDate": { - "label": "From date", - "malformed": "Invalid date" - }, - "toDate": { - "label": "To date", - "malformed": "Invalid date" - } - }, - "title": "Dates", - "tooltip": "Specify dates" - }, - "options": { - "button": "OPT", - "cloudThreshold": { - "label": "Cloud masking", - "tooltip": "Planet composites already removes clouds. Setting this to a value > 0 will remove additional clouds.", - "value": "{value}%" - }, - "form": { - "histogramMatching": { - "label": "Histogram matching", - "tooltip": "Normalize images included in mosaic using Histogram matching. This will slow down processing significant, and can cause some details to be lost. Overall consistency and noise level can, however, be significantly improved with this option enabled.", - "options": { - "DISABLED": "Disabled", - "ENABLED": "Enabled" - } - } - }, - "shadowThreshold": { - "label": "Cloud shadow masking", - "tooltip": "Planet composites already removes cloud shadows. Setting this to a value > 0 will remove additional cloud shadows.", - "value": "{value}%" - }, - "title": "Options", - "tooltip": "Configure mosaic options" - }, - "sources": { - "button": "SRC", - "form": { - "asset": { - "label": "Earth Engine asset ID", - "placeholder": "Enter an asset ID...", - "required": "You must enter an asset ID" - }, - "collectionType": { - "label": "Collection type" - }, - "collectionTypes": { - "BASEMAPS": { - "label": "Custom basemaps", - "tooltip": "Use image collection with Planet basemaps/level 1 composites" - }, - "DAILY": { - "label": "Daily imagery", - "tooltip": "Use image collection with daily Planet imagery/level 2 imagery" - }, - "NICFI": { - "label": "NICFI basemaps", - "tooltip": "Use image collection with the NICFI Planet basemaps/level 1 composites" - } - } - }, - "title": "Sources", - "tooltip": "Select image source" - } - }, - "preview": { - "description": "Preview of Planet mosaic, generated on the fly by EE.", - "label": "Planet mosaic" - }, - "tabPlaceholder": "Planet_mosaic" + "filter": { + "button": "FLT", + "filters": { + "title": "Image filters" + }, + "noFilters": "No filters added.", + "emptyFilters": "Empty", + "remove": { + "tooltip": "Remove filters." + }, + "title": "Collection Filter", + "tooltip": "Filter down collection to images of interest" }, - "radarMosaic": { - "create": "Radar mosaic", - "description": "Create a mosaic using Sentinel 1.", - "panel": { - "dates": { - "button": "DAT", - "form": { - "fromDate": { - "label": "From date", - "malformed": "Invalid date" - }, - "targetDate": { - "label": "Target date", - "malformed": "Invalid date" - }, - "toDate": { - "label": "To date", - "malformed": "Invalid date" - }, - "type": { - "CUSTOM_TIME_SCAN": { - "label": "Date range", - "tooltip": "Build a time-scan with data from a custom date range." - }, - "label": "Type", - "POINT_IN_TIME_MOSAIC": { - "label": "Date", - "tooltip": "Build a mosaic with data coming as close to a specified date as possible." - }, - "YEARLY_TIME_SCAN": { - "label": "Year", - "tooltip": "Build a time-scan with data from a specified calendar year." - } - }, - "year": { - "label": "Year", - "malformed": "Not a year" - } - }, - "title": "Dates", - "tooltip": "Specify dates" - }, - "options": { - "button": "OPT", - "form": { - "geometricCorrection": { - "ellipsoid": { - "label": "Ellipsoid", - "tooltip": "Ellipsoid correction (gamma0 correction) is applied." - }, - "label": "Geometric correction", - "none": { - "label": "None", - "tooltip": "No geometric correction is applied." - }, - "terrain": { - "label": "Terrain", - "tooltip": "Terrain correction is applied." - } - }, - "orbits": { - "ascending": { - "label": "Ascending", - "tooltip": "Include scenes from ascending orbits." - }, - "descending": { - "label": "Descending", - "tooltip": "Include scenes from descending orbits." - }, - "label": "Orbits" - }, - "orbitNumbers": { - "all": { - "label": "All", - "tooltip": "Include values from all orbit numbers for a pixel." - }, - "dominant": { - "label": "Dominant", - "tooltip": "Only include values from the dominant orbit number for a pixel." - }, - "adjacent": { - "label": "Adjacent", - "tooltip": "Include values from the dominant orbit of both ascending and descending orbits." - }, - "label": "Orbit overlap" - }, - "outlierRemoval": { - "aggressive": { - "label": "Aggressive", - "tooltip": "Remove more outliers." - }, - "label": "Outlier removal", - "moderate": { - "label": "Moderate", - "tooltip": "Remove outliers." - }, - "none": { - "label": "None", - "tooltip": "Don't remove outliers." - } - }, - "mask": { - "label": "Border noise correction", - "sides": { - "label": "Sides", - "tooltip": "Mask sides of slices." - }, - "firstLast": { - "label": "First/Last", - "tooltip": "Mask the beginning of the first slice and the end of the last slice." - } - }, - "maskOptions": { - "title": "Border noise correction options", - "minAngle": { - "label": "Min angle" - }, - "maxAngle": { - "label": "Max angle" - } - }, - "spatialSpeckleFilter": { - "label": "Spatial speckle filter", - "none": { - "label": "None", - "tooltip": "Don't apply any speckle filter." - }, - "boxcar": { - "label": "Boxcar", - "tooltip": "Boxcar filtering is applied." - }, - "gammaMap": { - "label": "Gamma Map", - "tooltip": "Gamma Map filtering is applied." - }, - "lee": { - "label": "Lee", - "tooltip": "Lee filtering is applied." - }, - "refinedLee": { - "label": "Refined Lee", - "tooltip": "Refined Lee filtering is applied. This can be very slow, but often gives good results." - }, - "leeSigma": { - "label": "Lee Sigma", - "tooltip": "Lee Sigma filtering is applied." - }, - "snic": { - "label": "SNIC", - "tooltip": "Simple Non-Iterative Clustering is used to filter out speckle." - } - }, - "spatialSpeckleFilterOptions": { - "kernelSize": { - "label": "Kernel size" - }, - "sigma": { - "label": "Sigma" - }, - "strongScatterers": { - "retain": { - "label": "Retain", - "tooltip": "Retain original value for strong scatterers. They don't tend to include much speckle and some speckle filtering algorithms tends to blur them." - }, - "filter": { - "label": "Filter", - "tooltip": "Apply spatial speckle filter also to strong scatterers. It can cause some speckle filtering algorithms to blur them." - }, - "label": "Strong scatterers" - }, - "strongScattererValue1": { - "label": "VV threshold", - "tooltip": "The threshold to consider VV a strong scatterer when performing Lee-Sigma spatial speckle filtering." - }, - "strongScattererValue2": { - "label": "VH threshold", - "tooltip": "The threshold to consider VH a strong scatterer when performing Lee-Sigma spatial speckle filtering." - }, - "snicSize": { - "label": "Seed spacing", - "tooltip": "The superpixel seed location spacing, in pixels. If 'seeds' image is provided, no grid is produced." - }, - "snicCompactness": { - "label": "Compactness", - "tooltip": "Compactness factor. Larger values cause clusters to be more compact (square). Setting this to 0 disables spatial distance weighting.", - "value": "{value}" - }, - "title": "Spatial speckle filter options" - }, - "multitemporalSpeckleFilterOptions": { - "numberOfImages": { - "label": "Number of images", - "value": "{value} images" - }, - "title": "Multitemporal speckle filter options" - }, - "multitemporalSpeckleFilter": { - "label": "Multitemporal speckle filter", - "none": { - "label": "None", - "tooltip": "Don't apply any multitemporal speckle filter." - }, - "quegan": { - "label": "Quegan", - "tooltip": "Apply Quegan multi-temporal filter." - }, - "rabasar": { - "label": "RABASAR", - "tooltip": "Apply RABASAR multi-temporal filter." - } - }, - "minObservations": { - "label": "Min observations", - "value": "{value} observation(s)" - } - }, - "title": "Options", - "tooltip": "Configure mosaic options" - } - }, - "preview": { - "description": "Preview of radar mosaic, generated on the fly by EE.", - "label": "Radar mosaic" - }, - "tabPlaceholder": "Radar_mosaic" + "mask": { + "button": "MSK", + "constraints": { + "title": "Image mask constraints" + }, + "noConstraints": "No constraints added.", + "emptyConstraints": "Empty", + "remove": { + "tooltip": "Remove masking constraints." + }, + "title": "Mask imagery", + "tooltip": "Mask out unwanted areas of imagery" }, - "baytsAlerts": { - "create": "BAYTS Alerts", - "description": "Generate forest change alerts using Sentinel-1 time-series and probabilistic change detection (based on the RADD processing pipeline).", - "imageLayerForm": { - "mosaicType": { - "label": "Mosaic", - "latest": { - "label": "Latest", - "tooltip": "Render a composite of the latest available imagery in the period" - }, - "median": { - "label": "Median", - "tooltip": "Render a median composite of the period" - } - }, - "visualizationType": { - "alerts": { - "label": "Alerts", - "tooltip": "Render alerts" - }, - "first": { - "label": "First", - "tooltip": "Render a composite of the first value in the monitoring period" - }, - "label": "Type", - "last": { - "label": "Last", - "tooltip": "Render a composite of the last value in the monitoring period" - } - }, - "previouslyConfirmed": { - "include": { - "label": "Include", - "tooltip": "Include alerts confirmed before the monitoring period" - }, - "exclude": { - "label": "Exclude", - "tooltip": "Exclude alerts confirmed before the monitoring period" - }, - "label": "Previously confirmed" - }, - "minConfidence": { - "all": { - "label": "All", - "tooltip": "Show all alerts independent on their confidence" - }, - "low": { - "label": "Low", - "tooltip": "Show alerts with low or high confidence" - }, - "high": { - "label": "High", - "tooltip": "Show only alerts with high confidence" - }, - "label": "Min confidence" - } + "composite": { + "button": "CMP", + "form": { + "type": { + "label": "Type", + "MOSAIC": { + "label": "Mosaic", + "tooltip": "Simple spatial composition" + }, + "MEDIAN": { + "label": "Median", + "tooltip": "Take the median of each band for the images in the collection" + }, + "MEAN": { + "label": "Mean", + "tooltip": "Take the mean of each band for the images in the collection" + }, + "MIN": { + "label": "Min", + "tooltip": "Use the minimum value of each band for the images in the collection" + }, + "MAX": { + "label": "Max", + "tooltip": "Use the maximum value of each band for the images in the collection" + }, + "SD": { + "label": "SD", + "tooltip": "Use the standard deviation of each band for the images in the collection" + }, + "MODE": { + "label": "Mode", + "tooltip": "Use the most common value of each band for the images in the collection. This only make sense for categorical bands" + } + } + }, + "title": "Composite", + "tooltip": "Combine images in image collection into a single image" + } + }, + "layers": { + "imageLayer": { + "preSets": "Pre-sets" + } + }, + "tabPlaceholder": "EE_Asset" + }, + "masking": { + "create": "Masking", + "description": "Apply a mask to an image.", + "layers": { + "imageLayer": { + "preSets": "Pre-sets" + } + }, + "panel": { + "inputImage": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID...", + "title": "Earth Engine Asset" + }, + "imageMask": { + "button": { + "label": "MSK", + "tooltip": "Select mask to use" }, - "layers": { - "imageLayer": { - "changes": "Changes", - "dates": "Dates", - "observations": "Observations" - } + "title": "Mask to use" + }, + "imageToMask": { + "button": { + "label": "IMG", + "tooltip": "Select image to mask" }, - "panel": { - "date": { - "button": "DAT", - "form": { - "durationUnit": { - "placeholder": "Unit...", - "DAYS": "Days", - "MONTHS": "Months", - "WEEKS": "Weeks" - }, - "monitoring": { - "duration": { - "label": "Monitoring duration", - "placeholder": "Duration..." - } - }, - "monitoringEnd": { - "label": "Monitoring end", - "tooltip": "The date when monitoring ends. You typically set this to today's date" - } - }, - "title": "Date range", - "tooltip": "Specify date range to get alerts for" - }, - "options": { - "button": "OPT", - "form": { - "highConfidenceThreshold": { - "label": "High-conf threshold", - "tooltip": "The needed change probability for a high-confidence alert" - }, - "lowConfidenceThreshold": { - "label": "Low-conf threshold", - "tooltip": "The needed change probability for a low-confidence alert", - "tooLarge": "Must be smaller than the high-conf threshold" - }, - "minChangeProbability": { - "label": "Min change prob", - "tooltip": "Min probability to consider an observation a potential change" - }, - "minNonForestProbability": { - "label": "Min non-forest prob", - "tooltip": "Min probability to consider an observation to potentially be non-forest" - }, - "maxDays": { - "label": "Max days", - "tooltip": "The max number of days before a lower confidence get unflagged" - }, - "normalization": { - "label": "Normalization", - "tooltip": "Enable or disable image normalization. When enabled, less false changes, due to seasonality, will be detected.", - "disabled": { - "label": "Disabled", - "tooltip": "Disable image normalization" - }, - "enabled": { - "label": "Enabled", - "tooltip": "Enable image normalization" - } - }, - "previousAlertsAsset": { - "label": "Previous alerts asset", - "placeholder": "Previous alerts asset...", - "tooltip": "EE asset containing previous alerts as a starting point for the algorithm", - "missingBands": "The asset is missing required bands: {missingBands}" - }, - "sensitivityButton": { - "tooltip": "Determine how sensitive the algorithm should be to detect changes", - "low": { - "label": "Low", - "tooltip": "Use low detection sensitivity" - }, - "medium": { - "label": "Medium", - "tooltip": "Use medium detection sensitivity" - }, - "high": { - "label": "High", - "tooltip": "Use high detection sensitivity" - } - }, - "sensitivity": { - "label": "Detection sensitivy", - "tooltip": "Determine how sensitive the algorithm should be to detect changes. Medium detection sensitivity is 1. The bigger the number, the less sensitive to change the algorithm will be.", - "value": "Value: {value}" - }, - "wetlandMaskAsset": { - "label": "Wetland Mask Asset", - "placeholder": "Wetland mask...", - "tooltip": "When a wetland mask is specified, the algorith will treat these areas slightly different" - } - }, - "title": "BAYTS alert options", - "tooltip": "Configure algorithm parameters." - }, - "preprocess": { - "button": "PRC", - "title": "Pre-process", - "tooltip": "Configure imagery pre-processing" + "title": "Image to mask" + }, + "recipe": { + "label": "Recipe", + "placeholder": "Select a recipe...", + "title": "Saved SEPAL Recipe" + } + } + }, + "tabPlaceholder": "Masking" + }, + "menu": { + "duplicateRecipe": { + "label": "Duplicate recipe", + "tooltip": "Duplicate this recipe." + }, + "exportRecipe": "Export recipe", + "removeRecipe.tooltip": "Remove recipe", + "selectRecipe.tooltip": "Select recipe", + "saveRecipe": "Save recipe...", + "searchRecipes": "Search recipes" + }, + "mosaic": { + "bands": { + "combinations": "Combinations", + "indexes": "Indexes", + "label": "Bands", + "metadata": "Metadata", + "months": "Months", + "panSharpen": "Pan sharpen", + "placeholder": "Select preview bands" + }, + "create": "Optical mosaic", + "error": { + "noImages": "All images have been filtered out. Update the recipe to ensure at least one image is included." + }, + "description": "Create a mosaic using Landsat or Sentinel 2.", + "mapToolbar": { + "mapRendering": { + "tooltip": "Start/stop map rendering", + "processing": "Processing", + "tiles": "{tiles} {tiles, plural, one {tile} other {tiles}}" + }, + "zoom": { + "tooltip": "Zoom" + }, + "layers": { + "tooltip": "Select layers to show" + }, + "options": { + "tooltip": "Map options" + }, + "unlink": { + "tooltip": "Unlink this map" + } + }, + "mapOptions": { + "location": { + "label": "Map location", + "tooltip": "Synchronize map location (center and zoom) with other maps or keep it independent", + "independent": "Independent", + "synchronized": "Synchronized", + "getOther": "Get location from other maps", + "useCurrent": "Use current location" + }, + "retile": { + "label": "Re-tile", + "tooltip": "Split Earth Engine processing of each tile into smaller sub-tiles. Please use this control sparingly, and only if really necessary, as it would cause a much higher Earth Engine resource utilization, and in most cases it would just slow down tile rendering. It should only be used when one or more tiles fail with \"User memory limit exceeded\" errors, possibly increasing the re-tile level one step at a time." + } + }, + "panel": { + "areaOfInterest": { + "button": "AOI", + "form": { + "bounds": { + "required": "Bounds are not loaded" + }, + "buffer": { + "info": "{buffer} km buffer", + "label": "Buffer", + "tooltip": "Area of interest and preview will take longer to show up when buffering is enabled." + }, + "assetBounds": { + "description": "Use bounds of selected EE asset.", + "title": "EE asset bounds" + }, + "country": { + "area": { + "label": "Province/area", + "placeholder": { + "loaded": "Select a province", + "loading": "Select a province", + "noAreas": "No provinces configured", + "noCountry": "Select a country first" }, - "reference": { - "asset": { - "label": "Earth Engine Asset", - "title": "Earth Engine Asset", - "notBayTSHistorical": "Asset is not a BAYTS historical image or image collection" - }, - "button": "REF", - "form": { - "asset": { - "invalid": "Asset must be an exported BAYTS historical asset", - "label": "BAYTS historical asset", - "placeholder": "Enter BAYTS historical asset ID", - "required": "An asset ID must be specified" - } - }, - "recipe": { - "label": "Saved SEPAL recipe", - "title": "Saved SEPAL recipe" - }, - "title": "Source", - "tooltip": "Select BAYTS historical asset or recipe to use as reference for alerts" + "title": "Select area" + }, + "country": { + "label": "Country", + "loadFailed": "Cannot load countries", + "placeholder": { + "loaded": "Select a country...", + "loading": "Loading countries..." } - }, - "preview": { - "description": "Preview of change alerts, generated on the fly by EE.", - "error": "Failed to generate preview", - "initializing": "Loading preview...", - "label": "Change alerts", - "loading": "Loading tiles: {pending}", - "tilesFailed": "{failed} failed" - }, - "reference": { - "asset": { - "loadError": "Failed to load asset" + }, + "required": "Country is required", + "title": "Select country/province" + }, + "eeTable": { + "column": { + "label": "Column", + "placeholder": { + "loaded": "Select a column...", + "loading": "Loading columns...", + "noEETable": "Enter a EE Table ID first" }, - "recipe": { - "loadError": "Failed to load recipe" - } - }, - "tabPlaceholder": "BAYTS_alerts" - }, - "baytsHistorical": { - "create": "BAYTS Historical Layer", - "description": "Create a Sentinel-1 historical layer as reference for BAYTS alerts.", - "panel": { - "dates": { - "button": "DAT", - "form": { - "fromDate": { - "label": "From date", - "malformed": "Invalid date" - }, - "toDate": { - "label": "To date", - "malformed": "Invalid date" - } - }, - "title": "Date range", - "tooltip": "Specify date range" + "required": "A column must be selected" + }, + "eeTable": { + "label": "EE Table", + "placeholder": "Enter a EE Table ID", + "required": "EE Table must be specified" + }, + "eeTableRowSelection": { + "FILTER": "Filter", + "INCLUDE_ALL": "Include all", + "label": "Rows" + }, + "row": { + "label": "Value", + "placeholder": { + "loaded": "Select a value...", + "loading": "Loading rows...", + "noColumn": "Select a column first", + "noEETable": "Enter a EE Table ID first", + "noRows": "No rows in EE Table" }, - "options": { - "button": "OPT", - "form": { - "geometricCorrection": { - "ellipsoid": { - "label": "Ellipsoid", - "tooltip": "Ellipsoid correction (gamma0 correction) is applied." - }, - "label": "Geometric correction", - "none": { - "label": "None", - "tooltip": "No geometric correction is applied." - }, - "terrain": { - "label": "Terrain", - "tooltip": "Terrain correction is applied." - } - }, - "orbits": { - "ascending": { - "label": "Ascending", - "tooltip": "Include scenes from ascending orbits." - }, - "descending": { - "label": "Descending", - "tooltip": "Include scenes from descending orbits." - }, - "label": "Orbits" - }, - "outlierRemoval": { - "aggressive": { - "label": "Aggressive", - "tooltip": "Remove more outliers." - }, - "label": "Outlier removal", - "moderate": { - "label": "Moderate", - "tooltip": "Remove outliers." - }, - "none": { - "label": "None", - "tooltip": "Don't remove outliers." - } - }, - "mask": { - "label": "Border noise correction", - "sides": { - "label": "Sides", - "tooltip": "Mask sides of slices." - }, - "firstLast": { - "label": "First/Last", - "tooltip": "Mask the beginning of the first slice and the end of the last slice." - } - }, - "maskOptions": { - "title": "Border noise correction options", - "minAngle": { - "label": "Min angle" - }, - "maxAngle": { - "label": "Max angle" - } - }, - "spatialSpeckleFilter": { - "label": "Spatial speckle filter", - "none": { - "label": "None", - "tooltip": "Don't apply any speckle filter." - }, - "boxcar": { - "label": "Boxcar", - "tooltip": "Boxcar filtering is applied." - }, - "gammaMap": { - "label": "Gamma Map", - "tooltip": "Gamma Map filtering is applied." - }, - "lee": { - "label": "Lee", - "tooltip": "Lee filtering is applied." - }, - "refinedLee": { - "label": "Refined Lee", - "tooltip": "Refined Lee filtering is applied. This can be very slow, but often gives good results." - }, - "leeSigma": { - "label": "Lee Sigma", - "tooltip": "Lee Sigma filtering is applied." - }, - "snic": { - "label": "SNIC", - "tooltip": "Simple Non-Iterative Clustering is used to filter out speckle." - } - }, - "spatialSpeckleFilterOptions": { - "kernelSize": { - "label": "Kernel size" - }, - "sigma": { - "label": "Sigma" - }, - "strongScatterers": { - "retain": { - "label": "Retain", - "tooltip": "Retain original value for strong scatterers. They don't tend to include much speckle and some speckle filtering algorithms tends to blur them." - }, - "filter": { - "label": "Filter", - "tooltip": "Apply spatial speckle filter also to strong scatterers. It can cause some speckle filtering algorithms to blur them." - }, - "label": "Strong scatterers" - }, - "strongScattererValue1": { - "label": "VV threshold", - "tooltip": "The threshold to consider VV a strong scatterer when performing Lee-Sigma spatial speckle filtering." - }, - "strongScattererValue2": { - "label": "VH threshold", - "tooltip": "The threshold to consider VH a strong scatterer when performing Lee-Sigma spatial speckle filtering." - }, - "snicSize": { - "label": "Seed spacing", - "tooltip": "The superpixel seed location spacing, in pixels. If 'seeds' image is provided, no grid is produced." - }, - "snicCompactness": { - "label": "Compactness", - "tooltip": "Compactness factor. Larger values cause clusters to be more compact (square). Setting this to 0 disables spatial distance weighting.", - "value": "{value}" - }, - "title": "Spatial speckle filter options" - }, - "multitemporalSpeckleFilterOptions": { - "numberOfImages": { - "label": "Number of images", - "value": "{value} images" - }, - "title": "Multitemporal speckle filter options" - }, - "multitemporalSpeckleFilter": { - "label": "Multitemporal speckle filter", - "none": { - "label": "None", - "tooltip": "Don't apply any multitemporal speckle filter." - }, - "quegan": { - "label": "Quegan", - "tooltip": "Apply Quegan multi-temporal filter." - }, - "rabasar": { - "label": "RABASAR", - "tooltip": "Apply RABASAR multi-temporal filter." - } - }, - "minObservations": { - "label": "Min observations", - "value": "{value} observation(s)" - } - }, - "title": "Options", - "tooltip": "Configure BAYTS historical layer options" - } - }, - "preview": { - "description": "Preview of BAYTS historical layer, generated on the fly by EE.", - "label": "BAYTS historical" - }, - "tabPlaceholder": "BAYTS_historical" - }, - "phenology": { - "create": "Land surface phenology", - "description": "Create land surface phenology.", - "panel": { - "dates": { - "button": "DAT", - "form": { - "fromYear": { - "label": "Start year", - "tooltip": "The first year to include (inclusive)" - }, - "toYear": { - "beforeFromYear": "Cannot be before start year", - "label": "End year", - "tooltip": "The final year to include (inclusive)" - } - }, - "title": "Dates", - "tooltip": "Specify dates" + "required": "A row must be selected" + }, + "title": "Select from EE Table" + }, + "fusionTable": { + "column": { + "label": "Column", + "placeholder": { + "loaded": "Select a column...", + "loading": "Loading columns...", + "noFusionTable": "Enter a Fusion Table ID first" }, - "preprocess": { - "button": "PRC", - "title": "Pre-process", - "tooltip": "Configure imagery pre-processing" + "required": "A column must be selected" + }, + "fusionTable": { + "label": "Fusion Table", + "placeholder": "Enter a Fusion Table ID", + "required": "Fusion Table must be specified" + }, + "fusionTableRowSelection": { + "FILTER": "Filter", + "INCLUDE_ALL": "Include all", + "label": "Rows" + }, + "row": { + "label": "Value", + "placeholder": { + "loaded": "Select a value...", + "loading": "Loading rows...", + "noColumn": "Select a column first", + "noFusionTable": "Enter a Fusion Table ID first", + "noRows": "No rows in Fusion Table" }, - "sources": { - "button": "SRC", - "classificationLoadError": "Failed to load classification: {error}", - "form": { - "classification": { - "label": "Classification", - "placeholder": "Select a classification...", - "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." - }, - "band": { - "label": "Analysis band" - }, - "cloudPercentageThreshold": { - "label": "Max cloud cover %", - "value": "{value}%", - "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" - }, - "dataSets": { - "label": "Data sets", - "optical": { - "label": "Optical" - }, - "options": { - "LANDSAT_7": { - "label": "L7", - "tooltip": "Landsat 7" - }, - "LANDSAT_7_T2": { - "label": "L7 T2", - "tooltip": "Landsat 7 Tier 2" - }, - "LANDSAT_8": { - "label": "L8", - "tooltip": "Landsat 8" - }, - "LANDSAT_8_T2": { - "label": "L8 T2", - "tooltip": "Landsat 8 Tier 2" - }, - "LANDSAT_9": { - "label": "L9", - "tooltip": "Landsat 9" - }, - "LANDSAT_9_T2": { - "label": "L9 T2", - "tooltip": "Landsat 9 Tier 2" - }, - "LANDSAT_TM": { - "label": "L4-5", - "tooltip": "Landsat 4-5" - }, - "LANDSAT_TM_T2": { - "label": "L4-5 T2", - "tooltip": "Landsat 4-5 Tier 2" - }, - "SENTINEL_1": { - "label": "S1", - "tooltip": "Sentinel 1 A+B" - }, - "SENTINEL_2": { - "label": "S2", - "tooltip": "Sentinel 2 A+B" - } - }, - "radar": { - "label": "Radar" - } - }, - "dataSetType": { - "label": "Type" - }, - "dataSetTypes": { - "OPTICAL": "Optical", - "PLANET": "Planet", - "RADAR": "Radar" - }, - "required": "A data source must be selected" - }, - "title": "Sources", - "tooltip": "Select image source" - } + "required": "A row must be selected" + }, + "title": "Select from Fusion Table" }, - "preview": { - "description": "Preview of land surface phenology, generated on the fly by EE.", - "label": "Land surface phenology" + "polygon": { + "description": "Draw a polygon on the map.", + "title": "Draw a polygon" }, - "tabPlaceholder": "Phenology" - }, - "projects": { - "title": "Projects", - "label": "Projects", - "tooltip": "Manage projects", - "noProjects": "No projects", - "selected": "Currently selected", - "add": "Add" + "section": { + "required": "A section must be selected" + } + }, + "title": "Area of interest", + "tooltip": "Specify area of interest" }, - "project": { - "title": "Project", - "description": "{count} {count, plural, one {recipe} other {recipes}}", - "form": { - "name.label": "Name", - "name.required": "Name is required", - "name.unique": "Project name is already in use" - }, - "edit": { - "tooltip": "Edit project" - }, - "remove": { - "tooltip": "Remove project", - "title": "Remove project", - "confirm": "All recipes belonging to this project will be removed as well.", - "error": "Cannot remove project" - }, - "update": { - "error": "Cannot update project" - }, - "noProject": "no project", - "noProjectOption": "[no project]" + "autoSelectScenes": { + "form": { + "max": { + "label": "Maximum number of scenes" + }, + "min": { + "label": "Minimum number of scenes" + }, + "selectScenes": "Select scenes" + }, + "selecting": "Selecting scenes...", + "title": "Auto-select scenes", + "tooltip": "Auto-select scenes" }, - "recipe": { - "lastUpdate": "Last update", - "loading": "Loading recipes", - "loadingError": "Failed to load recipes. Please reload page to try again.", - "name": "Name", - "newRecipe": { - "title": "Create recipe", - "tooltip": "Create a new SEPAL recipe", - "search.placeholder": "Search recipe types", - "tags": { - "ALL": "All", - "OPTICAL": "Optical", - "RADAR": "Radar", - "MOSAIC": "Mosaic", - "CHANGE": "Change", - "TIME_SERIES": "Time series", - "ALERTS": "Alerts" - } - }, - "edit": { - "label": "Edit" - }, - "select": { - "label": "Select", - "tooltip": { - "select": "Select all recipes", - "deselect": "Deselect all recipes" - } - }, - "remove": { - "label": "Remove", - "title": "Remove recipes", - "tooltip": "Remove selected recipes", - "confirm": "Remove {count} {count, plural, one {recipe} other {recipes}}?" - }, - "move": { - "label": "Move", - "title": "Move recipes", - "tooltip": "Move selected recipes to a project", - "destinationProject": "Destination project", - "confirm": "Move {count} {count, plural, one {recipe} other {recipes}} to project {project}?" - }, - "export": { - "success": "Recipe has been exported.", - "error": "Could not export recipe." - } + "clearSelectedScenes": { + "apply": "Clear scenes", + "message": "You are about to clear all selected scenes.", + "title": "Clear selected scenes", + "tooltip": "Clear selected scenes" }, - "remapping": { - "create": "Remapping", - "description": "Remap categorical and continuous image bands into new categories.", - "layers": { - "imageLayer": { - "preSets": "Pre-sets" + "composite": { + "button": "CMP", + "form": { + "cloudBuffer": { + "aggressive": { + "label": "Aggressive", + "tooltip": "Mask an additional 600m around each larger cloud. This helps prevent hazy pixels at the border of clouds to be included in the mosaic." + }, + "label": "Cloud buffering", + "moderate": { + "label": "Moderate", + "tooltip": "Mask an additional 120m around each larger cloud. This helps prevent hazy pixels at the border of clouds to be included in the mosaic." + }, + "none": { + "label": "None", + "tooltip": "Only mask the clouds. It might leave hazy pixels around masked clouds, but will minimize the amount of maxed pixels in the mosaic." + } + }, + "cloudDetection": { + "label": "Cloud detection", + "cloudScore": { + "label": "Cloud score", + "tooltip": "Use cloud scoring algorithm" + }, + "pino26": { + "label": "PINO 26", + "tooltip": "Use Pan-Tropical S2 cloud detection algorithm developed by Dario Simonetti" + }, + "qa": { + "label": "QA bands", + "tooltip": "Use pre-created QA bands" + } + }, + "cloudMasking": { + "aggressive": { + "label": "Aggressive", + "tooltip": "Rely on image source QA bands and a cloud scoring algorithm for cloud masking. This will probably mask out some built-up areas and other bright features." + }, + "label": "Cloud masking", + "moderate": { + "label": "Moderate", + "tooltip": "Rely only on image source QA bands for cloud masking" + }, + "custom": { + "label": "Custom" + }, + "noData": "No cloud masking applied", + "none": { + "label": "Off", + "tooltip": "Use cloud-free pixels if possible, but don't mask areas without cloud-free pixels" + }, + "sentinel2CloudProbability": { + "label": "S2 Cloud Probability", + "maxCloudProbability": { + "label": "Max cloud probability", + "info": "Max {percent}% cloud probability" } - }, - "panel": { - "legend": { - "button": "LEG", - "title": "Legend", - "tooltip": "Setup legend to remap imagery to", - "import": "Import..." + }, + "sentinel2CloudScorePlus": { + "label": "S2 Cloud Score+", + "band": { + "label": "Band", + "cs": { + "tooltip": "The cs band scores QA based on a spectral distance between the observed pixel and a (theoretical) clear reference observation. It can be thought of as a more instantaneous atmospheric similarity score (i.e., how similar is this pixel to what we'd expect to see in a perfectly a clear reference)." + }, + "cs_cdf": { + "tooltip": "The cs_cdf band represents the likelihood an observed pixel is clear based on an estimated cumulative distribution of scores for a given location through time. It captures an expectation of the estimated score through time (i.e., if we had all the scores for this pixel through time, how would this score rank?)." + } }, - "mapping": { - "button": { - "label": "MAP", - "tooltip": "Map input imagery to legend entries" - }, - "title": "Mapping" + "maxCloudProbability": { + "label": "Max cloud probability", + "tooltip": "Cloud Score+ provides likelihood of clear pixels. To harmonize the input with other cloud scoring algorithms, in SEPAL, we have inverted it to represent likelihood of cloudy pixels.", + "info": "Max {percent}% cloud probability" } - }, - "tabPlaceholder": "Remapping" - }, - "retrieve": { - "apply": "Retrieve", - "form": { - "assetId": { - "label": "Asset ID", - "placeholder": "Enter Asset ID...", - "tooltip": "The Asset ID your image should be exported to." - }, - "assetType": { - "label": "Asset type", - "Image": { - "label": "Image", - "tooltip": "Export recipe as a single image asset." - }, - "ImageCollection": { - "label": "Image collection", - "tooltip": "Export recipe tiled into an image collection. This is good for larger exports as it gives you feedback on the progress, and will allow you to resume failed or canceled export tasks." - } + }, + "landsatCFMask": { + "label": "Landsat CFMask", + "level": { + "off": "Off", + "moderate": "Moderate", + "aggressive": "Aggressive" }, - "bands": { - "atLeastOne": "At least one band must be retrieved", - "label": "Bands to retrieve" + "cloudMasking": { + "label": "Cloud Masking" }, - "crs": { - "label": "CRS", - "placeholder": "Enter CRS...", - "tooltip": "CRS to use for the exported image" + "cloudShadowMasking": { + "label": "Cloud Shadow Masking" }, - "crsTransform": { - "label": "CRS transform", - "placeholder": "Enter transform...", - "tooltip": "Optionally specity affine transform to use for the exported image" + "cirrusMasking": { + "label": "Cirrus Masking" }, - "destination": { - "GEE": "Google Earth Engine asset", - "label": "Destination", - "required": "A destination must be selected", - "SEPAL": "SEPAL workspace", - "DRIVE": "Google Drive" - }, - "fileDimensionsMultiple": { - "label": "File dimensions multiple", - "placeholder": "Multiples...", - "suffix": "multiples", - "tooLarge": "Multiple * shard size must be less than or equal to 131072", - "tooltip": "Multiples of the shard size to set the file dimentions to, the size to split each export into. E.g. for a shard size of 256 and a multiple of 10, the exported image will be downloaded it images with the maximum size of 2560x2560 pixels." + "dilatedCloud": { + "label": "Dilated clouds", + "tooltip": "Cloud dilation fills any “clear” pixels that are surrounded by clouds, reducing uncertainty concerning thin cloud or adjacent-to-cloud pixels. Dilation reduces ambiguity of a cloud-contaminated pixel at the cost of potentially high commission error; this is ideal for land use/land cover change assessments analyzing large spatiotemporal stacks of data, where cloud cover can adversely alter trending analysis.", + "keep": "Keep", + "remove": "Remove" + } + }, + "sepalCloudScore": { + "label": "SEPAL Cloud Score", + "maxCloudProbability": { + "label": "Max cloud probability", + "info": "Max {percent}% cloud probability" + } + }, + "pino26": { + "label": "Pino 26" + } + }, + "composingMethod": { + "label": "Composing method", + "median": { + "label": "Median", + "tooltip": "Use the median of remaining pixels. Pixel value will be artificial, and metadata for mosaic will be unavailable." + }, + "medoid": { + "label": "Medoid", + "tooltip": "Use the medoid of remaining pixels." + } + }, + "corrections": { + "brdf": { + "label": "BRDF", + "tooltip": "Correct for bidirectional reflectance distribution function (BRDF) effects." + }, + "calibrate": { + "label": "Calibrate", + "tooltip": "Calibrate bands to improve cross-sensor mosaic." + }, + "label": "Corrections", + "surfaceReflectance": { + "label": "SR", + "tooltip": "Use scenes atmospherically corrected surface reflectance." + } + }, + "brdfMultiplier": { + "label": "BRDF Multiplier", + "placeholder": "Multiplier...", + "tooltip": "Multiplier to increase the amount of BRDF correction to apply. Normally, values between 3-4 works well. If the correction is overcompensating the BRDF effect, lower the multiplier, and increase it when the effect is not compensated for enough." + }, + "filters": { + "dayOfYearPercentile": { + "label": "Date", + "max": "Select pixels closest to target day", + "off": "Don't filter based on day of year", + "percentile": "Exclude {percentile}% pixels (far from target)" + }, + "hazePercentile": { + "label": "Haze", + "max": "Select pixels with least haze", + "off": "Don't filter based on haze", + "percentile": "Exclude {percentile}% pixels (with haze)" + }, + "label": "Filters", + "noData": "No filters applied", + "ndviPercentile": { + "label": "NDVI", + "max": "Select pixels with highest NDVI", + "off": "Don't filter based on NDVI", + "percentile": "Exclude {percentile}% pixels (low NDVI)" + }, + "shadowPercentile": { + "label": "Shadow", + "max": "Select pixels with least shadow", + "off": "Don't filter based on shadow", + "percentile": "Exclude {percentile}% pixels (with shadow)" + }, + "tooltip": "Filter out pixels based on on percentile of selected properties" + }, + "holes": { + "label": "Masked out pixels", + "tooltips": "Control if some pixels can be completely masked out due to cloud and/or snow masking or not.", + "prevent": { + "label": "Prevent", + "tooltip": "Prevent pixels from being completely masked out due to cloud and/or snow masking. Pixels that would be completely masked out will retrain cloudy and/or snowy pixels." + }, + "allow": { + "label": "ALLOW", + "tooltip": "Allow pixels to be completely masked out if every acquisition are cloudy and/or snowy." + } + }, + "orbitOverlap": { + "label": "Orbit Overlap", + "keep": { + "label": "Keep", + "tooltip": "Keep overlap between Sentinel 2orbits. This additional data will result in better models in the overlap." + }, + "remove": { + "label": "Remove", + "tooltip": "Removes overlap between Sentinel 2 orbits. This adds an extra step in the preprocessing. This tends to make the BRDF effect more noticable, and it might be worth while trying to apply BRDF correction in conjuction with this feature." + } + }, + "tileOverlap": { + "label": "Tile Overlap", + "keep": { + "label": "Keep", + "tooltip": "Keep overlap between Sentinel 2 tiles. Areas with overlap will have twice or quadruple number of observations. All of these additional observations are all have the same value. Keeping them uses more memory than necessary and can lead to out of memory errors." + }, + "quickRemove": { + "label": "Quick remove", + "tooltip": "Makes an effort to remove most of the overlap between Sentinel 2 tiles. By removing the duplicate data, the memory needed in processing is reduced." + }, + "remove": { + "label": "Remove", + "tooltip": "Removes all overlap between Sentinel 2 tiles. This adds an extra step in the preprocessing. By removing the duplicate data, the memory needed in processing is reduced. There is no significant difference in this memory reduction with this option compared to the quick remove option." + } + }, + "snowMasking": { + "label": "Snow/ice masking", + "off": { + "label": "Off", + "tooltip": "Don't mask snow. Note that some clouds might get misclassified as snow, and because of this, disabling snow masking might lead to cloud artifacts." + }, + "on": { + "label": "On", + "tooltip": "Mask snow. This tend to leave some pixels with shadowy snow" + } + } + }, + "title": "Composite", + "tooltip": "Configure composite creation" + }, + "dates": { + "button": "DAT", + "form": { + "futureSeasons": { + "info": { + "none": "Don't include seasons from the future", + "number": "Include {futureSeasons} {futureSeasons, plural, one {season} other {seasons}} from the future" + }, + "label": "Future seasons" + }, + "pastSeasons": { + "info": { + "none": "Don't include seasons from the past", + "number": "Include {pastSeasons} {pastSeasons, plural, one {season} other {seasons}} from the past" + }, + "label": "Past seasons" + }, + "season": { + "label": "Season", + "malformed": "Invalid date", + "tooEarly": "Must not be before {min}", + "tooLate": "Must not be after {max}", + "tooltip": "Restrict the time of year to include imagery for." + }, + "targetDate": { + "label": "Target date", + "malformed": "Invalid date", + "tooltip": "The date in which pixels in the mosaic should ideally come from." + }, + "targetYear": { + "label": "Year", + "malformed": "Not a year", + "tooltip": "The year which pixels in the mosaic should come from." + } + }, + "title": "Dates", + "tooltip": "Specify dates" + }, + "layers": { + "title": "Layers" + }, + "scenes": { + "button": "SCN", + "form": { + "required": "Scene selection must be configured", + "targetDateWeight": { + "balanced": { + "label": "Balanced", + "tooltip": "Try to take images no too cloudy and not too far from the target date" + }, + "cloudFree": { + "label": "Cloud free", + "tooltip": "Prefer imagery as cloud-free as possible, ignoring the date" + }, + "label": "Priority", + "targetDate": { + "label": "Target date", + "tooltip": "Prefer imagery as close as possible to the target day" + } + }, + "type": { + "all": { + "label": "Use all scenes" + }, + "label": "Scene selection", + "select": { + "label": "Select scenes" + } + } + }, + "title": "Scenes", + "tooltip": "Configure scenes selection" + }, + "sceneSelection": { + "availableScenes": "Available scenes", + "loadingScenes": "Loading scenes...", + "noScenes": "No scenes are available for the current combination of area of interest, dates and data sources. Please revise the settings in AOI, DAT and/or SRC.", + "preview": { + "afterTarget": "{daysFromTarget} after", + "beforeTarget": "{daysFromTarget} before", + "cloudCover": "Cloud cover", + "dataSet": "Data set", + "date": "Acquisition date", + "daysFromTarget": "Days from target", + "label": "Preview", + "onTarget": "On target day" + }, + "selectedScenes": "Selected scenes" + }, + "sources": { + "button": "SRC", + "form": { + "dataSets": { + "label": "Datasets", + "oneDataSet": "Selected satellite family only have a single dataset.", + "options": { + "LANDSAT_7": { + "label": "L7", + "tooltip": "Landsat 7" }, - "scale": { - "label": "Scale", - "placeholder": "Scale...", - "suffix": "meters" + "LANDSAT_7_T2": { + "label": "L7 T2", + "tooltip": "Landsat 7 Tier 2" }, - "shardSize": { - "label": "Shard size", - "placeholder": "Size...", - "suffix": "pixels", - "tooltip": "The size Earth Engine should break down the image into for processing. If you're running out of memory, try with a smaller value." + "LANDSAT_8": { + "label": "L8", + "tooltip": "Landsat 8" }, - "sharing": { - "label": "Sharing", - "PRIVATE": { - "label": "Private", - "tooltip": "Only you can access this asset" - }, - "PUBLIC": { - "label": "Public", - "tooltip": "Anyone can access this asset" - } + "LANDSAT_8_T2": { + "label": "L8 T2", + "tooltip": "Landsat 8 Tier 2" }, - "task": { - "GEE": "Export recipe: {name} as a Google Earth Engine asset", - "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", - "DRIVE": "Retrieve recipe:{name} to Google Drive" + "LANDSAT_9": { + "label": "L9", + "tooltip": "Landsat 9" }, - "tileSize": { - "label": "Tile size", - "placeholder": "Size...", - "suffix": "degrees", - "tooltip": "The size to tile the images into. If you're running out of memory, try with a smaller value." + "LANDSAT_9_T2": { + "label": "L9 T2", + "tooltip": "Landsat 9 Tier 2" }, - "workspacePath": { - "label": "Workspace path", - "placeholder": "Enter workspace path...", - "tooltip": "Relative path in your workspace home directory where the image should be retrieved to." + "LANDSAT_TM": { + "label": "L4-5", + "tooltip": "Landsat 4-5" }, - "filenamePrefix" : { - "label" : "Filename prefix", - "placeholder" : "Enter filename prefix...", - "tooltip" : "Custom prefix that will be added to exported files" - } - }, - "title": "Retrieve", - "tooltip": "Retrieve recipe" - }, - "saveRecipe": { - "error": "Sorry, but something went wrong when trying to save your recipe. If you close the window/tab, you will lose any changes you've made since last successful save. To make sure you have some record the data within this recipe, you might want to export it. Go to the top right recipe menu and select \"Export recipe\".", - "form": { - "name": { - "label": "Name", - "required": "Recipe name is required" - } - }, - "title": "Save recipe" - }, - "timeSeries": { - "bands": { - "count": "Count" - }, - "chartPixel": { - "loadObservations": { - "error": "Failed to load observations", - "noData": "Pixel has no observations" - } - }, - "create": "Time series", - "description": "Download time series of optical and radar data to SEPAL.", - "panel": { - "dates": { - "button": "DAT", - "form": { - "endDate": { - "label": "End date", - "malformed": "Invalid date", - "required": "Date is required", - "tooltip": "The last date of the time-series, exclusive." - }, - "startDate": { - "beforeEnd": "Must be before end", - "label": "Start date", - "malformed": "Invalid date", - "required": "Date is required", - "tooltip": "The first date of the time-series, inclusive." - } - }, - "title": "Dates", - "tooltip": "Specify date range" + "LANDSAT_TM_T2": { + "label": "L4-5 T2", + "tooltip": "Landsat 4-5 Tier 2" }, - "preprocess": { - "button": "PRC", - "title": "Pre-process", - "tooltip": "Configure imagery pre-processing" + "SENTINEL_1": { + "label": "Sentinel 1", + "tooltip": "Sentinel 1A and Sentinel 1B" }, - "sources": { - "button": "SRC", - "classificationLoadError": "Failed to load classification: {error}", - "form": { - "classification": { - "label": "Classification", - "placeholder": "Select a classification...", - "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." - }, - "dataSets": { - "optical": { - "label": "Optical" - }, - "options": { - "LANDSAT_7": { - "label": "L7", - "tooltip": "Landsat 7" - }, - "LANDSAT_7_T2": { - "label": "L7 T2", - "tooltip": "Landsat 7 Tier 2" - }, - "LANDSAT_8": { - "label": "L8", - "tooltip": "Landsat 8" - }, - "LANDSAT_8_T2": { - "label": "L8 T2", - "tooltip": "Landsat 8 Tier 2" - }, - "LANDSAT_9": { - "label": "L9", - "tooltip": "Landsat 9" - }, - "LANDSAT_9_T2": { - "label": "L9 T2", - "tooltip": "Landsat 9 Tier 2" - }, - "LANDSAT_TM": { - "label": "L4-5", - "tooltip": "Landsat 4-5" - }, - "LANDSAT_TM_T2": { - "label": "L4-5 T2", - "tooltip": "Landsat 4-5 Tier 2" - }, - "SENTINEL_1": { - "label": "S1", - "tooltip": "Sentinel 1 A+B" - }, - "SENTINEL_2": { - "label": "S2", - "tooltip": "Sentinel 2 A+B" - } - }, - "radar": { - "label": "Radar" - } - }, - "required": "A data source must be selected" - }, - "title": "Sources", - "tooltip": "Select image source" + "SENTINEL_2": { + "label": "A + B", + "tooltip": "Sentinel 2A and Sentinel 2B" } + } }, - "tabPlaceholder": "Time_series" + "required": "A data source must be selected", + "source": { + "label": "Satellite family", + "options": { + "LANDSAT": "Landsat", + "SENTINEL_2": "Sentinel 2" + } + } + }, + "title": "Sources", + "tooltip": "Select image source" } + }, + "preview": { + "description": "Preview of optical mosaic, generated on the fly by EE.", + "error": "Failed to create preview", + "initializing": "Loading preview...", + "label": "Optical mosaic", + "loading": "Loading tiles: {pending}", + "tilesFailed": "{failed} failed", + "addScene": "Add scene", + "removeScene": "Remove scene" + }, + "sceneAreas": { + "description": "Manually select individual scenes to include in mosaic", + "error": "Failed to load scene areas", + "label": "Scene areas", + "loading": "Loading scene areas..." + }, + "tabPlaceholder": "Optical_mosaic" }, - "sources": { - "dataSets": { - "LANDSAT_7": { - "label": "L7", - "tooltip": "Landsat 7" - }, - "LANDSAT_7_T2": { - "label": "L7 T2", - "tooltip": "Landsat 7 Tier 2" - }, - "LANDSAT_8": { - "label": "L8", - "tooltip": "Landsat 8" - }, - "LANDSAT_8_T2": { - "label": "L8 T2", - "tooltip": "Landsat 8 Tier 2" - }, - "LANDSAT_9": { - "label": "L9", - "tooltip": "Landsat 9" - }, - "LANDSAT_9_T2": { - "label": "L9 T2", - "tooltip": "Landsat 9 Tier 2" - }, - "LANDSAT_TM": { - "label": "L4-5", - "tooltip": "Landsat 4-5" - }, - "LANDSAT_TM_T2": { - "label": "L4-5 T2", - "tooltip": "Landsat 4-5 Tier 2" - }, - "SENTINEL_1": { - "label": "S1", - "tooltip": "Sentinel 1 A+B" - }, - "SENTINEL_2": { - "label": "S2", - "tooltip": "Sentinel 2 A+B" + "planetMosaic": { + "create": "Planet mosaic", + "description": "Create a mosaic using Planet composites.", + "panel": { + "dates": { + "button": "DAT", + "form": { + "type": { + "DATE_RANGE": { + "label": "Date range", + "tooltip": "" + }, + "SINGLE_DATE": { + "label": "Single date", + "tooltip": "" + } + }, + "fromDate": { + "label": "From date", + "malformed": "Invalid date" + }, + "toDate": { + "label": "To date", + "malformed": "Invalid date" } + }, + "title": "Dates", + "tooltip": "Specify dates" }, - "optical": { - "label": "Optical" + "options": { + "button": "OPT", + "cloudThreshold": { + "label": "Cloud masking", + "tooltip": "Planet composites already removes clouds. Setting this to a value > 0 will remove additional clouds.", + "value": "{value}%" + }, + "form": { + "histogramMatching": { + "label": "Histogram matching", + "tooltip": "Normalize images included in mosaic using Histogram matching. This will slow down processing significant, and can cause some details to be lost. Overall consistency and noise level can, however, be significantly improved with this option enabled.", + "options": { + "DISABLED": "Disabled", + "ENABLED": "Enabled" + } + } + }, + "shadowThreshold": { + "label": "Cloud shadow masking", + "tooltip": "Planet composites already removes cloud shadows. Setting this to a value > 0 will remove additional cloud shadows.", + "value": "{value}%" + }, + "title": "Options", + "tooltip": "Configure mosaic options" }, - "radar": { - "label": "Radar" + "sources": { + "button": "SRC", + "form": { + "asset": { + "label": "Earth Engine asset ID", + "placeholder": "Enter an asset ID...", + "required": "You must enter an asset ID" + }, + "collectionType": { + "label": "Collection type" + }, + "collectionTypes": { + "BASEMAPS": { + "label": "Custom basemaps", + "tooltip": "Use image collection with Planet basemaps/level 1 composites" + }, + "DAILY": { + "label": "Daily imagery", + "tooltip": "Use image collection with daily Planet imagery/level 2 imagery" + }, + "NICFI": { + "label": "NICFI basemaps", + "tooltip": "Use image collection with the NICFI Planet basemaps/level 1 composites" + } + } + }, + "title": "Sources", + "tooltip": "Select image source" } + }, + "preview": { + "description": "Preview of Planet mosaic, generated on the fly by EE.", + "label": "Planet mosaic" + }, + "tabPlaceholder": "Planet_mosaic" }, - "tasks": { - "download": { - "progress": "Downloaded {files} {files, plural, one {file} other {files}} / {bytes} left" + "radarMosaic": { + "create": "Radar mosaic", + "description": "Create a mosaic using Sentinel 1.", + "panel": { + "dates": { + "button": "DAT", + "form": { + "fromDate": { + "label": "From date", + "malformed": "Invalid date" + }, + "targetDate": { + "label": "Target date", + "malformed": "Invalid date" + }, + "toDate": { + "label": "To date", + "malformed": "Invalid date" + }, + "type": { + "CUSTOM_TIME_SCAN": { + "label": "Date range", + "tooltip": "Build a time-scan with data from a custom date range." + }, + "label": "Type", + "POINT_IN_TIME_MOSAIC": { + "label": "Date", + "tooltip": "Build a mosaic with data coming as close to a specified date as possible." + }, + "YEARLY_TIME_SCAN": { + "label": "Year", + "tooltip": "Build a time-scan with data from a specified calendar year." + } + }, + "year": { + "label": "Year", + "malformed": "Not a year" + } + }, + "title": "Dates", + "tooltip": "Specify dates" }, - "ee": { - "export": { - "asset": { - "createFolder": "Create asset folder {assetId}", - "delete": "Delete asset {assetId}", - "prepareImageCollection": "Prepare image collection {assetId}", - "startExport": "Start export of {tileCount} tiles", - "sharing": "Sharing asset {assetId}", - "tilingImage": "Tiling image" + "options": { + "button": "OPT", + "form": { + "geometricCorrection": { + "ellipsoid": { + "label": "Ellipsoid", + "tooltip": "Ellipsoid correction (gamma0 correction) is applied." + }, + "label": "Geometric correction", + "none": { + "label": "None", + "tooltip": "No geometric correction is applied." + }, + "terrain": { + "label": "Terrain", + "tooltip": "Terrain correction is applied." + } + }, + "orbits": { + "ascending": { + "label": "Ascending", + "tooltip": "Include scenes from ascending orbits." + }, + "descending": { + "label": "Descending", + "tooltip": "Include scenes from descending orbits." + }, + "label": "Orbits" + }, + "orbitNumbers": { + "all": { + "label": "All", + "tooltip": "Include values from all orbit numbers for a pixel." + }, + "dominant": { + "label": "Dominant", + "tooltip": "Only include values from the dominant orbit number for a pixel." + }, + "adjacent": { + "label": "Adjacent", + "tooltip": "Include values from the dominant orbit of both ascending and descending orbits." + }, + "label": "Orbit overlap" + }, + "outlierRemoval": { + "aggressive": { + "label": "Aggressive", + "tooltip": "Remove more outliers." + }, + "label": "Outlier removal", + "moderate": { + "label": "Moderate", + "tooltip": "Remove outliers." + }, + "none": { + "label": "None", + "tooltip": "Don't remove outliers." + } + }, + "mask": { + "label": "Border noise correction", + "sides": { + "label": "Sides", + "tooltip": "Mask sides of slices." + }, + "firstLast": { + "label": "First/Last", + "tooltip": "Mask the beginning of the first slice and the end of the last slice." + } + }, + "maskOptions": { + "title": "Border noise correction options", + "minAngle": { + "label": "Min angle" + }, + "maxAngle": { + "label": "Max angle" + } + }, + "spatialSpeckleFilter": { + "label": "Spatial speckle filter", + "none": { + "label": "None", + "tooltip": "Don't apply any speckle filter." + }, + "boxcar": { + "label": "Boxcar", + "tooltip": "Boxcar filtering is applied." + }, + "gammaMap": { + "label": "Gamma Map", + "tooltip": "Gamma Map filtering is applied." + }, + "lee": { + "label": "Lee", + "tooltip": "Lee filtering is applied." + }, + "refinedLee": { + "label": "Refined Lee", + "tooltip": "Refined Lee filtering is applied. This can be very slow, but often gives good results." + }, + "leeSigma": { + "label": "Lee Sigma", + "tooltip": "Lee Sigma filtering is applied." + }, + "snic": { + "label": "SNIC", + "tooltip": "Simple Non-Iterative Clustering is used to filter out speckle." + } + }, + "spatialSpeckleFilterOptions": { + "kernelSize": { + "label": "Kernel size" + }, + "sigma": { + "label": "Sigma" + }, + "strongScatterers": { + "retain": { + "label": "Retain", + "tooltip": "Retain original value for strong scatterers. They don't tend to include much speckle and some speckle filtering algorithms tends to blur them." + }, + "filter": { + "label": "Filter", + "tooltip": "Apply spatial speckle filter also to strong scatterers. It can cause some speckle filtering algorithms to blur them." }, - "pending": "Submitting export task to Google Earth Engine", - "ready": "Waiting for Google Earth Engine to start export", - "running": "Google Earth Engine is exporting" + "label": "Strong scatterers" + }, + "strongScattererValue1": { + "label": "VV threshold", + "tooltip": "The threshold to consider VV a strong scatterer when performing Lee-Sigma spatial speckle filtering." + }, + "strongScattererValue2": { + "label": "VH threshold", + "tooltip": "The threshold to consider VH a strong scatterer when performing Lee-Sigma spatial speckle filtering." + }, + "snicSize": { + "label": "Seed spacing", + "tooltip": "The superpixel seed location spacing, in pixels. If 'seeds' image is provided, no grid is produced." + }, + "snicCompactness": { + "label": "Compactness", + "tooltip": "Compactness factor. Larger values cause clusters to be more compact (square). Setting this to 0 disables spatial distance weighting.", + "value": "{value}" + }, + "title": "Spatial speckle filter options" + }, + "multitemporalSpeckleFilterOptions": { + "numberOfImages": { + "label": "Number of images", + "value": "{value} images" + }, + "title": "Multitemporal speckle filter options" + }, + "multitemporalSpeckleFilter": { + "label": "Multitemporal speckle filter", + "none": { + "label": "None", + "tooltip": "Don't apply any multitemporal speckle filter." + }, + "quegan": { + "label": "Quegan", + "tooltip": "Apply Quegan multi-temporal filter." + }, + "rabasar": { + "label": "RABASAR", + "tooltip": "Apply RABASAR multi-temporal filter." + } + }, + "minObservations": { + "label": "Min observations", + "value": "{value} observation(s)" } + }, + "title": "Options", + "tooltip": "Configure mosaic options" + } + }, + "preview": { + "description": "Preview of radar mosaic, generated on the fly by EE.", + "label": "Radar mosaic" + }, + "tabPlaceholder": "Radar_mosaic" + }, + "baytsAlerts": { + "create": "BAYTS Alerts", + "description": "Generate forest change alerts using Sentinel-1 time-series and probabilistic change detection (based on the RADD processing pipeline).", + "imageLayerForm": { + "mosaicType": { + "label": "Mosaic", + "latest": { + "label": "Latest", + "tooltip": "Render a composite of the latest available imagery in the period" + }, + "median": { + "label": "Median", + "tooltip": "Render a median composite of the period" + } }, - "none": "You have no background tasks.", - "copyToClipboard": { - "tooltip": "Copy to clipboard", - "success": "Copied to clipboard" + "visualizationType": { + "alerts": { + "label": "Alerts", + "tooltip": "Render alerts" + }, + "first": { + "label": "First", + "tooltip": "Render a composite of the first value in the monitoring period" + }, + "label": "Type", + "last": { + "label": "Last", + "tooltip": "Render a composite of the last value in the monitoring period" + } }, - "info": { - "tooltip": "View task details" + "previouslyConfirmed": { + "include": { + "label": "Include", + "tooltip": "Include alerts confirmed before the monitoring period" + }, + "exclude": { + "label": "Exclude", + "tooltip": "Exclude alerts confirmed before the monitoring period" + }, + "label": "Previously confirmed" }, - "details": { - "id": "Task ID", - "status": "Status", - "recipeName": "Recipe Name", - "recipeType": "Recipe Type", - "recipeId": "Recipe ID", - "creationTime": "Start Time", - "updateTime": "Last Update", - "duration": "Duration", - "description": "Description", - "workspacePath" : "Output path", - "filenamePrefix" : "Filename prefix", - "actualWorkspacePath" : "Actual workspace path", - "section" :{ - "status" : "Status", - "conf" : "Recipe configuration", - "location" : "Location", - "progress" : "Progress" - }, - "sharing" : { - "label" : "Access", - "PRIVATE" : "Private", - "PUBLIC" : "Public" - }, - "destination" : { - "label" : "Destination", - "SEPAL" : "SEPAL workspace", - "GEE" : "Google Earth Engine asset", - "DRIVE" : "Google Drive" - }, - "recipeTypeNames" : { - "MOSAIC": "Optical Mosaic", - "RADAR_MOSAIC": "Radar Mosaic", - "PLANET_MOSAIC": "Planet Mosaic", - "TIME_SERIES": "Time Series", - "CCDC": "CCDC", - "CCDC_SLICE": "CCDC Slice", - "CHANGE_ALERTS": "Change Alerts", - "BAYTS_HISTORICAL": "BAYTS Historical", - "BAYTS_ALERTS": "BAYTS Alerts", - "CLASSIFICATION": "Classification", - "UNSUPERVISED_CLASSIFICATION": "Unsupervised Classification", - "REGRESSION": "Regression", - "CLASS_CHANGE": "Class Change", - "INDEX_CHANGE": "Index Change", - "STACK": "Stack", - "BAND_MATH": "Band Math", - "REMAPPING": "Remapping", - "PHENOLOGY": "Phenology", - "MASKING": "Masking", - "ASSET_MOSAIC": "Asset Mosaic", - "RECIPE_REF": "Recipe Reference", - "ASSET": "Asset" + "minConfidence": { + "all": { + "label": "All", + "tooltip": "Show all alerts independent on their confidence" + }, + "low": { + "label": "Low", + "tooltip": "Show alerts with low or high confidence" + }, + "high": { + "label": "High", + "tooltip": "Show only alerts with high confidence" + }, + "label": "Min confidence" + } + }, + "layers": { + "imageLayer": { + "changes": "Changes", + "dates": "Dates", + "observations": "Observations" + } + }, + "panel": { + "date": { + "button": "DAT", + "form": { + "durationUnit": { + "placeholder": "Unit...", + "DAYS": "Days", + "MONTHS": "Months", + "WEEKS": "Weeks" + }, + "monitoring": { + "duration": { + "label": "Monitoring duration", + "placeholder": "Duration..." + } + }, + "monitoringEnd": { + "label": "Monitoring end", + "tooltip": "The date when monitoring ends. You typically set this to today's date" } + }, + "title": "Date range", + "tooltip": "Specify date range to get alerts for" }, - "remove": { - "tooltip": "Remove task" - }, - "removeAll": { - "label": "Clear inactive tasks", - "tooltip": "Remove all completed, failed, or stopped tasks" + "options": { + "button": "OPT", + "form": { + "highConfidenceThreshold": { + "label": "High-conf threshold", + "tooltip": "The needed change probability for a high-confidence alert" + }, + "lowConfidenceThreshold": { + "label": "Low-conf threshold", + "tooltip": "The needed change probability for a low-confidence alert", + "tooLarge": "Must be smaller than the high-conf threshold" + }, + "minChangeProbability": { + "label": "Min change prob", + "tooltip": "Min probability to consider an observation a potential change" + }, + "minNonForestProbability": { + "label": "Min non-forest prob", + "tooltip": "Min probability to consider an observation to potentially be non-forest" + }, + "maxDays": { + "label": "Max days", + "tooltip": "The max number of days before a lower confidence get unflagged" + }, + "normalization": { + "label": "Normalization", + "tooltip": "Enable or disable image normalization. When enabled, less false changes, due to seasonality, will be detected.", + "disabled": { + "label": "Disabled", + "tooltip": "Disable image normalization" + }, + "enabled": { + "label": "Enabled", + "tooltip": "Enable image normalization" + } + }, + "previousAlertsAsset": { + "label": "Previous alerts asset", + "placeholder": "Previous alerts asset...", + "tooltip": "EE asset containing previous alerts as a starting point for the algorithm", + "missingBands": "The asset is missing required bands: {missingBands}" + }, + "sensitivityButton": { + "tooltip": "Determine how sensitive the algorithm should be to detect changes", + "low": { + "label": "Low", + "tooltip": "Use low detection sensitivity" + }, + "medium": { + "label": "Medium", + "tooltip": "Use medium detection sensitivity" + }, + "high": { + "label": "High", + "tooltip": "Use high detection sensitivity" + } + }, + "sensitivity": { + "label": "Detection sensitivy", + "tooltip": "Determine how sensitive the algorithm should be to detect changes. Medium detection sensitivity is 1. The bigger the number, the less sensitive to change the algorithm will be.", + "value": "Value: {value}" + }, + "wetlandMaskAsset": { + "label": "Wetland Mask Asset", + "placeholder": "Wetland mask...", + "tooltip": "When a wetland mask is specified, the algorith will treat these areas slightly different" + } + }, + "title": "BAYTS alert options", + "tooltip": "Configure algorithm parameters." }, - "stop": { - "tooltip": "Stop task" + "preprocess": { + "button": "PRC", + "title": "Pre-process", + "tooltip": "Configure imagery pre-processing" }, - "retrieve": { - "collection_to_asset": { - "progress": "Exported {completedTiles} of out of {totalTiles} tiles." - }, - "time_series_to_sepal": { - "assembling": "Assembling tile {currentTile} out of {totalTiles}...", - "creating_drive_folder": "Creating Google Drive Folders...", - "deleting_drive_folder": "Deleting Google Drive Folder.", - "progress": "Exported {currentTilePercent}% of tile {currentTile} out of {totalTiles}.", - "tiling": "Tiling AOI..." + "reference": { + "asset": { + "label": "Earth Engine Asset", + "title": "Earth Engine Asset", + "notBayTSHistorical": "Asset is not a BAYTS historical image or image collection" + }, + "button": "REF", + "form": { + "asset": { + "invalid": "Asset must be an exported BAYTS historical asset", + "label": "BAYTS historical asset", + "placeholder": "Enter BAYTS historical asset ID", + "required": "An asset ID must be specified" } + }, + "recipe": { + "label": "Saved SEPAL recipe", + "title": "Saved SEPAL recipe" + }, + "title": "Source", + "tooltip": "Select BAYTS historical asset or recipe to use as reference for alerts" + } + }, + "preview": { + "description": "Preview of change alerts, generated on the fly by EE.", + "error": "Failed to generate preview", + "initializing": "Loading preview...", + "label": "Change alerts", + "loading": "Loading tiles: {pending}", + "tilesFailed": "{failed} failed" + }, + "reference": { + "asset": { + "loadError": "Failed to load asset" }, - "status": { - "canceled": "Stopped", - "canceling": "Stopping", - "completed": "Completed!", - "EEException": "Earth Engine: {error}", - "executing": "Executing...", - "failed": "{error}", - "initializing": "Initializing..." + "recipe": { + "loadError": "Failed to load recipe" } + }, + "tabPlaceholder": "BAYTS_alerts" }, - "terminal": { - "server": { - "error": "Failed to start terminal" + "baytsHistorical": { + "create": "BAYTS Historical Layer", + "description": "Create a Sentinel-1 historical layer as reference for BAYTS alerts.", + "panel": { + "dates": { + "button": "DAT", + "form": { + "fromDate": { + "label": "From date", + "malformed": "Invalid date" + }, + "toDate": { + "label": "To date", + "malformed": "Invalid date" + } + }, + "title": "Date range", + "tooltip": "Specify date range" + }, + "options": { + "button": "OPT", + "form": { + "geometricCorrection": { + "ellipsoid": { + "label": "Ellipsoid", + "tooltip": "Ellipsoid correction (gamma0 correction) is applied." + }, + "label": "Geometric correction", + "none": { + "label": "None", + "tooltip": "No geometric correction is applied." + }, + "terrain": { + "label": "Terrain", + "tooltip": "Terrain correction is applied." + } + }, + "orbits": { + "ascending": { + "label": "Ascending", + "tooltip": "Include scenes from ascending orbits." + }, + "descending": { + "label": "Descending", + "tooltip": "Include scenes from descending orbits." + }, + "label": "Orbits" + }, + "outlierRemoval": { + "aggressive": { + "label": "Aggressive", + "tooltip": "Remove more outliers." + }, + "label": "Outlier removal", + "moderate": { + "label": "Moderate", + "tooltip": "Remove outliers." + }, + "none": { + "label": "None", + "tooltip": "Don't remove outliers." + } + }, + "mask": { + "label": "Border noise correction", + "sides": { + "label": "Sides", + "tooltip": "Mask sides of slices." + }, + "firstLast": { + "label": "First/Last", + "tooltip": "Mask the beginning of the first slice and the end of the last slice." + } + }, + "maskOptions": { + "title": "Border noise correction options", + "minAngle": { + "label": "Min angle" + }, + "maxAngle": { + "label": "Max angle" + } + }, + "spatialSpeckleFilter": { + "label": "Spatial speckle filter", + "none": { + "label": "None", + "tooltip": "Don't apply any speckle filter." + }, + "boxcar": { + "label": "Boxcar", + "tooltip": "Boxcar filtering is applied." + }, + "gammaMap": { + "label": "Gamma Map", + "tooltip": "Gamma Map filtering is applied." + }, + "lee": { + "label": "Lee", + "tooltip": "Lee filtering is applied." + }, + "refinedLee": { + "label": "Refined Lee", + "tooltip": "Refined Lee filtering is applied. This can be very slow, but often gives good results." + }, + "leeSigma": { + "label": "Lee Sigma", + "tooltip": "Lee Sigma filtering is applied." + }, + "snic": { + "label": "SNIC", + "tooltip": "Simple Non-Iterative Clustering is used to filter out speckle." + } + }, + "spatialSpeckleFilterOptions": { + "kernelSize": { + "label": "Kernel size" + }, + "sigma": { + "label": "Sigma" + }, + "strongScatterers": { + "retain": { + "label": "Retain", + "tooltip": "Retain original value for strong scatterers. They don't tend to include much speckle and some speckle filtering algorithms tends to blur them." + }, + "filter": { + "label": "Filter", + "tooltip": "Apply spatial speckle filter also to strong scatterers. It can cause some speckle filtering algorithms to blur them." + }, + "label": "Strong scatterers" + }, + "strongScattererValue1": { + "label": "VV threshold", + "tooltip": "The threshold to consider VV a strong scatterer when performing Lee-Sigma spatial speckle filtering." + }, + "strongScattererValue2": { + "label": "VH threshold", + "tooltip": "The threshold to consider VH a strong scatterer when performing Lee-Sigma spatial speckle filtering." + }, + "snicSize": { + "label": "Seed spacing", + "tooltip": "The superpixel seed location spacing, in pixels. If 'seeds' image is provided, no grid is produced." + }, + "snicCompactness": { + "label": "Compactness", + "tooltip": "Compactness factor. Larger values cause clusters to be more compact (square). Setting this to 0 disables spatial distance weighting.", + "value": "{value}" + }, + "title": "Spatial speckle filter options" + }, + "multitemporalSpeckleFilterOptions": { + "numberOfImages": { + "label": "Number of images", + "value": "{value} images" + }, + "title": "Multitemporal speckle filter options" + }, + "multitemporalSpeckleFilter": { + "label": "Multitemporal speckle filter", + "none": { + "label": "None", + "tooltip": "Don't apply any multitemporal speckle filter." + }, + "quegan": { + "label": "Quegan", + "tooltip": "Apply Quegan multi-temporal filter." + }, + "rabasar": { + "label": "RABASAR", + "tooltip": "Apply RABASAR multi-temporal filter." + } + }, + "minObservations": { + "label": "Min observations", + "value": "{value} observation(s)" + } + }, + "title": "Options", + "tooltip": "Configure BAYTS historical layer options" } + }, + "preview": { + "description": "Preview of BAYTS historical layer, generated on the fly by EE.", + "label": "BAYTS historical" + }, + "tabPlaceholder": "BAYTS_historical" }, - "unauthorized": { - "warning": "You are not logged in. Perhaps you have been inactive too long?" - }, - "user": { - "changePassword": { - "form": { - "confirmPassword": { - "label": "New password (confirm)", - "required": "New password is required" + "phenology": { + "create": "Land surface phenology", + "description": "Create land surface phenology.", + "panel": { + "dates": { + "button": "DAT", + "form": { + "fromYear": { + "label": "Start year", + "tooltip": "The first year to include (inclusive)" + }, + "toYear": { + "beforeFromYear": "Cannot be before start year", + "label": "End year", + "tooltip": "The final year to include (inclusive)" + } + }, + "title": "Dates", + "tooltip": "Specify dates" + }, + "preprocess": { + "button": "PRC", + "title": "Pre-process", + "tooltip": "Configure imagery pre-processing" + }, + "sources": { + "button": "SRC", + "classificationLoadError": "Failed to load classification: {error}", + "form": { + "classification": { + "label": "Classification", + "placeholder": "Select a classification...", + "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." + }, + "band": { + "label": "Analysis band" + }, + "cloudPercentageThreshold": { + "label": "Max cloud cover %", + "value": "{value}%", + "tooltip": "Filter out scenes with higher cloud cover higher than this threshold" + }, + "dataSets": { + "label": "Data sets", + "optical": { + "label": "Optical" + }, + "options": { + "LANDSAT_7": { + "label": "L7", + "tooltip": "Landsat 7" }, - "newPassword": { - "invalid": "Passwords must be at least 12 characters long", - "label": "New password", - "notMatching": "Passwords are not matching", - "required": "New password is required" + "LANDSAT_7_T2": { + "label": "L7 T2", + "tooltip": "Landsat 7 Tier 2" }, - "oldPassword": { - "incorrect": "Incorrect password", - "label": "Current password", - "required": "Current password is required" + "LANDSAT_8": { + "label": "L8", + "tooltip": "Landsat 8" + }, + "LANDSAT_8_T2": { + "label": "L8 T2", + "tooltip": "Landsat 8 Tier 2" + }, + "LANDSAT_9": { + "label": "L9", + "tooltip": "Landsat 9" + }, + "LANDSAT_9_T2": { + "label": "L9 T2", + "tooltip": "Landsat 9 Tier 2" + }, + "LANDSAT_TM": { + "label": "L4-5", + "tooltip": "Landsat 4-5" + }, + "LANDSAT_TM_T2": { + "label": "L4-5 T2", + "tooltip": "Landsat 4-5 Tier 2" + }, + "SENTINEL_1": { + "label": "S1", + "tooltip": "Sentinel 1 A+B" + }, + "SENTINEL_2": { + "label": "S2", + "tooltip": "Sentinel 2 A+B" } - }, - "label": "Password", - "success": "Password has been changed.", - "title": "Change password", - "updating": "Updating..." + }, + "radar": { + "label": "Radar" + } + }, + "dataSetType": { + "label": "Type" + }, + "dataSetTypes": { + "OPTICAL": "Optical", + "PLANET": "Planet", + "RADAR": "Radar" + }, + "required": "A data source must be selected" + }, + "title": "Sources", + "tooltip": "Select image source" + } + }, + "preview": { + "description": "Preview of land surface phenology, generated on the fly by EE.", + "label": "Land surface phenology" + }, + "tabPlaceholder": "Phenology" + }, + "projects": { + "title": "Projects", + "label": "Projects", + "tooltip": "Manage projects", + "noProjects": "No projects", + "selected": "Currently selected", + "add": "Add" + }, + "project": { + "title": "Project", + "description": "{count} {count, plural, one {recipe} other {recipes}}", + "form": { + "name.label": "Name", + "name.required": "Name is required", + "name.unique": "Project name is already in use" + }, + "edit": { + "tooltip": "Edit project" + }, + "remove": { + "tooltip": "Remove project", + "title": "Remove project", + "confirm": "All recipes belonging to this project will be removed as well.", + "error": "Cannot remove project" + }, + "update": { + "error": "Cannot update project" + }, + "noProject": "no project", + "noProjectOption": "[no project]" + }, + "recipe": { + "lastUpdate": "Last update", + "loading": "Loading recipes", + "loadingError": "Failed to load recipes. Please reload page to try again.", + "name": "Name", + "newRecipe": { + "title": "Create recipe", + "tooltip": "Create a new SEPAL recipe", + "search.placeholder": "Search recipe types", + "tags": { + "ALL": "All", + "OPTICAL": "Optical", + "RADAR": "Radar", + "MOSAIC": "Mosaic", + "CHANGE": "Change", + "TIME_SERIES": "Time series", + "ALERTS": "Alerts" + } + }, + "edit": { + "label": "Edit" + }, + "select": { + "label": "Select", + "tooltip": { + "select": "Select all recipes", + "deselect": "Deselect all recipes" + } + }, + "remove": { + "label": "Remove", + "title": "Remove recipes", + "tooltip": "Remove selected recipes", + "confirm": "Remove {count} {count, plural, one {recipe} other {recipes}}?" + }, + "move": { + "label": "Move", + "title": "Move recipes", + "tooltip": "Move selected recipes to a project", + "destinationProject": "Destination project", + "confirm": "Move {count} {count, plural, one {recipe} other {recipes}} to project {project}?" + }, + "export": { + "success": "Recipe has been exported.", + "error": "Could not export recipe." + } + }, + "remapping": { + "create": "Remapping", + "description": "Remap categorical and continuous image bands into new categories.", + "layers": { + "imageLayer": { + "preSets": "Pre-sets" + } + }, + "panel": { + "legend": { + "button": "LEG", + "title": "Legend", + "tooltip": "Setup legend to remap imagery to", + "import": "Import..." }, - "googleAccount": { - "form": { - "projectId": { - "label": "Google Cloud project", - "updated": "Google Cloud project has been updated" - } - }, - "connect": { - "label": "Connect" - }, - "connected": { - "info": "You have full access to SEPAL. If you disconnect your Google account from SEPAL, some features (like exporting to Earth Engine assets) will not be available.", - "label": "Connected", - "title": "Your SEPAL account is connected to your Google account" - }, - "disconnect": { - "label": "Disconnect", - "warning": { - "message": "Disconnecting your Google account would cause termination of any task started while connected. Tasks currently running: {taskCount}.", - "title": "Warning" - } - }, - "disconnected": { - "info": "You have limited access to SEPAL. If you connect your Google account to SEPAL, all features (including exporting to Earth Engine assets) will be available.", - "label": "Disconnected", - "success": "SEPAL is now disconnected from your Google account.", - "projectNeeded": "SEPAL has been disconnected from your Google account, as Google Earth Engine users are now required to use a Cloud Project.|Please reconnect and select a Cloud Project.|If you need to create one, please login to the Earth Engine Code Editor, and click on \"Register a new Cloud Project\" from the user settings.", - "title": "Your SEPAL account is not connected to your Google account" - }, - "missingScopes": { - "title": "Google Account reconnection required", - "message": "The permissions you have previously authorized on your Google account don't match with the ones currently required by SEPAL. Please, reconnect your Google account." - }, - "revoked": { - "title": "Google Account reconnection required", - "message": "SEPAL is unable to use your Google account. Please, reconnect your Google account." - }, - "unavailable": { - "title": "Earth Engine is not available", - "message": "Please make sure your Google Cloud Project has been registered." - }, - "unspecifiedError": { - "title": "Earth Engine is not available", - "message": "We are currently unable to connect to Google Earth Engine." - }, - "label": "Google account", - "title": "Google account" + "mapping": { + "button": { + "label": "MAP", + "tooltip": "Map input imagery to legend entries" + }, + "title": "Mapping" + } + }, + "tabPlaceholder": "Remapping" + }, + "retrieve": { + "apply": "Retrieve", + "form": { + "assetId": { + "label": "Asset ID", + "placeholder": "Enter Asset ID...", + "tooltip": "The Asset ID your image should be exported to." }, - "report": { - "resources": { - "max": "Max", - "instanceSpending": "Instance spending", - "storageSpending": "Storage spending", - "storageSpace": "Storage space", - "quota": "Quota", - "title": "Resources", - "used": "Used", - "used%": "Used" - }, - "sessions": { - "cost": "Cost", - "earliestTimeoutHours": "Keep alive", - "hourlyCost": "Hourly cost", - "instanceDescription": "Instance description", - "instanceType": "Instance type", - "noSessions": "No active sessions", - "time": "Date/time started", - "title": "Sessions" - }, - "title": "User report", - "usage": "Using {percentage}% of allocated quota", - "updateQuota": "Request additional resources" + "assetType": { + "label": "Asset type", + "Image": { + "label": "Image", + "tooltip": "Export recipe as a single image asset." + }, + "ImageCollection": { + "label": "Image collection", + "tooltip": "Export recipe tiled into an image collection. This is good for larger exports as it gives you feedback on the progress, and will allow you to resume failed or canceled export tasks." + } }, - "quotaUpdate": { - "title": "Request additional resources", - "info1": "Please propose suitable values for monthly spending and storage space, along with a justification for the requested change.", - "info2": "SEPAL administrators will review your request.", - "form": { - "created": "Created", - "modified": "Modified", - "message": { - "label": "Message", - "required": "Message is required" - } - }, - "update": { - "success": "Quota update request has been submitted.", - "error": "Quota update request error." - }, - "info": "Your SEPAL budget is not sufficient for this functionality. Please request additional resources using the button highlighted below." + "bands": { + "atLeastOne": "At least one band must be retrieved", + "label": "Bands to retrieve" + }, + "crs": { + "label": "CRS", + "placeholder": "Enter CRS...", + "tooltip": "CRS to use for the exported image" + }, + "crsTransform": { + "label": "CRS transform", + "placeholder": "Enter transform...", + "tooltip": "Optionally specity affine transform to use for the exported image" + }, + "destination": { + "GEE": "Google Earth Engine asset", + "label": "Destination", + "required": "A destination must be selected", + "SEPAL": "SEPAL workspace", + "DRIVE": "Google Drive" + }, + "fileDimensionsMultiple": { + "label": "File dimensions multiple", + "placeholder": "Multiples...", + "suffix": "multiples", + "tooLarge": "Multiple * shard size must be less than or equal to 131072", + "tooltip": "Multiples of the shard size to set the file dimentions to, the size to split each export into. E.g. for a shard size of 256 and a multiple of 10, the exported image will be downloaded it images with the maximum size of 2560x2560 pixels." }, - "status": { - "label": "Status", - "UNKNOWN": "Unknown", - "ACTIVE": "Operational", - "LOCKED": "Locked", - "PENDING": "Pending" + "scale": { + "label": "Scale", + "placeholder": "Scale...", + "suffix": "meters" }, - "activity": { - "label": "Activity", - "ACTIVE": "Active", - "INACTIVE_LOW": "Inactive/Low", - "INACTIVE_HIGH": "Inactive/High", - "INACTIVE_UNKNOWN": "Checking", - "NOTIFIED": "Notified", - "PURGED": "Purged" + "shardSize": { + "label": "Shard size", + "placeholder": "Size...", + "suffix": "pixels", + "tooltip": "The size Earth Engine should break down the image into for processing. If you're running out of memory, try with a smaller value." }, - "role": { - "admin": "Admin", - "user": "User" + "sharing": { + "label": "Sharing", + "PRIVATE": { + "label": "Private", + "tooltip": "Only you can access this asset" + }, + "PUBLIC": { + "label": "Public", + "tooltip": "Anyone can access this asset" + } }, - "privacyPolicy": { - "title": "Privacy Policy", - "accept": { - "label": "Accept", - "error": "Could not update user" + "task": { + "GEE": "Export recipe: {name} as a Google Earth Engine asset", + "SEPAL": "Retrieve recipe: {name} to SEPAL workspace", + "DRIVE": "Retrieve recipe: {name} to Google Drive" + }, + "tileSize": { + "label": "Tile size", + "placeholder": "Size...", + "suffix": "degrees", + "tooltip": "The size to tile the images into. If you're running out of memory, try with a smaller value." + }, + "workspacePath": { + "label": "Workspace path", + "placeholder": "Enter workspace path...", + "tooltip": "Relative path in your workspace home directory where the image should be retrieved to." + }, + "filenamePrefix": { + "label": "Filename prefix", + "placeholder": "Enter filename prefix...", + "tooltip": "Custom prefix that will be added to exported files" + } + }, + "title": "Retrieve", + "tooltip": "Retrieve recipe" + }, + "saveRecipe": { + "error": "Sorry, but something went wrong when trying to save your recipe. If you close the window/tab, you will lose any changes you've made since last successful save. To make sure you have some record the data within this recipe, you might want to export it. Go to the top right recipe menu and select \"Export recipe\".", + "form": { + "name": { + "label": "Name", + "required": "Recipe name is required" + } + }, + "title": "Save recipe" + }, + "timeSeries": { + "bands": { + "count": "Count" + }, + "chartPixel": { + "loadObservations": { + "error": "Failed to load observations", + "noData": "Pixel has no observations" + } + }, + "create": "Time series", + "description": "Download time series of optical and radar data to SEPAL.", + "panel": { + "dates": { + "button": "DAT", + "form": { + "endDate": { + "label": "End date", + "malformed": "Invalid date", + "required": "Date is required", + "tooltip": "The last date of the time-series, exclusive." + }, + "startDate": { + "beforeEnd": "Must be before end", + "label": "Start date", + "malformed": "Invalid date", + "required": "Date is required", + "tooltip": "The first date of the time-series, inclusive." } + }, + "title": "Dates", + "tooltip": "Specify date range" }, - "userDetails": { - "form": { - "email": { - "label": "Email", - "required": "Email is required", - "unique": "There is another user with this email" - }, - "emailNotifications": { - "disabled.label": "Disabled", - "enabled.label": "Enabled", - "label": "Email notifications", - "tooltip": "Enable or disable optional email notifications (e.g. task completed, task failed, etc.)" - }, - "manualMapRendering": { - "disabled.label": "Disabled", - "enabled.label": "Enabled", - "label": "Manual map rendering", - "tooltip": "Enable or disable manual map rendering" + "preprocess": { + "button": "PRC", + "title": "Pre-process", + "tooltip": "Configure imagery pre-processing" + }, + "sources": { + "button": "SRC", + "classificationLoadError": "Failed to load classification: {error}", + "form": { + "classification": { + "label": "Classification", + "placeholder": "Select a classification...", + "tooltip": "(Optional) Select a classification recipe and apply the classifier to every acquisition in the time-series." + }, + "dataSets": { + "optical": { + "label": "Optical" + }, + "options": { + "LANDSAT_7": { + "label": "L7", + "tooltip": "Landsat 7" }, - "lastUpdate": { - "label": "Last update" + "LANDSAT_7_T2": { + "label": "L7 T2", + "tooltip": "Landsat 7 Tier 2" }, - "monthlyBudget": { - "instanceSpending": { - "atLeast1": "Instance spending must be at least 1", - "label": "Instance spending", - "required:": "Instance spending is required" - }, - "storageSpending": { - "atLeast1": "Storage spending must be at least 1", - "label": "Storage spending", - "required:": "Storage spending is required" - }, - "storageQuota": { - "atLeast1": "Storage space must be at least 1", - "label": "Storage space", - "required:": "Storage space is required" - } + "LANDSAT_8": { + "label": "L8", + "tooltip": "Landsat 8" }, - "monthlyLimits": { - "label": "Limits", - "tooltip": "Maximum monthly spending (US$/month) for instances and storage, and maximum disk space (GB) allocated for storage." + "LANDSAT_8_T2": { + "label": "L8 T2", + "tooltip": "Landsat 8 Tier 2" }, - "budgetUpdateRequest": { - "label": "Budget update request" + "LANDSAT_9": { + "label": "L9", + "tooltip": "Landsat 9" }, - "name": { - "label": "Name", - "required": "Name is required" + "LANDSAT_9_T2": { + "label": "L9 T2", + "tooltip": "Landsat 9 Tier 2" }, - "organization": { - "label": "Organization", - "required": "Organization is required" + "LANDSAT_TM": { + "label": "L4-5", + "tooltip": "Landsat 4-5" }, - "intendedUse": { - "label": "Intended use", - "required": "Intended use is required" + "LANDSAT_TM_T2": { + "label": "L4-5 T2", + "tooltip": "Landsat 4-5 Tier 2" }, - "username": { - "format": "Username must start with a letter and can only contain letters and digits", - "label": "Username", - "required": "Username is required", - "unique": "There is another user with this username" + "SENTINEL_1": { + "label": "S1", + "tooltip": "Sentinel 1 A+B" }, - "password": { - "label": "Password" + "SENTINEL_2": { + "label": "S2", + "tooltip": "Sentinel 2 A+B" } - }, - "resetPassword": { - "label": "Reset", - "tooltip": "Send a password reset email to the user.", - "message": "The user will receive an email for setting a new password. Until that, the current password will still be valid." - }, - "lock": { - "label": "Lock", - "tooltip": "Lock the user", - "message": "The user will be locked and not be able to access SEPAL from now on.|Active sessions will be terminated, but all user data will be retained.", - "error": "Could not lock user.", - "success": "User has been locked." - }, - "unlock": { - "label": "Unlock", - "tooltip": "Unlock the user", - "message": "The user will be unlocked.|A password reset email will be sent to the user for reactivating the SEPAL account.", - "error": "Could not unlock user.", - "success": "User has been unlocked." - }, - "switchingToSEPALGoogleAccount": "Switching to SEPAL's Google Account", - "title": "User details", - "update": { - "error": "Could not update user." - }, - "confirmation": { - "message": "User role will be changed to \"{role}\"" - } + }, + "radar": { + "label": "Radar" + } + }, + "required": "A data source must be selected" + }, + "title": "Sources", + "tooltip": "Select image source" + } + }, + "tabPlaceholder": "Time_series" + } + }, + "sources": { + "dataSets": { + "LANDSAT_7": { + "label": "L7", + "tooltip": "Landsat 7" + }, + "LANDSAT_7_T2": { + "label": "L7 T2", + "tooltip": "Landsat 7 Tier 2" + }, + "LANDSAT_8": { + "label": "L8", + "tooltip": "Landsat 8" + }, + "LANDSAT_8_T2": { + "label": "L8 T2", + "tooltip": "Landsat 8 Tier 2" + }, + "LANDSAT_9": { + "label": "L9", + "tooltip": "Landsat 9" + }, + "LANDSAT_9_T2": { + "label": "L9 T2", + "tooltip": "Landsat 9 Tier 2" + }, + "LANDSAT_TM": { + "label": "L4-5", + "tooltip": "Landsat 4-5" + }, + "LANDSAT_TM_T2": { + "label": "L4-5 T2", + "tooltip": "Landsat 4-5 Tier 2" + }, + "SENTINEL_1": { + "label": "S1", + "tooltip": "Sentinel 1 A+B" + }, + "SENTINEL_2": { + "label": "S2", + "tooltip": "Sentinel 2 A+B" + } + }, + "optical": { + "label": "Optical" + }, + "radar": { + "label": "Radar" + } + }, + "tasks": { + "download": { + "progress": "Downloaded {files} {files, plural, one {file} other {files}} / {bytes} left" + }, + "ee": { + "export": { + "asset": { + "createFolder": "Create asset folder {assetId}", + "delete": "Delete asset {assetId}", + "prepareImageCollection": "Prepare image collection {assetId}", + "startExport": "Start export of {tileCount} tiles", + "sharing": "Sharing asset {assetId}", + "tilingImage": "Tiling image" }, - "userSession": { - "form": { - "keepAlive": { - "info": "Keep instance alive for at least {keepAliveUntil}.", - "label": "Keep alive" - }, - "name": { - "label": "Instance name" - } - }, - "stop": { - "error": "Could not stop session.", - "label": "Stop", - "message": "Stop session?", - "success": "Session stopped.", - "tooltip": "Stop session (cannot be resumed)." - }, - "suspend": { - "error": "Could not suspend session.", - "label": "Suspend", - "success": "Session has been suspended.", - "tooltip": "Suspend session. Suspended sessions can be resumed." - }, - "update": { - "error": "Could not update session.", - "success": "Session has been updated.", - "tooltip": "Edit session." - } + "pending": "Submitting export task to Google Earth Engine", + "ready": "Waiting for Google Earth Engine to start export", + "running": "Google Earth Engine is exporting" + } + }, + "none": "You have no background tasks.", + "copyToClipboard": { + "tooltip": "Copy to clipboard", + "success": "Copied to clipboard" + }, + "info": { + "tooltip": "View task details" + }, + "details": { + "id": "Task ID", + "status": "Status", + "recipeName": "Recipe Name", + "recipeType": "Recipe Type", + "recipeId": "Recipe ID", + "creationTime": "Start Time", + "updateTime": "Last Update", + "duration": "Duration", + "description": "Description", + "workspacePath": "Output path", + "filenamePrefix": "Filename prefix", + "actualWorkspacePath": "Actual workspace path", + "section": { + "status": "Status", + "conf": "Recipe configuration", + "location": "Location", + "progress": "Progress" + }, + "sharing": { + "label": "Access", + "PRIVATE": "Private", + "PUBLIC": "Public" + }, + "destination": { + "label": "Destination", + "SEPAL": "SEPAL workspace", + "GEE": "Google Earth Engine asset", + "DRIVE": "Google Drive" + }, + "recipeTypeNames": { + "MOSAIC": "Optical Mosaic", + "RADAR_MOSAIC": "Radar Mosaic", + "PLANET_MOSAIC": "Planet Mosaic", + "TIME_SERIES": "Time Series", + "CCDC": "CCDC", + "CCDC_SLICE": "CCDC Slice", + "CHANGE_ALERTS": "Change Alerts", + "BAYTS_HISTORICAL": "BAYTS Historical", + "BAYTS_ALERTS": "BAYTS Alerts", + "CLASSIFICATION": "Classification", + "UNSUPERVISED_CLASSIFICATION": "Unsupervised Classification", + "REGRESSION": "Regression", + "CLASS_CHANGE": "Class Change", + "INDEX_CHANGE": "Index Change", + "STACK": "Stack", + "BAND_MATH": "Band Math", + "REMAPPING": "Remapping", + "PHENOLOGY": "Phenology", + "MASKING": "Masking", + "ASSET_MOSAIC": "Asset Mosaic", + "RECIPE_REF": "Recipe Reference", + "ASSET": "Asset" + } + }, + "remove": { + "tooltip": "Remove task" + }, + "removeAll": { + "label": "Clear inactive tasks", + "tooltip": "Remove all completed, failed, or stopped tasks" + }, + "stop": { + "tooltip": "Stop task" + }, + "retrieve": { + "collection_to_asset": { + "progress": "Exported {completedTiles} of out of {totalTiles} tiles." + }, + "time_series_to_sepal": { + "assembling": "Assembling tile {currentTile} out of {totalTiles}...", + "creating_drive_folder": "Creating Google Drive Folders...", + "deleting_drive_folder": "Deleting Google Drive Folder.", + "progress": "Exported {currentTilePercent}% of tile {currentTile} out of {totalTiles}.", + "tiling": "Tiling AOI..." + } + }, + "status": { + "canceled": "Stopped", + "canceling": "Stopping", + "completed": "Completed!", + "EEException": "Earth Engine: {error}", + "executing": "Executing...", + "failed": "{error}", + "initializing": "Initializing..." + } + }, + "terminal": { + "server": { + "error": "Failed to start terminal" + } + }, + "unauthorized": { + "warning": "You are not logged in. Perhaps you have been inactive too long?" + }, + "user": { + "changePassword": { + "form": { + "confirmPassword": { + "label": "New password (confirm)", + "required": "New password is required" + }, + "newPassword": { + "invalid": "Passwords must be at least 12 characters long", + "label": "New password", + "notMatching": "Passwords are not matching", + "required": "New password is required" + }, + "oldPassword": { + "incorrect": "Incorrect password", + "label": "Current password", + "required": "Current password is required" } + }, + "label": "Password", + "success": "Password has been changed.", + "title": "Change password", + "updating": "Updating..." }, - "userMessage": { - "form": { - "contents": { - "label": "Contents", - "required": "Contents is required" - }, - "subject": { - "label": "Subject", - "required": "Subject is required" - }, - "preview": { - "label": "Preview" - } + "googleAccount": { + "form": { + "projectId": { + "label": "Google Cloud project", + "updated": "Google Cloud project has been updated" + } + }, + "connect": { + "label": "Connect" + }, + "connected": { + "info": "You have full access to SEPAL. If you disconnect your Google account from SEPAL, some features (like exporting to Earth Engine assets) will not be available.", + "label": "Connected", + "title": "Your SEPAL account is connected to your Google account" + }, + "disconnect": { + "label": "Disconnect", + "warning": { + "message": "Disconnecting your Google account would cause termination of any task started while connected. Tasks currently running: {taskCount}.", + "title": "Warning" + } + }, + "disconnected": { + "info": "You have limited access to SEPAL. If you connect your Google account to SEPAL, all features (including exporting to Earth Engine assets) will be available.", + "label": "Disconnected", + "success": "SEPAL is now disconnected from your Google account.", + "projectNeeded": "SEPAL has been disconnected from your Google account, as Google Earth Engine users are now required to use a Cloud Project.|Please reconnect and select a Cloud Project.|If you need to create one, please login to the Earth Engine Code Editor, and click on \"Register a new Cloud Project\" from the user settings.", + "title": "Your SEPAL account is not connected to your Google account" + }, + "missingScopes": { + "title": "Google Account reconnection required", + "message": "The permissions you have previously authorized on your Google account don't match with the ones currently required by SEPAL. Please, reconnect your Google account." + }, + "revoked": { + "title": "Google Account reconnection required", + "message": "SEPAL is unable to use your Google account. Please, reconnect your Google account." + }, + "unavailable": { + "title": "Earth Engine is not available", + "message": "Please make sure your Google Cloud Project has been registered." + }, + "unspecifiedError": { + "title": "Earth Engine is not available", + "message": "We are currently unable to connect to Google Earth Engine." + }, + "label": "Google account", + "title": "Google account" + }, + "report": { + "resources": { + "max": "Max", + "instanceSpending": "Instance spending", + "storageSpending": "Storage spending", + "storageSpace": "Storage space", + "quota": "Quota", + "title": "Resources", + "used": "Used", + "used%": "Used" + }, + "sessions": { + "cost": "Cost", + "earliestTimeoutHours": "Keep alive", + "hourlyCost": "Hourly cost", + "instanceDescription": "Instance description", + "instanceType": "Instance type", + "noSessions": "No active sessions", + "time": "Date/time started", + "title": "Sessions" + }, + "title": "User report", + "usage": "Using {percentage}% of allocated quota", + "updateQuota": "Request additional resources" + }, + "quotaUpdate": { + "title": "Request additional resources", + "info1": "Please propose suitable values for monthly spending and storage space, along with a justification for the requested change.", + "info2": "SEPAL administrators will review your request.", + "form": { + "created": "Created", + "modified": "Modified", + "message": { + "label": "Message", + "required": "Message is required" + } + }, + "update": { + "success": "Quota update request has been submitted.", + "error": "Quota update request error." + }, + "info": "Your SEPAL budget is not sufficient for this functionality. Please request additional resources using the button highlighted below." + }, + "status": { + "label": "Status", + "UNKNOWN": "Unknown", + "ACTIVE": "Operational", + "LOCKED": "Locked", + "PENDING": "Pending" + }, + "activity": { + "label": "Activity", + "ACTIVE": "Active", + "INACTIVE_LOW": "Inactive/Low", + "INACTIVE_HIGH": "Inactive/High", + "INACTIVE_UNKNOWN": "Checking", + "NOTIFIED": "Notified", + "PURGED": "Purged" + }, + "role": { + "admin": "Admin", + "user": "User" + }, + "privacyPolicy": { + "title": "Privacy Policy", + "accept": { + "label": "Accept", + "error": "Could not update user" + } + }, + "userDetails": { + "form": { + "email": { + "label": "Email", + "required": "Email is required", + "unique": "There is another user with this email" }, - "publish": { - "error": "Cannot publish user message", - "success": "User message has been published" + "emailNotifications": { + "disabled.label": "Disabled", + "enabled.label": "Enabled", + "label": "Email notifications", + "tooltip": "Enable or disable optional email notifications (e.g. task completed, task failed, etc.)" }, - "remove": { - "error": "Cannot remove user message", - "success": "User message has been removed" + "manualMapRendering": { + "disabled.label": "Disabled", + "enabled.label": "Enabled", + "label": "Manual map rendering", + "tooltip": "Enable or disable manual map rendering" }, - "title": "User message", - "update": { - "error": "Cannot update user message", - "success": "User message has been updated" + "lastUpdate": { + "label": "Last update" }, - "updateState": { - "error": "Cannot update user message state", - "success": "User message state has been updated" + "monthlyBudget": { + "instanceSpending": { + "atLeast1": "Instance spending must be at least 1", + "label": "Instance spending", + "required:": "Instance spending is required" + }, + "storageSpending": { + "atLeast1": "Storage spending must be at least 1", + "label": "Storage spending", + "required:": "Storage spending is required" + }, + "storageQuota": { + "atLeast1": "Storage space must be at least 1", + "label": "Storage space", + "required:": "Storage space is required" + } + }, + "monthlyLimits": { + "label": "Limits", + "tooltip": "Maximum monthly spending (US$/month) for instances and storage, and maximum disk space (GB) allocated for storage." + }, + "budgetUpdateRequest": { + "label": "Budget update request" + }, + "name": { + "label": "Name", + "required": "Name is required" + }, + "organization": { + "label": "Organization", + "required": "Organization is required" + }, + "intendedUse": { + "label": "Intended use", + "required": "Intended use is required" + }, + "username": { + "format": "Username must start with a letter and can only contain letters and digits", + "label": "Username", + "required": "Username is required", + "unique": "There is another user with this username" + }, + "password": { + "label": "Password" } + }, + "resetPassword": { + "label": "Reset", + "tooltip": "Send a password reset email to the user.", + "message": "The user will receive an email for setting a new password. Until that, the current password will still be valid." + }, + "lock": { + "label": "Lock", + "tooltip": "Lock the user", + "message": "The user will be locked and not be able to access SEPAL from now on.|Active sessions will be terminated, but all user data will be retained.", + "error": "Could not lock user.", + "success": "User has been locked." + }, + "unlock": { + "label": "Unlock", + "tooltip": "Unlock the user", + "message": "The user will be unlocked.|A password reset email will be sent to the user for reactivating the SEPAL account.", + "error": "Could not unlock user.", + "success": "User has been unlocked." + }, + "switchingToSEPALGoogleAccount": "Switching to SEPAL's Google Account", + "title": "User details", + "update": { + "error": "Could not update user." + }, + "confirmation": { + "message": "User role will be changed to \"{role}\"" + } }, - "userMessages": { - "author": "Message by {author}", - "edit": "Edit message", - "noMessages": "There are no messages", - "post": "Post message", - "remove": "Remove message", - "state": { - "READ": "Mark as unread", - "UNREAD": "Mark as read" + "userSession": { + "form": { + "keepAlive": { + "info": "Keep instance alive for at least {keepAliveUntil}.", + "label": "Keep alive" }, - "title": "User messages" + "name": { + "label": "Instance name" + } + }, + "stop": { + "error": "Could not stop session.", + "label": "Stop", + "message": "Stop session?", + "success": "Session stopped.", + "tooltip": "Stop session (cannot be resumed)." + }, + "suspend": { + "error": "Could not suspend session.", + "label": "Suspend", + "success": "Session has been suspended.", + "tooltip": "Suspend session. Suspended sessions can be resumed." + }, + "update": { + "error": "Could not update session.", + "success": "Session has been updated.", + "tooltip": "Edit session." + } + } + }, + "userMessage": { + "form": { + "contents": { + "label": "Contents", + "required": "Contents is required" + }, + "subject": { + "label": "Subject", + "required": "Subject is required" + }, + "preview": { + "label": "Preview" + } }, - "users": { - "count": "{count} {count, plural, one {user} other {users}}, {lastMonthUserCount} updated in the last month", - "filter": { - "search": { - "placeholder": "Search users" - }, - "status": { - "active": { - "label": "Active" - }, - "ignore": { - "label": "All" - }, - "overbudget": { - "label": "Overbudget" - }, - "pending": { - "label": "Pending" - }, - "locked": { - "label": "Locked" - }, - "budgetUpdateRequest": { - "label": "Budget update" - } - }, - "activity": { - "active": { - "label": "Normal" - }, - "notified": { - "label": "Notified" - }, - "erased": { - "label": "Erased" - } - } + "publish": { + "error": "Cannot publish user message", + "success": "User message has been published" + }, + "remove": { + "error": "Cannot remove user message", + "success": "User message has been removed" + }, + "title": "User message", + "update": { + "error": "Cannot update user message", + "success": "User message has been updated" + }, + "updateState": { + "error": "Cannot update user message state", + "success": "User message state has been updated" + } + }, + "userMessages": { + "author": "Message by {author}", + "edit": "Edit message", + "noMessages": "There are no messages", + "post": "Post message", + "remove": "Remove message", + "state": { + "READ": "Mark as unread", + "UNREAD": "Mark as read" + }, + "title": "User messages" + }, + "users": { + "count": "{count} {count, plural, one {user} other {users}}, {lastMonthUserCount} updated in the last month", + "filter": { + "search": { + "placeholder": "Search users" + }, + "status": { + "active": { + "label": "Active" }, - "invite": { - "label": "Invite a new user" + "ignore": { + "label": "All" }, - "page": "Page {pageNumber} of {pageCount}" - }, - "widget": { - "assetDestination": { - "exists": { - "notReplaceable": "This asset already exists, but isn't an image or image collection, so you cannot overwrite it", - "replaceable": "This asset already exists, you have to confirm if you want to overwrite it or not" - }, - "loadError": "Unable to load asset", - "replace": { - "label": "Replace", - "tooltip": "Replace previous asset with this one." - }, - "resume": { - "label": "Resume", - "tooltip": "Keep existing images in the collection and only export missing ones." - }, - "strategy": { - "label": "Strategy" - } + "overbudget": { + "label": "Overbudget" }, - "workspaceDestination" : { - "taskPending": "A task is already pending in this folder. Please choose a different folder.", - "notEmpty": "This folder must be completely empty. Please choose a different folder or remove its contents.", - "loadError": "Unable to load the workspace. Please try again." + "pending": { + "label": "Pending" }, - "assetInput": { - "loadError": "Unable to load asset" + "locked": { + "label": "Locked" }, - "codeEditor": { - "info": "Press Ctrl+Space to autocomplete", - "help1": "Enter an arithmetic expression, using input imagery and earlier calculation steps as input.", - "help2": "A subset of the EE expressions are supported. Notably, you cannot assign variables to name bands, use indexes to refer to band, or use the `b()` syntax.", - "example": "Example: {example}", - "eeAutoComplete": { - "bandNames": { - "section": "Band names" - }, - "imagesAndCalculations": { - "section": "Images and calculations" - }, - "mathFunctions": { - "section": "Functions" - }, - "mathConstants": { - "section": "Mathematical constants" - } - }, - "eeLint": { - "undefinedVariable": "\"{variableName}\" is not defined. \n\nVariables must be the name of images, name of calculations declared before this function, math functions, or math constants.", - "invalidBand": "\"{imageName}\" doesn't contain band \"{bandName}\".", - "invalidBandCount": "Images must contain the same number of bands or only 1 band. Got 2 and 3. \"{imageName}\" contains {bandCount} bands while another image contains {expectedBandCount}.", - "invalidArgCount": "Function expect {expectedArgCount} argument, got {argCount}.", - "syntaxError": "Syntax error." - } + "budgetUpdateRequest": { + "label": "Budget update" + } + }, + "activity": { + "active": { + "label": "Normal" }, - "comboBox": { - "loading": "Loading..." + "notified": { + "label": "Notified" }, - "confirm": { - "title": "Confirmation required", - "label": "Confirm" + "erased": { + "label": "Erased" + } + } + }, + "invite": { + "label": "Invite a new user" + }, + "page": "Page {pageNumber} of {pageCount}" + }, + "widget": { + "assetDestination": { + "exists": { + "notReplaceable": "This asset already exists, but isn't an image or image collection, so you cannot overwrite it", + "replaceable": "This asset already exists, you have to confirm if you want to overwrite it or not" + }, + "loadError": "Unable to load asset", + "replace": { + "label": "Replace", + "tooltip": "Replace previous asset with this one." + }, + "resume": { + "label": "Resume", + "tooltip": "Keep existing images in the collection and only export missing ones." + }, + "strategy": { + "label": "Strategy" + } + }, + "workspaceDestination": { + "taskPending": "A task is already pending in this folder. Please choose a different folder.", + "notEmpty": "This folder must be completely empty. Please choose a different folder or remove its contents.", + "loadError": "Unable to load the workspace. Please try again." + }, + "assetInput": { + "loadError": "Unable to load asset" + }, + "codeEditor": { + "info": "Press Ctrl+Space to autocomplete", + "help1": "Enter an arithmetic expression, using input imagery and earlier calculation steps as input.", + "help2": "A subset of the EE expressions are supported. Notably, you cannot assign variables to name bands, use indexes to refer to band, or use the `b()` syntax.", + "example": "Example: {example}", + "eeAutoComplete": { + "bandNames": { + "section": "Band names" }, - "error": "Error", - "fileSelect": { - "multiple": { - "drop": "Drop file(s) here.", - "dropOrClick": "Drop file(s) here, or click to select file(s)", - "reject": "Cannot drop file(s). Wrong file type?" - }, - "single": { - "drop": "Drop file here", - "dropOrClick": "Drop file here, or click to select file", - "reject": "Cannot drop file. Wrong file type, or more than one file?" - } + "imagesAndCalculations": { + "section": "Images and calculations" }, - "imageConstraints": { - "bit": { - "label": "bit", - "tooltip": "Apply a bitmask to the band. This is typically used for QA bands" - }, - "band": { - "label": "Band", - "notSelected": "[No band selected]" - }, - "property": { - "label": "Property", - "notSelected": "[No property selected]" - }, - "image": { - "label": "Image", - "notSelected": "[No image selected]" - }, - "logicalAnd": { - "tooltip": "All constraints must be true" - }, - "logicalOr": { - "tooltip": "One of the constraints must be true" - }, - "operator": { - "equals": { - "label": "=" - }, - "greaterThan": { - "label": ">" - }, - "greaterThanOrEquals": { - "label": "≥" - }, - "label": "Operator", - "lessThan": { - "label": "<" - }, - "lessThanOrEquals": { - "label": "≤" - }, - "range": { - "label": "Range" - } - }, - "bitRange": { - "from": { - "label": "From bit" - }, - "to": { - "label": "To bit" - } - }, - "range": { - "from": { - "label": "From value" - }, - "to": { - "label": "To value" - } - }, - "value": { - "label": "Value" - }, - "inclusive": { - "label": "incl", - "tooltip": "Determine if value is inclusive or not" - } + "mathFunctions": { + "section": "Functions" }, - "legend": { - "edit": { - "tooltip": "Update legend" - }, - "editLegendPanel": { - "title": "Update legend" - }, - "noEntries": "No legend entries" + "mathConstants": { + "section": "Mathematical constants" + } + }, + "eeLint": { + "undefinedVariable": "\"{variableName}\" is not defined. \n\nVariables must be the name of images, name of calculations declared before this function, math functions, or math constants.", + "invalidBand": "\"{imageName}\" doesn't contain band \"{bandName}\".", + "invalidBandCount": "Images must contain the same number of bands or only 1 band. Got 2 and 3. \"{imageName}\" contains {bandCount} bands while another image contains {expectedBandCount}.", + "invalidArgCount": "Function expect {expectedArgCount} argument, got {argCount}.", + "syntaxError": "Syntax error." + } + }, + "comboBox": { + "loading": "Loading..." + }, + "confirm": { + "title": "Confirmation required", + "label": "Confirm" + }, + "error": "Error", + "fileSelect": { + "multiple": { + "drop": "Drop file(s) here.", + "dropOrClick": "Drop file(s) here, or click to select file(s)", + "reject": "Cannot drop file(s). Wrong file type?" + }, + "single": { + "drop": "Drop file here", + "dropOrClick": "Drop file here, or click to select file", + "reject": "Cannot drop file. Wrong file type, or more than one file?" + } + }, + "imageConstraints": { + "bit": { + "label": "bit", + "tooltip": "Apply a bitmask to the band. This is typically used for QA bands" + }, + "band": { + "label": "Band", + "notSelected": "[No band selected]" + }, + "property": { + "label": "Property", + "notSelected": "[No property selected]" + }, + "image": { + "label": "Image", + "notSelected": "[No image selected]" + }, + "logicalAnd": { + "tooltip": "All constraints must be true" + }, + "logicalOr": { + "tooltip": "One of the constraints must be true" + }, + "operator": { + "equals": { + "label": "=" }, - "list": { - "noResults": "No results" + "greaterThan": { + "label": ">" }, - "loading": "Loading...", - "notification": { - "dismiss": "Click to dismiss", - "dismissOrWait": "Click to dismiss or wait {timeout} {timeout, plural, one {second} other {seconds}}", - "error": { - "title": "Error" - }, - "warning": { - "title": "Warning" - } + "greaterThanOrEquals": { + "label": "≥" }, - "recipeInput": { - "label": "Recipe", - "placeholder": "Select a recipe...", - "all": { - "tooltip": "Show all recipes or only recipes in current project" - } + "label": "Operator", + "lessThan": { + "label": "<" }, - "safetyButton": { - "label": "Proceed" + "lessThanOrEquals": { + "label": "≤" }, - "seasonSelect": { - "from": "From", - "to": "To" + "range": { + "label": "Range" + } + }, + "bitRange": { + "from": { + "label": "From bit" }, - "tabs": { - "addTab": { - "tooltip": "New tab" - }, - "newTab": "New_Tab" + "to": { + "label": "To bit" } + }, + "range": { + "from": { + "label": "From value" + }, + "to": { + "label": "To value" + } + }, + "value": { + "label": "Value" + }, + "inclusive": { + "label": "incl", + "tooltip": "Determine if value is inclusive or not" + } + }, + "legend": { + "edit": { + "tooltip": "Update legend" + }, + "editLegendPanel": { + "title": "Update legend" + }, + "noEntries": "No legend entries" + }, + "list": { + "noResults": "No results" + }, + "loading": "Loading...", + "notification": { + "dismiss": "Click to dismiss", + "dismissOrWait": "Click to dismiss or wait {timeout} {timeout, plural, one {second} other {seconds}}", + "error": { + "title": "Error" + }, + "warning": { + "title": "Warning" + } + }, + "recipeInput": { + "label": "Recipe", + "placeholder": "Select a recipe...", + "all": { + "tooltip": "Show all recipes or only recipes in current project" + } + }, + "safetyButton": { + "label": "Proceed" + }, + "seasonSelect": { + "from": "From", + "to": "To" }, - "chromeOnHighResDisplayWarning": "The browser you are using can have issues when showing multiple layers at the same time on very high resolution displays, like yours. If your screen flickers or freezes, please reduce the number of layers or use a different browser, like Firefox or Safari, until the issue has been resolved." -} \ No newline at end of file + "tabs": { + "addTab": { + "tooltip": "New tab" + }, + "newTab": "New_Tab" + } + }, + "chromeOnHighResDisplayWarning": "The browser you are using can have issues when showing multiple layers at the same time on very high resolution displays, like yours. If your screen flickers or freezes, please reduce the number of layers or use a different browser, like Firefox or Safari, until the issue has been resolved." +} From 3649b179fbbb224b734c5147be129cf52b31cbda Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 12 Mar 2026 13:54:23 +0100 Subject: [PATCH 09/10] Used database to look for active/pending tasks. --- modules/gui/src/api/tasks.js | 5 ++ .../recipe/timeSeries/timeSeriesRecipe.js | 1 - .../src/app/home/body/tasks/taskDetails.jsx | 52 +++++++++++-------- .../task/endpoint/TaskEndpoint.groovy | 18 ++++++- modules/task/src/rxjs/fileSystem.js | 21 +------- modules/task/src/tasks/imageSepalExport.js | 16 ++---- .../task/src/tasks/timeSeriesSepalExport.js | 16 ++---- 7 files changed, 60 insertions(+), 69 deletions(-) diff --git a/modules/gui/src/api/tasks.js b/modules/gui/src/api/tasks.js index 4b29895b1..d14d240b1 100644 --- a/modules/gui/src/api/tasks.js +++ b/modules/gui/src/api/tasks.js @@ -9,6 +9,11 @@ export default { loadDetails$: taskId => get$(`/api/tasks/task/${taskId}/details`), + listExisting$: ({outputPath, destination, status}) => + get$('/api/tasks', { + query: {outputPath, destination, status} + }), + submit$: task => postJson$('/api/tasks', { body: task diff --git a/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js b/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js index e66b5877d..4ec13038f 100644 --- a/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js +++ b/modules/gui/src/app/home/body/process/recipe/timeSeries/timeSeriesRecipe.js @@ -1,4 +1,3 @@ -import _ from 'lodash' import moment from 'moment' import api from '~/apiRegistry' diff --git a/modules/gui/src/app/home/body/tasks/taskDetails.jsx b/modules/gui/src/app/home/body/tasks/taskDetails.jsx index 520785769..f17d7eed7 100644 --- a/modules/gui/src/app/home/body/tasks/taskDetails.jsx +++ b/modules/gui/src/app/home/body/tasks/taskDetails.jsx @@ -3,7 +3,9 @@ import React from 'react' import api from '~/apiRegistry' import {copyToClipboard} from '~/clipboard' +import {compose} from '~/compose' import format from '~/format' +import {withSubscriptions} from '~/subscription' import {msg} from '~/translate' import {Button} from '~/widget/button' import {Label} from '~/widget/label' @@ -13,7 +15,7 @@ import {Widget} from '~/widget/widget' import styles from './taskDetails.module.css' -export class TaskDetails extends React.Component { +class _TaskDetails extends React.Component { constructor(props) { super(props) this.state = { @@ -28,27 +30,28 @@ export class TaskDetails extends React.Component { } loadTaskDetails() { - const {taskId} = this.props - - api.tasks.loadDetails$(taskId).subscribe({ - next: task => { - this.setState({ - task, - duration: this.calculateDuration(task) - }) - - // Set up interval only if task is still running - if (task.status === 'ACTIVE') { - this.intervalId = setInterval(() => { - this.setState({duration: this.calculateDuration(task)}) - }, 1000) + const {taskId, addSubscription} = this.props + + addSubscription( + api.tasks.loadDetails$(taskId).subscribe({ + next: task => { + this.setState({ + task, + duration: this.calculateDuration(task) + }) + + // Set up interval only if task is still running + if (task.status === 'ACTIVE') { + this.intervalId = setInterval(() => { + this.setState({duration: this.calculateDuration(task)}) + }, 1000) + } + }, + error: error => { + console.error('Failed to load task details:', error) } - }, - error: error => { - // Error is logged but not displayed in UI - console.error('Failed to load task details:', error) - } - }) + }) + ) } componentWillUnmount() { @@ -261,8 +264,13 @@ export class TaskDetails extends React.Component { } } -TaskDetails.propTypes = { +_TaskDetails.propTypes = { taskId: PropTypes.string.isRequired, onClose: PropTypes.func.isRequired, description: PropTypes.string } + +export const TaskDetails = compose( + _TaskDetails, + withSubscriptions() +) diff --git a/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy b/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy index 503da7f80..31da9f9cb 100644 --- a/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy +++ b/modules/sepal-server/src/main/groovy/org/openforis/sepal/component/task/endpoint/TaskEndpoint.groovy @@ -25,7 +25,21 @@ class TaskEndpoint { get('/tasks') { response.contentType = 'application/json' - def tasks = component.submit(new UserTasks(username: currentUser.username)).collect { + def outputPath = params.optional('outputPath', String) + def destination = params.optional('destination', String) + def status = params.optional('status', String) + def tasks = component.submit(new UserTasks(username: currentUser.username)) + if (status) { + def statuses = status.split(',').collect { Task.State.valueOf(it.trim()) } + tasks = tasks.findAll { statuses.contains(it.state) } + } + if (outputPath) { + tasks = tasks.findAll { it.params?.taskInfo?.outputPath == outputPath } + } + if (destination) { + tasks = tasks.findAll { it.params?.taskInfo?.destination == destination } + } + def result = tasks.collect { [ id: it.id, recipeId: it.recipeId, @@ -34,7 +48,7 @@ class TaskEndpoint { statusDescription: it.statusDescription ] } - send toJson(tasks) + send toJson(result) } post('/tasks') { diff --git a/modules/task/src/rxjs/fileSystem.js b/modules/task/src/rxjs/fileSystem.js index 38e7648fe..46bed0e97 100644 --- a/modules/task/src/rxjs/fileSystem.js +++ b/modules/task/src/rxjs/fileSystem.js @@ -47,23 +47,4 @@ const mkdirSafe$ = (preferredPath, options = {}) => defer(() => { ) }) -const createLock$ = dir => { - const lockPath = Path.join(dir, '.task_pending') - return defer(() => - fromPromise(fs.promises.writeFile(lockPath, '')).pipe( - map(() => lockPath) - ) - ) -} - -const releaseLock$ = dir => { - const lockPath = Path.join(dir, '.task_pending') - return defer(() => - fromPromise(fs.promises.unlink(lockPath)).pipe( - map(() => lockPath), - catchError(() => of(null)) - ) - ) -} - -module.exports = {exists$, ls$, mkdir$, mkdirSafe$, createLock$, releaseLock$} +module.exports = {exists$, ls$, mkdir$, mkdirSafe$} diff --git a/modules/task/src/tasks/imageSepalExport.js b/modules/task/src/tasks/imageSepalExport.js index 2f2d38175..da771ac3b 100644 --- a/modules/task/src/tasks/imageSepalExport.js +++ b/modules/task/src/tasks/imageSepalExport.js @@ -1,6 +1,6 @@ -const {concat, forkJoin, switchMap, finalize} = require('rxjs') +const {concat, forkJoin, switchMap} = require('rxjs') const moment = require('moment') -const {mkdir$, createLock$, releaseLock$} = require('#task/rxjs/fileSystem') +const {mkdir$} = require('#task/rxjs/fileSystem') const {createVrt$, setBandNames$} = require('#sepal/gdal') const {exportImageToSepal$} = require('../jobs/export/toSepal') const ImageFactory = require('#sepal/ee/imageFactory') @@ -20,15 +20,9 @@ module.exports = { // the UI already validated the path here, no need to have mkdirsafe here return mkdir$(preferredDownloadDir, {recursive: true}).pipe( switchMap(downloadDir => { - return createLock$(downloadDir).pipe( - switchMap(() => { - return concat( - export$(taskId, {description, exportPrefix, recipe, downloadDir, bands, ...retrieveOptions}), - postProcess$({description: exportPrefix, downloadDir, bands}) - ).pipe( - finalize(() => releaseLock$(downloadDir).subscribe()) - ) - }) + return concat( + export$(taskId, {description, exportPrefix, recipe, downloadDir, bands, ...retrieveOptions}), + postProcess$({description: exportPrefix, downloadDir, bands}) ) }) ) diff --git a/modules/task/src/tasks/timeSeriesSepalExport.js b/modules/task/src/tasks/timeSeriesSepalExport.js index 849243460..9948be340 100644 --- a/modules/task/src/tasks/timeSeriesSepalExport.js +++ b/modules/task/src/tasks/timeSeriesSepalExport.js @@ -4,8 +4,8 @@ const {hasImagery: hasRadarImagery} = require('#sepal/ee/radar/collection') const {hasImagery: hasPlanetImagery} = require('#sepal/ee/planet/collection') const tile = require('#sepal/ee/tile') const {exportImageToSepal$} = require('../jobs/export/toSepal') -const {mkdir$, createLock$, releaseLock$} = require('#task/rxjs/fileSystem') -const {concat, forkJoin, from, of, map, mergeMap, scan, switchMap, tap, finalize} = require('rxjs') +const {mkdir$} = require('#task/rxjs/fileSystem') +const {concat, forkJoin, from, of, map, mergeMap, scan, switchMap, tap} = require('rxjs') const {swallow} = require('#sepal/rxjs') const Path = require('path') const {terminal$} = require('#sepal/terminal') @@ -33,17 +33,7 @@ module.exports = { // the UI already validated the path here, no need to have mkdirsafe here return mkdir$(preferredDownloadDir, {recursive: true}).pipe( switchMap(downloadDir => { - return createLock$(downloadDir).pipe( - switchMap(lockPath => { - log.debug('Created lock for time series export', {lockPath}) - return export$(taskId, {description: exportPrefix, downloadDir, ...retrieveOptions}).pipe( - finalize(() => { - log.debug('Releasing lock for time series export', {lockPath}) - releaseLock$(downloadDir).subscribe() - }) - ) - }) - ) + return export$(taskId, {description: exportPrefix, downloadDir, ...retrieveOptions}) }) ) }) From 71fa8d7bc3e232546bc259de1cac5954dcd9bd44 Mon Sep 17 00:00:00 2001 From: dfguerrerom Date: Thu, 12 Mar 2026 14:24:10 +0100 Subject: [PATCH 10/10] Added task conflict detection for GEE and SEPAL export destinations --- .../ccdcSlice/panels/retrieve/retrieve.jsx | 45 +++++- .../mosaic/panels/retrieve/retrievePanel.jsx | 49 +++++- modules/gui/src/locale/en/translations.json | 1 + modules/gui/src/widget/assetDestination.jsx | 140 +++++++++++++++--- .../gui/src/widget/workspaceDestination.jsx | 118 ++++++++++----- 5 files changed, 284 insertions(+), 69 deletions(-) diff --git a/modules/gui/src/app/home/body/process/recipe/ccdcSlice/panels/retrieve/retrieve.jsx b/modules/gui/src/app/home/body/process/recipe/ccdcSlice/panels/retrieve/retrieve.jsx index cbac6e5dd..32999c818 100644 --- a/modules/gui/src/app/home/body/process/recipe/ccdcSlice/panels/retrieve/retrieve.jsx +++ b/modules/gui/src/app/home/body/process/recipe/ccdcSlice/panels/retrieve/retrieve.jsx @@ -84,18 +84,24 @@ const mapRecipeToProps = recipe => ({ }) class _Retrieve extends React.Component { - state = {more: false} - constructor(props) { super(props) + this.state = { + more: false, + destinationValidationPending: this.requiresDestinationValidation(props) + } const {recipeId, inputs: {scale}} = this.props this.recipeActions = RecipeActions(recipeId) if (!scale.value) scale.set(30) + this.onDestinationChange = this.onDestinationChange.bind(this) + this.onDestinationValidityCheckChange = this.onDestinationValidityCheckChange.bind(this) } render() { - const {more} = this.state + const {form} = this.props + const {more, destinationValidationPending} = this.state + const invalid = destinationValidationPending || form.isInvalid() return ( + applyLabel={msg('process.ccdcSlice.panel.retrieve.apply')} + invalid={invalid}>