kopia lustrzana https://github.com/OpenDroneMap/WebODM
UI fixes, handle non-georeferenced datasets
rodzic
1ccad9312d
commit
53943c3f69
|
@ -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"))
|
||||
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue