npm install support for plugins, started moving volume code into plugin, API url mountpoints

pull/412/head
Piero Toffanin 2018-03-19 12:26:10 -04:00
rodzic c157eb358a
commit 2005194d94
21 zmienionych plików z 149 dodań i 314 usunięć

Wyświetl plik

@ -21,10 +21,6 @@ class TaskIDsSerializer(serializers.BaseSerializer):
def to_representation(self, obj): def to_representation(self, obj):
return obj.id return obj.id
class geojsonSerializer(serializers.Serializer):
"""docstring for geojsonSeria"""
geometry = serializers.JSONField(help_text="polygon contour to get volume")
class TaskSerializer(serializers.ModelSerializer): class TaskSerializer(serializers.ModelSerializer):
project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all()) project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all())
processing_node = serializers.PrimaryKeyRelatedField(queryset=ProcessingNode.objects.all()) processing_node = serializers.PrimaryKeyRelatedField(queryset=ProcessingNode.objects.all())
@ -196,15 +192,15 @@ class TaskNestedView(APIView):
queryset = models.Task.objects.all().defer('orthophoto_extent', 'dtm_extent', 'dsm_extent', 'console_output', ) queryset = models.Task.objects.all().defer('orthophoto_extent', 'dtm_extent', 'dsm_extent', 'console_output', )
permission_classes = (IsAuthenticatedOrReadOnly, ) permission_classes = (IsAuthenticatedOrReadOnly, )
def get_and_check_task(self, request, pk, project_pk, annotate={}): def get_and_check_task(self, request, pk, annotate={}):
try: try:
task = self.queryset.annotate(**annotate).get(pk=pk, project=project_pk) task = self.queryset.annotate(**annotate).get(pk=pk)
except (ObjectDoesNotExist, ValidationError): except (ObjectDoesNotExist, ValidationError):
raise exceptions.NotFound() raise exceptions.NotFound()
# Check for permissions, unless the task is public # Check for permissions, unless the task is public
if not task.public: if not task.public:
get_and_check_project(request, project_pk) get_and_check_project(request, task.project.id)
return task return task
@ -214,7 +210,7 @@ class TaskTiles(TaskNestedView):
""" """
Get a tile image Get a tile image
""" """
task = self.get_and_check_task(request, pk, project_pk) task = self.get_and_check_task(request, pk)
tile_path = task.get_tile_path(tile_type, z, x, y) tile_path = task.get_tile_path(tile_type, z, x, y)
if os.path.isfile(tile_path): if os.path.isfile(tile_path):
tile = open(tile_path, "rb") tile = open(tile_path, "rb")
@ -228,7 +224,7 @@ class TaskTilesJson(TaskNestedView):
""" """
Get tile.json for this tasks's asset type Get tile.json for this tasks's asset type
""" """
task = self.get_and_check_task(request, pk, project_pk) task = self.get_and_check_task(request, pk)
extent_map = { extent_map = {
'orthophoto': task.orthophoto_extent, 'orthophoto': task.orthophoto_extent,
@ -259,7 +255,7 @@ class TaskDownloads(TaskNestedView):
""" """
Downloads a task asset (if available) Downloads a task asset (if available)
""" """
task = self.get_and_check_task(request, pk, project_pk) task = self.get_and_check_task(request, pk)
# Check and download # Check and download
try: try:
@ -287,7 +283,7 @@ class TaskAssets(TaskNestedView):
""" """
Downloads a task asset (if available) Downloads a task asset (if available)
""" """
task = self.get_and_check_task(request, pk, project_pk) task = self.get_and_check_task(request, pk)
# Check for directory traversal attacks # Check for directory traversal attacks
try: try:
@ -305,16 +301,3 @@ class TaskAssets(TaskNestedView):
content_type=(mimetypes.guess_type(asset_filename)[0] or "application/zip")) content_type=(mimetypes.guess_type(asset_filename)[0] or "application/zip"))
response['Content-Disposition'] = "inline; filename={}".format(asset_filename) response['Content-Disposition'] = "inline; filename={}".format(asset_filename)
return response return response
class TaskVolume(TaskNestedView):
def post(self, request, pk=None, project_pk=None):
task = self.get_and_check_task(request, pk, project_pk)
serializer = geojsonSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
# geometry = serializer.data.get('geometry')
# if geometry is None:
# raise exceptions.ValidationError("A geoson file are not available.")
result=task.get_volume(request.data)
response = Response(result, status=status.HTTP_200_OK)
return response

