Skip to content

Commit 8ed22c3

Browse files
authored
Merge pull request #1628 from pierotofy/compact
Fix thermal layer name in project map view, add compact feature, onboarding settings
2 parents ef2dda8 + 73124f7 commit 8ed22c3

File tree

17 files changed

+214
-98
lines changed

17 files changed

+214
-98
lines changed

app/api/tasks.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ def restart(self, *args, **kwargs):
153153
def remove(self, *args, **kwargs):
154154
return self.set_pending_action(pending_actions.REMOVE, *args, perms=('delete_project', ), **kwargs)
155155

156+
@action(detail=True, methods=['post'])
157+
def compact(self, *args, **kwargs):
158+
return self.set_pending_action(pending_actions.COMPACT, *args, perms=('delete_project', ), **kwargs)
159+
156160
@action(detail=True, methods=['get'])
157161
def output(self, request, pk=None, project_pk=None):
158162
"""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 2.2.27 on 2025-03-11 20:07
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('app', '0040_auto_20241106_1832'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='task',
15+
name='compacted',
16+
field=models.BooleanField(default=False, help_text='A flag indicating whether this task was compacted', verbose_name='Compact'),
17+
),
18+
migrations.AlterField(
19+
model_name='task',
20+
name='pending_action',
21+
field=models.IntegerField(blank=True, choices=[(1, 'CANCEL'), (2, 'REMOVE'), (3, 'RESTART'), (4, 'RESIZE'), (5, 'IMPORT'), (6, 'COMPACT')], db_index=True, help_text='A requested action to be performed on the task. The selected action will be performed by the worker at the next iteration.', null=True, verbose_name='Pending Action'),
22+
),
23+
]

app/models/task.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class Task(models.Model):
235235
(pending_actions.RESTART, 'RESTART'),
236236
(pending_actions.RESIZE, 'RESIZE'),
237237
(pending_actions.IMPORT, 'IMPORT'),
238+
(pending_actions.COMPACT, 'COMPACT'),
238239
)
239240

240241
TASK_PROGRESS_LAST_VALUE = 0.85
@@ -285,6 +286,8 @@ class Task(models.Model):
285286
tags = models.TextField(db_index=True, default="", blank=True, help_text=_("Task tags"), verbose_name=_("Tags"))
286287
orthophoto_bands = fields.JSONField(default=list, blank=True, help_text=_("List of orthophoto bands"), verbose_name=_("Orthophoto Bands"))
287288
size = models.FloatField(default=0.0, blank=True, help_text=_("Size of the task on disk in megabytes"), verbose_name=_("Size"))
289+
compacted = models.BooleanField(default=False, help_text=_("A flag indicating whether this task was compacted"), verbose_name=_("Compact"))
290+
288291

289292
class Meta:
290293
verbose_name = _("Task")
@@ -807,6 +810,14 @@ def callback(progress):
807810

808811
# Stop right here!
809812
return
813+
814+
elif self.pending_action == pending_actions.COMPACT:
815+
logger.info("Compacting {}".format(self))
816+
time.sleep(2) # Purely to make sure the user sees the "compacting..." message in the UI since this is so fast
817+
self.compact()
818+
self.pending_action = None
819+
self.save()
820+
return
810821

811822
if self.processing_node:
812823
# Need to update status (first time, queued or running?)
@@ -1121,7 +1132,6 @@ def update_orthophoto_bands_field(self, commit=False):
11211132
self.orthophoto_bands = bands
11221133
if commit: self.save()
11231134

1124-
11251135
def delete(self, using=None, keep_parents=False):
11261136
task_id = self.id
11271137
from app.plugins import signals as plugin_signals
@@ -1142,6 +1152,19 @@ def delete(self, using=None, keep_parents=False):
11421152

11431153
plugin_signals.task_removed.send_robust(sender=self.__class__, task_id=task_id)
11441154

1155+
def compact(self):
1156+
# Remove all images
1157+
images_path = self.task_path()
1158+
images = [os.path.join(images_path, i) for i in self.scan_images()]
1159+
for im in images:
1160+
try:
1161+
os.unlink(im)
1162+
except Exception as e:
1163+
logger.warning(e)
1164+
1165+
self.compacted = True
1166+
self.update_size(commit=True)
1167+
11451168
def set_failure(self, error_message):
11461169
logger.error("FAILURE FOR {}: {}".format(self, error_message))
11471170
self.last_error = error_message
@@ -1157,7 +1180,8 @@ def find_all_files_matching(self, regex):
11571180
def check_if_canceled(self):
11581181
# Check if task has been canceled/removed
11591182
if Task.objects.only("pending_action").get(pk=self.id).pending_action in [pending_actions.CANCEL,
1160-
pending_actions.REMOVE]:
1183+
pending_actions.REMOVE,
1184+
pending_actions.COMPACT]:
11611185
raise TaskInterruptedException()
11621186

11631187
def resize_images(self):

app/pending_actions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
RESTART = 3
44
RESIZE = 4
55
IMPORT = 5
6+
COMPACT = 6

app/static/app/js/classes/PendingActions.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ const CANCEL = 1,
44
REMOVE = 2,
55
RESTART = 3,
66
RESIZE = 4,
7-
IMPORT = 5;
7+
IMPORT = 5,
8+
COMPACT = 6;
89

910
let pendingActions = {
1011
[CANCEL]: {
@@ -21,6 +22,9 @@ let pendingActions = {
2122
},
2223
[IMPORT]: {
2324
descr: _("Importing...")
25+
},
26+
[COMPACT]: {
27+
descr: _("Compacting...")
2428
}
2529
};
2630

@@ -30,6 +34,7 @@ export default {
3034
RESTART: RESTART,
3135
RESIZE: RESIZE,
3236
IMPORT: IMPORT,
37+
COMPACT: COMPACT,
3338

3439
description: function(pendingAction) {
3540
if (pendingActions[pendingAction]) return pendingActions[pendingAction].descr;

app/static/app/js/components/Map.jsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ class Map extends React.Component {
106106
}
107107
}
108108

109-
typeToHuman = (type) => {
109+
typeToHuman = (type, thermal = false) => {
110110
switch(type){
111111
case "orthophoto":
112112
return _("Orthophoto");
113113
case "plant":
114-
return this.props.thermal ? _("Thermal") : _("Plant Health");
114+
return thermal ? _("Thermal") : _("Plant Health");
115115
case "dsm":
116116
return _("Surface Model");
117117
case "dtm":
@@ -120,12 +120,12 @@ class Map extends React.Component {
120120
return "";
121121
}
122122

123-
typeToIcon = (type) => {
123+
typeToIcon = (type, thermal = false) => {
124124
switch(type){
125125
case "orthophoto":
126126
return "far fa-image fa-fw"
127127
case "plant":
128-
return this.props.thermal ? "fa fa-thermometer-half fa-fw" : "fa fa-seedling fa-fw";
128+
return thermal ? "fa fa-thermometer-half fa-fw" : "fa fa-seedling fa-fw";
129129
case "dsm":
130130
case "dtm":
131131
return "fa fa-chart-area fa-fw";
@@ -265,8 +265,12 @@ class Map extends React.Component {
265265
});
266266

267267
// Associate metadata with this layer
268-
meta.name = this.typeToHuman(type);
269-
meta.icon = this.typeToIcon(type);
268+
let thermal = typeof(mres) === 'object' && mres.band_descriptions &&
269+
Array.isArray(mres.band_descriptions) && mres.band_descriptions.length > 0 &&
270+
mres.band_descriptions[0].indexOf("lwir") !== -1;
271+
272+
meta.name = this.typeToHuman(type, this.props.thermal || thermal);
273+
meta.icon = this.typeToIcon(type, this.props.thermal || thermal);
270274
meta.type = type;
271275
meta.raster = true;
272276
meta.autoExpand = this.taskCount === 1 && type === this.props.mapType;

app/static/app/js/components/NewTaskPanel.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ class NewTaskPanel extends React.Component {
239239
ref={(domNode) => { if (domNode) this.taskForm = domNode; }}
240240
/>
241241

242-
{this.state.editTaskFormLoaded && this.props.showAlign && this.state.showMapPreview ?
242+
{this.state.editTaskFormLoaded && this.props.showAlign && this.state.showMapPreview && this.state.alignTasks.length > 0 ?
243243
<div>
244244
<div className="form-group">
245245
<label className="col-sm-2 control-label">{_("Alignment")}</label>

app/static/app/js/components/TaskListItem.jsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,13 @@ class TaskListItem extends React.Component {
461461

462462
const disabled = this.state.actionButtonsDisabled ||
463463
([pendingActions.CANCEL,
464-
pendingActions.REMOVE,
464+
pendingActions.REMOVE,
465+
pendingActions.COMPACT,
465466
pendingActions.RESTART].indexOf(task.pending_action) !== -1);
466467
const editable = this.props.hasPermission("change") && [statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1;
467468
const actionLoading = this.state.actionLoading;
468-
469+
const showAssetButtons = task.status === statusCodes.COMPLETED;
470+
469471
let expanded = "";
470472
if (this.state.expanded){
471473
let showOrthophotoMissingWarning = false,
@@ -484,7 +486,7 @@ class TaskListItem extends React.Component {
484486
});
485487
};
486488

487-
if (task.status === statusCodes.COMPLETED){
489+
if (showAssetButtons){
488490
if (task.available_assets.indexOf("orthophoto.tif") !== -1 || task.available_assets.indexOf("dsm.tif") !== -1){
489491
addActionButton(" " + _("View Map"), "btn-primary", "fa fa-globe", () => {
490492
location.href = `/map/project/${task.project}/task/${task.id}/`;
@@ -514,7 +516,8 @@ class TaskListItem extends React.Component {
514516
if ([statusCodes.FAILED, statusCodes.COMPLETED, statusCodes.CANCELED].indexOf(task.status) !== -1 &&
515517
task.processing_node &&
516518
this.props.hasPermission("change") &&
517-
!imported){
519+
!imported &&
520+
!task.compacted){
518521
// By default restart reruns every pipeline
519522
// step from the beginning
520523
const rerunFrom = task.can_rerun_from.length > 1 ?
@@ -534,7 +537,7 @@ class TaskListItem extends React.Component {
534537
}
535538

536539
actionButtons = (<div className="action-buttons">
537-
{task.status === statusCodes.COMPLETED ?
540+
{showAssetButtons ?
538541
<AssetDownloadButtons task={this.state.task} disabled={disabled} />
539542
: ""}
540543
{actionButtons.map(button => {
@@ -672,7 +675,6 @@ class TaskListItem extends React.Component {
672675
</div>
673676
</div>
674677
<div className="row clearfix">
675-
<ErrorMessage bind={[this, 'actionError']} />
676678
{actionButtons}
677679
</div>
678680
<TaskPluginActionButtons task={task} disabled={disabled} />
@@ -734,6 +736,10 @@ class TaskListItem extends React.Component {
734736
type = 'neutral';
735737
}
736738

739+
if (task.pending_action === pendingActions.COMPACT){
740+
statusIcon = 'fa fa-cog fa-spin fa-fw';
741+
}
742+
737743
statusLabel = getStatusLabel(status, type, progress);
738744
}
739745

@@ -764,8 +770,15 @@ class TaskListItem extends React.Component {
764770

765771
if (this.props.hasPermission("delete")){
766772
taskActions.push(
767-
<li key="sep" role="separator" className="divider"></li>,
773+
<li key="sep" role="separator" className="divider"></li>
768774
);
775+
776+
if (task.status === statusCodes.COMPLETED && !task.compacted){
777+
addTaskAction(_("Compact"), "fa fa-database", this.genActionApiCall("compact", {
778+
confirm: _("Compacting will free disk space by permanently deleting the original images used for processing. It will no longer be possible to restart the task. Maps and models will remain in place. Continue?"),
779+
defaultError: _("Cannot compact task.")
780+
}));
781+
}
769782

770783
addTaskAction(_("Delete"), "fa fa-trash", this.genActionApiCall("remove", {
771784
confirm: _("All information related to this task, including images, maps and models will be deleted. Continue?"),
@@ -816,6 +829,7 @@ class TaskListItem extends React.Component {
816829
: ""}
817830
</div>
818831
</div>
832+
<ErrorMessage bind={[this, 'actionError']} />
819833
{expanded}
820834
</div>
821835
);

0 commit comments

Comments
 (0)