UI fixes, handle non-georeferenced datasets

pull/1086/head
Piero Toffanin 2021-11-05 15:27:13 -04:00
rodzic 1ccad9312d
commit 53943c3f69
9 zmienionych plików z 68 dodań i 11 usunięć

Wyświetl plik

@ -518,6 +518,9 @@ class Export(TaskNestedView):
except ValueError:
raise exceptions.ValidationError(_("Invalid EPSG code: %(value)s") % {'value': epsg})
if epsg is not None and task.epsg is None:
raise exceptions.ValidationError(_("Cannot use epsg on non-georeferenced dataset"))
if (formula and not bands) or (not formula and bands):
raise exceptions.ValidationError(_("Both formula and bands parameters are required"))

Wyświetl plik

@ -3,6 +3,7 @@
from django.db import migrations, models
import rasterio
import os
from app.pointcloud_utils import is_pointcloud_georeferenced
from webodm import settings
def update_epsg_fields(apps, schema_editor):
@ -23,7 +24,16 @@ def update_epsg_fields(apps, schema_editor):
break # We assume all assets are in the same CRS
except Exception as e:
print(e)
# If point cloud is not georeferenced, dataset is not georeferenced
# (2D assets might be using pseudo-georeferencing)
point_cloud = os.path.join(settings.MEDIA_ROOT, "project", str(t.project.id), "task", str(t.id), "assets", "odm_georeferencing", "odm_georeferenced_model.laz")
if epsg is not None and os.path.isfile(point_cloud):
if not is_pointcloud_georeferenced(point_cloud):
print("{} is not georeferenced".format(t))
epsg = None
print("Updating {} (with epsg: {})".format(t, epsg))
t.epsg = epsg
@ -41,7 +51,8 @@ def remove_all_zip(apps, schema_editor):
print("Cleaned up {}".format(asset_path))
except Exception as e:
print(e)
class Migration(migrations.Migration):
dependencies = [

Wyświetl plik

@ -32,6 +32,7 @@ from app import pending_actions
from django.contrib.gis.db.models.fields import GeometryField
from app.cogeo import assure_cogeo
from app.pointcloud_utils import is_pointcloud_georeferenced
from app.testwatch import testWatch
from app.security import path_traversal_check
from nodeodm import status_codes
@ -933,7 +934,8 @@ class Task(models.Model):
'id': str(self.id),
'project': self.project.id,
'available_assets': self.available_assets,
'public': self.public
'public': self.public,
'epsg': self.epsg
}
def generate_deferred_asset(self, archive, directory, stream=False):
@ -980,8 +982,16 @@ class Task(models.Model):
break # We assume all assets are in the same CRS
except Exception as e:
logger.warning(e)
self.epsg = epsg
# If point cloud is not georeferenced, dataset is not georeferenced
# (2D assets might be using pseudo-georeferencing)
point_cloud = self.assets_path(self.ASSETS_MAP['georeferenced_model.laz'])
if epsg is not None and os.path.isfile(point_cloud):
if not is_pointcloud_georeferenced(point_cloud):
logger.info("{} is not georeferenced".format(self))
epsg = None
self.epsg = epsg
if commit: self.save()

Wyświetl plik

@ -1,6 +1,8 @@
import logging
import os
import subprocess
import json
from app.security import double_quote
logger = logging.getLogger('app.logger')
@ -21,3 +23,15 @@ def export_pointcloud(input, output, **opts):
'--writers.ply.storage_mode', 'little endian']
subprocess.check_output(["pdal", "translate", input, output] + reprojection_args + extra_args)
def is_pointcloud_georeferenced(laz_path):
if not os.path.isfile(laz_path):
return False
try:
j = json.loads(subprocess.check_output(["pdal", "info", "--summary", laz_path]))
return 'summary' in j and 'srs' in j['summary']
except Exception as e:
logger.warning(e)
return True # Assume georeferenced

Wyświetl plik