Wyświetl plik

@ -1,8 +1,9 @@
from django.conf.urls import url, include from django.conf.urls import url, include
from app.api.presets import PresetViewSet from app.api.presets import PresetViewSet
from app.plugins import get_api_url_patterns
from .projects import ProjectViewSet from .projects import ProjectViewSet
from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskDownloads, TaskAssets, TaskVolume from .tasks import TaskViewSet, TaskTiles, TaskTilesJson, TaskDownloads, TaskAssets
from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView from .processingnodes import ProcessingNodeViewSet, ProcessingNodeOptionsView
from rest_framework_nested import routers from rest_framework_nested import routers
from rest_framework_jwt.views import obtain_jwt_token from rest_framework_jwt.views import obtain_jwt_token
@ -28,8 +29,9 @@ urlpatterns = [
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>.+)$', TaskDownloads.as_view()), url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/download/(?P<asset>.+)$', TaskDownloads.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()), url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/assets/(?P<unsafe_asset_path>.+)$', TaskAssets.as_view()),
url(r'projects/(?P<project_pk>[^/.]+)/tasks/(?P<pk>[^/.]+)/volume$', TaskVolume.as_view()),
url(r'^auth/', include('rest_framework.urls')), url(r'^auth/', include('rest_framework.urls')),
url(r'^token-auth/', obtain_jwt_token), url(r'^token-auth/', obtain_jwt_token),
] ]
urlpatterns += get_api_url_patterns()

Wyświetl plik

@ -1,158 +0,0 @@
from osgeo import gdal, gdalnumeric, ogr
from PIL import Image, ImageDraw
import os
import numpy as np
import json
def clip_raster(raster, geojson, gt=None, nodata=-9999):
'''
Clips a raster (given as either a gdal.Dataset or as a numpy.array
instance) to a polygon layer provided by a Shapefile (or other vector
layer). If a numpy.array is given, a "GeoTransform" must be provided
(via dataset.GetGeoTransform() in GDAL). Returns an array. Clip features
must be a dissolved, single-part geometry (not multi-part). Modified from:
http://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html
#clip-a-geotiff-with-shapefile
Arguments:
rast A gdal.Dataset or a NumPy array
features_path The path to the clipping features
gt An optional GDAL GeoTransform to use instead
nodata The NoData value; defaults to -9999.
'''
def array_to_image(a):
'''
Converts a gdalnumeric array to a Python Imaging Library (PIL) Image.
'''
i = Image.fromstring('L',(a.shape[1], a.shape[0]),
(a.astype('b')).tostring())
return i
def convertJson(jsdata):
return json.dumps(jsdata)
def image_to_array(i):
'''
Converts a Python Imaging Library (PIL) array to a gdalnumeric image.
'''
a = gdalnumeric.fromstring(i.tobytes(), 'b')
a.shape = i.im.size[1], i.im.size[0]
return a
def world_to_pixel(geo_matrix, x, y):
'''
Uses a gdal geomatrix (gdal.GetGeoTransform()) to calculate
the pixel location of a geospatial coordinate; from:
http://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html#clip-a-geotiff-with-shapefile
'''
ulX = geo_matrix[0]
ulY = geo_matrix[3]
xDist = geo_matrix[1]
yDist = geo_matrix[5]
rtnX = geo_matrix[2]
rtnY = geo_matrix[4]
pixel = int((x - ulX) / xDist)
line = int((ulY - y) / xDist)
return (pixel, line)
rast=gdal.Open(raster)
# Can accept either a gdal.Dataset or numpy.array instance
if not isinstance(rast, np.ndarray):
gt = rast.GetGeoTransform()
rast = rast.ReadAsArray()
# Create an OGR layer from a boundary shapefile
geo = convertJson(geojson)
features = ogr.Open(geo)
if features.GetDriver().GetName() == 'ESRI Shapefile':
lyr = features.GetLayer(os.path.split(os.path.splitext(features_path)[0])[1])
else:
lyr = features.GetLayer()
# Get the first feature
poly = lyr.GetNextFeature()
# Convert the layer extent to image pixel coordinates
minX, maxX, minY, maxY = lyr.GetExtent()
ulX, ulY = world_to_pixel(gt, minX, maxY)
lrX, lrY = world_to_pixel(gt, maxX, minY)
# Calculate the pixel size of the new image
pxWidth = int(lrX - ulX)
pxHeight = int(lrY - ulY)
# If the clipping features extend out-of-bounds and ABOVE the raster...
if gt[3] < maxY:
# In such a case... ulY ends up being negative--can't have that!
iY = ulY
ulY = 0
# Multi-band image?
try:
clip = rast[:, ulY:lrY, ulX:lrX]
except IndexError:
clip = rast[ulY:lrY, ulX:lrX]
# Create a new geomatrix for the image
gt2 = list(gt)
gt2[0] = minX
gt2[3] = maxY
# Map points to pixels for drawing the boundary on a blank 8-bit,
# black and white, mask image.
points = []
pixels = []
geom = poly.GetGeometryRef()
pts = geom.GetGeometryRef(0)
for p in range(pts.GetPointCount()):
points.append((pts.GetX(p), pts.GetY(p)))
for p in points:
pixels.append(world_to_pixel(gt2, p[0], p[1]))
raster_poly = Image.new('L', (pxWidth, pxHeight), 1)
rasterize = ImageDraw.Draw(raster_poly)
rasterize.polygon(pixels, 0) # Fill with zeroes
# If the clipping features extend out-of-bounds and ABOVE the raster...
if gt[3] < maxY:
# The clip features were "pushed down" to match the bounds of the
# raster; this step "pulls" them back up
premask = image_to_array(raster_poly)
# We slice out the piece of our clip features that are "off the map"
mask = np.ndarray((premask.shape[-2] - abs(iY), premask.shape[-1]), premask.dtype)
mask[:] = premask[abs(iY):, :]
mask.resize(premask.shape) # Then fill in from the bottom
# Most importantly, push the clipped piece down
gt2[3] = maxY - (maxY - gt[3])
else:
mask = image_to_array(raster_poly)
# Clip the image using the mask
try:
clip = gdalnumeric.choose(mask, (clip, nodata))
# If the clipping features extend out-of-bounds and BELOW the raster...
except ValueError:
# We have to cut the clipping features to the raster!
rshp = list(mask.shape)
if mask.shape[-2] != clip.shape[-2]:
rshp[0] = clip.shape[-2]
if mask.shape[-1] != clip.shape[-1]:
rshp[1] = clip.shape[-1]
mask.resize(*rshp, refcheck=False)
clip = gdalnumeric.choose(mask, (clip, nodata))
# return (clip, ulX, ulY, gt2)
return clip

Wyświetl plik

@ -1,35 +0,0 @@
import json
from osgeo import osr
def spatialref(epsg_code):
spatialref = osr.SpatialReference()
spatialref.ImportFromEPSG(epsg_code)
return spatialref
def spatialrefWQT(dataset):
spatialref = osr.SpatialReference()
spatialref.ImportFromWkt(dataset.GetProjectionRef())
return spatialref
def reprojson(geojson, dataset):
crsin= spatialref(4326)
crsout = spatialrefWQT(dataset)
coordinate_transformation = osr.CoordinateTransformation(crsin, crsout)
# Define dictionary representation of output feature collection
fc_out = {"geometry":{"type":"Polygon","coordinates":[]}}
# Iterate through each feature of the feature collection
new_coords = []
# Project/transform coordinate pairs of each ring
# (iteration required in case geometry type is MultiPolygon, or there are holes)
for ring in geojson['geometry']['coordinates']:
coords=[(entry[0],entry[1]) for entry in ring]
for i in range(len(coords)):
x2, y2, z= coordinate_transformation.TransformPoint(coords[i][0], coords[i][1])
new_coords.append([x2, y2])
fc_out['geometry']['coordinates'] = [new_coords]
return fc_out

