Skip to content

Commit

Permalink
Merge pull request #1504 from OpenDroneMap/units
Browse files Browse the repository at this point in the history
Minor improvements to Units support
  • Loading branch information
pierotofy committed May 28, 2024
2 parents 044e139 + 741a327 commit f381f52
Show file tree
Hide file tree
Showing 24 changed files with 508 additions and 135 deletions.
5 changes: 5 additions & 0 deletions app/models/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,10 @@ def duplicate(self, set_new_name=True):
logger.warning("Task {} doesn't have folder, will skip copying".format(self))

self.project.owner.profile.clear_used_quota_cache()

from app.plugins import signals as plugin_signals
plugin_signals.task_duplicated.send_robust(sender=self.__class__, task_id=task.id)

return task
except Exception as e:
logger.warning("Cannot duplicate task: {}".format(str(e)))
Expand Down Expand Up @@ -936,6 +940,7 @@ def get_map_items(self):
'meta': {
'task': {
'id': str(self.id),
'name': self.name,
'project': self.project.id,
'public': self.public,
'camera_shots': camera_shots,
Expand Down
1 change: 1 addition & 0 deletions app/plugins/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
task_removed = django.dispatch.Signal(providing_args=["task_id"])
task_failed = django.dispatch.Signal(providing_args=["task_id"])
task_resizing_images = django.dispatch.Signal(providing_args=["task_id"])
task_duplicated = django.dispatch.Signal(providing_args=["task_id"])

processing_node_removed = django.dispatch.Signal(providing_args=["processing_node_id"])
7 changes: 5 additions & 2 deletions app/static/app/js/MapView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ class MapView extends React.Component {
selectedMapType: 'auto',
title: "",
public: false,
shareButtons: true
shareButtons: true,
permissions: ["view"]
};

static propTypes = {
mapItems: PropTypes.array.isRequired, // list of dictionaries where each dict is a {mapType: 'orthophoto', url: <tiles.json>},
selectedMapType: PropTypes.oneOf(['auto', 'orthophoto', 'plant', 'dsm', 'dtm']),
title: PropTypes.string,
public: PropTypes.bool,
shareButtons: PropTypes.bool
shareButtons: PropTypes.bool,
permissions: PropTypes.array
};

constructor(props){
Expand Down Expand Up @@ -130,6 +132,7 @@ class MapView extends React.Component {
mapType={this.state.selectedMapType}
public={this.props.public}
shareButtons={this.props.shareButtons}
permissions={this.props.permissions}
/>
</div>
</div>);
Expand Down
17 changes: 17 additions & 0 deletions app/static/app/js/classes/Units.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class UnitSystem{
volumeUnit(cbmeters, opts = {}){ throw new Error("Not implemented"); }

getName(){ throw new Error("Not implemented"); }
getKey(){ throw new Error("Not implemented"); }

area(sqmeters, opts = {}){
sqmeters = parseFloat(sqmeters);
Expand All @@ -187,6 +188,10 @@ class UnitSystem{
return new ValueUnit(val, unit);
}

elevation(meters){
return this.length(meters, { fixedUnit: true });
}

length(meters, opts = {}){
meters = parseFloat(meters);
if (isNaN(meters)) return NanUnit();
Expand Down Expand Up @@ -233,6 +238,10 @@ class MetricSystem extends UnitSystem{
return _("Metric");
}

getKey(){
return "metric";
}

lengthUnit(meters, opts = {}){
if (opts.fixedUnit) return units.meters;

Expand All @@ -259,6 +268,10 @@ class ImperialSystem extends UnitSystem{
return _("Imperial");
}

getKey(){
return "imperial";
}

feet(){
return units.feet;
}
Expand Down Expand Up @@ -310,6 +323,10 @@ class ImperialUSSystem extends ImperialSystem{
return _("Imperial (US)");
}

getKey(){
return "imperialUS";
}

feet(){
return units.feet_us;
}
Expand Down
12 changes: 12 additions & 0 deletions app/static/app/js/classes/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ export default {

isMobile: function(){
return navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i);
},

userInputToFilename(text, extension = ""){
return text.replace(/[^0-9a-zA-Z-_]+/g, '').replace(/(\s|\/)+/g, "-") + extension;
},

uuidv4(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
var r = Math.random() * 16 | 0,
v = c === 'x' ? r : r & 0x3 | 0x8;
return v.toString(16);
});
}
};

8 changes: 7 additions & 1 deletion app/static/app/js/classes/plugins/Map.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ export default {
],

functions: [
"handleClick"
"handleClick",
"addAnnotation",
"updateAnnotation",
"deleteAnnotation",
"toggleAnnotation",
"annotationDeleted",
"downloadAnnotations"
]
};

9 changes: 5 additions & 4 deletions app/static/app/js/components/LayersControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class LayersControlButton extends React.Component {
static propTypes = {
layers: PropTypes.array.isRequired,
overlays: PropTypes.array.isRequired,
annotations: PropTypes.array.isRequired,
map: PropTypes.object.isRequired
}

Expand Down Expand Up @@ -36,7 +37,7 @@ class LayersControlButton extends React.Component {
title="Layers"
onClick={this.handleOpen}
className="leaflet-control-layers-control-button leaflet-bar-part theme-secondary"></a>
<LayersControlPanel map={this.props.map} layers={this.props.layers} overlays={this.props.overlays} onClose={this.handleClose} />
<LayersControlPanel map={this.props.map} layers={this.props.layers} overlays={this.props.overlays} annotations={this.props.annotations} onClose={this.handleClose} />
</div>);
}
}
Expand All @@ -51,13 +52,13 @@ export default L.Control.extend({
this.map = map;

L.DomEvent.disableClickPropagation(this.container);
this.update(this.options.layers, []);
this.update(this.options.layers, [], []);

return this.container;
},

update: function(layers, overlays){
ReactDOM.render(<LayersControlButton map={this.map} layers={layers} overlays={overlays}/>, this.container);
update: function(layers, overlays, annotations){
ReactDOM.render(<LayersControlButton map={this.map} layers={layers} overlays={overlays} annotations={annotations} />, this.container);
}
});

158 changes: 158 additions & 0 deletions app/static/app/js/components/LayersControlAnnotations.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React from 'react';
import PropTypes from 'prop-types';
import '../css/LayersControlAnnotations.scss';
import PluginsAPI from '../classes/plugins/API';
import { Checkbox, ExpandButton } from './Toggle';
import { _ } from '../classes/gettext';

class AnnotationLayer extends React.Component{
static propTypes = {
parent: PropTypes.object,
layer: PropTypes.object
}

constructor(props){
super(props);

this.state = {
visible: !!props.layer._map
}
}

componentDidUpdate(prevProps, prevState){
if (prevState.visible !== this.state.visible && this.props.parent.state.visible){
PluginsAPI.Map.toggleAnnotation(this.props.layer, this.state.visible);
}
}

componentDidMount(){
PluginsAPI.Map.onUpdateAnnotation(this.handleUpdate);
}

componentWillUnmount(){
PluginsAPI.Map.offUpdateAnnotation(this.handleUpdate);
}

handleUpdate = (layer, name) => {
if (this.props.layer === layer){
const meta = layer[Symbol.for("meta")];
meta.name = name;
this.forceUpdate();
}
}

handleFocus = () => {
const { layer } = this.props;
if (!layer._map) return;

if (layer.options.bounds || layer.getBounds){
const bounds = layer.options.bounds !== undefined ?
layer.options.bounds :
layer.getBounds();
layer._map.fitBounds(bounds);
}else if (layer._latlng){
layer._map.setView(layer._latlng, 22);
}

// if (layer.getPopup()) layer.openPopup();
}

handleDelete = () => {
if (window.confirm(_('Are you sure you want to delete this?'))){
PluginsAPI.Map.deleteAnnotation(this.props.layer);
}
}

render(){
const { layer } = this.props;
const meta = layer[Symbol.for("meta")];

return (<div className="layers-control-layer layers-control-annotations">
<div className="layer-control-title">
<Checkbox bind={[this, 'visible']}/> <a className="layer-label" href="javascript:void(0)" onClick={this.handleFocus}><div className="annotation-name">{meta.name}</div></a>
<a className="layer-action" href="javascript:void(0)" onClick={this.handleDelete} title={_("Delete")}><i className="fa fa-trash"></i></a>
</div>
</div>);
}
}

export default class LayersControlAnnotations extends React.Component {
static defaultProps = {
expanded: true,
visible: true,
layers: []
};

static propTypes = {
expanded: PropTypes.bool,
visible: PropTypes.bool,
layers: PropTypes.array
}

constructor(props){
super(props);

let visible = false;
for (let i = 0; i < props.layers.length; i++){
if (props.layers[i]._map){
visible = true;
break;
}
}

this.state = {
visible,
expanded: props.expanded
};

this.annRefs = new Array(props.layers.length);
}

handleAnnotationsClick = () => {
this.setState({expanded: !this.state.expanded});
}

componentDidUpdate(prevProps, prevState){
if (prevState.visible !== this.state.visible){
this.annRefs.forEach(ann => {
let visible = this.state.visible ? ann.state.visible : false;
PluginsAPI.Map.toggleAnnotation(ann.props.layer, visible);
});
}
}

handleExportGeoJSON = e => {
if (PluginsAPI.Map.downloadAnnotations("geojson")) return;
else{
// TODO?
}
}

handleDelete = () => {
if (window.confirm(_('Are you sure you want to delete this?'))){
this.props.layers.forEach(layer => {
PluginsAPI.Map.deleteAnnotation(layer);
});
}
}


render(){
const { layers } = this.props;


return (<div className="layers-control-layer">
<div className="layer-control-title">
<ExpandButton bind={[this, 'expanded']} /><Checkbox bind={[this, 'visible']} className="annotation-toggle" />
<a title={_("Annotations")} className="layer-label" href="javascript:void(0);" onClick={this.handleAnnotationsClick}><div className="layer-title"><i className="layer-icon fa fa-sticky-note fa-fw"></i> {_("Annotations")}</div></a>
<a className="layer-action" href="javascript:void(0)" onClick={this.handleExportGeoJSON}><i title={_("Export to GeoJSON")} className="fa fa-download"></i></a>
<a className="layer-action" href="javascript:void(0)" onClick={this.handleDelete}><i title={_("Delete")} className="fa fa-trash"></i></a>
</div>

<div className={"layer-expanded " + (!this.state.expanded ? "hide" : "")}>
{layers.map((layer, i) => <AnnotationLayer parent={this} ref={domNode => this.annRefs[i] = domNode} key={i} layer={layer} />)}
</div>
</div>);

}
}
16 changes: 13 additions & 3 deletions app/static/app/js/components/LayersControlLayer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export default class LayersControlLayer extends React.Component {
}
}

handleLayerClick = () => {
handleZoomToClick = () => {
const { layer } = this.props;

const bounds = layer.options.bounds !== undefined ?
Expand All @@ -120,6 +120,14 @@ export default class LayersControlLayer extends React.Component {
if (layer.getPopup()) layer.openPopup();
}

handleLayerClick = () => {
if (this.props.overlay){
this.setState({visible: !this.state.visible});
}else{
this.setState({expanded: !this.state.expanded});
}
}

handleSelectColor = e => {
this.setState({colorMap: e.target.value});
}
Expand Down Expand Up @@ -287,8 +295,10 @@ export default class LayersControlLayer extends React.Component {
}

return (<div className="layers-control-layer">
{!this.props.overlay ? <ExpandButton bind={[this, 'expanded']} /> : <div className="overlayIcon"><i className={meta.icon || "fa fa-vector-square fa-fw"}></i></div>}<Checkbox bind={[this, 'visible']}/>
<a title={meta.name} className="layer-label" href="javascript:void(0);" onClick={this.handleLayerClick}>{meta.name}</a>
<div className="layer-control-title">
{!this.props.overlay ? <ExpandButton bind={[this, 'expanded']} /> : <div className="paddingSpace"></div>}<Checkbox bind={[this, 'visible']}/>
<a title={meta.name} className="layer-label" href="javascript:void(0);" onClick={this.handleLayerClick}><i className={"layer-icon " + (meta.icon || "fa fa-vector-square fa-fw")}></i><div className="layer-title">{meta.name}</div></a> <a className="layer-action" href="javascript:void(0)" onClick={this.handleZoomToClick}><i title={_("Zoom To")} className="fa fa-expand"></i></a>
</div>

{this.state.expanded ?
<div className="layer-expanded">
Expand Down
Loading

0 comments on commit f381f52

Please sign in to comment.