@ -144,6 +144,7 @@ class ModelView extends React.Component {
showTexturedModel: false,
initializingModel: false,
selectedCamera: null,
modalOpen: false
};
this.pointCloud = null;
@ -639,12 +640,14 @@ class ModelView extends React.Component {
<div id="potree_sidebar_container"> </div>
</div>
<div className="model-action-buttons">
<div className={"model-action-buttons " + (this.state.modalOpen ? "modal-open" : "")}>
<AssetDownloadButtons
task={this.props.task}
direction="up"
showLabel={false}
buttonClass="btn-secondary" />
buttonClass="btn-secondary"
onModalOpen={() => this.setState({modalOpen: true})}
onModalClose={() => this.setState({modalOpen: false})} />
{(this.props.shareButtons && !this.props.public) ?
<ShareButton
ref={(ref) => { this.shareButton = ref; }}

Wyświetl plik

@ -19,7 +19,9 @@ class AssetDownloadButtons extends React.Component {
task: PropTypes.object.isRequired,
direction: PropTypes.string,
buttonClass: PropTypes.string,
showLabel: PropTypes.bool
showLabel: PropTypes.bool,
onModalOpen: PropTypes.func,
onModalClose: PropTypes.func
};
constructor(props){
@ -32,6 +34,7 @@ class AssetDownloadButtons extends React.Component {
onHide = () => {
this.setState({exportDialogProps: null});
if (this.props.onModalClose) this.props.onModalClose();
}
render(){
@ -71,6 +74,7 @@ class AssetDownloadButtons extends React.Component {
exportParams: asset.exportParams,
assetLabel: asset.label
}});
if (this.props.onModalOpen) this.props.onModalOpen();
}
}
return (<li key={i}>

Wyświetl plik

@ -73,7 +73,7 @@ export default class ExportAssetPanel extends React.Component {
this.state = {
error: "",
format: props.exportFormats[0],
epsg: this.props.task.epsg || "4326",
epsg: this.props.task.epsg || null,
customEpsg: Storage.getItem("last_export_custom_epsg") || "4326",
exporting: false
}
@ -107,8 +107,10 @@ export default class ExportAssetPanel extends React.Component {
}
params.format = format;
params.epsg = this.getEpsg();
console.log(params);
const epsg = this.getEpsg();
if (epsg) params.epsg = this.getEpsg();
return params;
}

Wyświetl plik

@ -68,6 +68,10 @@
bottom: 12px;
right: 6px;
&.modal-open{
z-index: 999999;
}
.switchModeButton{
position: initial;
}
@ -82,7 +86,6 @@
margin-right: 8px;
}
}
/* Potree specific */
#potree_map{

Wyświetl plik

@ -250,6 +250,9 @@ class TestApiTask(BootTransactionTestCase):
# No processing node is set
self.assertTrue(task.processing_node is None)
# EPSG should be null
self.assertTrue(task.epsg is None)
# tiles.json, bounds, metadata should not be accessible at this point
tile_types = ['orthophoto', 'dsm', 'dtm']
endpoints = ['tiles.json', 'bounds', 'metadata']
@ -318,6 +321,7 @@ class TestApiTask(BootTransactionTestCase):
# Processing should have started and a UUID is assigned
# Calling process pending tasks should finish the process
# and invoke the plugins completed signal
time.sleep(0.5)
task.refresh_from_db()
self.assertTrue(task.status in [status_codes.RUNNING, status_codes.COMPLETED]) # Sometimes this finishes before we get here
self.assertTrue(len(task.uuid) > 0)
@ -895,6 +899,9 @@ class TestApiTask(BootTransactionTestCase):
self.assertTrue(os.path.exists(task.assets_path("dsm_tiles")))
self.assertTrue(os.path.exists(task.assets_path("dtm_tiles")))
# EPSG should be populated
self.assertEqual(task.epsg, 32615)
# Can access only tiles of available assets
res = client.get("/api/projects/{}/tasks/{}/dsm/tiles.json".format(project.id, task.id))
self.assertEqual(res.status_code, status.HTTP_200_OK)