kopia lustrzana https://github.com/OpenDroneMap/WebODM
164 wiersze
6.1 KiB
Python
164 wiersze
6.1 KiB
Python
import json
|
|
from datetime import datetime
|
|
import os
|
|
from urllib.parse import urlencode
|
|
|
|
import piexif
|
|
from PIL import Image
|
|
from rest_framework import serializers
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
|
|
from app.plugins import GlobalDataStore, get_site_settings, signals as plugin_signals
|
|
from app.plugins.views import TaskView
|
|
from app.plugins.worker import task
|
|
|
|
from webodm import settings
|
|
from django.dispatch import receiver
|
|
|
|
import requests
|
|
|
|
import logging
|
|
|
|
logger = logging.getLogger('app.logger')
|
|
ds = GlobalDataStore('openaerialmap')
|
|
|
|
|
|
def get_key_for(task_id, key):
|
|
return "task_{}_{}".format(str(task_id), key)
|
|
|
|
|
|
def get_task_info(task_id):
|
|
return ds.get_json(get_key_for(task_id, "info"), {
|
|
'sharing': False,
|
|
'shared': False,
|
|
'error': ''
|
|
})
|
|
|
|
|
|
def set_task_info(task_id, json):
|
|
return ds.set_json(get_key_for(task_id, "info"), json)
|
|
|
|
|
|
@receiver(plugin_signals.task_removed, dispatch_uid="oam_on_task_removed")
|
|
@receiver(plugin_signals.task_completed, dispatch_uid="oam_on_task_completed")
|
|
def oam_cleanup(sender, task_id, **kwargs):
|
|
# When a task is removed, simply remove clutter
|
|
# When a task is re-processed, make sure we can re-share it if we shared a task previously
|
|
|
|
logger.info("Cleaning up OAM datastore for task {}".format(str(task_id)))
|
|
ds.del_key(get_key_for(task_id, "info"))
|
|
|
|
|
|
class Info(TaskView):
|
|
def get(self, request, pk=None):
|
|
task = self.get_and_check_task(request, pk)
|
|
|
|
task_info = get_task_info(task.id)
|
|
|
|
# Populate fields from first image in task
|
|
imgs = [f for f in task.scan_images() if not f.lower().endswith(".txt")]
|
|
if len(imgs) > 0:
|
|
img = imgs[0]
|
|
img_path = task.get_image_path(img)
|
|
im = Image.open(img_path)
|
|
|
|
# TODO: for better data we could look over all images
|
|
# and find actual end and start time
|
|
# Here we're picking an image at random and assuming a one hour flight
|
|
if not 'sensor' in task_info:
|
|
task_info['endDate'] = datetime.utcnow().timestamp() * 1000
|
|
task_info['sensor'] = ''
|
|
task_info['title'] = task.name
|
|
task_info['provider'] = get_site_settings().organization_name
|
|
|
|
if 'exif' in im.info:
|
|
exif_dict = piexif.load(im.info['exif'])
|
|
if 'Exif' in exif_dict:
|
|
if piexif.ExifIFD.DateTimeOriginal in exif_dict['Exif']:
|
|
try:
|
|
parsed_date = datetime.strptime(exif_dict['Exif'][piexif.ExifIFD.DateTimeOriginal].decode('ascii'),
|
|
'%Y:%m:%d %H:%M:%S')
|
|
task_info['endDate'] = parsed_date.timestamp() * 1000
|
|
except ValueError:
|
|
# Ignore date field if we can't parse it
|
|
pass
|
|
if '0th' in exif_dict:
|
|
if piexif.ImageIFD.Make in exif_dict['0th']:
|
|
task_info['sensor'] = exif_dict['0th'][piexif.ImageIFD.Make].decode('ascii').strip(' \t\r\n\0')
|
|
|
|
if piexif.ImageIFD.Model in exif_dict['0th']:
|
|
task_info['sensor'] = (task_info['sensor'] + " " + exif_dict['0th'][piexif.ImageIFD.Model].decode('ascii')).strip(' \t\r\n\0')
|
|
|
|
task_info['startDate'] = task_info['endDate'] - 60 * 60 * 1000
|
|
set_task_info(task.id, task_info)
|
|
else:
|
|
task_info['noImages'] = True
|
|
|
|
return Response(task_info, status=status.HTTP_200_OK)
|
|
|
|
|
|
class JSONSerializer(serializers.Serializer):
|
|
oamParams = serializers.JSONField(help_text="OpenAerialMap share parameters (sensor, title, provider, etc.)")
|
|
|
|
|
|
class Share(TaskView):
|
|
def post(self, request, pk=None):
|
|
task = self.get_and_check_task(request, pk)
|
|
|
|
serializer = JSONSerializer(data=request.data)
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
oam_params = serializer['oamParams'].value
|
|
|
|
task_info = get_task_info(task.id)
|
|
task_info['sharing'] = True
|
|
task_info['oam_upload_id'] = ''
|
|
task_info['error'] = ''
|
|
set_task_info(task.id, task_info)
|
|
|
|
upload_orthophoto_to_oam.delay(task.id,
|
|
task.get_asset_download_path('orthophoto.tif'),
|
|
oam_params)
|
|
|
|
return Response(task_info, status=status.HTTP_200_OK)
|
|
|
|
|
|
@task
|
|
def upload_orthophoto_to_oam(task_id, orthophoto_path, oam_params):
|
|
# Upload to temporary central location since
|
|
# OAM requires a public URL and not all WebODM
|
|
# instances are public
|
|
|
|
res = requests.post('https://www.webodm.org/oam/upload',
|
|
files=[
|
|
('file', ('orthophoto.tif', open(orthophoto_path, 'rb'), 'image/tiff')),
|
|
]).json()
|
|
|
|
task_info = get_task_info(task_id)
|
|
|
|
if 'url' in res:
|
|
orthophoto_public_url = res['url']
|
|
logger.info("Orthophoto uploaded to intermediary public URL " + orthophoto_public_url)
|
|
|
|
# That's OK... we :heart: dronedeploy
|
|
res = requests.post('https://api.openaerialmap.org/dronedeploy?{}'.format(urlencode(oam_params)),
|
|
json={
|
|
'download_path': orthophoto_public_url
|
|
}).json()
|
|
|
|
if 'results' in res and 'upload' in res['results']:
|
|
task_info['oam_upload_id'] = res['results']['upload']
|
|
task_info['shared'] = True
|
|
else:
|
|
task_info['error'] = 'Could not upload orthophoto to OAM. The server replied: {}'.format(json.dumps(res))
|
|
|
|
# Attempt to cleanup intermediate results
|
|
requests.get('https://www.webodm.org/oam/cleanup/{}'.format(os.path.basename(orthophoto_public_url)))
|
|
else:
|
|
err_message = res['error'] if 'error' in res else json.dumps(res)
|
|
task_info['error'] = 'Could not upload orthophoto to intermediate location: {}.'.format(err_message)
|
|
|
|
task_info['sharing'] = False
|
|
set_task_info(task_id, task_info)
|