OpenDroneMap-WebODM/coreplugins/contours/public/ContoursPanel.jsx

358 wiersze
13 KiB
JavaScript

import React from 'react';
import PropTypes from 'prop-types';
import Storage from 'webodm/classes/Storage';
import L from 'leaflet';
import './ContoursPanel.scss';
import ErrorMessage from 'webodm/components/ErrorMessage';
import Workers from 'webodm/classes/Workers';
import { _ } from 'webodm/classes/gettext';
export default class ContoursPanel extends React.Component {
static defaultProps = {
};
static propTypes = {
onClose: PropTypes.func.isRequired,
tasks: PropTypes.object.isRequired,
isShowed: PropTypes.bool.isRequired,
map: PropTypes.object.isRequired
}
constructor(props){
super(props);
this.state = {
error: "",
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",
layers: [],
loading: true,
task: props.tasks[0] || null,
previewLoading: false,
exportLoading: false,
previewLayer: null,
};
}
componentDidUpdate(){
if (this.props.isShowed && this.state.loading){
const {id, project} = this.state.task;
this.loadingReq = $.getJSON(`/api/projects/${project}/tasks/${id}/`)
.done(res => {
const { available_assets } = res;
let layers = [];
if (available_assets.indexOf("dsm.tif") !== -1) layers.push("DSM");
if (available_assets.indexOf("dtm.tif") !== -1) layers.push("DTM");
if (layers.length > 0){
this.setState({layers, layer: layers[0]});
}else{
this.setState({permanentError: _("No DSM or DTM is available. To export contours, make sure to process a task with either the --dsm or --dtm option checked.")});
}
})
.fail(() => {
this.setState({permanentError: _("Cannot retrieve information for task. Are you are connected to the internet?")})
})
.always(() => {
this.setState({loading: false});
this.loadingReq = null;
});
}
}
componentWillUnmount(){
if (this.loadingReq){
this.loadingReq.abort();
this.loadingReq = null;
}
if (this.generateReq){
this.generateReq.abort();
this.generateReq = null;
}
}
handleSelectInterval = e => {
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});
}
handleChangeCustomInterval = e => {
this.setState({customInterval: e.target.value});
}
handleSelectEpsg = e => {
this.setState({epsg: e.target.value});
}
handleChangeCustomEpsg = e => {
this.setState({customEpsg: e.target.value});
}
getFormValues = () => {
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
};
}
addGeoJSONFromURL = (url, cb) => {
const { map } = this.props;
$.getJSON(url)
.done((geojson) => {
try{
this.handleRemovePreview();
this.setState({previewLayer: L.geoJSON(geojson, {
onEachFeature: (feature, layer) => {
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.state.previewLayer.addTo(map);
cb();
}catch(e){
cb(e.message);
}
})
.fail(cb);
}
handleRemovePreview = () => {
const { map } = this.props;
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`,
data: data
}).done(result => {
if (result.celery_task_id){
Workers.waitForCompletion(result.celery_task_id, error => {
if (error) this.setState({[loadingProp]: false, error});
else{
const fileUrl = `/api/plugins/contours/task/${taskId}/contours/download/${result.celery_task_id}`;
// Preview
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});
}
}
}, `/api/plugins/contours/task/${taskId}/contours/check/`);
}else if (result.error){
this.setState({[loadingProp]: false, error: result.error});
}else{
this.setState({[loadingProp]: false, error: "Invalid response: " + result});
}
}).fail(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, 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) ||
(simplify === "custom" && !customSimplify);
let content = "";
if (loading) content = (<span><i className="fa fa-circle-notch fa-spin"></i> {_("Loading")}</span>);
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 ">
<select className="form-control" value={interval} onChange={this.handleSelectInterval}>
{intervalValues.map(iv => <option value={iv}>{iv} {_("meter")}</option>)}
<option value="custom">{_("Custom")}</option>
</select>
</div>
</div>
{interval === "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={customInterval} onChange={this.handleChangeCustomInterval} /><span> {_("meter")}</span>
</div>
</div>
: ""}
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">{_("Layer:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={layer} onChange={this.handleSelectLayer}>
{layers.map(l => <option value={l}>{l}</option>)}
</select>
</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 ">
<select className="form-control" value={epsg} onChange={this.handleSelectEpsg}>
<option value="4326">WGS84 (EPSG:4326)</option>
<option value="3857">Web Mercator (EPSG:3857)</option>
<option value="custom">{_("Custom")} EPSG</option>
</select>
</div>
</div>
{epsg === "custom" ?
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">EPSG:</label>
<div className="col-sm-9 ">
<input type="number" className="form-control custom-interval" value={customEpsg} onChange={this.handleChangeCustomEpsg} />
</div>
</div>
: ""}
<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-notch"/> : <i className="glyphicon glyphicon-eye-open"/>} {_("Preview")}
</button>
<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-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="far fa-file 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="far fa-file-archive fa-fw"></i> ShapeFile (.SHP)
</a>
</li>
</ul>
</div>
</div>
</div>
</div>);
}
return (<div className="contours-panel">
<span className="close-button" onClick={this.props.onClose}/>
<div className="title">{_("Contours")}</div>
<hr/>
{content}
</div>);
}
}