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

Add plugin to get align from WCS service (geoserver) #1538

Merged
merged 3 commits into from
Aug 13, 2024
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
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": "contacto@greenbot.cl",
"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 %}
Loading