kopia lustrzana https://github.com/OpenDroneMap/WebODM
commit
9b49ad777d
|
@ -3,10 +3,80 @@ import os
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from app.plugins.views import TaskView, CheckTask, GetTaskResult
|
from app.plugins.views import TaskView, CheckTask, GetTaskResult
|
||||||
from worker.tasks import execute_grass_script
|
from app.plugins.worker import run_function_async
|
||||||
from app.plugins.grass_engine import grass, GrassEngineException, cleanup_grass_context
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
class ContoursException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def calc_contours(dem, epsg, interval, output_format, simplify):
|
||||||
|
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 * 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):
|
class TaskContoursGenerate(TaskView):
|
||||||
def post(self, request, pk=None):
|
def post(self, request, pk=None):
|
||||||
task = self.get_and_check_task(request, pk)
|
task = self.get_and_check_task(request, pk)
|
||||||
|
@ -23,36 +93,24 @@ class TaskContoursGenerate(TaskView):
|
||||||
elif layer == 'DTM':
|
elif layer == 'DTM':
|
||||||
dem = os.path.abspath(task.get_asset_download_path("dtm.tif"))
|
dem = os.path.abspath(task.get_asset_download_path("dtm.tif"))
|
||||||
else:
|
else:
|
||||||
raise GrassEngineException('{} is not a valid layer.'.format(layer))
|
raise ContoursException('{} is not a valid layer.'.format(layer))
|
||||||
|
|
||||||
context = grass.create_context({'auto_cleanup' : False})
|
|
||||||
epsg = int(request.data.get('epsg', '3857'))
|
epsg = int(request.data.get('epsg', '3857'))
|
||||||
interval = float(request.data.get('interval', 1))
|
interval = float(request.data.get('interval', 1))
|
||||||
format = request.data.get('format', 'GPKG')
|
format = request.data.get('format', 'GPKG')
|
||||||
supported_formats = ['GPKG', 'ESRI Shapefile', 'DXF', 'GeoJSON']
|
supported_formats = ['GPKG', 'ESRI Shapefile', 'DXF', 'GeoJSON']
|
||||||
if not format in supported_formats:
|
if not format in supported_formats:
|
||||||
raise GrassEngineException("Invalid format {} (must be one of: {})".format(format, ",".join(supported_formats)))
|
raise ContoursException("Invalid format {} (must be one of: {})".format(format, ",".join(supported_formats)))
|
||||||
simplify = float(request.data.get('simplify', 0.01))
|
simplify = float(request.data.get('simplify', 0.01))
|
||||||
|
|
||||||
context.add_param('dem_file', dem)
|
celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify).task_id
|
||||||
context.add_param('interval', interval)
|
|
||||||
context.add_param('format', format)
|
|
||||||
context.add_param('simplify', simplify)
|
|
||||||
context.add_param('epsg', epsg)
|
|
||||||
context.set_location(dem)
|
|
||||||
|
|
||||||
celery_task_id = execute_grass_script.delay(os.path.join(
|
|
||||||
os.path.dirname(os.path.abspath(__file__)),
|
|
||||||
"calc_contours.py"
|
|
||||||
), context.serialize(), 'file').task_id
|
|
||||||
|
|
||||||
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
|
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
|
||||||
except GrassEngineException as e:
|
except ContoursException as e:
|
||||||
return Response({'error': str(e)}, status=status.HTTP_200_OK)
|
return Response({'error': str(e)}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
class TaskContoursCheck(CheckTask):
|
class TaskContoursCheck(CheckTask):
|
||||||
def on_error(self, result):
|
def on_error(self, result):
|
||||||
cleanup_grass_context(result['context'])
|
pass
|
||||||
|
|
||||||
def error_check(self, result):
|
def error_check(self, result):
|
||||||
contours_file = result.get('file')
|
contours_file = result.get('file')
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
#%module
|
|
||||||
#% description: Calculate contours
|
|
||||||
#%end
|
|
||||||
#%option
|
|
||||||
#% key: dem_file
|
|
||||||
#% type: string
|
|
||||||
#% required: yes
|
|
||||||
#% multiple: no
|
|
||||||
#% description: GeoTIFF DEM containing the surface to calculate contours
|
|
||||||
#%end
|
|
||||||
#%option
|
|
||||||
#% key: interval
|
|
||||||
#% type: double
|
|
||||||
#% required: yes
|
|
||||||
#% multiple: no
|
|
||||||
#% description: Contours interval
|
|
||||||
#%end
|
|
||||||
#%option
|
|
||||||
#% key: format
|
|
||||||
#% type: string
|
|
||||||
#% required: yes
|
|
||||||
#% multiple: no
|
|
||||||
#% description: OGR output format
|
|
||||||
#%end
|
|
||||||
#%option
|
|
||||||
#% key: simplify
|
|
||||||
#% type: double
|
|
||||||
#% required: yes
|
|
||||||
#% multiple: no
|
|
||||||
#% description: OGR output format
|
|
||||||
#%end
|
|
||||||
#%option
|
|
||||||
#% key: epsg
|
|
||||||
#% type: string
|
|
||||||
#% required: yes
|
|
||||||
#% multiple: no
|
|
||||||
#% description: target EPSG code
|
|
||||||
#%end
|
|
||||||
|
|
||||||
# output: If successful, prints the full path to the contours file. Otherwise it prints "error"
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
from grass.pygrass.modules import Module
|
|
||||||
import grass.script as grass
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def main():
|
|
||||||
ext = ""
|
|
||||||
if opts['format'] == "GeoJSON":
|
|
||||||
ext = "json"
|
|
||||||
elif opts['format'] == "GPKG":
|
|
||||||
ext = "gpkg"
|
|
||||||
elif opts['format'] == "DXF":
|
|
||||||
ext = "dxf"
|
|
||||||
elif opts['format'] == "ESRI Shapefile":
|
|
||||||
ext = "shp"
|
|
||||||
|
|
||||||
MIN_CONTOUR_LENGTH = 5
|
|
||||||
Module("r.external", input=opts['dem_file'], output="dem", overwrite=True)
|
|
||||||
Module("g.region", raster="dem")
|
|
||||||
Module("r.contour", input="dem", output="contours", step=opts["interval"], overwrite=True)
|
|
||||||
Module("v.generalize", input="contours", output="contours_smooth", method="douglas", threshold=opts["simplify"], overwrite=True)
|
|
||||||
Module("v.generalize", input="contours_smooth", output="contours_simplified", method="chaiken", threshold=1, overwrite=True)
|
|
||||||
Module("v.generalize", input="contours_simplified", output="contours_final", method="douglas", threshold=opts["simplify"], overwrite=True)
|
|
||||||
Module("v.edit", map="contours_final", tool="delete", threshold=[-1,0,-MIN_CONTOUR_LENGTH], query="length")
|
|
||||||
Module("v.out.ogr", input="contours_final", output="temp.gpkg", format="GPKG")
|
|
||||||
|
|
||||||
subprocess.check_call(["ogr2ogr", "-t_srs", "EPSG:%s" % opts['epsg'],
|
|
||||||
'-overwrite', '-f', opts["format"], "output.%s" % ext, "temp.gpkg"], stdout=subprocess.DEVNULL)
|
|
||||||
|
|
||||||
if os.path.isfile("output.%s" % ext):
|
|
||||||
if opts["format"] == "ESRI Shapefile":
|
|
||||||
ext="zip"
|
|
||||||
os.makedirs("contours")
|
|
||||||
contour_files = glob.glob("output.*")
|
|
||||||
for cf in contour_files:
|
|
||||||
shutil.move(cf, os.path.join("contours", os.path.basename(cf)))
|
|
||||||
|
|
||||||
shutil.make_archive('output', 'zip', 'contours/')
|
|
||||||
|
|
||||||
print(os.path.join(os.getcwd(), "output.%s" % ext))
|
|
||||||
else:
|
|
||||||
print("error")
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
opts, _ = grass.parser()
|
|
||||||
sys.exit(main())
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "WebODM",
|
"name": "WebODM",
|
||||||
"version": "2.1.2",
|
"version": "2.1.3",
|
||||||
"description": "User-friendly, extendable application and API for processing aerial imagery.",
|
"description": "User-friendly, extendable application and API for processing aerial imagery.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -175,7 +175,6 @@ def execute_grass_script(script, serialized_context = {}, out_key='output'):
|
||||||
logger.error(str(e))
|
logger.error(str(e))
|
||||||
return {'error': str(e), 'context': ctx.serialize()}
|
return {'error': str(e), 'context': ctx.serialize()}
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
||||||
def export_raster(self, input, **opts):
|
def export_raster(self, input, **opts):
|
||||||
try:
|
try:
|
||||||
|
@ -238,4 +237,3 @@ def check_quotas():
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
p.clear_quota_deadline()
|
p.clear_quota_deadline()
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue