kopia lustrzana https://github.com/OpenDroneMap/WebODM
Display ground control points on map
rodzic
3ce7cb3cef
commit
afa064ac20
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
];
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
&.fullscreen{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
i{
|
||||
font-size: 200%;
|
||||
|
|
|
@ -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": {
|
||||
|
|
Ładowanie…
Reference in New Issue