-
-
Notifications
You must be signed in to change notification settings - Fork 979
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
588 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .plugin import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import os | ||
|
||
from rest_framework import status | ||
from rest_framework.response import Response | ||
from app.plugins.views import TaskView, CheckTask, GetTaskResult | ||
from app.plugins.worker import run_function_async | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
class ContoursException(Exception): | ||
pass | ||
|
||
def calc_contours(dem, epsg, interval, output_format, simplify, zfactor = 1): | ||
import os | ||
import subprocess | ||
import tempfile | ||
import shutil | ||
import glob | ||
from webodm import settings | ||
|
||
ext = "" | ||
if output_format == "GeoJSON": | ||
ext = "json" | ||
elif output_format == "GPKG": | ||
ext = "gpkg" | ||
elif output_format == "DXF": | ||
ext = "dxf" | ||
elif output_format == "ESRI Shapefile": | ||
ext = "shp" | ||
MIN_CONTOUR_LENGTH = 10 | ||
|
||
tmpdir = os.path.join(settings.MEDIA_TMP, os.path.basename(tempfile.mkdtemp('_contours', dir=settings.MEDIA_TMP))) | ||
gdal_contour_bin = shutil.which("gdal_contour") | ||
ogr2ogr_bin = shutil.which("ogr2ogr") | ||
|
||
if gdal_contour_bin is None: | ||
return {'error': 'Cannot find gdal_contour'} | ||
if ogr2ogr_bin is None: | ||
return {'error': 'Cannot find ogr2ogr'} | ||
|
||
contours_file = f"contours.gpkg" | ||
p = subprocess.Popen([gdal_contour_bin, "-q", "-a", "level", "-3d", "-f", "GPKG", "-i", str(interval), dem, contours_file], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
out, err = p.communicate() | ||
|
||
out = out.decode('utf-8').strip() | ||
err = err.decode('utf-8').strip() | ||
success = p.returncode == 0 | ||
|
||
if not success: | ||
return {'error', f'Error calling gdal_contour: {str(err)}'} | ||
|
||
outfile = os.path.join(tmpdir, f"output.{ext}") | ||
p = subprocess.Popen([ogr2ogr_bin, outfile, contours_file, "-simplify", str(simplify), "-f", output_format, "-t_srs", f"EPSG:{epsg}", "-nln", "contours", | ||
"-dialect", "sqlite", "-sql", f"SELECT ID, ROUND(level * {zfactor}, 5) AS level, GeomFromGML(AsGML(ATM_Transform(GEOM, ATM_Scale(ATM_Create(), 1, 1, {zfactor})), 10)) as GEOM FROM contour WHERE ST_Length(GEOM) >= {MIN_CONTOUR_LENGTH}"], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
out, err = p.communicate() | ||
|
||
out = out.decode('utf-8').strip() | ||
err = err.decode('utf-8').strip() | ||
success = p.returncode == 0 | ||
|
||
if not success: | ||
return {'error', f'Error calling ogr2ogr: {str(err)}'} | ||
|
||
if not os.path.isfile(outfile): | ||
return {'error': f'Cannot find output file: {outfile}'} | ||
|
||
if output_format == "ESRI Shapefile": | ||
ext="zip" | ||
shp_dir = os.path.join(tmpdir, "contours") | ||
os.makedirs(shp_dir) | ||
contour_files = glob.glob(os.path.join(tmpdir, "output.*")) | ||
for cf in contour_files: | ||
shutil.move(cf, shp_dir) | ||
|
||
shutil.make_archive(os.path.join(tmpdir, 'output'), 'zip', shp_dir) | ||
outfile = os.path.join(tmpdir, f"output.{ext}") | ||
|
||
return {'file': outfile} | ||
|
||
|
||
class TaskContoursGenerate(TaskView): | ||
def post(self, request, pk=None): | ||
task = self.get_and_check_task(request, pk) | ||
|
||
layer = request.data.get('layer', None) | ||
if layer == 'DSM' and task.dsm_extent is None: | ||
return Response({'error': _('No DSM layer is available.')}) | ||
elif layer == 'DTM' and task.dtm_extent is None: | ||
return Response({'error': _('No DTM layer is available.')}) | ||
|
||
try: | ||
if layer == 'DSM': | ||
dem = os.path.abspath(task.get_asset_download_path("dsm.tif")) | ||
elif layer == 'DTM': | ||
dem = os.path.abspath(task.get_asset_download_path("dtm.tif")) | ||
else: | ||
raise ContoursException('{} is not a valid layer.'.format(layer)) | ||
|
||
epsg = int(request.data.get('epsg', '3857')) | ||
interval = float(request.data.get('interval', 1)) | ||
format = request.data.get('format', 'GPKG') | ||
supported_formats = ['GPKG', 'ESRI Shapefile', 'DXF', 'GeoJSON'] | ||
if not format in supported_formats: | ||
raise ContoursException("Invalid format {} (must be one of: {})".format(format, ",".join(supported_formats))) | ||
simplify = float(request.data.get('simplify', 0.01)) | ||
zfactor = float(request.data.get('zfactor', 1)) | ||
|
||
celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify, zfactor).task_id | ||
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK) | ||
except ContoursException as e: | ||
return Response({'error': str(e)}, status=status.HTTP_200_OK) | ||
|
||
class TaskContoursCheck(CheckTask): | ||
def on_error(self, result): | ||
pass | ||
|
||
def error_check(self, result): | ||
contours_file = result.get('file') | ||
if not contours_file or not os.path.exists(contours_file): | ||
return _('Could not generate contour file. This might be a bug.') | ||
|
||
class TaskContoursDownload(GetTaskResult): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"name": "Object Detect", | ||
"webodmMinVersion": "2.5.8", | ||
"description": "Detect objects using AI in orthophotos", | ||
"version": "1.0.0", | ||
"author": "Piero Toffanin", | ||
"email": "pt@uav4geo.com", | ||
"repository": "https://github.com/OpenDroneMap/WebODM", | ||
"tags": ["object", "detect", "ai"], | ||
"homepage": "https://github.com/OpenDroneMap/WebODM", | ||
"experimental": false, | ||
"deprecated": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from app.plugins import PluginBase | ||
from app.plugins import MountPoint | ||
# from .api import TaskContoursGenerate | ||
# from .api import TaskContoursCheck | ||
# from .api import TaskContoursDownload | ||
|
||
|
||
class Plugin(PluginBase): | ||
def include_js_files(self): | ||
return ['main.js'] | ||
|
||
def build_jsx_components(self): | ||
return ['ObjDetect.jsx'] | ||
|
||
def api_mount_points(self): | ||
return [ | ||
# MountPoint('task/(?P<pk>[^/.]+)/contours/generate', TaskContoursGenerate.as_view()), | ||
# MountPoint('task/[^/.]+/contours/check/(?P<celery_task_id>.+)', TaskContoursCheck.as_view()), | ||
# MountPoint('task/[^/.]+/contours/download/(?P<celery_task_id>.+)', TaskContoursDownload.as_view()), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import L from 'leaflet'; | ||
import ReactDOM from 'ReactDOM'; | ||
import React from 'React'; | ||
import PropTypes from 'prop-types'; | ||
import './ObjDetect.scss'; | ||
import ObjDetectPanel from './ObjDetectPanel'; | ||
|
||
class ObjDetectButton extends React.Component { | ||
static propTypes = { | ||
tasks: PropTypes.object.isRequired, | ||
map: PropTypes.object.isRequired | ||
} | ||
|
||
constructor(props){ | ||
super(props); | ||
|
||
this.state = { | ||
showPanel: false | ||
}; | ||
} | ||
|
||
handleOpen = () => { | ||
this.setState({showPanel: true}); | ||
} | ||
|
||
handleClose = () => { | ||
this.setState({showPanel: false}); | ||
} | ||
|
||
render(){ | ||
const { showPanel } = this.state; | ||
|
||
return (<div className={showPanel ? "open" : ""}> | ||
<a href="javascript:void(0);" | ||
onClick={this.handleOpen} | ||
className="leaflet-control-objdetect-button leaflet-bar-part theme-secondary"></a> | ||
<ObjDetectPanel map={this.props.map} isShowed={showPanel} tasks={this.props.tasks} onClose={this.handleClose} /> | ||
</div>); | ||
} | ||
} | ||
|
||
export default L.Control.extend({ | ||
options: { | ||
position: 'topright' | ||
}, | ||
|
||
onAdd: function (map) { | ||
var container = L.DomUtil.create('div', 'leaflet-control-objdetect leaflet-bar leaflet-control'); | ||
L.DomEvent.disableClickPropagation(container); | ||
ReactDOM.render(<ObjDetectButton map={this.options.map} tasks={this.options.tasks} />, container); | ||
|
||
return container; | ||
} | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
.leaflet-control-objdetect{ | ||
z-index: 999 !important; | ||
|
||
a.leaflet-control-objdetect-button{ | ||
background: url(icon.svg) no-repeat 0 0; | ||
background-size: 26px 26px; | ||
border-radius: 2px; | ||
} | ||
|
||
div.objdetect-panel{ display: none; } | ||
|
||
.open{ | ||
a.leaflet-control-objdetect-button{ | ||
display: none; | ||
} | ||
|
||
div.objdetect-panel{ | ||
display: block; | ||
} | ||
} | ||
} | ||
.leaflet-touch .leaflet-control-objdetect a { | ||
background-position: 2px 2px; | ||
} |
Oops, something went wrong.