Skip to content

Commit

Permalink
Merge pull request OpenDroneMap#1538 from diegoaces/align_plugin
Browse files Browse the repository at this point in the history
Add plugin to get align from WCS service (geoserver)
  • Loading branch information
pierotofy authored Aug 13, 2024
2 parents 109adc7 + b592d48 commit 081604e
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 0 deletions.
2 changes: 2 additions & 0 deletions coreplugins/align-service/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .plugin import *
from . import signals
Empty file.
17 changes: 17 additions & 0 deletions coreplugins/align-service/manifest.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]",
"repository": "https://github.com/OpenDroneMap/WebODM",
"tags": [
"service",
"orthophoto",
"dem"
],
"homepage": "https://github.com/OpenDroneMap/WebODM",
"experimental": true,
"deprecated": false
}
108 changes: 108 additions & 0 deletions coreplugins/align-service/plugin.py
Original file line number Diff line number Diff line change
@@ -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'),
}
97 changes: 97 additions & 0 deletions coreplugins/align-service/process.py
Original file line number Diff line number Diff line change
@@ -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
26 changes: 26 additions & 0 deletions coreplugins/align-service/signals.py
Original file line number Diff line number Diff line change
@@ -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")
85 changes: 85 additions & 0 deletions coreplugins/align-service/templates/index.html
Original file line number Diff line number Diff line change
@@ -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 %}

0 comments on commit 081604e

Please sign in to comment.