forked from OpenDroneMap/WebODM
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request OpenDroneMap#1538 from diegoaces/align_plugin
Add plugin to get align from WCS service (geoserver)
- Loading branch information
Showing
7 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .plugin import * | ||
from . import signals |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 %} |