kopia lustrzana https://github.com/OpenDroneMap/WebODM
Merge pull request #1538 from diegoaces/align_plugin
Add plugin to get align from WCS service (geoserver)pull/1544/head
commit
081604e0dd
|
@ -0,0 +1,2 @@
|
|||
from .plugin import *
|
||||
from . import signals
|
|
@ -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
|
||||
}
|
|
@ -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'),
|
||||
}
|
|
@ -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
|
|
@ -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")
|
|
@ -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 %}
|
Ładowanie…
Reference in New Issue