Display ground control points on map

pull/1060/head
Piero Toffanin 2021-09-24 16:54:21 -04:00
rodzic 3ce7cb3cef
commit afa064ac20
7 zmienionych plików z 120 dodań i 5 usunięć

Wyświetl plik

@ -1,12 +1,12 @@
import os
import io
import math
from .tasks import TaskNestedView
from rest_framework import exceptions
from app.models import ImageUpload
from app.models.task import assets_directory_path
from PIL import Image
from PIL import Image, ImageDraw
from django.http import HttpResponse
from .tasks import download_file_response
import numpy as np
@ -26,6 +26,19 @@ def normalize(img):
return Image.fromarray(arr)
def hex2rgb(hex_color):
"""
Adapted from https://stackoverflow.com/questions/29643352/converting-hex-to-rgb-value-in-python/29643643
"""
hex_color = hex_color.lstrip('#')
print(hex_color)
if len(hex_color) != 6:
return tuple((255, 255, 255))
try:
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
except ValueError:
return tuple((255, 255, 255))
class Thumbnail(TaskNestedView):
def get(self, request, pk=None, project_pk=None, image_filename=""):
"""
@ -49,6 +62,31 @@ class Thumbnail(TaskNestedView):
quality = int(self.request.query_params.get('quality', 75))
if quality < 0 or quality > 100:
raise ValueError()
center_x = float(self.request.query_params.get('center_x', '0.5'))
center_y = float(self.request.query_params.get('center_y', '0.5'))
if center_x < -0.5 or center_x > 1.5 or center_y < -0.5 or center_y > 1.5:
raise ValueError()
draw_points = self.request.query_params.getlist('draw_point')
point_colors = self.request.query_params.getlist('point_color')
point_radiuses = self.request.query_params.getlist('point_radius')
points = []
i = 0
for p in draw_points:
coords = list(map(float, p.split(",")))
if len(coords) != 2:
raise ValueError()
points.append({
'x': coords[0],
'y': coords[1],
'color': hex2rgb(point_colors[i]) if i < len(point_colors) else (255, 255, 255),
'radius': float(point_radiuses[i]) if i < len(point_radiuses) else 1,
})
i += 1
except ValueError:
raise exceptions.ValidationError("Invalid query parameters")
@ -57,6 +95,24 @@ class Thumbnail(TaskNestedView):
if img.mode != 'RGB':
img = normalize(img)
img = img.convert('RGB')
w, h = img.size
# Draw points
for p in points:
d = ImageDraw.Draw(img)
r = p['radius']
d.ellipse([(p['x'] * w - r, p['y'] * h - r),
(p['x'] * w + r, p['y'] * h + r)], outline=p['color'], width=max(1.0, math.floor(r / 3.0)))
# Move image center
if center_x != 0.5 or center_y != 0.5:
img = img.crop((
w * (center_x - 0.5),
h * (center_y - 0.5),
w * (center_x + 0.5),
h * (center_y + 0.5)
))
img.thumbnail((thumb_size, thumb_size))
output = io.BytesIO()
img.save(output, format='JPEG', quality=quality)

Wyświetl plik

@ -210,7 +210,6 @@ class TaskViewSet(viewsets.ViewSet):
task.images_count = models.ImageUpload.objects.filter(task=task).count()
# Update other parameters such as processing node, task name, etc.
print(f"Amount of images currently in task: {task.images_count}")
serializer = TaskSerializer(task, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()

Wyświetl plik

@ -196,6 +196,7 @@ class Task(models.Model):
'cameras.json': 'cameras.json',
'shots.geojson': os.path.join('odm_report', 'shots.geojson'),
'report.pdf': os.path.join('odm_report', 'report.pdf'),
'ground_control_points.geojson': os.path.join('odm_georeferencing', 'ground_control_points.geojson'),
}
STATUS_CODES = (
@ -860,6 +861,9 @@ class Task(models.Model):
camera_shots = ''
if 'shots.geojson' in self.available_assets: camera_shots = '/api/projects/{}/tasks/{}/download/shots.geojson'.format(self.project.id, self.id)
ground_control_points = ''
if 'ground_control_points.geojson' in self.available_assets: ground_control_points = '/api/projects/{}/tasks/{}/download/ground_control_points.geojson'.format(self.project.id, self.id)
return {
'tiles': [{'url': self.get_tile_base_url(t), 'type': t} for t in types],
'meta': {
@ -867,7 +871,8 @@ class Task(models.Model):
'id': str(self.id),
'project': self.project.id,
'public': self.public,
'camera_shots': camera_shots
'camera_shots': camera_shots,
'ground_control_points': ground_control_points
}
}
}

Wyświetl plik

@ -49,9 +49,11 @@ const api = {
new AssetDownload(_("Textured Model"),"textured_model.zip","fab fa-connectdevelop"),
new AssetDownload(_("Camera Parameters"),"cameras.json","fa fa-camera"),
new AssetDownload(_("Camera Shots (GeoJSON)"),"shots.geojson","fa fa-camera"),
new AssetDownload(_("Ground Control Points (GeoJSON)"),"ground_control_points.geojson","far fa-dot-circle"),
new AssetDownload(_("Quality Report"),"report.pdf","far fa-file-pdf"),
new AssetDownloadSeparator(),
new AssetDownload(_("All Assets"),"all.zip","far fa-file-archive")
];

Wyświetl plik

@ -13,6 +13,7 @@ import Dropzone from '../vendor/dropzone';
import $ from 'jquery';
import ErrorMessage from './ErrorMessage';
import ImagePopup from './ImagePopup';
import GCPPopup from './GCPPopup';
import SwitchModeButton from './SwitchModeButton';
import ShareButton from './ShareButton';
import AssetDownloads from '../classes/AssetDownloads';
@ -266,6 +267,57 @@ class Map extends React.Component {
this.addedCameraShots = true;
}
// Add ground control points layer if available
if (meta.task && meta.task.ground_control_points && !this.addedGroundControlPoints){
const gcpMarker = L.AwesomeMarkers.icon({
icon: 'dot-circle',
markerColor: 'blue',
prefix: 'fa'
});
const gcpLayer = new L.GeoJSON.AJAX(meta.task.ground_control_points, {
style: function (feature) {
return {
opacity: 1,
fillOpacity: 0.7,
color: "#000000"
}
},
pointToLayer: function (feature, latlng) {
return new L.marker(latlng, {
icon: gcpMarker
});
},
onEachFeature: function (feature, layer) {
if (feature.properties && feature.properties.observations) {
// TODO!
let root = null;
const lazyrender = () => {
if (!root) root = document.createElement("div");
ReactDOM.render(<GCPPopup task={meta.task} feature={feature}/>, root);
return root;
}
layer.bindPopup(L.popup(
{
lazyrender,
maxHeight: 450,
minWidth: 320
}));
}
}
});
gcpLayer[Symbol.for("meta")] = {name: name + " " + _("(GCPs)"), icon: "far fa-dot-circle fa-fw"};
this.setState(update(this.state, {
overlays: {$push: [gcpLayer]}
}));
gcpLayer.addTo(this.map); // TODO REMOVE
this.addedGroundControlPoints = true;
}
done();
})
.fail((_, __, err) => done(err))

Wyświetl plik

@ -19,6 +19,7 @@
&.fullscreen{
display: flex;
align-items: center;
justify-content: center;
color: white;
i{
font-size: 200%;

Wyświetl plik

@ -1,6 +1,6 @@
{
"name": "WebODM",
"version": "1.9.6",
"version": "1.9.7",
"description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js",
"scripts": {