diff --git a/coreplugins/align-service/__init__.py b/coreplugins/align-service/__init__.py new file mode 100644 index 000000000..faa8ef42c --- /dev/null +++ b/coreplugins/align-service/__init__.py @@ -0,0 +1,2 @@ +from .plugin import * +from . import signals diff --git a/coreplugins/align-service/manifest.json b/coreplugins/align-service/manifest.json new file mode 100644 index 000000000..fc250d7c8 --- /dev/null +++ b/coreplugins/align-service/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Align generator service", + "webodmMinVersion": "0.6.2", + "description": "Plugin to get align from external service for WebODM", + "version": "1.0.0", + "author": "Diego Acuña, Greenbot Labs", + "email": "contacto@greenbot.cl", + "repository": "https://github.com/OpenDroneMap/WebODM", + "tags": [ + "service", + "orthophoto", + "dem" + ], + "homepage": "https://github.com/OpenDroneMap/WebODM", + "experimental": true, + "deprecated": false +} \ No newline at end of file diff --git a/coreplugins/align-service/plugin.py b/coreplugins/align-service/plugin.py new file mode 100644 index 000000000..d4694d9c1 --- /dev/null +++ b/coreplugins/align-service/plugin.py @@ -0,0 +1,108 @@ +from django.contrib.auth.decorators import login_required, permission_required +from django import forms +from django.contrib import messages +from django.shortcuts import render + +from app.plugins import PluginBase, Menu, MountPoint +from django.utils.translation import gettext as _ + + +class ConfigurationForm(forms.Form): + service_url = forms.CharField( + label='Url service', + max_length=100, + required=True, + ) + coverage_id = forms.CharField( + label='Coverage Id', + max_length=100, + required=True, + ) + token = forms.CharField( + label='Token ', + max_length=100, + required=True, + ) + task_id = forms.CharField( + label='Task Id ', + max_length=100, + required=True, + ) + buffer_size = forms.IntegerField( + label='Buffer size in meters', + required=True, + min_value=0, + max_value=1000, + ) + bot_task_resizing_images = forms.BooleanField( + label='Activate align generator', + required=False, + help_text='This will generate a file from service to align the images', + ) + + def save_settings(self): + save(self.cleaned_data) + + def test_signal(self, request): + from app.plugins.signals import task_resizing_images + config_data = config() + task_token = config_data.get("task_id") + task_resizing_images.send(sender=self, task_id=task_token) + messages.success(request, "Test ok") + + +class Plugin(PluginBase): + def main_menu(self): + return [Menu(_("Align Generator"), self.public_url(""), "fa fa-ruler-vertical fa-fw")] + + def app_mount_points(self): + @login_required + @permission_required('is_superuser', login_url='/dashboard') + def index(request): + if request.method == "POST": + + form = ConfigurationForm(request.POST) + apply_configuration = request.POST.get("apply_configuration") + signal_test = request.POST.get("test_signal") + if form.is_valid() and signal_test: + form.test_signal(request) + elif form.is_valid() and apply_configuration: + form.save_settings() + messages.success(request, "Settings applied successfully!") + else: + config_data = config() + form = ConfigurationForm(initial=config_data) + + return render(request, self.template_path('index.html'), {'form': form, 'title': 'Align generator'}) + + return [ + MountPoint('$', index), + ] + + +def save(data: dict): + from app.plugins.functions import get_current_plugin + plugin = get_current_plugin(only_active=True) + data_store = plugin.get_global_data_store() + + data_store.set_string('service_url', data.get('service_url')), + data_store.set_string('coverage_id', data.get('coverage_id')), + data_store.set_string('token', data.get('token')), + data_store.set_string('task_id', data.get('task_id')), + data_store.set_int('buffer_size', data.get('buffer_size')), + data_store.set_bool('bot_task_resizing_images', data.get('bot_task_resizing_images')), + + +def config(): + from app.plugins.functions import get_current_plugin + plugin = get_current_plugin(only_active=True) + data_store = plugin.get_global_data_store() + + return { + 'service_url': data_store.get_string('service_url'), + 'coverage_id': data_store.get_string('coverage_id'), + 'task_id': data_store.get_string('task_id'), + 'token': data_store.get_string('token'), + 'buffer_size': data_store.get_int('buffer_size'), + 'bot_task_resizing_images': data_store.get_bool('bot_task_resizing_images'), + } diff --git a/coreplugins/align-service/process.py b/coreplugins/align-service/process.py new file mode 100644 index 000000000..0791d8ffc --- /dev/null +++ b/coreplugins/align-service/process.py @@ -0,0 +1,97 @@ +import requests +import logging +import piexif + +from osgeo import ogr, osr +from PIL import Image +from .plugin import config + + +point_ref = osr.SpatialReference() +point_ref.ImportFromEPSG(4326) + +out_ref = osr.SpatialReference() +out_ref.ImportFromEPSG(32718) + +logger = logging.getLogger('app.logger') + +def get_decimal_from_dms(dms, ref): + degrees = dms[0][0] / dms[0][1] + minutes = dms[1][0] / dms[1][1] + seconds = dms[2][0] / dms[2][1] + decimal = degrees + minutes / 60 + seconds / 3600 + if ref in [b'S', b'W']: + decimal = -decimal + return decimal + + +def generate_align_tif(coords, task): + config_data = config() + ring = ogr.Geometry(ogr.wkbLinearRing) + ring.AssignSpatialReference(point_ref) + + for point in coords: + ring.AddPoint(point[1], point[0]) + + ring.CloseRings() + polygon = ogr.Geometry(ogr.wkbPolygon) + polygon.AssignSpatialReference(point_ref) + polygon.AddGeometry(ring) + + buffer_size = config_data.get("buffer_size") + if buffer_size > 0: + meter_ref = osr.SpatialReference() + meter_ref.ImportFromEPSG(32718) + + polygon.TransformTo(meter_ref) + polygon = polygon.Buffer(buffer_size) + # polygon.TransformTo(out_ref) + + min_long, max_long, min_lat, max_lat = polygon.GetEnvelope() + + subset_e = "E({0}, {1})".format(min_long, max_long) + subset_n = "N({0}, {1})".format(min_lat, max_lat) + + url_server = config_data.get("service_url") + coverage_id = config_data.get("coverage_id") + token = config_data.get("token") + service_type = "WCS" + request_type = "GetCoverage" + version_number = "2.0.0" + format_type = "geotiff" + + url_geoserver = (f"{url_server}service={service_type}&request={request_type}&version={version_number}" + f"&coverageId={coverage_id}&format={format_type}&subset={subset_e}&subset={subset_n}" + f"&authkey={token}") + result = requests.get(url_geoserver) + + # save align file + align_file = task.task_path() + "align.tif" + if result.status_code == 200: + with open(align_file, 'wb') as f: + f.write(result.content) + else: + logger.error(f"Error requesting align file: {result.status_code}") + + +def get_coords_from_images(images, task): + coords = [] + for image in images: + if image.endswith(".tif"): + pass + else: + img = Image.open(task.get_image_path(image)) + try: + exif_dict = piexif.load(img.info['exif']) + gps_data = exif_dict.get('GPS', {}) + + if gps_data: + latitude = get_decimal_from_dms(gps_data.get(piexif.GPSIFD.GPSLatitude), + gps_data.get(piexif.GPSIFD.GPSLatitudeRef)) + + longitude = get_decimal_from_dms(gps_data.get(piexif.GPSIFD.GPSLongitude), + gps_data.get(piexif.GPSIFD.GPSLongitudeRef)) + coords.append([longitude, latitude]) + except Exception as e: + logger.error(f"Error getting GPS data from image {image}: {e}") + return coords diff --git a/coreplugins/align-service/signals.py b/coreplugins/align-service/signals.py new file mode 100644 index 000000000..a35f3a6f1 --- /dev/null +++ b/coreplugins/align-service/signals.py @@ -0,0 +1,26 @@ +import logging +from django.dispatch import receiver +from app.plugins.signals import task_resizing_images +from app.plugins.functions import get_current_plugin +from . import config +from app.models import Task + +from .process import get_coords_from_images, generate_align_tif + +logger = logging.getLogger('app.logger') + + +@receiver(task_resizing_images) +def handle_task_resizing_images(sender, task_id, **kwargs): + if get_current_plugin(only_active=True) is None: + return + + config_data = config() + if config_data.get("bot_task_resizing_images"): + task = Task.objects.get(id=task_id) + coords = get_coords_from_images(task.scan_images(), task) + + if coords: + generate_align_tif(coords, task) + else: + logger.info("No GPS data found") diff --git a/coreplugins/align-service/templates/index.html b/coreplugins/align-service/templates/index.html new file mode 100644 index 000000000..df6dffc03 --- /dev/null +++ b/coreplugins/align-service/templates/index.html @@ -0,0 +1,85 @@ +{% extends "app/plugins/templates/base.html" %} +{% load i18n %} + +{% block content %} +
+ This plugin allows you to get the align file. + It is necessary to have a service that provides the required DEM to obtain the rectifying TIFF. +
+ Please, configure the service URL, token and buffer size to start using the plugin. +