Wyświetl plik

@ -3,15 +3,6 @@ import os
import shutil import shutil
import zipfile import zipfile
import uuid as uuid_module import uuid as uuid_module
import json
import osgeo.ogr
import gdal
import struct
import statistics
from .vertex import rings
from .repro_json import reprojson
from .cliprasterpol import clip_raster
import json import json
from shlex import quote from shlex import quote
@ -588,25 +579,6 @@ class Task(models.Model):
self.pending_action = None self.pending_action = None
self.save() self.save()
def get_volume(self, geojson):
try:
raster_path= self.assets_path("odm_dem", "dsm.tif")
raster=gdal.Open(raster_path)
gt=raster.GetGeoTransform()
rb=raster.GetRasterBand(1)
gdal.UseExceptions()
geosom = reprojson(geojson, raster)
coords=[(entry[0],entry[1]) for entry in rings(raster_path, geosom)]
GSD=gt[1]
volume=0
med=statistics.median(entry[2] for entry in rings(raster_path, geosom))
clip=clip_raster(raster_path, geosom, gt=None, nodata=-9999)
return ((clip-med)*GSD*GSD)[clip!=-9999.0].sum()
except FileNotFoundError as e:
logger.warning(e)
def find_all_files_matching(self, regex): def find_all_files_matching(self, regex):
directory = full_task_directory_path(self.id, self.project.id) directory = full_task_directory_path(self.id, self.project.id)
return [os.path.join(directory, f) for f in os.listdir(directory) if return [os.path.join(directory, f) for f in os.listdir(directory) if

Wyświetl plik

@ -1,40 +0,0 @@
from osgeo import ogr
import gdal
import struct
import json
def convertJson(jsdata):
return json.dumps(jsdata)
def rings(raster, geojson):
src=gdal.Open(raster)
gtx=src.GetGeoTransform()
rbu=src.GetRasterBand(1)
gdal.UseExceptions()
geo=convertJson(geojson)
geojsom= ogr.Open(geo)
layer1 = geojsom.GetLayer(0)
vertices = []
for feat in layer1:
geom = feat.GetGeometryRef()
ring = geom.GetGeometryRef(0)
points = ring.GetPointCount()
for p in range(points):
lon, lat, z = ring.GetPoint(p)
px = int((lon - gtx[0]) / gtx[1]) #x pixel
py = int((lat - gtx[3]) / gtx[5]) #y pixel
try:
structval=rbu.ReadRaster(px,py,1,1,buf_type=gdal.GDT_Float32) #Assumes 32 bit int- 'float'
intval = struct.unpack('f' , structval) #assume float
val=intval[0]
vertices.append((px, py, val))
except:
val=-9999 #or some value to indicate a fail
return vertices

Wyświetl plik

@ -1,6 +1,7 @@
import os import os
import logging import logging
import importlib import importlib
import subprocess
import django import django
import json import json
@ -13,27 +14,48 @@ logger = logging.getLogger('app.logger')
def register_plugins(): def register_plugins():
for plugin in get_active_plugins(): for plugin in get_active_plugins():
# Check for package.json in public directory
# and run npm install if needed
if plugin.path_exists("public/package.json") and not plugin.path_exists("public/node_modules"):
logger.info("Running npm install for {}".format(plugin.get_name()))
subprocess.call(['npm', 'install'], cwd=plugin.get_path("public"))
plugin.register() plugin.register()
logger.info("Registered {}".format(plugin)) logger.info("Registered {}".format(plugin))
def get_url_patterns(): def get_app_url_patterns():
""" """
@return the patterns to expose the /public directory of each plugin (if needed) @return the patterns to expose the /public directory of each plugin (if needed) and
each mount point
""" """
url_patterns = [] url_patterns = []
for plugin in get_active_plugins(): for plugin in get_active_plugins():
for mount_point in plugin.mount_points(): for mount_point in plugin.app_mount_points():
url_patterns.append(url('^plugins/{}/{}'.format(plugin.get_name(), mount_point.url), url_patterns.append(url('^plugins/{}/{}'.format(plugin.get_name(), mount_point.url),
mount_point.view, mount_point.view,
*mount_point.args, *mount_point.args,
**mount_point.kwargs)) **mount_point.kwargs))
if plugin.has_public_path(): if plugin.path_exists("public"):
url_patterns.append(url('^plugins/{}/(.*)'.format(plugin.get_name()), url_patterns.append(url('^plugins/{}/(.*)'.format(plugin.get_name()),
django.views.static.serve, django.views.static.serve,
{'document_root': plugin.get_path("public")})) {'document_root': plugin.get_path("public")}))
return url_patterns
def get_api_url_patterns():
"""
@return the patterns to expose the plugin API mount points (if any)
"""
url_patterns = []
for plugin in get_active_plugins():
for mount_point in plugin.api_mount_points():
url_patterns.append(url('^plugins/{}/{}'.format(plugin.get_name(), mount_point.url),
mount_point.view,
*mount_point.args,
**mount_point.kwargs))
return url_patterns return url_patterns
@ -85,6 +107,11 @@ def get_active_plugins():
return plugins return plugins
def get_plugin_by_name(name):
plugins = get_active_plugins()
res = list(filter(lambda p: p.get_name() == name, plugins))
return res[0] if res else None
def get_plugins_path(): def get_plugins_path():
current_path = os.path.dirname(os.path.realpath(__file__)) current_path = os.path.dirname(os.path.realpath(__file__))

Wyświetl plik

@ -5,7 +5,7 @@ class MountPoint:
""" """
:param url: path to mount this view to, relative to plugins directory :param url: path to mount this view to, relative to plugins directory
:param view: Django view :param view: Django/DjangoRestFramework view
:param args: extra args to pass to url() call :param args: extra args to pass to url() call
:param kwargs: extra kwargs to pass to url() call :param kwargs: extra kwargs to pass to url() call
""" """

Wyświetl plik

@ -46,8 +46,8 @@ class PluginBase(ABC):
""" """
return "plugins/{}/templates/{}".format(self.get_name(), path) return "plugins/{}/templates/{}".format(self.get_name(), path)
def has_public_path(self): def path_exists(self, path):
return os.path.isdir(self.get_path("public")) return os.path.exists(self.get_path(path))
def include_js_files(self): def include_js_files(self):
""" """
@ -73,7 +73,7 @@ class PluginBase(ABC):
""" """
return [] return []
def mount_points(self): def app_mount_points(self):
""" """
Should be overriden by plugins that want to connect Should be overriden by plugins that want to connect
custom Django views custom Django views
@ -81,5 +81,13 @@ class PluginBase(ABC):
""" """
return [] return []
def api_mount_points(self):
"""
Should be overriden by plugins that want to add
new API mount points
:return: [] of MountPoint objects
"""
return []
def __str__(self): def __str__(self):
return "[{}]".format(self.get_module_name()) return "[{}]".format(self.get_module_name())

Wyświetl plik

@ -4,8 +4,6 @@ import 'leaflet/dist/leaflet.css';
import Leaflet from 'leaflet'; import Leaflet from 'leaflet';
import async from 'async'; import async from 'async';
import 'leaflet-measure/dist/leaflet-measure.css';
import 'leaflet-measure/dist/leaflet-measure';
import 'leaflet-draw/dist/leaflet.draw.css'; import 'leaflet-draw/dist/leaflet.draw.css';
import 'leaflet-draw/dist/leaflet.draw'; import 'leaflet-draw/dist/leaflet.draw';
@ -105,6 +103,7 @@ class Map extends React.Component {
// Associate metadata with this layer // Associate metadata with this layer
meta.name = info.name; meta.name = info.name;
window.meta = meta;
layer[Symbol.for("meta")] = meta; layer[Symbol.for("meta")] = meta;
if (forceAddLayers || prevSelectedLayers.indexOf(layerId(layer)) !== -1){ if (forceAddLayers || prevSelectedLayers.indexOf(layerId(layer)) !== -1){
@ -185,8 +184,6 @@ class Map extends React.Component {
map: this.map map: this.map
}); });
measureControl.addTo(this.map);
const featureGroup = L.featureGroup(); const featureGroup = L.featureGroup();
featureGroup.addTo(this.map); featureGroup.addTo(this.map);
@ -217,12 +214,13 @@ class Map extends React.Component {
this.map.on(L.Draw.Event.CREATED, function(e) { this.map.on(L.Draw.Event.CREATED, function(e) {
e.layer.feature = {geometry: {type: 'Polygon'} }; e.layer.feature = {geometry: {type: 'Polygon'} };
featureGroup.addLayer(e.layer); featureGroup.addLayer(e.layer);
const meta = window.meta;
var paramList; var paramList;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
async: false, async: false,
url: '/api/projects/4/tasks/7/volume', url: `/api/projects/${meta.task.project}/tasks/${meta.task.id}/volume`,
data: JSON.stringify(e.layer.toGeoJSON()), data: JSON.stringify(e.layer.toGeoJSON()),
contentType: "application/json", contentType: "application/json",
success: function (msg) { success: function (msg) {
@ -238,11 +236,13 @@ class Map extends React.Component {
this.map.on(L.Draw.Event.EDITED, function(e) { this.map.on(L.Draw.Event.EDITED, function(e) {
e.layers.eachLayer(function(layer) { e.layers.eachLayer(function(layer) {
const meta = window.meta;
var paramList = null; var paramList = null;
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
async: false, async: false,
url: '/api/projects/1/tasks/4/volume', url: `/api/projects/${meta.task.project}/tasks/${meta.task.id}/volume`,
data: JSON.stringify(layer.toGeoJSON()), data: JSON.stringify(layer.toGeoJSON()),
contentType: "application/json", contentType: "application/json",
success: function (msg) { success: function (msg) {

Wyświetl plik

@ -1,6 +1,9 @@
import os
from django.test import Client from django.test import Client
from rest_framework import status from rest_framework import status
from app.plugins import get_plugin_by_name
from .classes import BootTestCase from .classes import BootTestCase
class TestPlugins(BootTestCase): class TestPlugins(BootTestCase):
@ -37,6 +40,11 @@ class TestPlugins(BootTestCase):
# And our menu entry # And our menu entry
self.assertContains(res, '<li><a href="/plugins/test/menu_url/"><i class="test-icon"></i> Test</a></li>', html=True) self.assertContains(res, '<li><a href="/plugins/test/menu_url/"><i class="test-icon"></i> Test</a></li>', html=True)
# A node_modules directory has been created as a result of npm install
# because we have a package.json in the public director
test_plugin = get_plugin_by_name("test")
self.assertTrue(os.path.exists(test_plugin.get_path("public/node_modules")))
# TODO: # TODO:
# test API endpoints # test API endpoints
# test python hooks # test python hooks

Wyświetl plik

@ -4,7 +4,7 @@ from django.shortcuts import render_to_response
from django.template import RequestContext from django.template import RequestContext
from .views import app as app_views, public as public_views from .views import app as app_views, public as public_views
from .plugins import get_url_patterns from .plugins import get_app_url_patterns
from app.boot import boot from app.boot import boot
from webodm import settings from webodm import settings
@ -30,7 +30,7 @@ urlpatterns = [
# TODO: is there a way to place plugins /public directories # TODO: is there a way to place plugins /public directories
# into the static build directories and let nginx serve them? # into the static build directories and let nginx serve them?
urlpatterns += get_url_patterns() urlpatterns += get_app_url_patterns()
handler404 = app_views.handler404 handler404 = app_views.handler404
handler500 = app_views.handler500 handler500 = app_views.handler500

Wyświetl plik

@ -6,7 +6,7 @@ class Plugin(PluginBase):
def main_menu(self): def main_menu(self):
return [Menu("GCP Interface", self.public_url(""), "fa fa-map-marker fa-fw")] return [Menu("GCP Interface", self.public_url(""), "fa fa-map-marker fa-fw")]
def mount_points(self): def app_mount_points(self):
return [ return [
MountPoint('$', lambda request: render(request, self.template_path("app.html"), {'title': 'GCP Editor'})) MountPoint('$', lambda request: render(request, self.template_path("app.html"), {'title': 'GCP Editor'}))
] ]

Wyświetl plik

@ -12,7 +12,7 @@ class Plugin(PluginBase):
def include_css_files(self): def include_css_files(self):
return ['test.css'] return ['test.css']
def mount_points(self): def app_mount_points(self):
return [ return [
MountPoint('/app_mountpoint/$', lambda request: render(request, self.template_path("app.html"), {'title': 'Test'})) MountPoint('/app_mountpoint/$', lambda request: render(request, self.template_path("app.html"), {'title': 'Test'}))
] ]

Wyświetl plik

@ -0,0 +1,14 @@
{
"name": "public",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"pad-left": "^2.1.0"
}
}

Wyświetl plik

@ -0,0 +1,21 @@
from rest_framework import serializers
from rest_framework import status
from rest_framework.response import Response
from app.api.tasks import TaskNestedView
class GeoJSONSerializer(serializers.Serializer):
geometry = serializers.JSONField(help_text="Polygon contour defining the volume area to compute")
class TaskVolume(TaskNestedView):
def post(self, request, pk=None):
task = self.get_and_check_task(request, pk)
serializer = GeoJSONSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
#result=task.get_volume(serializer.geometry)
return Response(serializer.geometry, status=status.HTTP_200_OK)

Wyświetl plik

@ -3,7 +3,7 @@
"webodmMinVersion": "0.5.0", "webodmMinVersion": "0.5.0",
"description": "A plugin to compute volume measurements from a DSM", "description": "A plugin to compute volume measurements from a DSM",
"version": "0.1.0", "version": "0.1.0",
"author": "Piero Toffanin", "author": "Abdelkoddouss Izem, Piero Toffanin",
"email": "pt@masseranolabs.com", "email": "pt@masseranolabs.com",
"repository": "https://github.com/OpenDroneMap/WebODM", "repository": "https://github.com/OpenDroneMap/WebODM",
"tags": ["volume", "measurements"], "tags": ["volume", "measurements"],

Wyświetl plik

@ -1,5 +1,30 @@
from app.plugins import MountPoint
from app.plugins import PluginBase from app.plugins import PluginBase
from .api import TaskVolume
class Plugin(PluginBase): class Plugin(PluginBase):
def include_js_files(self): def api_mount_points(self):
return ['hello.js'] return [
MountPoint('task/(?P<pk>[^/.]+)/calculate$', TaskVolume.as_view())
]
# def get_volume(self, geojson):
# try:
# raster_path= self.assets_path("odm_dem", "dsm.tif")
# raster=gdal.Open(raster_path)
# gt=raster.GetGeoTransform()
# rb=raster.GetRasterBand(1)
# gdal.UseExceptions()
# geosom = reprojson(geojson, raster)
# coords=[(entry[0],entry[1]) for entry in rings(raster_path, geosom)]
# GSD=gt[1]
# volume=0
# print(rings(raster_path, geosom))
# print(GSD)
# med=statistics.median(entry[2] for entry in rings(raster_path, geosom))
# clip=clip_raster(raster_path, geosom, gt=None, nodata=-9999)
# return ((clip-med)*GSD*GSD)[clip!=-9999.0].sum()
#
# except FileNotFoundError as e:
# logger.warning(e)

Wyświetl plik

@ -1,6 +0,0 @@
PluginsAPI.Map.willAddControls(function(options){
console.log("GOT: ", options);
});
PluginsAPI.Map.didAddControls(function(options){
console.log("GOT2: ", options);
});

Wyświetl plik

@ -0,0 +1,14 @@
{
"name": "volume",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"leaflet-draw": "^1.0.2"
}
}