Merge pull request #1538 from diegoaces/align_plugin

Add plugin to get align from WCS service (geoserver)
pull/1544/head
Piero Toffanin 2024-08-12 23:51:11 -04:00 zatwierdzone przez GitHub
commit 081604e0dd
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
7 zmienionych plików z 335 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,2 @@
from .plugin import *
from . import signals

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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'),
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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")

Wyświetl plik

@ -0,0 +1,85 @@
{% extends "app/plugins/templates/base.html" %}
{% load i18n %}
{% block content %}
<h2>{% trans 'Align file from task image' %}</h2>
<p>
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.
</p>
Please, configure the service URL, token and buffer size to start using the plugin.
<hr>
<form action="/plugins/align-service/" method="post" class="mt-6">
{% csrf_token %}
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="service_url">{{ form.service_url.label }}</label>
<input name="service_url" value="{{ form.service_url.value }}" type="text" class="form-control"
placeholder="https://you_url_service"/>
{{ form.service_url.errors }}
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="coverage_id">{{ form.coverage_id.label }}</label>
<input name="coverage_id" value="{{ form.coverage_id.value }}" type="text" class="form-control"
placeholder="space__coverage"/>
{{ form.coverage_id.errors }}
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="token">{{ form.token.label }}</label>
<input name="token" value="{{ form.token.value }}" type="text" class="form-control"
placeholder="token_service"/>
{{ form.token.errors }}
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="task_id">{{ form.task_id.label }}</label>
<input name="task_id" value="{{ form.task_id.value }}" type="text" class="form-control"
placeholder="task_id to genera example tif"/>
{{ form.task_id.errors }}
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group mb-3">
<label for="buffer_size">{{ form.buffer_size.label }}</label>
<input name="buffer_size" value="{{ form.buffer_size.value }}" type="number"
class="form-control"
placeholder="Buffer Size"/>
{{ form.buffer_size.errors }}
</div>
</div>
</div>
<div class="checkbox mb-3">
<label for="bot_task_resizing_images">
<input name="bot_task_resizing_images" {% if form.bot_task_resizing_images.value %} checked {% endif %}
type="checkbox"> {{ form.bot_task_resizing_images.label }}
</label>
{{ form.bot_task_resizing_images.errors }}
</div>
<p>
{{ form.non_field_errors }}
</p>
<div>
<button name="apply_configuration" value="yes" class="btn btn-primary">Apply Settings</button>
<button name="test_signal" value="yes" class="btn btn-info">Generate example tif</button>
</div>
</form>
{% endblock %}