2019-03-29 17:12:43 +00:00
import React from 'react' ;
import PropTypes from 'prop-types' ;
2019-03-29 20:12:00 +00:00
import Storage from 'webodm/classes/Storage' ;
2019-03-29 17:12:43 +00:00
import L from 'leaflet' ;
import './ContoursPanel.scss' ;
2019-03-30 23:07:24 +00:00
import ErrorMessage from 'webodm/components/ErrorMessage' ;
2020-01-17 19:55:37 +00:00
import Workers from 'webodm/classes/Workers' ;
2020-12-21 19:18:43 +00:00
import { _ } from 'webodm/classes/gettext' ;
2024-05-09 19:46:30 +00:00
import { systems , getUnitSystem , onUnitSystemChanged , offUnitSystemChanged , toMetric } from 'webodm/classes/Units' ;
2019-03-29 17:12:43 +00:00
export default class ContoursPanel extends React . Component {
static defaultProps = {
} ;
static propTypes = {
2019-03-30 23:07:24 +00:00
onClose : PropTypes . func . isRequired ,
tasks : PropTypes . object . isRequired ,
2019-04-01 20:49:56 +00:00
isShowed : PropTypes . bool . isRequired ,
map : PropTypes . object . isRequired
2019-03-29 17:12:43 +00:00
}
constructor ( props ) {
super ( props ) ;
2024-05-09 19:46:30 +00:00
const unitSystem = getUnitSystem ( ) ;
const defaultInterval = unitSystem === "metric" ? "1" : "4" ;
const defaultSimplify = unitSystem === "metric" ? "0.2" : "0.6" ;
// Remove legacy parameters
Storage . removeItem ( "last_contours_interval" ) ;
Storage . removeItem ( "last_contours_custom_interval" ) ;
Storage . removeItem ( "last_contours_simplify" ) ;
Storage . removeItem ( "last_contours_custom_simplify" ) ;
2019-03-29 17:12:43 +00:00
this . state = {
2019-03-29 20:12:00 +00:00
error : "" ,
2019-03-30 23:07:24 +00:00
permanentError : "" ,
2024-05-09 19:46:30 +00:00
interval : Storage . getItem ( "last_contours_interval_" + unitSystem ) || defaultInterval ,
customInterval : Storage . getItem ( "last_contours_custom_interval_" + unitSystem ) || defaultInterval ,
simplify : Storage . getItem ( "last_contours_simplify_" + unitSystem ) || defaultSimplify ,
customSimplify : Storage . getItem ( "last_contours_custom_simplify_" + unitSystem ) || defaultSimplify ,
2019-03-29 20:12:00 +00:00
layer : "" ,
2019-03-30 23:07:24 +00:00
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 ,
2019-04-02 18:40:21 +00:00
exportLoading : false ,
previewLayer : null ,
2024-05-09 19:46:30 +00:00
unitSystem
2019-03-29 17:12:43 +00:00
} ;
}
2024-05-09 19:46:30 +00:00
componentDidMount ( ) {
onUnitSystemChanged ( this . unitsChanged ) ;
}
2019-03-30 23:07:24 +00:00
componentDidUpdate ( ) {
if ( this . props . isShowed && this . state . loading ) {
const { id , project } = this . state . task ;
2024-05-09 19:46:30 +00:00
2019-03-30 23:07:24 +00:00
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 {
2020-12-21 19:18:43 +00:00
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." ) } ) ;
2019-03-30 23:07:24 +00:00
}
} )
. fail ( ( ) => {
2020-12-21 19:18:43 +00:00
this . setState ( { permanentError : _ ( "Cannot retrieve information for task. Are you are connected to the internet?" ) } )
2019-03-30 23:07:24 +00:00
} )
. always ( ( ) => {
this . setState ( { loading : false } ) ;
this . loadingReq = null ;
} ) ;
}
2019-03-29 17:12:43 +00:00
}
componentWillUnmount ( ) {
2019-03-30 23:07:24 +00:00
if ( this . loadingReq ) {
this . loadingReq . abort ( ) ;
this . loadingReq = null ;
}
2019-04-01 20:49:56 +00:00
if ( this . generateReq ) {
this . generateReq . abort ( ) ;
this . generateReq = null ;
}
2024-05-09 19:46:30 +00:00
offUnitSystemChanged ( this . unitsChanged ) ;
}
unitsChanged = e => {
this . saveInputValues ( ) ;
const unitSystem = e . detail ;
const defaultInterval = unitSystem === "metric" ? "1" : "4" ;
const defaultSimplify = unitSystem === "metric" ? "0.2" : "0.5" ;
const interval = Storage . getItem ( "last_contours_interval_" + unitSystem ) || defaultInterval ;
const customInterval = Storage . getItem ( "last_contours_custom_interval_" + unitSystem ) || defaultInterval ;
const simplify = Storage . getItem ( "last_contours_simplify_" + unitSystem ) || defaultSimplify ;
const customSimplify = Storage . getItem ( "last_contours_custom_simplify_" + unitSystem ) || defaultSimplify ;
this . setState ( { unitSystem , interval , customInterval , simplify , customSimplify } ) ;
2019-03-29 17:12:43 +00:00
}
2019-03-29 20:12:00 +00:00
handleSelectInterval = e => {
this . setState ( { interval : e . target . value } ) ;
}
2019-04-02 18:40:21 +00:00
handleSelectSimplify = e => {
this . setState ( { simplify : e . target . value } ) ;
}
handleChangeCustomSimplify = e => {
this . setState ( { customSimplify : e . target . value } ) ;
}
2019-03-29 20:12:00 +00:00
handleSelectLayer = e => {
this . setState ( { layer : e . target . value } ) ;
}
handleChangeCustomInterval = e => {
this . setState ( { customInterval : e . target . value } ) ;
}
2019-03-30 23:07:24 +00:00
handleSelectEpsg = e => {
2019-04-01 20:49:56 +00:00
this . setState ( { epsg : e . target . value } ) ;
2019-03-30 23:07:24 +00:00
}
handleChangeCustomEpsg = e => {
this . setState ( { customEpsg : e . target . value } ) ;
}
2024-05-10 17:44:12 +00:00
getFormValues = ( preview ) => {
2019-04-02 18:40:21 +00:00
const { interval , customInterval , epsg , customEpsg ,
2024-05-09 19:46:30 +00:00
simplify , customSimplify , layer , unitSystem } = this . state ;
const su = systems [ unitSystem ] ;
let meterInterval = interval !== "custom" ? interval : customInterval ;
let meterSimplify = simplify !== "custom" ? simplify : customSimplify ;
meterInterval = toMetric ( meterInterval , su . lengthUnit ( 1 ) ) . value ;
meterSimplify = toMetric ( meterSimplify , su . lengthUnit ( 1 ) ) . value ;
2024-05-10 17:44:12 +00:00
const zfactor = preview ? 1 : su . lengthUnit ( 1 ) . factor ;
2024-05-09 19:46:30 +00:00
2019-03-30 23:07:24 +00:00
return {
2024-05-09 19:46:30 +00:00
interval : meterInterval ,
2019-03-30 23:07:24 +00:00
epsg : epsg !== "custom" ? epsg : customEpsg ,
2024-05-09 19:46:30 +00:00
simplify : meterSimplify ,
2024-05-10 17:44:12 +00:00
zfactor ,
2019-03-30 23:07:24 +00:00
layer
} ;
2019-03-29 20:12:00 +00:00
}
2019-04-01 20:49:56 +00:00
addGeoJSONFromURL = ( url , cb ) => {
const { map } = this . props ;
2024-05-09 19:46:30 +00:00
const us = systems [ this . state . unitSystem ] ;
2019-04-01 20:49:56 +00:00
$ . getJSON ( url )
. done ( ( geojson ) => {
try {
2019-04-02 18:40:21 +00:00
this . handleRemovePreview ( ) ;
2019-04-01 20:49:56 +00:00
2019-04-02 18:40:21 +00:00
this . setState ( { previewLayer : L . geoJSON ( geojson , {
2019-04-01 20:49:56 +00:00
onEachFeature : ( feature , layer ) => {
2019-04-02 18:40:21 +00:00
if ( feature . properties && feature . properties . level !== undefined ) {
2024-05-16 18:00:46 +00:00
layer . bindPopup ( ` <div style="margin-right: 32px;"><b> ${ _ ( "Elevation:" ) } </b> ${ us . elevation ( feature . properties . level ) } </div> ` ) ;
2019-04-01 20:49:56 +00:00
}
} ,
style : feature => {
// TODO: different colors for different elevations?
return { color : "yellow" } ;
}
2019-04-02 18:40:21 +00:00
} ) } ) ;
this . state . previewLayer . addTo ( map ) ;
2019-04-01 20:49:56 +00:00
cb ( ) ;
} catch ( e ) {
cb ( e . message ) ;
}
} )
. fail ( cb ) ;
}
2019-04-02 18:40:21 +00:00
handleRemovePreview = ( ) => {
const { map } = this . props ;
2019-03-30 23:07:24 +00:00
2019-04-02 18:40:21 +00:00
if ( this . state . previewLayer ) {
map . removeLayer ( this . state . previewLayer ) ;
this . setState ( { previewLayer : null } ) ;
}
}
2024-05-09 19:46:30 +00:00
saveInputValues = ( ) => {
const us = this . state . unitSystem ;
// Save settings
Storage . setItem ( "last_contours_interval_" + us , this . state . interval ) ;
Storage . setItem ( "last_contours_custom_interval_" + us , this . state . customInterval ) ;
Storage . setItem ( "last_contours_simplify_" + us , this . state . simplify ) ;
Storage . setItem ( "last_contours_custom_simplify_" + us , this . state . customSimplify ) ;
Storage . setItem ( "last_contours_epsg" , this . state . epsg ) ;
Storage . setItem ( "last_contours_custom_epsg" , this . state . customEpsg ) ;
}
2019-04-02 18:40:21 +00:00
generateContours = ( data , loadingProp , isPreview ) => {
this . setState ( { [ loadingProp ] : true , error : "" } ) ;
2019-04-01 20:49:56 +00:00
const taskId = this . state . task . id ;
2024-05-09 19:46:30 +00:00
this . saveInputValues ( ) ;
2019-03-30 23:07:24 +00:00
2019-04-01 20:49:56 +00:00
this . generateReq = $ . ajax ( {
2019-03-30 23:07:24 +00:00
type : 'POST' ,
2019-04-01 20:49:56 +00:00
url : ` /api/plugins/contours/task/ ${ taskId } /contours/generate ` ,
2019-03-30 23:07:24 +00:00
data : data
} ) . done ( result => {
if ( result . celery _task _id ) {
2020-01-17 19:55:37 +00:00
Workers . waitForCompletion ( result . celery _task _id , error => {
2019-04-02 18:40:21 +00:00
if ( error ) this . setState ( { [ loadingProp ] : false , error } ) ;
2019-04-01 20:49:56 +00:00
else {
const fileUrl = ` /api/plugins/contours/task/ ${ taskId } /contours/download/ ${ result . celery _task _id } ` ;
// Preview
2019-04-02 18:40:21 +00:00
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 } ) ;
}
2019-04-01 20:49:56 +00:00
}
2020-01-17 19:55:37 +00:00
} , ` /api/plugins/contours/task/ ${ taskId } /contours/check/ ` ) ;
2019-03-30 23:07:24 +00:00
} else if ( result . error ) {
2019-04-02 18:40:21 +00:00
this . setState ( { [ loadingProp ] : false , error : result . error } ) ;
2019-03-30 23:07:24 +00:00
} else {
2019-04-02 18:40:21 +00:00
this . setState ( { [ loadingProp ] : false , error : "Invalid response: " + result } ) ;
2019-03-30 23:07:24 +00:00
}
} ) . fail ( error => {
2019-04-02 18:40:21 +00:00
this . setState ( { [ loadingProp ] : false , error : JSON . stringify ( error ) } ) ;
2019-03-30 23:07:24 +00:00
} ) ;
2019-03-29 20:12:00 +00:00
}
2019-04-02 18:40:21 +00:00
handleExport = ( format ) => {
return ( ) => {
2024-05-10 17:44:12 +00:00
const data = this . getFormValues ( false ) ;
2019-04-02 18:40:21 +00:00
data . format = format ;
this . generateContours ( data , 'exportLoading' , false ) ;
} ;
}
handleShowPreview = ( ) => {
this . setState ( { previewLoading : true } ) ;
2024-05-10 17:44:12 +00:00
const data = this . getFormValues ( true ) ;
2019-04-02 18:40:21 +00:00
data . epsg = 4326 ;
data . format = "GeoJSON" ;
this . generateContours ( data , 'previewLoading' , true ) ;
}
2019-03-29 17:12:43 +00:00
render ( ) {
2019-03-30 23:07:24 +00:00
const { loading , task , layers , error , permanentError , interval , customInterval , layer ,
2019-04-02 18:40:21 +00:00
epsg , customEpsg , exportLoading ,
simplify , customSimplify ,
2024-05-09 19:46:30 +00:00
previewLoading , previewLayer , unitSystem } = this . state ;
const us = systems [ unitSystem ] ;
const lengthUnit = us . lengthUnit ( 1 ) ;
const intervalStart = unitSystem === "metric" ? 1 : 4 ;
const intervalValues = [ intervalStart / 4 , intervalStart / 2 , intervalStart , intervalStart * 2 , intervalStart * 4 ] ;
2020-12-21 19:18:43 +00:00
const simplifyValues = [ { label : _ ( 'Do not simplify' ) , value : 0 } ,
2024-05-09 19:46:30 +00:00
{ label : _ ( 'Normal' ) , value : unitSystem === "metric" ? 0.2 : 0.5 } ,
{ label : _ ( 'Aggressive' ) , value : unitSystem === "metric" ? 1 : 4 } ] ;
2019-03-29 17:12:43 +00:00
2019-03-30 23:07:24 +00:00
const disabled = ( interval === "custom" && ! customInterval ) ||
2019-04-02 18:40:21 +00:00
( epsg === "custom" && ! customEpsg ) ||
( simplify === "custom" && ! customSimplify ) ;
2019-03-29 20:12:00 +00:00
2019-03-30 23:07:24 +00:00
let content = "" ;
2020-12-21 20:20:46 +00:00
if ( loading ) content = ( < span > < i className = "fa fa-circle-notch fa-spin" > < / i > { _ ( "Loading…" ) } < / span > ) ;
2019-03-30 23:07:24 +00:00
else if ( permanentError ) content = ( < div className = "alert alert-warning" > { permanentError } < / div > ) ;
else {
content = ( < div >
2019-04-02 18:40:21 +00:00
< ErrorMessage bind = { [ this , "error" ] } / >
2019-03-29 20:12:00 +00:00
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Interval:" ) } < / label >
2019-03-29 20:12:00 +00:00
< div className = "col-sm-9 " >
2019-03-30 23:07:24 +00:00
< select className = "form-control" value = { interval } onChange = { this . handleSelectInterval } >
2024-05-09 19:46:30 +00:00
{ intervalValues . map ( iv => < option value = { iv } > { iv } { lengthUnit . label } < / option > ) }
2020-12-21 19:18:43 +00:00
< option value = "custom" > { _ ( "Custom" ) } < / option >
2019-03-30 23:07:24 +00:00
< / select >
2019-03-29 20:12:00 +00:00
< / div >
< / div >
2019-03-30 23:07:24 +00:00
{ interval === "custom" ?
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Value:" ) } < / label >
2019-03-30 23:07:24 +00:00
< div className = "col-sm-9 " >
2024-05-09 19:46:30 +00:00
< input type = "number" className = "form-control custom-interval" value = { customInterval } onChange = { this . handleChangeCustomInterval } / > < span > { lengthUnit . label } < / span >
2019-03-30 23:07:24 +00:00
< / div >
< / div >
: "" }
2019-03-29 20:12:00 +00:00
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Layer:" ) } < / label >
2019-03-29 20:12:00 +00:00
< div className = "col-sm-9 " >
2019-03-30 23:07:24 +00:00
< select className = "form-control" value = { layer } onChange = { this . handleSelectLayer } >
{ layers . map ( l => < option value = { l } > { l } < / option > ) }
< / select >
2019-03-29 20:12:00 +00:00
< / div >
< / div >
2019-04-02 18:40:21 +00:00
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Simplify:" ) } < / label >
2019-04-02 18:40:21 +00:00
< div className = "col-sm-9 " >
< select className = "form-control" value = { simplify } onChange = { this . handleSelectSimplify } >
2024-05-09 19:46:30 +00:00
{ simplifyValues . map ( sv => < option value = { sv . value } > { sv . label } ( { sv . value } { lengthUnit . label } ) < / option > ) }
2020-12-21 19:18:43 +00:00
< option value = "custom" > { _ ( "Custom" ) } < / option >
2019-04-02 18:40:21 +00:00
< / select >
< / div >
< / div >
{ simplify === "custom" ?
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Value:" ) } < / label >
2019-04-02 18:40:21 +00:00
< div className = "col-sm-9 " >
2024-05-09 19:46:30 +00:00
< input type = "number" className = "form-control custom-interval" value = { customSimplify } onChange = { this . handleChangeCustomSimplify } / > < span > { lengthUnit . label } < / span >
2019-04-02 18:40:21 +00:00
< / div >
< / div >
: "" }
2019-03-30 23:07:24 +00:00
< div className = "row form-group form-inline" >
2020-12-21 19:18:43 +00:00
< label className = "col-sm-3 control-label" > { _ ( "Projection:" ) } < / label >
2019-03-30 23:07:24 +00:00
< div className = "col-sm-9 " >
< select className = "form-control" value = { epsg } onChange = { this . handleSelectEpsg } >
2021-11-01 16:26:04 +00:00
< option value = "4326" > { _ ( "Lat/Lon" ) } ( EPSG : 4326 ) < / option >
< option value = "3857" > { _ ( "Web Mercator" ) } ( EPSG : 3857 ) < / option >
2020-12-21 19:18:43 +00:00
< option value = "custom" > { _ ( "Custom" ) } EPSG < / option >
2019-03-30 23:07:24 +00:00
< / 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 >
: "" }
2019-03-29 20:12:00 +00:00
2019-04-02 18:40:21 +00:00
< 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" >
2020-12-21 19:18:43 +00:00
{ previewLoading ? < i className = "fa fa-spin fa-circle-notch" / > : < i className = "glyphicon glyphicon-eye-open" / > } { _ ( "Preview" ) }
2019-03-30 23:07:24 +00:00
< / button >
2019-04-02 18:40:21 +00:00
< div className = "btn-group" >
< button disabled = { disabled || exportLoading } type = "button" className = "btn btn-sm btn-primary" data - toggle = "dropdown" >
2020-12-21 19:18:43 +00:00
{ exportLoading ? < i className = "fa fa-spin fa-circle-notch" / > : < i className = "glyphicon glyphicon-download" / > } { _ ( "Export" ) }
2019-04-02 18:40:21 +00:00
< / 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" ) } >
2019-11-14 22:12:55 +00:00
< i className = "far fa-file fa-fw" > < / i > AutoCAD ( . DXF )
2019-04-02 18:40:21 +00:00
< / 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" ) } >
2019-11-14 22:12:55 +00:00
< i className = "far fa-file-archive fa-fw" > < / i > ShapeFile ( . SHP )
2019-04-02 18:40:21 +00:00
< / a >
< / li >
< / ul >
< / div >
2019-03-30 23:07:24 +00:00
< / div >
2019-03-29 20:12:00 +00:00
< / div >
2019-03-30 23:07:24 +00:00
< / div > ) ;
}
2019-03-29 20:12:00 +00:00
2019-03-30 23:07:24 +00:00
return ( < div className = "contours-panel" >
< span className = "close-button" onClick = { this . props . onClose } / >
2020-12-21 19:18:43 +00:00
< div className = "title" > { _ ( "Contours" ) } < / div >
2019-03-30 23:07:24 +00:00
< hr / >
{ content }
2019-03-29 17:12:43 +00:00
< / div > ) ;
}
2020-12-21 20:20:46 +00:00
}