Contours plugin working, simplification parameter addition

pull/639/head
Piero Toffanin 2019-04-02 14:40:21 -04:00
rodzic 305b4257bd
commit 133c382a77
4 zmienionych plików z 157 dodań i 66 usunięć

Wyświetl plik

@ -112,6 +112,7 @@ class GrassContext:
}
def cleanup(self):
return
if os.path.exists(self.get_cwd()):
shutil.rmtree(self.get_cwd())

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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>);