kopia lustrzana https://github.com/OpenDroneMap/WebODM
Archive cesium ion plugin
rodzic
754b3a5896
commit
3f939eeae8
|
@ -1,25 +0,0 @@
|
|||
<p align="center">
|
||||
<img src="https://github.com/AnalyticalGraphicsInc/cesium/wiki/logos/Cesium_Logo_Color.jpg" width="50%" />
|
||||
</p>
|
||||
|
||||
The Cesium Ion WebODM add-on enables you to publish and stream even the most massive of 3D Content on the web thanks to Cesium Ion.
|
||||
|
||||
With 3D Tiles, even multi-gigabyte models can be streamed to any device without having to download the entire model up front. By loading 3D Tiles into CesiumJS, you can fuse your model with other datasets, add geospatial context to place it at a real world location, or overlay additional details and analysis.
|
||||
|
||||
Learn more at https://cesium.com
|
||||
|
||||
## Setup
|
||||
|
||||
1. Grab a token with, `assets:list, assets:read, assets:write` permissions from [Cesium Ion](https://cesium.com/ion/tokens).
|
||||
|
||||
1. Under the Cesium Ion tab in WebODM set the token to the code generated in ion.
|
||||
|
||||
## Testing
|
||||
|
||||
1. WebODM provides open datasets which can be used as a testbench for the add-on. You will have to create an account in order to access the data. Download datasets [here](https://demo.webodm.org/dashboard/).
|
||||
|
||||
1. To use the dataset click on a **task**, then in the **download assets** dropdown, select all. This should download a zip on to your machine.
|
||||
|
||||
1. In your instance of WebODM, create a project, click import in the upper-right hand corner, and selet the zip we just downloaded from the demo.
|
||||
|
||||
1. After the import completes and a website reload all Cesium Ion functions should be available for testing.
|
|
@ -1,22 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "formik",
|
||||
"url": "https://github.com/jaredpalmer/formik",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "yup",
|
||||
"url": "https://github.com/jquense/yup",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "react-bootstrap",
|
||||
"url": "https://github.com/react-bootstrap/react-bootstrap",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "boto3",
|
||||
"url": "https://github.com/boto/boto3",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
]
|
|
@ -1 +0,0 @@
|
|||
from .plugin import *
|
|
@ -1,314 +0,0 @@
|
|||
import sys
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
from os import path
|
||||
from enum import Enum
|
||||
from itertools import chain as iter_chain
|
||||
|
||||
from app.plugins.views import TaskView
|
||||
from app.plugins.worker import run_function_async
|
||||
from app.plugins.data_store import GlobalDataStore
|
||||
from app.plugins import signals as plugin_signals
|
||||
|
||||
from worker.celery import app
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from rest_framework.fields import ChoiceField, CharField, JSONField
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from rest_framework import serializers
|
||||
|
||||
from .globals import PROJECT_NAME, ION_API_URL
|
||||
from .uploader import upload_to_ion
|
||||
|
||||
|
||||
pluck = lambda dic, *keys: [dic[k] if k in dic else None for k in keys]
|
||||
|
||||
|
||||
### ###
|
||||
# API UTILS #
|
||||
### ###
|
||||
def get_key_for(task_id, key):
|
||||
return "task_{}_{}".format(str(task_id), key)
|
||||
|
||||
|
||||
def del_asset_info(task_id, asset_type, ds=None):
|
||||
if ds is None:
|
||||
ds = GlobalDataStore(PROJECT_NAME)
|
||||
ds.del_key(get_key_for(task_id, asset_type.value))
|
||||
|
||||
|
||||
def set_asset_info(task_id, asset_type, json, ds=None):
|
||||
if ds is None:
|
||||
ds = GlobalDataStore(PROJECT_NAME)
|
||||
return ds.set_json(get_key_for(task_id, asset_type.value), json)
|
||||
|
||||
|
||||
def get_asset_info(task_id, asset_type, default=None, ds=None):
|
||||
if default is None:
|
||||
default = {
|
||||
"id": None,
|
||||
"upload": {"progress": 0, "active": False},
|
||||
"process": {"progress": 0, "active": False},
|
||||
"error": "",
|
||||
}
|
||||
if ds is None:
|
||||
ds = GlobalDataStore(PROJECT_NAME)
|
||||
return ds.get_json(get_key_for(task_id, asset_type.value), default)
|
||||
|
||||
|
||||
def is_asset_task(asset_meta):
|
||||
is_error = len(asset_meta["error"]) > 0
|
||||
return asset_meta["upload"]["active"] or asset_meta["process"]["active"] or is_error
|
||||
|
||||
|
||||
def get_processing_assets(task_id):
|
||||
ispc = app.control.inspect()
|
||||
ion_tasks = set()
|
||||
active = set()
|
||||
from uuid import UUID
|
||||
|
||||
for wtask in iter_chain(*ispc.active().values(), *ispc.reserved().values()):
|
||||
args = eval(wtask["args"])
|
||||
if len(args) < 2:
|
||||
continue
|
||||
ion_tasks.add((str(args[0]), AssetType[args[1]]))
|
||||
|
||||
for asset_type in AssetType:
|
||||
asset_info = get_asset_info(task_id, asset_type)
|
||||
ion_task_id = (task_id, asset_type)
|
||||
if not is_asset_task(asset_info) or ion_task_id in ion_tasks:
|
||||
continue
|
||||
active.add(asset_type)
|
||||
|
||||
return active
|
||||
|
||||
|
||||
### ###
|
||||
# MODEL CONFIG #
|
||||
### ###
|
||||
class AssetType(str, Enum):
|
||||
ORTHOPHOTO = "ORTHOPHOTO"
|
||||
TERRAIN_MODEL = "TERRAIN_MODEL"
|
||||
SURFACE_MODEL = "SURFACE_MODEL"
|
||||
POINTCLOUD = "POINTCLOUD"
|
||||
TEXTURED_MODEL = "TEXTURED_MODEL"
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
RASTER_IMAGERY = "RASTER_IMAGERY"
|
||||
RASTER_TERRAIN = "RASTER_TERRAIN"
|
||||
TERRAIN_DATABASE = "TERRAIN_DATABASE"
|
||||
CITYGML = "CITYGML"
|
||||
KML = "KML"
|
||||
CAPTURE = "3D_CAPTURE"
|
||||
MODEL = "3D_MODEL"
|
||||
POINTCLOUD = "POINT_CLOUD"
|
||||
|
||||
|
||||
class OutputType(str, Enum):
|
||||
IMAGERY = "IMAGERY"
|
||||
TILES = "3DTILES"
|
||||
TERRAIN = "TERRAIN"
|
||||
|
||||
|
||||
ASSET_TO_FILE = {
|
||||
AssetType.ORTHOPHOTO: "orthophoto.tif",
|
||||
AssetType.TERRAIN_MODEL: "dtm.tif",
|
||||
AssetType.SURFACE_MODEL: "dsm.tif",
|
||||
AssetType.POINTCLOUD: "georeferenced_model.laz",
|
||||
AssetType.TEXTURED_MODEL: "textured_model.zip",
|
||||
}
|
||||
|
||||
FILE_TO_ASSET = dict([reversed(i) for i in ASSET_TO_FILE.items()])
|
||||
|
||||
ASSET_TO_OUTPUT = {
|
||||
AssetType.ORTHOPHOTO: OutputType.IMAGERY,
|
||||
AssetType.TERRAIN_MODEL: OutputType.TERRAIN,
|
||||
AssetType.SURFACE_MODEL: OutputType.TERRAIN,
|
||||
AssetType.POINTCLOUD: OutputType.TILES,
|
||||
AssetType.TEXTURED_MODEL: OutputType.TILES,
|
||||
}
|
||||
|
||||
ASSET_TO_SOURCE = {
|
||||
AssetType.ORTHOPHOTO: SourceType.RASTER_IMAGERY,
|
||||
AssetType.TERRAIN_MODEL: SourceType.RASTER_TERRAIN,
|
||||
AssetType.SURFACE_MODEL: SourceType.RASTER_TERRAIN,
|
||||
AssetType.POINTCLOUD: SourceType.POINTCLOUD,
|
||||
AssetType.TEXTURED_MODEL: SourceType.CAPTURE,
|
||||
}
|
||||
|
||||
### ###
|
||||
# RECIEVERS #
|
||||
### ###
|
||||
@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
|
||||
for asset_type in AssetType:
|
||||
del_asset_info(task_id, asset_type)
|
||||
|
||||
|
||||
### ###
|
||||
# API VIEWS #
|
||||
### ###
|
||||
class EnumField(ChoiceField):
|
||||
default_error_messages = {"invalid": _("No matching enum type.")}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.enum_type = kwargs.pop("enum_type")
|
||||
choices = [enum_item.value for enum_item in self.enum_type]
|
||||
self.choice_set = set(choices)
|
||||
super().__init__(choices, **kwargs)
|
||||
|
||||
def to_internal_value(self, data):
|
||||
if data in self.choice_set:
|
||||
return self.enum_type[data]
|
||||
self.fail("invalid")
|
||||
|
||||
def to_representation(self, value):
|
||||
if not value:
|
||||
return None
|
||||
return value.value
|
||||
|
||||
|
||||
class UploadSerializer(serializers.Serializer):
|
||||
token = CharField()
|
||||
name = CharField()
|
||||
asset_type = EnumField(enum_type=AssetType)
|
||||
description = CharField(default="", required=False, allow_blank=True)
|
||||
attribution = CharField(default="", required=False, allow_blank=True)
|
||||
options = JSONField(default={}, required=False)
|
||||
|
||||
|
||||
class UpdateIonAssets(serializers.Serializer):
|
||||
token = CharField()
|
||||
|
||||
|
||||
class ShareTaskView(TaskView):
|
||||
def get(self, request, pk=None):
|
||||
task = self.get_and_check_task(request, pk)
|
||||
|
||||
assets = []
|
||||
for file_name in task.available_assets:
|
||||
if file_name not in FILE_TO_ASSET:
|
||||
continue
|
||||
asset_type = FILE_TO_ASSET[file_name]
|
||||
|
||||
asset_info = get_asset_info(task.id, asset_type)
|
||||
ion_id = asset_info["id"]
|
||||
is_error = len(asset_info["error"]) > 0
|
||||
is_task = is_asset_task(asset_info)
|
||||
is_exported = asset_info["id"] is not None and not is_task
|
||||
|
||||
assets.append(
|
||||
{
|
||||
"type": asset_type,
|
||||
"isError": is_error,
|
||||
"isTask": is_task,
|
||||
"isExported": is_exported,
|
||||
**asset_info,
|
||||
}
|
||||
)
|
||||
|
||||
return Response({"items": assets}, status=status.HTTP_200_OK)
|
||||
|
||||
def post(self, request, pk=None):
|
||||
task = self.get_and_check_task(request, pk)
|
||||
serializer = UploadSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
token, asset_type, name, description, attribution, options = pluck(
|
||||
serializer.validated_data,
|
||||
"token",
|
||||
"asset_type",
|
||||
"name",
|
||||
"description",
|
||||
"attribution",
|
||||
"options",
|
||||
)
|
||||
asset_path = task.get_asset_download_path(ASSET_TO_FILE[asset_type])
|
||||
|
||||
# Skip already processing tasks
|
||||
if asset_type not in get_processing_assets(task.id):
|
||||
if asset_type == AssetType.TEXTURED_MODEL and "position" not in options:
|
||||
extent = None
|
||||
if task.dsm_extent is not None:
|
||||
extent = task.dsm_extent.extent
|
||||
if task.dtm_extent is not None:
|
||||
extent = task.dtm_extent.extent
|
||||
if extent is None:
|
||||
print(f"Unable to find task boundary: {task}")
|
||||
else:
|
||||
lng, lat = extent[0], extent[1]
|
||||
# height is set to zero as model is already offset
|
||||
options["position"] = [lng, lat, 0]
|
||||
|
||||
del_asset_info(task.id, asset_type)
|
||||
asset_info = get_asset_info(task.id, asset_type)
|
||||
asset_info["upload"]["active"] = True
|
||||
set_asset_info(task.id, asset_type, asset_info)
|
||||
|
||||
run_function_async(upload_to_ion,
|
||||
task.id,
|
||||
asset_type,
|
||||
token,
|
||||
asset_path,
|
||||
name,
|
||||
description,
|
||||
attribution,
|
||||
options,
|
||||
)
|
||||
else:
|
||||
print(f"Ignore running ion task {task.id} {str(asset_type)}")
|
||||
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class RefreshIonTaskView(TaskView):
|
||||
def post(self, request, pk=None):
|
||||
serializer = UpdateIonAssets(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
task = self.get_and_check_task(request, pk)
|
||||
token = serializer.validated_data["token"]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
is_updated = False
|
||||
# ion cleanup check
|
||||
for asset_type in AssetType:
|
||||
asset_info = get_asset_info(task.id, asset_type)
|
||||
ion_id = asset_info["id"]
|
||||
if ion_id is None:
|
||||
continue
|
||||
res = requests.get(f"{ION_API_URL}/assets/{ion_id}", headers=headers)
|
||||
if res.status_code != 200:
|
||||
del_asset_info(task.id, asset_type)
|
||||
is_updated = True
|
||||
|
||||
# dead task cleanup
|
||||
for asset_type in get_processing_assets(task.id):
|
||||
del_asset_info(task.id, asset_type)
|
||||
is_updated = True
|
||||
|
||||
return Response({"updated": is_updated}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ClearErrorsTaskView(TaskView):
|
||||
def post(self, request, pk=None):
|
||||
task = self.get_and_check_task(request, pk)
|
||||
for asset_type in AssetType:
|
||||
asset_info = get_asset_info(task.id, asset_type)
|
||||
if len(asset_info["error"]) <= 0:
|
||||
continue
|
||||
del_asset_info(task.id, asset_type)
|
||||
|
||||
return Response({"complete": True}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import json
|
||||
import requests
|
||||
|
||||
from django import forms
|
||||
from django.contrib import messages
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from .globals import ION_API_URL
|
||||
|
||||
|
||||
class TokenForm(forms.Form):
|
||||
token = forms.CharField(
|
||||
label="",
|
||||
required=False,
|
||||
max_length=1024,
|
||||
widget=forms.TextInput(attrs={"placeholder": "Token"}),
|
||||
)
|
||||
|
||||
|
||||
def JsonResponse(dictionary):
|
||||
return HttpResponse(json.dumps(dictionary), content_type="application/json")
|
||||
|
||||
|
||||
def HomeView(plugin):
|
||||
@login_required
|
||||
def view(request):
|
||||
ds = plugin.get_user_data_store(request.user)
|
||||
|
||||
# if this is a POST request we need to process the form data
|
||||
if request.method == "POST":
|
||||
form = TokenForm(request.POST)
|
||||
if form.is_valid():
|
||||
token = form.cleaned_data["token"].strip()
|
||||
if len(token) > 0:
|
||||
messages.success(request, "Updated Cesium ion Token!")
|
||||
else:
|
||||
messages.info(request, "Reset Cesium ion Token")
|
||||
ds.set_string("token", token)
|
||||
|
||||
form = TokenForm(initial={"token": ds.get_string("token", default="")})
|
||||
|
||||
return render(
|
||||
request,
|
||||
plugin.template_path("app.html"),
|
||||
{"title": "Cesium Ion", "form": form},
|
||||
)
|
||||
|
||||
return view
|
||||
|
||||
|
||||
def LoadButtonView(plugin):
|
||||
@login_required
|
||||
def view(request):
|
||||
ds = plugin.get_user_data_store(request.user)
|
||||
token = ds.get_string("token")
|
||||
|
||||
return render(
|
||||
request,
|
||||
plugin.template_path("load_buttons.js"),
|
||||
{
|
||||
"token": token,
|
||||
"app_name": plugin.get_name(),
|
||||
"api_url": plugin.public_url("").rstrip("/"),
|
||||
"ion_url": ION_API_URL,
|
||||
},
|
||||
content_type="text/javascript",
|
||||
)
|
||||
|
||||
return view
|
|
@ -1,2 +0,0 @@
|
|||
PROJECT_NAME = __name__.split(".")[-2]
|
||||
ION_API_URL = "https://api.cesium.com/v1"
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "Cesium Ion",
|
||||
"webodmMinVersion": "0.6.0",
|
||||
"description": "Upload and tile ODM assets with Cesium ion.",
|
||||
"version": "1.2.1",
|
||||
"author": "Cesium GS, Inc",
|
||||
"email": "hello@cesium.com",
|
||||
"repository": "",
|
||||
"tags": ["cesium ion", "ceium"],
|
||||
"homepage": "https://cesium.com",
|
||||
"experimental": true,
|
||||
"deprecated": false
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
from os import path, mkdir, walk, remove as removeFile
|
||||
from zipfile import ZipFile, ZIP_DEFLATED
|
||||
from shutil import rmtree
|
||||
from tempfile import mkdtemp
|
||||
|
||||
|
||||
DELETE_EXTENSIONS = (".conf", ".vec", ".spt")
|
||||
OBJ_FILE_EXTENSION = ".obj"
|
||||
MTL_FILE_EXTENSION = ".mtl"
|
||||
|
||||
|
||||
class IonInvalidZip(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def file_walk(directory):
|
||||
for root, _, file_names in walk(directory):
|
||||
for file_name in file_names:
|
||||
yield path.join(root, file_name)
|
||||
|
||||
|
||||
def zip_dir(zip_name, directory, destructive=False):
|
||||
with ZipFile(zip_name, mode="w", compression=ZIP_DEFLATED) as zipfile:
|
||||
for file_path in file_walk(directory):
|
||||
relpath = path.relpath(file_path, directory)
|
||||
zipfile.write(file_path, relpath)
|
||||
if destructive:
|
||||
removeFile(file_path)
|
||||
|
||||
|
||||
def to_ion_texture_model(texture_model_path, dest_directory=None, minimize_space=True):
|
||||
is_tmp = False
|
||||
if dest_directory is None:
|
||||
is_tmp = True
|
||||
dest_directory = mkdtemp()
|
||||
dest_file = path.join(dest_directory, path.basename(texture_model_path))
|
||||
try:
|
||||
unzip_dir = path.join(dest_directory, "_tmp")
|
||||
mkdir(unzip_dir)
|
||||
with ZipFile(texture_model_path) as zipfile:
|
||||
zipfile.extractall(unzip_dir)
|
||||
|
||||
files_to_delete = set()
|
||||
found_geo = False
|
||||
for file_name in file_walk(unzip_dir):
|
||||
if file_name.endswith(DELETE_EXTENSIONS):
|
||||
files_to_delete.add(file_name)
|
||||
elif file_name.endswith(".obj"):
|
||||
if "_geo" in path.basename(file_name):
|
||||
found_geo = True
|
||||
else:
|
||||
file_name = path.splitext(file_name)[0]
|
||||
files_to_delete.add(file_name + OBJ_FILE_EXTENSION)
|
||||
files_to_delete.add(file_name + MTL_FILE_EXTENSION)
|
||||
|
||||
if not found_geo:
|
||||
raise IonInvalidZip("Unable to find geo file")
|
||||
|
||||
for file_name in files_to_delete:
|
||||
if not path.isfile(file_name):
|
||||
continue
|
||||
removeFile(file_name)
|
||||
|
||||
zip_dir(dest_file, unzip_dir, destructive=minimize_space)
|
||||
rmtree(unzip_dir)
|
||||
except Exception as e:
|
||||
if is_tmp:
|
||||
rmtree(dest_directory)
|
||||
raise e
|
||||
|
||||
return dest_file, dest_directory
|
|
@ -1,39 +0,0 @@
|
|||
import re
|
||||
import json
|
||||
|
||||
from app.plugins import PluginBase, Menu, MountPoint, logger
|
||||
|
||||
from .globals import PROJECT_NAME
|
||||
from .api_views import ShareTaskView, RefreshIonTaskView, ClearErrorsTaskView
|
||||
from .app_views import HomeView, LoadButtonView
|
||||
|
||||
|
||||
class Plugin(PluginBase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.name = PROJECT_NAME
|
||||
|
||||
def main_menu(self):
|
||||
return [Menu("Cesium Ion", self.public_url(""), "fa-cesium fa fa-fw")]
|
||||
|
||||
def include_js_files(self):
|
||||
return ["load_buttons.js"]
|
||||
|
||||
def include_css_files(self):
|
||||
return ["font.css", "build/TaskView.css"]
|
||||
|
||||
def build_jsx_components(self):
|
||||
return ["TaskView.jsx"]
|
||||
|
||||
def api_mount_points(self):
|
||||
return [
|
||||
MountPoint("task/(?P<pk>[^/.]+)/share", ShareTaskView.as_view()),
|
||||
MountPoint("task/(?P<pk>[^/.]+)/refresh", RefreshIonTaskView.as_view()),
|
||||
MountPoint("task/(?P<pk>[^/.]+)/clear", ClearErrorsTaskView.as_view()),
|
||||
]
|
||||
|
||||
def app_mount_points(self):
|
||||
return [
|
||||
MountPoint("$", HomeView(self)),
|
||||
MountPoint("load_buttons.js$", LoadButtonView(self)),
|
||||
]
|
|
@ -1,268 +0,0 @@
|
|||
import React, { Component, Fragment } from "react";
|
||||
|
||||
import ErrorMessage from "webodm/components/ErrorMessage";
|
||||
import { Button } from "react-bootstrap";
|
||||
|
||||
import IonAssetButton from "./components/IonAssetButton";
|
||||
import UploadDialog from "./components/UploadDialog";
|
||||
import TasksDialog from "./components/TasksDialog";
|
||||
import AppContext from "./components/AppContext";
|
||||
import {
|
||||
ImplicitTaskFetcher as TaskFetcher,
|
||||
APIFetcher
|
||||
} from "./components/Fetcher";
|
||||
import { AssetStyles } from "./defaults";
|
||||
import { fetchCancelable, getCookie } from "./utils";
|
||||
|
||||
export default class TaskView extends Component {
|
||||
state = {
|
||||
error: "",
|
||||
currentAsset: null,
|
||||
isTasksDialog: false,
|
||||
isUploadDialogLoading: false
|
||||
};
|
||||
|
||||
cancelableFetch = null;
|
||||
timeoutHandler = null;
|
||||
refreshAssets = null;
|
||||
|
||||
onOpenUploadDialog = asset => this.setState({ currentAsset: asset });
|
||||
|
||||
onHideUploadDialog = () =>
|
||||
this.setState({ currentAsset: null, isUploadDialogLoading: false });
|
||||
|
||||
showTaskDialog = () => this.setState({ isTasksDialog: true });
|
||||
|
||||
hideTaskDialog = () => this.setState({ isTasksDialog: false });
|
||||
|
||||
onUploadAsset = data => {
|
||||
const { task, token, apiURL } = this.props;
|
||||
const { currentAsset } = this.state;
|
||||
const payload = Object.assign({}, data);
|
||||
|
||||
if (currentAsset === null) {
|
||||
console.warning("Submissions on invalid asset");
|
||||
return;
|
||||
}
|
||||
|
||||
payload.token = token;
|
||||
payload.asset_type = currentAsset;
|
||||
|
||||
this.setState({ isUploadDialogLoading: true });
|
||||
this.cancelableFetch = fetchCancelable(
|
||||
`/api${apiURL}/task/${task.id}/share`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie("csrftoken"),
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
}
|
||||
)
|
||||
.promise.then(this.refreshAssets)
|
||||
.finally(this.onHideUploadDialog);
|
||||
};
|
||||
|
||||
onClearFailedAssets = () => {
|
||||
const { task, apiURL } = this.props;
|
||||
|
||||
this.cancelableFetch = fetchCancelable(
|
||||
`/api${apiURL}/task/${task.id}/clear`,
|
||||
{
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: {
|
||||
"X-CSRFToken": getCookie("csrftoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.cancelableFetch.promise.then(this.refreshAssets);
|
||||
};
|
||||
|
||||
onAssetsRefreshed = ({ items = [] }) => {
|
||||
const { isTasksDialog } = this.state;
|
||||
const hasTasks = items.some(item => item.isTask);
|
||||
|
||||
if (!hasTasks) this.hideTaskDialog();
|
||||
if (items.some(item => item.isTask && !item.isError)) {
|
||||
const timeout = 4000 / (isTasksDialog ? 2 : 1);
|
||||
this.timeoutHandler = setTimeout(this.refreshAssets, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
onCleanStatus = ({ updated = false }) => {
|
||||
if (!updated || this.refreshAssets == null) return;
|
||||
this.refreshAssets();
|
||||
};
|
||||
|
||||
onErrorUploadDialog = msg => {
|
||||
this.setState({ error: msg });
|
||||
this.onHideUploadDialog();
|
||||
};
|
||||
|
||||
handleAssetSelect = data => asset => {
|
||||
const idMap = data.items
|
||||
.filter(item => item.isExported)
|
||||
.reduce((accum, item) => {
|
||||
accum[item.type] = item.id;
|
||||
return accum;
|
||||
}, {});
|
||||
window.open(`https://cesium.com/ion/assets/${idMap[asset]}`);
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timeoutHandler !== null) {
|
||||
clearTimeout(this.timeoutHandler);
|
||||
this.timeoutHandler = null;
|
||||
}
|
||||
if (this.cancelableFetch !== null) {
|
||||
try {
|
||||
this.cancelableFetch.cancel();
|
||||
this.cancelableFetch = null;
|
||||
} catch (exception) {
|
||||
console.warning("Failed to clear dead requests");
|
||||
console.warning(exception);
|
||||
}
|
||||
}
|
||||
|
||||
this.refreshAssets = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { task, token } = this.props;
|
||||
const {
|
||||
isTasksDialog,
|
||||
isUploadDialogLoading,
|
||||
isRefreshTask,
|
||||
currentAsset
|
||||
} = this.state;
|
||||
const isUploadDialog = currentAsset !== null;
|
||||
const assetName = isUploadDialog ? AssetStyles[currentAsset].name : "";
|
||||
|
||||
return (
|
||||
<AppContext.Provider value={this.props}>
|
||||
<ErrorMessage bind={[this, "error"]} />
|
||||
<div className={"ion-dropdowns"}>
|
||||
<TaskFetcher
|
||||
path={"share"}
|
||||
onLoad={this.onAssetsRefreshed}
|
||||
onBindRefresh={method => (this.refreshAssets = method)}
|
||||
>
|
||||
{({ isError, data = {} }) => {
|
||||
// Asset Export and View Selector
|
||||
const { items = [] } = data;
|
||||
const available = items
|
||||
.filter(
|
||||
item =>
|
||||
(!item.isExported && !item.isTask) ||
|
||||
item.isError
|
||||
)
|
||||
.map(item => item.type);
|
||||
const exported = items
|
||||
.filter(item => item.isExported)
|
||||
.map(item => item.type);
|
||||
|
||||
// Tasks Selector
|
||||
const processing = items.filter(
|
||||
item => item.isTask
|
||||
);
|
||||
const isTasks = processing.length > 0;
|
||||
const isErrors = processing.some(
|
||||
item => item.isError
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{available.length > 0 && (
|
||||
<IonAssetButton
|
||||
assets={available}
|
||||
onSelect={this.onOpenUploadDialog}
|
||||
>
|
||||
Tile in Cesium Ion
|
||||
</IonAssetButton>
|
||||
)}
|
||||
|
||||
{exported.length > 0 && (
|
||||
<IonAssetButton
|
||||
assets={exported}
|
||||
onSelect={this.handleAssetSelect(
|
||||
data
|
||||
)}
|
||||
>
|
||||
View in Cesium Ion
|
||||
</IonAssetButton>
|
||||
)}
|
||||
{items.length <= 0 && (
|
||||
<Button
|
||||
className={"ion-btn"}
|
||||
bsStyle={"primary"}
|
||||
bsSize={"small"}
|
||||
onClick={this.refreshAssets}
|
||||
>
|
||||
<i className={"fa fa-cesium"} />
|
||||
Refresh Available ion Assets
|
||||
</Button>
|
||||
)}
|
||||
{isTasks && (
|
||||
<Button
|
||||
className={"ion-btn"}
|
||||
bsStyle={
|
||||
isErrors ? "danger" : "primary"
|
||||
}
|
||||
bsSize={"small"}
|
||||
onClick={this.showTaskDialog}
|
||||
>
|
||||
<i className={"fa fa-cesium"} />
|
||||
View ion Tasks
|
||||
</Button>
|
||||
)}
|
||||
<TasksDialog
|
||||
show={isTasksDialog}
|
||||
tasks={processing}
|
||||
onHide={this.hideTaskDialog}
|
||||
onClearFailed={this.onClearFailedAssets}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}}
|
||||
</TaskFetcher>
|
||||
</div>
|
||||
|
||||
<APIFetcher path={"projects/"} params={{ id: task.project }}>
|
||||
{({ isLoading, isError, data }) => {
|
||||
const initialValues = {};
|
||||
|
||||
if (!isLoading && !isError && data.results.length > 0) {
|
||||
const project = data.results[0];
|
||||
initialValues.name = `${project.name} | ${task.name} — ${assetName}`;
|
||||
initialValues.description = project.description;
|
||||
}
|
||||
|
||||
return (
|
||||
<UploadDialog
|
||||
title={`Tile in Cesium ion — ${assetName}`}
|
||||
initialValues={initialValues}
|
||||
show={isUploadDialog}
|
||||
loading={isUploadDialogLoading}
|
||||
asset={currentAsset}
|
||||
onHide={this.onHideUploadDialog}
|
||||
onSubmit={this.onUploadAsset}
|
||||
onError={this.onErrorUploadDialog}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</APIFetcher>
|
||||
<TaskFetcher
|
||||
method={"POST"}
|
||||
path={"refresh"}
|
||||
body={JSON.stringify({ token })}
|
||||
onLoad={this.onCleanStatus}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
const AppContext = React.createContext({
|
||||
apiUrl: null,
|
||||
ionURL: null,
|
||||
token: null,
|
||||
task: null
|
||||
});
|
||||
|
||||
export default AppContext;
|
|
@ -1,50 +0,0 @@
|
|||
import { Formik, Field, getIn } from "formik";
|
||||
|
||||
import {
|
||||
FormGroup,
|
||||
ControlLabel,
|
||||
FormControl,
|
||||
Checkbox,
|
||||
HelpBlock
|
||||
} from "react-bootstrap";
|
||||
|
||||
const BootstrapFieldComponent = ({
|
||||
field,
|
||||
form: { touched, errors },
|
||||
label,
|
||||
help,
|
||||
type = "",
|
||||
showIcon = true,
|
||||
...props
|
||||
}) => {
|
||||
const isError = getIn(errors, field.name) && getIn(touched, field.name);
|
||||
const errorMsg = getIn(errors, field.name);
|
||||
let ControlComponent = FormControl;
|
||||
|
||||
const testType = type.toLowerCase();
|
||||
if (testType === "checkbox") ControlComponent = Checkbox;
|
||||
else if (testType === "textarea" || testType === "select")
|
||||
props.componentClass = testType;
|
||||
else props.type = type;
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
controlId={field.name}
|
||||
validationState={isError ? "error" : null}
|
||||
style={{ marginLeft: 0, marginRight: 0 }}
|
||||
>
|
||||
{label && <ControlLabel>{label}</ControlLabel>}
|
||||
<ControlComponent {...field} {...props} />
|
||||
{isError && <HelpBlock>{errorMsg}</HelpBlock>}
|
||||
{help && !isError && <HelpBlock>{help}</HelpBlock>}
|
||||
{isError && showIcon && <FormControl.Feedback />}
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const BootstrapField = props => (
|
||||
<Field component={BootstrapFieldComponent} {...props} />
|
||||
);
|
||||
|
||||
export { BootstrapFieldComponent };
|
||||
export default BootstrapField;
|
|
@ -1,140 +0,0 @@
|
|||
import React, { PureComponent } from "react";
|
||||
|
||||
import AppContext from "./AppContext";
|
||||
import { fetchCancelable, getCookie } from "../utils";
|
||||
|
||||
export class Fetcher extends PureComponent {
|
||||
static defaultProps = {
|
||||
url: "",
|
||||
path: "",
|
||||
method: "GET",
|
||||
onBindRefresh: () => {},
|
||||
onError: () => {},
|
||||
onLoad: () => {}
|
||||
};
|
||||
|
||||
state = {
|
||||
isLoading: true,
|
||||
isError: false
|
||||
};
|
||||
|
||||
cancelableFetch = null;
|
||||
|
||||
fetch = () => {
|
||||
const {
|
||||
url,
|
||||
path,
|
||||
onError,
|
||||
onLoad,
|
||||
refresh,
|
||||
children,
|
||||
params,
|
||||
...options
|
||||
} = this.props;
|
||||
|
||||
let queryURL = `${url}/${path}`;
|
||||
if (params !== undefined) {
|
||||
const serializedParams = `?${Object.keys(params)
|
||||
.map(key =>
|
||||
[key, params[key]].map(encodeURIComponent).join("=")
|
||||
)
|
||||
.join("&")}`;
|
||||
queryURL = queryURL.replace(/[\/\?]+$/, "");
|
||||
queryURL += serializedParams;
|
||||
}
|
||||
|
||||
this.cancelableFetch = fetchCancelable(queryURL, options);
|
||||
return this.cancelableFetch.promise
|
||||
.then(res => {
|
||||
if (res.status !== 200) throw new Error(res.status);
|
||||
return res.json();
|
||||
})
|
||||
.then(data => {
|
||||
this.setState({ data, isLoading: false });
|
||||
onLoad(data);
|
||||
})
|
||||
.catch(out => {
|
||||
if (out.isCanceled) return;
|
||||
this.setState({ error: out, isLoading: false, isError: true });
|
||||
onError(out);
|
||||
})
|
||||
.finally(() => (this.cancelableFetch = null));
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.fetch();
|
||||
this.props.onBindRefresh(this.fetch);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.onBindRefresh(null);
|
||||
if (this.cancelableFetch === null) return;
|
||||
this.cancelableFetch.cancel();
|
||||
this.cancelableFetch = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
if (children == null) return null;
|
||||
if (typeof children !== "function")
|
||||
return React.cloneElement(children, this.state);
|
||||
else return children(this.state);
|
||||
}
|
||||
}
|
||||
|
||||
const ImplicitFetcher = ({
|
||||
url,
|
||||
getURL = null,
|
||||
getOptions = null,
|
||||
...options
|
||||
}) => (
|
||||
<AppContext.Consumer>
|
||||
{context => (
|
||||
<Fetcher
|
||||
url={getURL !== null ? getURL(context, options) : url}
|
||||
{...(getOptions !== null ? getOptions(context, options) : {})}
|
||||
{...options}
|
||||
/>
|
||||
)}
|
||||
</AppContext.Consumer>
|
||||
);
|
||||
|
||||
const APIFetcher = props => (
|
||||
<Fetcher
|
||||
url={"/api"}
|
||||
credentials={"same-origin"}
|
||||
headers={{
|
||||
"X-CSRFToken": getCookie("csrftoken"),
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const ImplicitTaskFetcher = props => (
|
||||
<ImplicitFetcher
|
||||
getURL={({ apiURL, task }) => `/api${apiURL}/task/${task.id}`}
|
||||
credentials={"same-origin"}
|
||||
headers={{
|
||||
"X-CSRFToken": getCookie("csrftoken"),
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const ImplicitIonFetcher = props => (
|
||||
<ImplicitFetcher
|
||||
getURL={({ ionURL }) => ionURL}
|
||||
getOptions={({ token }) => ({
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export { APIFetcher, ImplicitTaskFetcher, ImplicitIonFetcher };
|
|
@ -1,35 +0,0 @@
|
|||
import React from "react";
|
||||
import { connect } from "formik";
|
||||
|
||||
class FormikErrorFocus extends React.Component {
|
||||
isObject(value) {
|
||||
return (
|
||||
value && typeof value === "object" && value.constructor === Object
|
||||
);
|
||||
}
|
||||
getKeysRecursively = object => {
|
||||
if (!this.isObject(object)) {
|
||||
return "";
|
||||
}
|
||||
const currentKey = Object.keys(object)[0];
|
||||
if (!this.getKeysRecursively(object[currentKey])) {
|
||||
return currentKey;
|
||||
}
|
||||
return currentKey + "." + this.getKeysRecursively(object[currentKey]);
|
||||
};
|
||||
componentDidUpdate(prevProps) {
|
||||
const { isSubmitting, isValidating, errors } = prevProps.formik;
|
||||
const keys = Object.keys(errors);
|
||||
if (keys.length > 0 && isSubmitting && !isValidating) {
|
||||
const selectorKey = this.getKeysRecursively(errors);
|
||||
const selector = `[id="${selectorKey}"], [name="${selectorKey}"] `;
|
||||
const errorElement = document.querySelector(selector);
|
||||
if (errorElement) errorElement.focus();
|
||||
console.warn(errors);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export default connect(FormikErrorFocus);
|
|
@ -1,60 +0,0 @@
|
|||
import React, { PureComponent, Fragment } from "react";
|
||||
|
||||
import { DropdownButton, MenuItem } from "react-bootstrap";
|
||||
|
||||
import IonAssetLabel from "./IonAssetLabel";
|
||||
import { AssetStyles } from "../defaults";
|
||||
|
||||
import "./IonAssetButton.scss";
|
||||
|
||||
export default class IonAssetButton extends PureComponent {
|
||||
static defaultProps = {
|
||||
assets: [],
|
||||
assetComponent: IonAssetLabel,
|
||||
onSelect: () => {}
|
||||
};
|
||||
|
||||
handleClick = asset => () => this.props.onSelect(asset);
|
||||
|
||||
render() {
|
||||
const {
|
||||
assets,
|
||||
onSelect,
|
||||
children,
|
||||
assetComponent: AssetComponent
|
||||
} = this.props;
|
||||
|
||||
const menuItems = assets
|
||||
.sort((a, b) =>
|
||||
AssetStyles[a].name.localeCompare(AssetStyles[b].name)
|
||||
)
|
||||
.map(asset => (
|
||||
<MenuItem
|
||||
key={asset}
|
||||
tag={"a"}
|
||||
onClick={this.handleClick(asset)}
|
||||
>
|
||||
<AssetComponent asset={asset} showIcon={true} />
|
||||
</MenuItem>
|
||||
));
|
||||
|
||||
const title = (
|
||||
<Fragment>
|
||||
<i className={"fa fa-cesium"} />
|
||||
{children}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
id={"cesiumIonUploadDropdown"}
|
||||
bsStyle={"primary"}
|
||||
bsSize={"small"}
|
||||
className={"ion-btn"}
|
||||
title={title}
|
||||
>
|
||||
{menuItems}
|
||||
</DropdownButton>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
.ion-btn {
|
||||
.fa-cesium {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.caret {
|
||||
margin-left: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.ion-dropdowns .dropdown {
|
||||
float: none;
|
||||
margin-right: 4px;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import React, { PureComponent, Fragment } from "react";
|
||||
import { AssetStyles } from "../defaults";
|
||||
|
||||
const IonAssetLabel = ({ asset, showIcon = false, ...options }) => (
|
||||
<Fragment>
|
||||
{showIcon && <i className={`${AssetStyles[asset].icon}`} />}
|
||||
{" "}
|
||||
{AssetStyles[asset].name}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default IonAssetLabel;
|
|
@ -1,19 +0,0 @@
|
|||
.ion-tasks .list-group .list-group-item {
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
padding: 20px 0;
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
import React, { Component, Fragment } from "react";
|
||||
|
||||
import {
|
||||
Row,
|
||||
Col,
|
||||
Modal,
|
||||
Button,
|
||||
ListGroup,
|
||||
ListGroupItem,
|
||||
ProgressBar,
|
||||
Glyphicon
|
||||
} from "react-bootstrap";
|
||||
|
||||
import IonAssetLabel from "./IonAssetLabel";
|
||||
import "./TaskDialog.scss";
|
||||
|
||||
const TaskStatusItem = ({
|
||||
asset,
|
||||
progress,
|
||||
task,
|
||||
helpText = "",
|
||||
active = true,
|
||||
bsStyle = "primary"
|
||||
}) => (
|
||||
<ListGroupItem>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<p style={{ fontWeight: "bold" }}>
|
||||
<IonAssetLabel asset={asset} showIcon={true} />
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<p className={"pull-right"}>Status: {task}</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<ProgressBar active={active} now={progress} bsStyle={bsStyle} />
|
||||
{helpText && <small>{helpText}</small>}
|
||||
</ListGroupItem>
|
||||
);
|
||||
|
||||
export default class TaskDialog extends Component {
|
||||
static defaultProps = {
|
||||
tasks: [],
|
||||
taskComponent: TaskStatusItem
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
tasks,
|
||||
taskComponent: TaskComponent,
|
||||
onClearFailed,
|
||||
onHide,
|
||||
...options
|
||||
} = this.props;
|
||||
|
||||
let hasErrors = false;
|
||||
|
||||
const taskItems = tasks.map(
|
||||
({ type: asset, upload, process, error }) => {
|
||||
let task,
|
||||
style,
|
||||
active = true,
|
||||
progress = 0;
|
||||
|
||||
if (upload.active) {
|
||||
progress = upload.progress;
|
||||
task = "Uploading";
|
||||
style = "info";
|
||||
} else if (process.active) {
|
||||
progress = process.progress;
|
||||
task = "Processing";
|
||||
style = "success";
|
||||
}
|
||||
|
||||
if (error.length > 0) {
|
||||
task = "Error";
|
||||
style = "danger";
|
||||
active = false;
|
||||
console.error(error);
|
||||
hasErrors = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<TaskStatusItem
|
||||
key={asset}
|
||||
asset={asset}
|
||||
progress={progress * 100}
|
||||
task={task}
|
||||
bsStyle={style}
|
||||
helpText={error}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal className={"ion-tasks"} onHide={onHide} {...options}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<i className={"fa fa-cesium"} /> Cesium Ion Tasks
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ListGroup>{taskItems}</ListGroup>
|
||||
|
||||
{hasErrors && (
|
||||
<Button
|
||||
className={"center-block"}
|
||||
bsSize={"small"}
|
||||
bsStyle={"danger"}
|
||||
onClick={onClearFailed}
|
||||
>
|
||||
<Glyphicon
|
||||
style={{ marginRight: "0.5em" }}
|
||||
glyph={"trash"}
|
||||
/>
|
||||
Remove Failed Tasks
|
||||
</Button>
|
||||
)}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button bsStyle={"primary"} onClick={onHide}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
import React, { Component, Fragment } from "react";
|
||||
|
||||
import FormDialog from "webodm/components/FormDialog";
|
||||
import { Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { Row, Col, Modal, Button } from "react-bootstrap";
|
||||
|
||||
import BootstrapField from "./BootstrapField";
|
||||
import FormikErrorFocus from "./FormikErrorFocus";
|
||||
import { ImplicitIonFetcher as IonFetcher } from "./Fetcher";
|
||||
import { AssetType, SourceType } from "../defaults";
|
||||
import "./UploadDialog.scss";
|
||||
|
||||
export default class UploadDialog extends Component {
|
||||
static AssetSourceType = {
|
||||
[AssetType.ORTHOPHOTO]: SourceType.RASTER_IMAGERY,
|
||||
[AssetType.TERRAIN_MODEL]: SourceType.RASTER_TERRAIN,
|
||||
[AssetType.SURFACE_MODEL]: SourceType.RASTER_TERRAIN,
|
||||
[AssetType.POINTCLOUD]: SourceType.POINTCLOUD,
|
||||
[AssetType.TEXTURED_MODEL]: SourceType.CAPTURE
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
show: true,
|
||||
asset: null,
|
||||
loading: false,
|
||||
initialValues: {
|
||||
name: "",
|
||||
description: "",
|
||||
attribution: "",
|
||||
options: {
|
||||
baseTerrainId: "",
|
||||
textureFormat: false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleError = msg => error => {
|
||||
this.props.onError(msg);
|
||||
console.error(error);
|
||||
};
|
||||
|
||||
onSubmit = values => {
|
||||
const { asset, onSubmit } = this.props;
|
||||
values = JSON.parse(JSON.stringify(values));
|
||||
const { options = {} } = values;
|
||||
|
||||
switch (UploadDialog.AssetSourceType[asset]) {
|
||||
case SourceType.RASTER_TERRAIN:
|
||||
if (options.baseTerrainId === "")
|
||||
delete options["baseTerrainId"];
|
||||
else options.baseTerrainId = parseInt(options.baseTerrainId);
|
||||
options.toMeters = 1;
|
||||
options.heightReference = "WGS84";
|
||||
options.waterMask = false;
|
||||
break;
|
||||
case SourceType.CAPTURE:
|
||||
options.textureFormat = options.textureFormat ? "WEBP" : "AUTO";
|
||||
break;
|
||||
}
|
||||
|
||||
onSubmit(values);
|
||||
};
|
||||
|
||||
getSourceFields() {
|
||||
switch (UploadDialog.AssetSourceType[this.props.asset]) {
|
||||
case SourceType.RASTER_TERRAIN:
|
||||
const loadOptions = ({ isLoading, isError, data }) => {
|
||||
if (isLoading || isError)
|
||||
return <option disabled>LOADING...</option>;
|
||||
const userItems = data.items
|
||||
.filter(item => item.type === "TERRAIN")
|
||||
.map(item => (
|
||||
<option key={item.id} value={item.id}>
|
||||
{item.name}
|
||||
</option>
|
||||
));
|
||||
return [
|
||||
<option key={"mean-sea-level"} value={""}>
|
||||
Mean Sea Level
|
||||
</option>,
|
||||
...userItems
|
||||
];
|
||||
};
|
||||
|
||||
return (
|
||||
<BootstrapField
|
||||
name={"options.baseTerrainId"}
|
||||
label={"Base Terrain: "}
|
||||
type={"select"}
|
||||
>
|
||||
<IonFetcher
|
||||
path="assets"
|
||||
onError={this.handleError(
|
||||
"Failed to load terrain options. " +
|
||||
"Please check your token!"
|
||||
)}
|
||||
>
|
||||
{loadOptions}
|
||||
</IonFetcher>
|
||||
</BootstrapField>
|
||||
);
|
||||
case SourceType.CAPTURE:
|
||||
return (
|
||||
<BootstrapField
|
||||
name={"options.textureFormat"}
|
||||
type={"checkbox"}
|
||||
help={
|
||||
"Will produce WebP images, which are typically 25-34% smaller than " +
|
||||
"equivalent JPEG images which leads to faster streaming and reduced " +
|
||||
"data usage. 3D Tiles produced with this option require a client " +
|
||||
"that supports the glTF EXT_texture_webp extension, such as " +
|
||||
"CesiumJS 1.54 or newer, and a browser that supports WebP, such as " +
|
||||
"Chrome or Firefox 65 and newer."
|
||||
}
|
||||
>
|
||||
Use WebP images
|
||||
</BootstrapField>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getValidation() {
|
||||
const schema = {};
|
||||
|
||||
switch (UploadDialog.AssetSourceType[this.props.asset]) {
|
||||
case SourceType.RASTER_TERRAIN:
|
||||
schema.baseTerrainId = Yup.string();
|
||||
break;
|
||||
case SourceType.CAPTURE:
|
||||
schema.textureFormat = Yup.boolean();
|
||||
break;
|
||||
}
|
||||
|
||||
return Yup.object().shape({
|
||||
name: Yup.string().required("A name is required!"),
|
||||
options: Yup.object()
|
||||
.shape(schema)
|
||||
.default({})
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
initialValues,
|
||||
onHide,
|
||||
title,
|
||||
loading: isLoading,
|
||||
...options
|
||||
} = this.props;
|
||||
|
||||
delete options["asset"];
|
||||
delete options["onSubmit"];
|
||||
|
||||
const mergedInitialValues = {
|
||||
...UploadDialog.defaultProps.initialValues,
|
||||
...initialValues
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal className={"ion-upload"} onHide={onHide} {...options}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
<i className={"fa fa-cesium"} /> {title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Formik
|
||||
initialValues={mergedInitialValues}
|
||||
onSubmit={this.onSubmit}
|
||||
enableReinitialize
|
||||
validationSchema={this.getValidation()}
|
||||
>
|
||||
{({ handleSubmit = () => {} }) => (
|
||||
<form>
|
||||
<Modal.Body>
|
||||
<BootstrapField
|
||||
name={"name"}
|
||||
label={"Name: "}
|
||||
type={"text"}
|
||||
/>
|
||||
<BootstrapField
|
||||
name={"description"}
|
||||
label={"Description: "}
|
||||
type={"textarea"}
|
||||
rows={"3"}
|
||||
/>
|
||||
<BootstrapField
|
||||
name={"attribution"}
|
||||
label={"Attribution: "}
|
||||
type={"text"}
|
||||
/>
|
||||
{this.getSourceFields()}
|
||||
|
||||
<FormikErrorFocus />
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer>
|
||||
<Button onClick={onHide}>Close</Button>
|
||||
{isLoading && (
|
||||
<Button bsStyle="primary" disabled>
|
||||
<i
|
||||
className={"fa fa-sync fa-spin"}
|
||||
/>
|
||||
Submitting...
|
||||
</Button>
|
||||
)}
|
||||
{!isLoading && (
|
||||
<Button
|
||||
bsStyle="primary"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<i className={"fa fa-upload"} />
|
||||
Submit
|
||||
</Button>
|
||||
)}
|
||||
</Modal.Footer>
|
||||
</form>
|
||||
)}
|
||||
</Formik>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
.modal-backdrop {
|
||||
z-index: 100000 !important;
|
||||
}
|
||||
|
||||
.ion-upload.modal button i {
|
||||
margin-right: 1em;
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import AssetType from "./AssetType";
|
||||
|
||||
const AssetStyles = {
|
||||
[AssetType.ORTHOPHOTO]: {
|
||||
name: "Orthophoto",
|
||||
icon: "far fa-image"
|
||||
},
|
||||
[AssetType.TERRAIN_MODEL]: {
|
||||
name: "Terrain Model",
|
||||
icon: "fa fa-chart-area"
|
||||
},
|
||||
[AssetType.SURFACE_MODEL]: {
|
||||
name: "Surface Model",
|
||||
icon: "fa fa-chart-area"
|
||||
},
|
||||
[AssetType.POINTCLOUD]: {
|
||||
name: "Pointcloud",
|
||||
icon: "fa fa-cube"
|
||||
},
|
||||
[AssetType.TEXTURED_MODEL]: {
|
||||
name: "Texture Model",
|
||||
icon: "fab fa-connectdevelop"
|
||||
}
|
||||
};
|
||||
|
||||
export default AssetStyles;
|
|
@ -1,9 +0,0 @@
|
|||
const AssetType = {
|
||||
ORTHOPHOTO: "ORTHOPHOTO",
|
||||
TERRAIN_MODEL: "TERRAIN_MODEL",
|
||||
SURFACE_MODEL: "SURFACE_MODEL",
|
||||
POINTCLOUD: "POINTCLOUD",
|
||||
TEXTURED_MODEL: "TEXTURED_MODEL"
|
||||
};
|
||||
|
||||
export default AssetType;
|
|
@ -1,12 +0,0 @@
|
|||
const SourceType = {
|
||||
RASTER_IMAGERY: "RASTER_IMAGERY",
|
||||
RASTER_TERRAIN: "RASTER_TERRAIN",
|
||||
TERRAIN_DATABASE: "TERRAIN_DATABASE",
|
||||
CITYGML: "CITYGML",
|
||||
KML: "KML",
|
||||
CAPTURE: "3D_CAPTURE",
|
||||
MODEL: "3D_MODEL",
|
||||
POINTCLOUD: "POINT_CLOUD"
|
||||
};
|
||||
|
||||
export default SourceType;
|
|
@ -1,5 +0,0 @@
|
|||
import AssetType from "./AssetType";
|
||||
import SourceType from "./SourceType";
|
||||
import AssetStyles from "./AssetStyles";
|
||||
|
||||
export { AssetType, SourceType, AssetStyles };
|
|
@ -1,25 +0,0 @@
|
|||
@font-face {
|
||||
font-family: 'fa-cesium';
|
||||
src:
|
||||
url('fonts/fa-cesium.ttf?rw87j5') format('truetype'),
|
||||
url('fonts/fa-cesium.woff?rw87j5') format('woff'),
|
||||
url('fonts/fa-cesium.svg?rw87j5#fa-cesium') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.fa-cesium:before {
|
||||
content: "\e900";
|
||||
|
||||
font-family: 'fa-cesium' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Generated by IcoMoon</metadata>
|
||||
<defs>
|
||||
<font id="fa-cesium" horiz-adv-x="1024">
|
||||
<font-face units-per-em="1024" ascent="960" descent="-64" />
|
||||
<missing-glyph horiz-adv-x="1024" />
|
||||
<glyph unicode=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="" glyph-name="cesium" horiz-adv-x="1005" d="M964.309 524.102c-16.578 0-32.613-9.381-45.192-26.269l-158.092-213.11c-26.157-35.268-63.363-55.524-102.025-55.524h-0.555c-38.686 0-75.855 20.257-102 55.524l-158.080 213.11c-12.579 16.886-28.589 26.269-45.229 26.269-16.566 0-32.626-9.381-45.155-26.269l-158.154-213.11c-25.984-35.008-62.844-55.191-101.111-55.524 80.596-173.115 253.265-293.199 453.846-293.199 277.424 0 502.419 229.232 502.419 511.962 0 20.072-1.358 39.687-3.617 58.993-11.023 11.036-23.836 17.146-37.058 17.146zM502.562 959.999c-277.535 0-502.543-229.232-502.543-511.999 0-45.019 6.258-88.409 16.961-130.046 9.48-7.394 20.084-11.764 30.848-11.764 16.677 0 32.687 9.295 45.291 26.195l158.092 213.122c26.095 35.354 63.338 55.586 101.925 55.586 38.612 0 75.793-20.232 101.975-55.586l152.055-204.925 6.58-8.197c12.554-16.799 28.528-26.058 44.995-26.195 16.418 0.136 32.404 9.394 44.933 26.195l6.641 8.197 152.105 204.925c26.095 35.354 63.313 55.586 101.901 55.586 6.11 0 12.283-0.679 18.319-1.691-63.412 208.876-254.315 360.599-480.078 360.599zM670.356 634.891c-29.021 0-52.463 23.972-52.463 53.562 0 29.515 23.442 53.438 52.463 53.438 29.058 0 52.524-23.923 52.524-53.438 0-29.601-23.466-53.562-52.524-53.562z" />
|
||||
</font></defs></svg>
|
Przed Szerokość: | Wysokość: | Rozmiar: 1.6 KiB |
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"scripts": {
|
||||
"dev": "webpack --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"formik": "^1.5.7",
|
||||
"moment": "^2.24.0",
|
||||
"react-bootstrap": "^0.32.4",
|
||||
"yup": "^0.27.0"
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
const makeCancelable = promise => {
|
||||
let hasCanceled_ = false;
|
||||
|
||||
const wrappedPromise = new Promise((resolve, reject) => {
|
||||
promise.then(
|
||||
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
|
||||
error =>
|
||||
hasCanceled_ ? reject({ isCanceled: true }) : reject(error)
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
promise: wrappedPromise,
|
||||
cancel() {
|
||||
hasCanceled_ = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export { makeCancelable };
|
||||
|
||||
const fetchCancelable = (...args) => makeCancelable(fetch(...args));
|
||||
|
||||
export default fetchCancelable;
|
|
@ -1,9 +0,0 @@
|
|||
export default function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length == 2)
|
||||
return parts
|
||||
.pop()
|
||||
.split(";")
|
||||
.shift();
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
import fetchCancelable from "./fetchCancelable";
|
||||
import getCookie from "./getCookie";
|
||||
|
||||
export { getCookie, fetchCancelable };
|
|
@ -1,64 +0,0 @@
|
|||
{% extends "app/plugins/templates/base.html" %}
|
||||
{% block content %}
|
||||
<style>
|
||||
.alert {
|
||||
position: absolute;
|
||||
z-index: 100000;
|
||||
width: 79vw;
|
||||
bottom: 0.5em;
|
||||
right: 1em;
|
||||
width: 18em;
|
||||
}
|
||||
#navbar-top.cesium-navbar {
|
||||
margin: -15px;
|
||||
margin-top: -10px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
#navbar-top.cesium-navbar > .navbar-text {
|
||||
margin: 0;
|
||||
left: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
#navbar-top.cesium-navbar .description {
|
||||
font-size: 10px;
|
||||
margin-left: 28px
|
||||
}
|
||||
</style>
|
||||
<nav id="navbar-top" class="navbar-default cesium-navbar">
|
||||
<h4 class="navbar-text">
|
||||
<i class="fa fa-cesium fa-fw"></i> <strong>Cesium Ion</strong>
|
||||
<p class="description">
|
||||
Use Cesium Ion's simple workflow to create 3D maps of your geospatial
|
||||
data for visualization, analysis, and sharing
|
||||
</p>
|
||||
</h4>
|
||||
</div>
|
||||
{% if not form.token.value %}
|
||||
<h5>
|
||||
<strong>Instructions</strong>
|
||||
</h5>
|
||||
<ol>
|
||||
<li>
|
||||
Generate a token at
|
||||
<a href="https://cesium.com/ion/tokens" target="_blank"> cesium.com/ion/tokens </a>
|
||||
with <b>all permissions:</b>
|
||||
<i>assets:list, assets:read, assets:write, geocode.</i>
|
||||
</li>
|
||||
<li>Copy and paste the token into the form below.</li>
|
||||
</ol>
|
||||
{% else %}
|
||||
<p><b>You are all set!</b> To share a task, select it from the <a href="/dashboard/">dashboard</a> and press the <b>Tile in Cesium ion</b> button.</p>
|
||||
<p>
|
||||
<a class="btn btn-sm btn-primary" href="/dashboard"><i class="fa fa-external-link"></i> Go To Dashboard</a>
|
||||
<a class="btn btn-sm btn-default" href="https://cesium.com/ion" target="_blank"><i class="fa fa-cesium"></i> Open Cesium Ion</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="oam-form oam-token-form">
|
||||
<h5><b>Token Settings</b></h5>
|
||||
{% csrf_token %}
|
||||
{% include "app/plugins/templates/form.html" %}
|
||||
<button type="submit" class="btn btn-primary"><i class="fa fa-save fa-fw"></i> Set Token</i></button>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,13 +0,0 @@
|
|||
PluginsAPI.Dashboard.addTaskActionButton(
|
||||
["{{ app_name }}/build/TaskView.js"],
|
||||
function(args, TaskView) {
|
||||
if ("{{ token }}"){
|
||||
return React.createElement(TaskView, {
|
||||
task: args.task,
|
||||
token: "{{ token }}",
|
||||
apiURL: "{{ api_url }}",
|
||||
ionURL: "{{ ion_url }}"
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
|
@ -1,190 +0,0 @@
|
|||
# Arg order is very important for task deconstruction.
|
||||
# If order is changed make sure that the refresh API call is updated
|
||||
|
||||
def upload_to_ion(
|
||||
task_id,
|
||||
asset_type,
|
||||
token,
|
||||
asset_path,
|
||||
name,
|
||||
description="",
|
||||
attribution="",
|
||||
options={},
|
||||
):
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import requests
|
||||
from os import path
|
||||
from shutil import rmtree
|
||||
from enum import Enum
|
||||
from app.plugins import logger
|
||||
from .api_views import (
|
||||
get_asset_info,
|
||||
set_asset_info,
|
||||
AssetType,
|
||||
ASSET_TO_OUTPUT,
|
||||
ASSET_TO_SOURCE,
|
||||
ASSET_TO_FILE,
|
||||
pluck,
|
||||
)
|
||||
from .model_tools import (
|
||||
to_ion_texture_model,
|
||||
IonInvalidZip,
|
||||
)
|
||||
from .globals import ION_API_URL
|
||||
class LoggerAdapter(logging.LoggerAdapter):
|
||||
def __init__(self, prefix, logger):
|
||||
super().__init__(logger, {})
|
||||
self.prefix = prefix
|
||||
|
||||
def process(self, msg, kwargs):
|
||||
return "[%s] %s" % (self.prefix, msg), kwargs
|
||||
|
||||
class TaskUploadProgress(object):
|
||||
def __init__(self, file_path, task_id, asset_type, logger=None, log_step_size=0.05):
|
||||
self._task_id = task_id
|
||||
self._asset_type = asset_type
|
||||
self._logger = logger
|
||||
|
||||
self._uploaded_bytes = 0
|
||||
self._total_bytes = float(path.getsize(file_path))
|
||||
self._asset_info = get_asset_info(task_id, asset_type)
|
||||
|
||||
self._last_log = 0
|
||||
self._log_step_size = log_step_size
|
||||
|
||||
@property
|
||||
def asset_info(self):
|
||||
return self._asset_info
|
||||
|
||||
def __call__(self, total_bytes):
|
||||
self._uploaded_bytes += total_bytes
|
||||
progress = self._uploaded_bytes / self._total_bytes
|
||||
if progress == 1:
|
||||
progress = 1
|
||||
|
||||
self._asset_info["upload"]["progress"] = progress
|
||||
if self._logger is not None and progress - self._last_log > self._log_step_size:
|
||||
self._logger.info(f"Upload progress: {progress * 100}%")
|
||||
self._last_log = progress
|
||||
|
||||
set_asset_info(self._task_id, self._asset_type, self._asset_info)
|
||||
|
||||
asset_logger = LoggerAdapter(prefix=f"Task {task_id} {asset_type}", logger=logger)
|
||||
asset_type = AssetType[asset_type]
|
||||
asset_info = get_asset_info(task_id, asset_type)
|
||||
del_directory = None
|
||||
|
||||
try:
|
||||
import boto3
|
||||
except ImportError:
|
||||
import subprocess
|
||||
|
||||
asset_logger.info(f"Manually installing boto3...")
|
||||
subprocess.call([sys.executable, "-m", "pip", "install", "boto3"])
|
||||
import boto3
|
||||
|
||||
try:
|
||||
# Update asset_path based off
|
||||
if asset_type == AssetType.TEXTURED_MODEL:
|
||||
try:
|
||||
asset_path, del_directory = to_ion_texture_model(asset_path)
|
||||
logger.info("Created ion texture model!")
|
||||
except IonInvalidZip as e:
|
||||
logger.info("Non geo-referenced texture model, using default file.")
|
||||
except Exception as e:
|
||||
logger.warning("Failed to convert to ion texture model")
|
||||
logger.warning(e)
|
||||
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"attribution": attribution,
|
||||
"type": ASSET_TO_OUTPUT[asset_type],
|
||||
"options": {**options, "sourceType": ASSET_TO_SOURCE[asset_type]},
|
||||
}
|
||||
|
||||
# Create Asset Request
|
||||
asset_logger.info(f"Creating asset of type {asset_type}")
|
||||
res = requests.post(f"{ION_API_URL}/assets", json=data, headers=headers)
|
||||
res.raise_for_status()
|
||||
ion_info, upload_meta, on_complete = pluck(
|
||||
res.json(), "assetMetadata", "uploadLocation", "onComplete"
|
||||
)
|
||||
ion_id = ion_info["id"]
|
||||
access_key, secret_key, token, endpoint, bucket, file_prefix = pluck(
|
||||
upload_meta,
|
||||
"accessKey",
|
||||
"secretAccessKey",
|
||||
"sessionToken",
|
||||
"endpoint",
|
||||
"bucket",
|
||||
"prefix",
|
||||
)
|
||||
|
||||
# Upload
|
||||
asset_logger.info("Starting upload")
|
||||
uploat_stats = TaskUploadProgress(asset_path, task_id, asset_type, asset_logger)
|
||||
key = path.join(file_prefix, ASSET_TO_FILE[asset_type])
|
||||
boto3.client(
|
||||
"s3",
|
||||
endpoint_url=endpoint,
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
aws_session_token=token,
|
||||
).upload_file(asset_path, Bucket=bucket, Key=key, Callback=uploat_stats)
|
||||
asset_info = uploat_stats.asset_info
|
||||
asset_info["id"] = ion_id
|
||||
asset_info["upload"]["active"] = False
|
||||
asset_info["process"]["active"] = True
|
||||
set_asset_info(task_id, asset_type, asset_info)
|
||||
|
||||
# On Complete Handler
|
||||
asset_logger.info("Upload complete")
|
||||
method, url, fields = pluck(on_complete, "method", "url", "fields")
|
||||
res = requests.request(method, url=url, headers=headers, data=fields)
|
||||
res.raise_for_status()
|
||||
|
||||
# Processing Status Refresh
|
||||
asset_logger.info("Starting processing")
|
||||
refresh = True
|
||||
while refresh:
|
||||
res = requests.get(f"{ION_API_URL}/assets/{ion_id}", headers=headers)
|
||||
res.raise_for_status()
|
||||
|
||||
state, percent_complete = pluck(res.json(), "status", "percentComplete")
|
||||
progress = float(percent_complete) / 100
|
||||
if "ERROR" in state.upper():
|
||||
asset_info["error"] = f"Processing failed"
|
||||
asset_logger.info("Processing failed...")
|
||||
refresh = False
|
||||
if progress >= 1:
|
||||
refresh = False
|
||||
|
||||
if asset_info["process"]["progress"] != progress:
|
||||
asset_info["process"]["progress"] = progress
|
||||
asset_logger.info(f"Processing {percent_complete}% - {state}")
|
||||
set_asset_info(task_id, asset_type, asset_info)
|
||||
time.sleep(2)
|
||||
|
||||
asset_logger.info("Processing complete")
|
||||
asset_info["process"]["progress"] = 1
|
||||
asset_info["process"]["active"] = False
|
||||
except requests.exceptions.HTTPError as e:
|
||||
if e.response.status_code == 401:
|
||||
asset_info["error"] = "Invalid ion token!"
|
||||
elif e.response.status_code == 404:
|
||||
asset_info["error"] = "Missing permisssions on ion token!"
|
||||
else:
|
||||
asset_info["error"] = str(e)
|
||||
asset_logger.error(e)
|
||||
except Exception as e:
|
||||
asset_info["error"] = str(e)
|
||||
asset_logger.error(e)
|
||||
|
||||
if del_directory != None:
|
||||
rmtree(del_directory)
|
||||
|
||||
set_asset_info(task_id, asset_type, asset_info)
|
Ładowanie…
Reference in New Issue