kopia lustrzana https://github.com/OpenDroneMap/WebODM
Contours plugin working, simplification parameter addition
rodzic
305b4257bd
commit
133c382a77
|
@ -112,6 +112,7 @@ class GrassContext:
|
|||
}
|
||||
|
||||
def cleanup(self):
|
||||
return
|
||||
if os.path.exists(self.get_cwd()):
|
||||
shutil.rmtree(self.get_cwd())
|
||||
|
||||
|
|
|
@ -43,7 +43,8 @@ class TaskContoursGenerate(TaskView):
|
|||
context.add_param('format', format)
|
||||
context.add_param('simplify', simplify)
|
||||
context.add_param('epsg', epsg)
|
||||
context.set_location('epsg:' + str(epsg))
|
||||
#context.set_location('epsg:' + str(epsg))
|
||||
context.set_location(dem)
|
||||
|
||||
celery_task_id = execute_grass_script.delay(os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
|
@ -66,9 +67,9 @@ class TaskContoursCheck(TaskView):
|
|||
return Response({'ready': True, 'error': result['error']})
|
||||
|
||||
contours_file = result.get('output')
|
||||
if not contours_file:
|
||||
if not contours_file or not os.path.exists(contours_file):
|
||||
cleanup_grass_context(result['context'])
|
||||
return Response({'ready': True, 'error': 'No contours file was generated. This could be a bug.'})
|
||||
return Response({'ready': True, 'error': 'Contours file could not be generated. This might be a bug.'})
|
||||
|
||||
request.session['contours_' + celery_task_id] = contours_file
|
||||
return Response({'ready': True})
|
||||
|
@ -94,7 +95,7 @@ class TaskContoursDownload(TaskView):
|
|||
content_type=(mimetypes.guess_type(filename)[0] or "application/zip"))
|
||||
|
||||
response['Content-Type'] = mimetypes.guess_type(filename)[0] or "application/zip"
|
||||
response['Content-Disposition'] = "inline; filename={}".format(filename)
|
||||
response['Content-Disposition'] = "attachment; filename={}".format(filename)
|
||||
response['Content-Length'] = filesize
|
||||
|
||||
return response
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
ext=""
|
||||
if [ "${format}" = "GeoJSON" ]; then
|
||||
ext="geojson"
|
||||
ext="json"
|
||||
elif [ "${format}" = "GPKG" ]; then
|
||||
ext="gpkg"
|
||||
elif [ "${format}" = "DXF" ]; then
|
||||
|
@ -19,8 +19,20 @@ elif [ "${format}" = "ESRI Shapefile" ]; then
|
|||
ext="shp"
|
||||
fi
|
||||
|
||||
gdal_contour -a elevation -i ${interval} -f GPKG "${dem_file}" contours.gpkg > /dev/null
|
||||
ogr2ogr -dialect SQLite -where "ST_Length(geom) > 4" -simplify ${simplify} -t_srs EPSG:${epsg} -overwrite -f "${format}" output.$$ext contours.gpkg > /dev/null
|
||||
#gdal_contour -a elevation -i ${interval} -f GPKG "${dem_file}" contours.gpkg > /dev/null
|
||||
#ogr2ogr -dialect SQLite -where "ST_Length(geom) > 4" -simplify ${simplify} -t_srs EPSG:${epsg} -overwrite -f "${format}" output.$$ext contours.gpkg > /dev/null
|
||||
|
||||
MIN_CONTOUR_LENGTH=5
|
||||
|
||||
r.external input="${dem_file}" output=dem --overwrite
|
||||
r.contour input=dem output=contours step=${interval} --overwrite
|
||||
v.generalize --overwrite input=contours output=contours_smooth method=douglas threshold=${simplify}
|
||||
v.generalize --overwrite input=contours_smooth output=contours_simplified method=chaiken threshold=1
|
||||
v.generalize --overwrite input=contours_simplified output=contours_final method=douglas threshold=${simplify}
|
||||
v.edit map=contours_final tool=delete threshold=-1,0,-$$MIN_CONTOUR_LENGTH query=length
|
||||
v.out.ogr input=contours_final output=temp.gpkg format="GPKG"
|
||||
|
||||
ogr2ogr -t_srs EPSG:${epsg} -overwrite -f "${format}" output.$$ext temp.gpkg > /dev/null
|
||||
|
||||
if [ -e "output.$$ext" ]; then
|
||||
# ESRI ShapeFile extra steps to compress into a zip archive
|
||||
|
|
|
@ -23,6 +23,8 @@ export default class ContoursPanel extends React.Component {
|
|||
permanentError: "",
|
||||
interval: Storage.getItem("last_contours_interval") || "1",
|
||||
customInterval: Storage.getItem("last_contours_custom_interval") || "1",
|
||||
simplify: Storage.getItem("last_contours_simplify") || "0.2",
|
||||
customSimplify: Storage.getItem("last_contours_custom_simplify") || "0.2",
|
||||
layer: "",
|
||||
epsg: Storage.getItem("last_contours_epsg") || "4326",
|
||||
customEpsg: Storage.getItem("last_contours_custom_epsg") || "4326",
|
||||
|
@ -30,6 +32,8 @@ export default class ContoursPanel extends React.Component {
|
|||
loading: true,
|
||||
task: props.tasks[0] || null,
|
||||
previewLoading: false,
|
||||
exportLoading: false,
|
||||
previewLayer: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -76,6 +80,14 @@ export default class ContoursPanel extends React.Component {
|
|||
this.setState({interval: e.target.value});
|
||||
}
|
||||
|
||||
handleSelectSimplify = e => {
|
||||
this.setState({simplify: e.target.value});
|
||||
}
|
||||
|
||||
handleChangeCustomSimplify = e => {
|
||||
this.setState({customSimplify: e.target.value});
|
||||
}
|
||||
|
||||
handleSelectLayer = e => {
|
||||
this.setState({layer: e.target.value});
|
||||
}
|
||||
|
@ -93,10 +105,12 @@ export default class ContoursPanel extends React.Component {
|
|||
}
|
||||
|
||||
getFormValues = () => {
|
||||
const { interval, customInterval, epsg, customEpsg, layer } = this.state;
|
||||
const { interval, customInterval, epsg, customEpsg,
|
||||
simplify, customSimplify, layer } = this.state;
|
||||
return {
|
||||
interval: interval !== "custom" ? interval : customInterval,
|
||||
epsg: epsg !== "custom" ? epsg : customEpsg,
|
||||
simplify: simplify !== "custom" ? simplify : customSimplify,
|
||||
layer
|
||||
};
|
||||
}
|
||||
|
@ -133,23 +147,20 @@ export default class ContoursPanel extends React.Component {
|
|||
$.getJSON(url)
|
||||
.done((geojson) => {
|
||||
try{
|
||||
if (this.previewLayer){
|
||||
map.removeLayer(this.previewLayer);
|
||||
this.previewLayer = null;
|
||||
}
|
||||
this.handleRemovePreview();
|
||||
|
||||
this.previewLayer = L.geoJSON(geojson, {
|
||||
this.setState({previewLayer: L.geoJSON(geojson, {
|
||||
onEachFeature: (feature, layer) => {
|
||||
if (feature.properties && feature.properties.elevation !== undefined) {
|
||||
layer.bindPopup(`<b>Elevation:</b> ${feature.properties.elevation} meters`);
|
||||
if (feature.properties && feature.properties.level !== undefined) {
|
||||
layer.bindPopup(`<b>Elevation:</b> ${feature.properties.level} meters`);
|
||||
}
|
||||
},
|
||||
style: feature => {
|
||||
// TODO: different colors for different elevations?
|
||||
return {color: "yellow"};
|
||||
}
|
||||
});
|
||||
this.previewLayer.addTo(map);
|
||||
})});
|
||||
this.state.previewLayer.addTo(map);
|
||||
|
||||
cb();
|
||||
}catch(e){
|
||||
|
@ -159,15 +170,27 @@ export default class ContoursPanel extends React.Component {
|
|||
.fail(cb);
|
||||
}
|
||||
|
||||
handleShowPreview = () => {
|
||||
this.setState({previewLoading: true});
|
||||
handleRemovePreview = () => {
|
||||
const { map } = this.props;
|
||||
|
||||
const data = this.getFormValues();
|
||||
data.epsg = 4326;
|
||||
data.format = "GeoJSON";
|
||||
data.simplify = 0.05;
|
||||
if (this.state.previewLayer){
|
||||
map.removeLayer(this.state.previewLayer);
|
||||
this.setState({previewLayer: null});
|
||||
}
|
||||
}
|
||||
|
||||
generateContours = (data, loadingProp, isPreview) => {
|
||||
this.setState({[loadingProp]: true, error: ""});
|
||||
const taskId = this.state.task.id;
|
||||
|
||||
// Save settings for next time
|
||||
Storage.setItem("last_contours_interval", this.state.interval);
|
||||
Storage.setItem("last_contours_custom_interval", this.state.customInterval);
|
||||
Storage.setItem("last_contours_simplify", this.state.simplify);
|
||||
Storage.setItem("last_contours_custom_simplify", this.state.customSimplify);
|
||||
Storage.setItem("last_contours_epsg", this.state.epsg);
|
||||
Storage.setItem("last_contours_custom_epsg", this.state.customEpsg);
|
||||
|
||||
this.generateReq = $.ajax({
|
||||
type: 'POST',
|
||||
url: `/api/plugins/contours/task/${taskId}/contours/generate`,
|
||||
|
@ -175,46 +198,70 @@ export default class ContoursPanel extends React.Component {
|
|||
}).done(result => {
|
||||
if (result.celery_task_id){
|
||||
this.waitForCompletion(taskId, result.celery_task_id, error => {
|
||||
if (error) this.setState({previewLoading: false, error});
|
||||
if (error) this.setState({[loadingProp]: false, error});
|
||||
else{
|
||||
const fileUrl = `/api/plugins/contours/task/${taskId}/contours/download/${result.celery_task_id}`;
|
||||
|
||||
// Preview
|
||||
this.addGeoJSONFromURL(fileUrl, e => {
|
||||
if (e) this.setState({error: JSON.stringify(e)});
|
||||
this.setState({previewLoading: false});
|
||||
});
|
||||
|
||||
// Download
|
||||
// location.href = ;
|
||||
// this.setState({previewLoading: false});
|
||||
if (isPreview){
|
||||
this.addGeoJSONFromURL(fileUrl, e => {
|
||||
if (e) this.setState({error: JSON.stringify(e)});
|
||||
this.setState({[loadingProp]: false});
|
||||
});
|
||||
}else{
|
||||
// Download
|
||||
location.href = fileUrl;
|
||||
this.setState({[loadingProp]: false});
|
||||
}
|
||||
}
|
||||
});
|
||||
}else if (result.error){
|
||||
this.setState({previewLoading: false, error: result.error});
|
||||
this.setState({[loadingProp]: false, error: result.error});
|
||||
}else{
|
||||
this.setState({previewLoading: false, error: "Invalid response: " + result});
|
||||
this.setState({[loadingProp]: false, error: "Invalid response: " + result});
|
||||
}
|
||||
}).fail(error => {
|
||||
this.setState({previewLoading: false, error: JSON.stringify(error)});
|
||||
this.setState({[loadingProp]: false, error: JSON.stringify(error)});
|
||||
});
|
||||
}
|
||||
|
||||
handleExport = (format) => {
|
||||
return () => {
|
||||
const data = this.getFormValues();
|
||||
data.format = format;
|
||||
this.generateContours(data, 'exportLoading', false);
|
||||
};
|
||||
}
|
||||
|
||||
handleShowPreview = () => {
|
||||
this.setState({previewLoading: true});
|
||||
|
||||
const data = this.getFormValues();
|
||||
data.epsg = 4326;
|
||||
data.format = "GeoJSON";
|
||||
this.generateContours(data, 'previewLoading', true);
|
||||
}
|
||||
|
||||
render(){
|
||||
const { loading, task, layers, error, permanentError, interval, customInterval, layer,
|
||||
epsg, customEpsg,
|
||||
previewLoading } = this.state;
|
||||
epsg, customEpsg, exportLoading,
|
||||
simplify, customSimplify,
|
||||
previewLoading, previewLayer } = this.state;
|
||||
const intervalValues = [0.25, 0.5, 1, 1.5, 2];
|
||||
const simplifyValues = [{label: 'Do not simplify', value: 0},
|
||||
{label: 'Normal', value: 0.2},
|
||||
{label: 'Aggressive', value: 1}];
|
||||
|
||||
const disabled = (interval === "custom" && !customInterval) ||
|
||||
(epsg === "custom" && !customEpsg);
|
||||
(epsg === "custom" && !customEpsg) ||
|
||||
(simplify === "custom" && !customSimplify);
|
||||
|
||||
let content = "";
|
||||
if (loading) content = (<span><i className="fa fa-circle-o-notch fa-spin"></i> Loading...</span>);
|
||||
else if (error) content = (<ErrorMessage bind={[this, "error"]} />);
|
||||
else if (permanentError) content = (<div className="alert alert-warning">{permanentError}</div>);
|
||||
else{
|
||||
content = (<div>
|
||||
<ErrorMessage bind={[this, "error"]} />
|
||||
<div className="row form-group form-inline">
|
||||
<label className="col-sm-3 control-label">Interval:</label>
|
||||
<div className="col-sm-9 ">
|
||||
|
@ -242,6 +289,24 @@ export default class ContoursPanel extends React.Component {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="row form-group form-inline">
|
||||
<label className="col-sm-3 control-label">Simplify:</label>
|
||||
<div className="col-sm-9 ">
|
||||
<select className="form-control" value={simplify} onChange={this.handleSelectSimplify}>
|
||||
{simplifyValues.map(sv => <option value={sv.value}>{sv.label} ({sv.value} meter)</option>)}
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{simplify === "custom" ?
|
||||
<div className="row form-group form-inline">
|
||||
<label className="col-sm-3 control-label">Value:</label>
|
||||
<div className="col-sm-9 ">
|
||||
<input type="number" className="form-control custom-interval" value={customSimplify} onChange={this.handleChangeCustomSimplify} /><span> meter</span>
|
||||
</div>
|
||||
</div>
|
||||
: ""}
|
||||
|
||||
<div className="row form-group form-inline">
|
||||
<label className="col-sm-3 control-label">Projection:</label>
|
||||
<div className="col-sm-9 ">
|
||||
|
@ -261,34 +326,46 @@ export default class ContoursPanel extends React.Component {
|
|||
</div>
|
||||
: ""}
|
||||
|
||||
<div className="text-right action-buttons">
|
||||
<button onClick={this.handleShowPreview}
|
||||
disabled={disabled || previewLoading} type="button" className="btn btn-sm btn-primary btn-preview">
|
||||
{previewLoading ? <i className="fa fa-spin fa-circle-o-notch"/> : <i className="glyphicon glyphicon-eye-open"/>} Preview
|
||||
</button>
|
||||
|
||||
<div className="btn-group">
|
||||
<button disabled={disabled} type="button" className="btn btn-sm btn-primary" data-toggle="dropdown">
|
||||
<i className="glyphicon glyphicon-download"></i> Export
|
||||
<div className="row action-buttons">
|
||||
<div className="col-sm-3">
|
||||
{previewLayer ? <a title="Delete Preview" href="javascript:void(0);" onClick={this.handleRemovePreview}>
|
||||
<i className="fa fa-trash"></i>
|
||||
</a> : ""}
|
||||
</div>
|
||||
<div className="col-sm-9 text-right">
|
||||
<button onClick={this.handleShowPreview}
|
||||
disabled={disabled || previewLoading} type="button" className="btn btn-sm btn-primary btn-preview">
|
||||
{previewLoading ? <i className="fa fa-spin fa-circle-o-notch"/> : <i className="glyphicon glyphicon-eye-open"/>} Preview
|
||||
</button>
|
||||
<button disabled={disabled} type="button" className="btn btn-sm dropdown-toggle btn-primary" data-toggle="dropdown"><span className="caret"></span></button>
|
||||
<ul className="dropdown-menu pull-right">
|
||||
<li>
|
||||
<a href="javascript:void(0);">
|
||||
<i className="fa fa-globe fa-fw"></i> GeoPackage (.GPKG)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);">
|
||||
<i className="fa fa-file-o fa-fw"></i> AutoCAD (.DXF)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);">
|
||||
<i className="fa fa-file-zip-o fa-fw"></i> ShapeFile (.SHP)
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div className="btn-group">
|
||||
<button disabled={disabled || exportLoading} type="button" className="btn btn-sm btn-primary" data-toggle="dropdown">
|
||||
{exportLoading ? <i className="fa fa-spin fa-circle-o-notch"/> : <i className="glyphicon glyphicon-download" />} Export
|
||||
</button>
|
||||
<button disabled={disabled|| exportLoading} type="button" className="btn btn-sm dropdown-toggle btn-primary" data-toggle="dropdown"><span className="caret"></span></button>
|
||||
<ul className="dropdown-menu pull-right">
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleExport("GPKG")}>
|
||||
<i className="fa fa-globe fa-fw"></i> GeoPackage (.GPKG)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleExport("DXF")}>
|
||||
<i className="fa fa-file-o fa-fw"></i> AutoCAD (.DXF)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleExport("GeoJSON")}>
|
||||
<i className="fa fa-code fa-fw"></i> GeoJSON (.JSON)
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="javascript:void(0);" onClick={this.handleExport("ESRI Shapefile")}>
|
||||
<i className="fa fa-file-zip-o fa-fw"></i> ShapeFile (.SHP)
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
|
Ładowanie…
Reference in New Issue