Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDAL based contours #1405

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 77 additions & 19 deletions coreplugins/contours/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,80 @@
from rest_framework import status
from rest_framework.response import Response
from app.plugins.views import TaskView, CheckTask, GetTaskResult
from worker.tasks import execute_grass_script
from app.plugins.grass_engine import grass, GrassEngineException, cleanup_grass_context
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):
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):
def post(self, request, pk=None):
task = self.get_and_check_task(request, pk)
Expand All @@ -23,36 +93,24 @@ def post(self, request, pk=None):
elif layer == 'DTM':
dem = os.path.abspath(task.get_asset_download_path("dtm.tif"))
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'))
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 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))

context.add_param('dem_file', dem)
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

celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify).task_id
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)

class TaskContoursCheck(CheckTask):
def on_error(self, result):
cleanup_grass_context(result['context'])
pass

def error_check(self, result):
contours_file = result.get('file')
Expand Down
93 changes: 0 additions & 93 deletions coreplugins/contours/calc_contours.py

This file was deleted.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "WebODM",
"version": "2.1.2",
"version": "2.1.3",
"description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 0 additions & 2 deletions worker/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ def execute_grass_script(script, serialized_context = {}, out_key='output'):
logger.error(str(e))
return {'error': str(e), 'context': ctx.serialize()}


@app.task(bind=True)
def export_raster(self, input, **opts):
try:
Expand Down Expand Up @@ -238,4 +237,3 @@ def check_quotas():
break
else:
p.clear_quota_deadline()