Porównaj commity

...

45 Commity

Autor SHA1 Wiadomość Data
Stephen Mather fa058e7567
Merge pull request #1502 from smathermather/drop-gitter
Update README.md to reflect no gitter
2024-05-14 23:23:03 -04:00
Stephen Mather a60dec4e69
Update README.md to reflect no gitter 2024-05-14 23:22:25 -04:00
Piero Toffanin c098b976c6 Bump measure plugin version 2024-05-13 15:20:17 -04:00
Piero Toffanin 32ba4ed707
Merge pull request #1496 from pierotofy/mapapi
Imperial units support, improvements, fixes, plugins API expansion
2024-05-13 15:02:46 -04:00
Piero Toffanin 7272ca55bc Update manifests 2024-05-13 14:35:59 -04:00
Piero Toffanin e1cb0c83bb Update locale strings 2024-05-13 13:56:56 -04:00
Piero Toffanin 7ab95bc8b1 Measure plugin support for imperial units 2024-05-13 13:54:49 -04:00
Piero Toffanin 80a7f2048d Potree units sync 2024-05-13 13:04:56 -04:00
Piero Toffanin e9c2409ea9 Bump version 2024-05-11 17:19:32 -04:00
Piero Toffanin 9ee58f7216 Reformat 2024-05-11 17:18:54 -04:00
Piero Toffanin 289ef48b12 Speed up DSM/DTM tiler 2024-05-11 17:08:53 -04:00
Piero Toffanin 8468fdff5c Refactor get_asset_file_or_stream 2024-05-11 15:01:26 -04:00
Piero Toffanin 35fc60aa2c Elevation layer units update works 2024-05-11 12:51:37 -04:00
Piero Toffanin 57ccd23234 Fix login next redirect 2024-05-11 12:43:40 -04:00
Piero Toffanin 4e2ffbb768 PoC elevation histogram imperial units display 2024-05-10 22:01:37 -04:00
Piero Toffanin 3b55ebd3e5 Add unit options 2024-05-10 21:24:34 -04:00
Piero Toffanin 1fc7e11c86 Contours plugin imperial units export/preview working 2024-05-10 13:44:12 -04:00
Piero Toffanin d76eacabd3 Contours imperial preview working 2024-05-09 15:46:30 -04:00
Piero Toffanin 75678b7a84 Fix unit selector 2024-05-09 14:03:23 -04:00
Piero Toffanin 681482983c Add volume units 2024-05-09 12:35:29 -04:00
Piero Toffanin adf9c7dc5f Add US imperial 2024-05-09 11:08:51 -04:00
Piero Toffanin ece6bba200 Moar unit tests 2024-05-09 09:27:10 -04:00
Piero Toffanin d46b582dcd Merge branch 'master' of https://github.com/OpenDroneMap/WebODM into mapapi 2024-05-08 12:13:21 -04:00
Piero Toffanin dd6b46a2c9
Merge pull request #1499 from pierotofy/upcheck
Add file size check on upload
2024-05-08 11:56:56 -04:00
Piero Toffanin 5375eb8a19 Add file size check on upload 2024-05-08 11:29:35 -04:00
Piero Toffanin e0eb7cad7e Add units tests 2024-05-08 10:48:29 -04:00
Piero Toffanin 9a8013d6ce Revert debug commit 2024-05-07 11:56:33 -04:00
Piero Toffanin f0cd13a464 Merge branch 'master' of https://github.com/OpenDroneMap/WebODM 2024-05-07 11:54:42 -04:00
Piero Toffanin 5c663b8435 More upload retries, don't update totalCount on failure 2024-05-07 11:54:33 -04:00
Piero Toffanin 30eff78d3b Units work 2024-05-07 11:53:21 -04:00
Piero Toffanin 9af1ee018b Merge branch 'master' of https://github.com/OpenDroneMap/WebODM into mapapi 2024-05-07 09:46:57 -04:00
Piero Toffanin 568e80a941
Merge pull request #1498 from pierotofy/kmzfix
Use export format AUTO for KMZ
2024-05-06 00:33:50 -04:00
Piero Toffanin cb173bd48c Use export format AUTO for KMZ 2024-05-06 00:24:32 -04:00
Piero Toffanin b86411c298
Merge pull request #1497 from pierotofy/upfix
Fix file size check on upload error retry
2024-05-05 16:10:07 -04:00
Piero Toffanin 06ccd29d09 Fix file size check on upload error retry 2024-05-05 15:12:39 -04:00
Piero Toffanin bf24be7b72 Started adding app-wide unit selector logic 2024-05-02 16:47:47 -04:00
Piero Toffanin d73558256a Cleaner map controls, fix opacity label alignment 2024-05-02 16:17:06 -04:00
Piero Toffanin 59e104946c Better error message on worker failure 2024-05-02 16:08:41 -04:00
Piero Toffanin 972b06d03b Fix triangle icon, bump version 2024-05-01 15:43:32 -04:00
Piero Toffanin 2352d838cf Shorten Lightning Network --> Lightning 2024-05-01 14:01:40 -04:00
Piero Toffanin e7337f3b5d Silence annoying React deprecation notice of useful functionality 2024-05-01 13:39:10 -04:00
Piero Toffanin a44c2ce86f Add isMobile function 2024-05-01 12:34:47 -04:00
Piero Toffanin 6f5d68d6ed Expand Map JS API 2024-04-30 19:17:28 -04:00
Piero Toffanin 04af329f78 Fix invalid PropType 2024-04-27 11:28:19 -04:00
Piero Toffanin cc2b7d5265 Fix build_plugins on Windows 2024-04-20 13:55:25 -04:00
43 zmienionych plików z 936 dodań i 249 usunięć

Wyświetl plik

@ -276,7 +276,7 @@ We have several channels of communication for people to ask questions and to get
- [OpenDroneMap Community Forum](http://community.opendronemap.org/c/webodm)
- [Report Issues](https://github.com/OpenDroneMap/WebODM/issues)
We also have a [Gitter Chat](https://gitter.im/OpenDroneMap/web-development), but the preferred way to communicate is via the [OpenDroneMap Community Forum](http://community.opendronemap.org/c/webodm).
The preferred way to communicate is via the [OpenDroneMap Community Forum](http://community.opendronemap.org/c/webodm).
## Support the Project

Wyświetl plik

@ -204,18 +204,17 @@ class TaskViewSet(viewsets.ViewSet):
raise exceptions.NotFound()
files = flatten_files(request.FILES)
if len(files) == 0:
raise exceptions.ValidationError(detail=_("No files uploaded"))
task.handle_images_upload(files)
uploaded = task.handle_images_upload(files)
task.images_count = len(task.scan_images())
# Update other parameters such as processing node, task name, etc.
serializer = TaskSerializer(task, data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response({'success': True}, status=status.HTTP_200_OK)
return Response({'success': True, 'uploaded': uploaded}, status=status.HTTP_200_OK)
@action(detail=True, methods=['post'])
def duplicate(self, request, pk=None, project_pk=None):
@ -366,19 +365,20 @@ class TaskDownloads(TaskNestedView):
# Check and download
try:
asset_fs, is_zipstream = task.get_asset_file_or_zipstream(asset)
asset_fs = task.get_asset_file_or_stream(asset)
except FileNotFoundError:
raise exceptions.NotFound(_("Asset does not exist"))
if not is_zipstream and not os.path.isfile(asset_fs):
is_stream = not isinstance(asset_fs, str)
if not is_stream and not os.path.isfile(asset_fs):
raise exceptions.NotFound(_("Asset does not exist"))
download_filename = request.GET.get('filename', get_asset_download_filename(task, asset))
if not is_zipstream:
return download_file_response(request, asset_fs, 'attachment', download_filename=download_filename)
else:
if is_stream:
return download_file_stream(request, asset_fs, 'attachment', download_filename=download_filename)
else:
return download_file_response(request, asset_fs, 'attachment', download_filename=download_filename)
"""
Raw access to the task's asset folder resources

Wyświetl plik

@ -399,7 +399,7 @@ class Tiles(TaskNestedView):
# Hillshading is not a local tile operation and
# requires neighbor tiles to be rendered seamlessly
if hillshade is not None:
tile_buffer = tilesize
tile_buffer = 16
try:
if expr is not None:
@ -471,17 +471,17 @@ class Tiles(TaskNestedView):
# Remove elevation data from edge buffer tiles
# (to keep intensity uniform across tiles)
elevation = tile.data[0]
elevation[0:tilesize, 0:tilesize] = nodata
elevation[tilesize*2:tilesize*3, 0:tilesize] = nodata
elevation[0:tilesize, tilesize*2:tilesize*3] = nodata
elevation[tilesize*2:tilesize*3, tilesize*2:tilesize*3] = nodata
elevation[0:tile_buffer, 0:tile_buffer] = nodata
elevation[tile_buffer+tilesize:tile_buffer*2+tilesize, 0:tile_buffer] = nodata
elevation[0:tile_buffer, tile_buffer+tilesize:tile_buffer*2+tilesize] = nodata
elevation[tile_buffer+tilesize:tile_buffer*2+tilesize, tile_buffer+tilesize:tile_buffer*2+tilesize] = nodata
intensity = ls.hillshade(elevation, dx=dx, dy=dy, vert_exag=hillshade)
intensity = intensity[tilesize:tilesize * 2, tilesize:tilesize * 2]
intensity = intensity[tile_buffer:tile_buffer+tilesize, tile_buffer:tile_buffer+tilesize]
if intensity is not None:
rgb = tile.post_process(in_range=(rescale_arr,))
rgb_data = rgb.data[:,tilesize:tilesize * 2, tilesize:tilesize * 2]
rgb_data = rgb.data[:,tile_buffer:tilesize+tile_buffer, tile_buffer:tilesize+tile_buffer]
if colormap:
rgb, _discard_ = apply_cmap(rgb_data, colormap.get(color_map))
if rgb.data.shape[0] != 3:
@ -490,7 +490,7 @@ class Tiles(TaskNestedView):
intensity = intensity * 255.0
rgb = hsv_blend(rgb, intensity)
if rgb is not None:
mask = tile.mask[tilesize:tilesize * 2, tilesize:tilesize * 2]
mask = tile.mask[tile_buffer:tilesize+tile_buffer, tile_buffer:tilesize+tile_buffer]
return HttpResponse(
render(rgb, mask, img_format=driver, **options),
content_type="image/{}".format(ext)

Wyświetl plik

@ -450,16 +450,16 @@ class Task(models.Model):
return False
def get_asset_file_or_zipstream(self, asset):
def get_asset_file_or_stream(self, asset):
"""
Get a stream to an asset
:param asset: one of ASSETS_MAP keys
:return: (path|stream, is_zipstream:bool)
:return: (path|stream)
"""
if asset in self.ASSETS_MAP:
value = self.ASSETS_MAP[asset]
if isinstance(value, str):
return self.assets_path(value), False
return self.assets_path(value)
elif isinstance(value, dict):
if 'deferred_path' in value and 'deferred_compress_dir' in value:
@ -469,7 +469,7 @@ class Task(models.Model):
paths = [p for p in paths if os.path.basename(p['fs']) not in value['deferred_exclude_files']]
if len(paths) == 0:
raise FileNotFoundError("No files available for download")
return zipfly.ZipStream(paths), True
return zipfly.ZipStream(paths)
else:
raise FileNotFoundError("{} is not a valid asset (invalid dict values)".format(asset))
else:
@ -1163,6 +1163,7 @@ class Task(models.Model):
return path_traversal_check(p, self.task_path())
def handle_images_upload(self, files):
uploaded = {}
for file in files:
name = file.name
if name is None:
@ -1181,6 +1182,9 @@ class Task(models.Model):
else:
with open(file.temporary_file_path(), 'rb') as f:
shutil.copyfileobj(f, fd)
uploaded[name] = os.path.getsize(dst_path)
return uploaded
def update_size(self, commit=False):
try:

Wyświetl plik

@ -110,7 +110,7 @@ def build_plugins():
# Create entry configuration
entry = {}
for e in plugin.build_jsx_components():
entry[os.path.splitext(os.path.basename(e))[0]] = [os.path.join('.', e)]
entry[os.path.splitext(os.path.basename(e))[0]] = ['./' + e]
wpc_content = tmpl.substitute({
'entry_json': json.dumps(entry)
})

Wyświetl plik

@ -51,14 +51,13 @@ def export_raster(input, output, **opts):
output_raster = output
jpg_background = 255 # white
# KMZ is special, we just export it as PNG with EPSG:4326
# KMZ is special, we just export it as GeoTIFF
# and then call GDAL to tile/package it
kmz = export_format == "kmz"
if kmz:
export_format = "png"
epsg = 4326
export_format = "gtiff-rgb"
path_base, _ = os.path.splitext(output)
output_raster = path_base + ".png"
output_raster = path_base + ".kmz.tif"
if export_format == "jpg":
driver = "JPEG"
@ -282,4 +281,4 @@ def export_raster(input, output, **opts):
if kmz:
subprocess.check_output(["gdal_translate", "-of", "KMLSUPEROVERLAY",
"-co", "Name={}".format(name),
"-co", "FORMAT=PNG", output_raster, output])
"-co", "FORMAT=AUTO", output_raster, output])

Wyświetl plik

@ -10,6 +10,7 @@ import PropTypes from 'prop-types';
import * as THREE from 'THREE';
import $ from 'jquery';
import { _, interpolate } from './classes/gettext';
import { getUnitSystem, setUnitSystem } from './classes/Units';
require('./vendor/OBJLoader');
require('./vendor/MTLLoader');
@ -301,6 +302,20 @@ class ModelView extends React.Component {
viewer.setPointBudget(10*1000*1000);
viewer.setEDLEnabled(true);
viewer.loadSettingsFromURL();
const currentUnit = getUnitSystem();
const origSetUnit = viewer.setLengthUnitAndDisplayUnit;
viewer.setLengthUnitAndDisplayUnit = (lengthUnit, displayUnit) => {
if (displayUnit === 'm') setUnitSystem('metric');
else if (displayUnit === 'ft'){
// Potree doesn't have US/international imperial, so
// we default to international unless the user has previously
// selected US
if (currentUnit === 'metric') setUnitSystem("imperial");
else setUnitSystem(currentUnit);
}
origSetUnit.call(viewer, lengthUnit, displayUnit);
};
viewer.loadGUI(() => {
viewer.setLanguage('en');
@ -335,7 +350,7 @@ class ModelView extends React.Component {
directional.position.z = 99999999999;
viewer.scene.scene.add( directional );
this.pointCloudFilePath(pointCloudPath => {
this.pointCloudFilePath(pointCloudPath =>{
Potree.loadPointCloud(pointCloudPath, "Point Cloud", e => {
if (e.type == "loading_failed"){
this.setState({error: "Could not load point cloud. This task doesn't seem to have one. Try processing the task again."});
@ -351,6 +366,12 @@ class ModelView extends React.Component {
viewer.fitToScreen();
if (getUnitSystem() === 'metric'){
viewer.setLengthUnitAndDisplayUnit('m', 'm');
}else{
viewer.setLengthUnitAndDisplayUnit('m', 'ft');
}
// Load saved scene (if any)
$.ajax({
type: "GET",

Wyświetl plik

@ -18,6 +18,14 @@ class Storage{
console.warn("Failed to call setItem " + key, e);
}
}
static removeItem(key){
try{
localStorage.removeItem(key);
}catch(e){
console.warn("Failed to call removeItem " + key, e);
}
}
}
export default Storage;

Wyświetl plik

@ -0,0 +1,378 @@
import { _ } from './gettext';
const types = {
LENGTH: 1,
AREA: 2,
VOLUME: 3
};
const units = {
acres: {
factor: (1 / (0.3048 * 0.3048)) / 43560,
abbr: 'ac',
round: 5,
label: _("Acres"),
type: types.AREA
},
acres_us: {
factor: Math.pow(3937 / 1200, 2) / 43560,
abbr: 'ac (US)',
round: 5,
label: _("Acres"),
type: types.AREA
},
feet: {
factor: 1 / 0.3048,
abbr: 'ft',
round: 4,
label: _("Feet"),
type: types.LENGTH
},
feet_us:{
factor: 3937 / 1200,
abbr: 'ft (US)',
round: 4,
label: _("Feet"),
type: types.LENGTH
},
hectares: {
factor: 0.0001,
abbr: 'ha',
round: 4,
label: _("Hectares"),
type: types.AREA
},
meters: {
factor: 1,
abbr: 'm',
round: 3,
label: _("Meters"),
type: types.LENGTH
},
kilometers: {
factor: 0.001,
abbr: 'km',
round: 5,
label: _("Kilometers"),
type: types.LENGTH
},
centimeters: {
factor: 100,
abbr: 'cm',
round: 1,
label: _("Centimeters"),
type: types.LENGTH
},
miles: {
factor: (1 / 0.3048) / 5280,
abbr: 'mi',
round: 5,
label: _("Miles"),
type: types.LENGTH
},
miles_us: {
factor: (3937 / 1200) / 5280,
abbr: 'mi (US)',
round: 5,
label: _("Miles"),
type: types.LENGTH
},
sqfeet: {
factor: 1 / (0.3048 * 0.3048),
abbr: 'ft²',
round: 2,
label: _("Square Feet"),
type: types.AREA
},
sqfeet_us: {
factor: Math.pow(3937 / 1200, 2),
abbr: 'ft² (US)',
round: 2,
label: _("Square Feet"),
type: types.AREA
},
sqmeters: {
factor: 1,
abbr: 'm²',
round: 2,
label: _("Square Meters"),
type: types.AREA
},
sqkilometers: {
factor: 0.000001,
abbr: 'km²',
round: 5,
label: _("Square Kilometers"),
type: types.AREA
},
sqmiles: {
factor: Math.pow((1 / 0.3048) / 5280, 2),
abbr: 'mi²',
round: 5,
label: _("Square Miles"),
type: types.AREA
},
sqmiles_us: {
factor: Math.pow((3937 / 1200) / 5280, 2),
abbr: 'mi² (US)',
round: 5,
label: _("Square Miles"),
type: types.AREA
},
cbmeters:{
factor: 1,
abbr: 'm³',
round: 4,
label: _("Cubic Meters"),
type: types.VOLUME
},
cbyards:{
factor: Math.pow(1/(0.3048*3), 3),
abbr: 'yd³',
round: 4,
label: _("Cubic Yards"),
type: types.VOLUME
},
cbyards_us:{
factor: Math.pow(3937/3600, 3),
abbr: 'yd³ (US)',
round: 4,
label: _("Cubic Yards"),
type: types.VOLUME
}
};
class ValueUnit{
constructor(value, unit){
this.value = value;
this.unit = unit;
}
toString(opts = {}){
const mul = Math.pow(10, opts.precision !== undefined ? opts.precision : this.unit.round);
const rounded = (Math.round(this.value * mul) / mul).toString();
let withCommas = "";
let parts = rounded.split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
withCommas = parts.join(".");
return `${withCommas} ${this.unit.abbr}`;
}
}
class NanUnit{
constructor(){
this.value = NaN;
this.unit = units.meters; // Don't matter
}
toString(){
return "NaN";
}
}
class UnitSystem{
lengthUnit(meters, opts = {}){ throw new Error("Not implemented"); }
areaUnit(sqmeters, opts = {}){ throw new Error("Not implemented"); }
volumeUnit(cbmeters, opts = {}){ throw new Error("Not implemented"); }
getName(){ throw new Error("Not implemented"); }
area(sqmeters, opts = {}){
sqmeters = parseFloat(sqmeters);
if (isNaN(sqmeters)) return NanUnit();
const unit = this.areaUnit(sqmeters, opts);
const val = unit.factor * sqmeters;
return new ValueUnit(val, unit);
}
length(meters, opts = {}){
meters = parseFloat(meters);
if (isNaN(meters)) return NanUnit();
const unit = this.lengthUnit(meters, opts);
const val = unit.factor * meters;
return new ValueUnit(val, unit);
}
volume(cbmeters, opts = {}){
cbmeters = parseFloat(cbmeters);
if (isNaN(cbmeters)) return NanUnit();
const unit = this.volumeUnit(cbmeters, opts);
const val = unit.factor * cbmeters;
return new ValueUnit(val, unit);
}
};
function toMetric(valueUnit, unit){
let value = NaN;
if (typeof valueUnit === "object" && unit === undefined){
value = valueUnit.value;
unit = valueUnit.unit;
}else{
value = parseFloat(valueUnit);
}
if (isNaN(value)) return NanUnit();
const val = value / unit.factor;
if (unit.type === types.LENGTH){
return new ValueUnit(val, units.meters);
}else if (unit.type === types.AREA){
return new ValueUnit(val, unit.sqmeters);
}else if (unit.type === types.VOLUME){
return new ValueUnit(val, unit.cbmeters);
}else{
throw new Error(`Unrecognized unit type: ${unit.type}`);
}
}
class MetricSystem extends UnitSystem{
getName(){
return _("Metric");
}
lengthUnit(meters, opts = {}){
if (opts.fixedUnit) return units.meters;
if (meters < 1) return units.centimeters;
else if (meters >= 1000) return units.kilometers;
else return units.meters;
}
areaUnit(sqmeters, opts = {}){
if (opts.fixedUnit) return units.sqmeters;
if (sqmeters >= 10000 && sqmeters < 1000000) return units.hectares;
else if (sqmeters >= 1000000) return units.sqkilometers;
return units.sqmeters;
}
volumeUnit(cbmeters, opts = {}){
return units.cbmeters;
}
}
class ImperialSystem extends UnitSystem{
getName(){
return _("Imperial");
}
feet(){
return units.feet;
}
sqfeet(){
return units.sqfeet;
}
miles(){
return units.miles;
}
sqmiles(){
return units.sqmiles;
}
acres(){
return units.acres;
}
cbyards(){
return units.cbyards;
}
lengthUnit(meters, opts = {}){
if (opts.fixedUnit) return this.feet();
const feet = this.feet().factor * meters;
if (feet >= 5280) return this.miles();
else return this.feet();
}
areaUnit(sqmeters, opts = {}){
if (opts.fixedUnit) return this.sqfeet();
const sqfeet = this.sqfeet().factor * sqmeters;
if (sqfeet >= 43560 && sqfeet < 27878400) return this.acres();
else if (sqfeet >= 27878400) return this.sqmiles();
else return this.sqfeet();
}
volumeUnit(cbmeters, opts = {}){
return this.cbyards();
}
}
class ImperialUSSystem extends ImperialSystem{
getName(){
return _("Imperial (US)");
}
feet(){
return units.feet_us;
}
sqfeet(){
return units.sqfeet_us;
}
miles(){
return units.miles_us;
}
sqmiles(){
return units.sqmiles_us;
}
acres(){
return units.acres_us;
}
cbyards(){
return units.cbyards_us;
}
}
const systems = {
metric: new MetricSystem(),
imperial: new ImperialSystem(),
imperialUS: new ImperialUSSystem()
}
// Expose to allow every part of the app to access this information
function getUnitSystem(){
return localStorage.getItem("unit_system") || "metric";
}
function setUnitSystem(system){
let prevSystem = getUnitSystem();
localStorage.setItem("unit_system", system);
if (prevSystem !== system){
document.dispatchEvent(new CustomEvent("onUnitSystemChanged", { detail: system }));
}
}
function onUnitSystemChanged(callback){
document.addEventListener("onUnitSystemChanged", callback);
}
function offUnitSystemChanged(callback){
document.removeEventListener("onUnitSystemChanged", callback);
}
function unitSystem(){
return systems[getUnitSystem()];
}
export {
systems,
types,
toMetric,
unitSystem,
getUnitSystem,
setUnitSystem,
onUnitSystemChanged,
offUnitSystemChanged
};

Wyświetl plik

@ -103,6 +103,10 @@ export default {
var sizes = ['bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
},
isMobile: function(){
return navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i);
}
};

Wyświetl plik

@ -21,7 +21,7 @@ export default {
}).fail(error => {
console.warn(error);
if (errorCount++ < 10) setTimeout(() => check(), 2000);
else cb(JSON.stringify(error));
else cb(error.statusText);
});
};

Wyświetl plik

@ -15,13 +15,6 @@ export default class ApiFactory{
// are more robust as we can detect more easily if
// things break
// TODO: we should consider refactoring this code
// to use functions instead of events. Originally
// we chose to use events because that would have
// decreased coupling, but since all API pubsub activity
// evolved to require a call to the PluginsAPI object, we might have
// added a bunch of complexity for no real advantage here.
const addEndpoint = (obj, eventName, preTrigger = () => {}) => {
const emitResponse = response => {
// Timeout needed for modules that have no dependencies
@ -99,6 +92,26 @@ export default class ApiFactory{
obj = Object.assign(obj, api.helpers);
}
// Handle syncronous function on/off/export
(api.functions || []).forEach(func => {
let callbacks = [];
obj[func] = (...args) => {
for (let i = 0; i < callbacks.length; i++){
if ((callbacks[i])(...args)) return true;
}
return false;
};
const onName = "on" + func[0].toUpperCase() + func.slice(1);
const offName = "off" + func[0].toUpperCase() + func.slice(1);
obj[onName] = f => {
callbacks.push(f);
};
obj[offName] = f => {
callbacks = callbacks.filter(cb => cb !== f);
};
});
return obj;
}

Wyświetl plik

@ -19,7 +19,11 @@ export default {
endpoints: [
["willAddControls", leafletPreCheck],
["didAddControls", layersControlPreCheck],
["addActionButton", leafletPreCheck],
["addActionButton", leafletPreCheck]
],
functions: [
"handleClick"
]
};

Wyświetl plik

@ -3,11 +3,14 @@ import PropTypes from 'prop-types';
import '../css/Histogram.scss';
import d3 from 'd3';
import { _ } from '../classes/gettext';
import { onUnitSystemChanged, offUnitSystemChanged } from '../classes/Units';
export default class Histogram extends React.Component {
static defaultProps = {
width: 280,
colorMap: null,
unitForward: value => value,
unitBackward: value => value,
onUpdate: null,
loading: false,
min: null,
@ -16,6 +19,8 @@ export default class Histogram extends React.Component {
static propTypes = {
statistics: PropTypes.object.isRequired,
colorMap: PropTypes.array,
unitForward: PropTypes.func,
unitBackward: PropTypes.func,
width: PropTypes.number,
onUpdate: PropTypes.func,
loading: PropTypes.bool,
@ -68,8 +73,8 @@ export default class Histogram extends React.Component {
const st = {
min: min,
max: max,
minInput: min.toFixed(3),
maxInput: max.toFixed(3)
minInput: this.props.unitForward(min).toFixed(3),
maxInput: this.props.unitForward(max).toFixed(3)
};
if (!this.state){
@ -113,11 +118,14 @@ export default class Histogram extends React.Component {
let x = d3.scale.linear()
.domain(this.rangeX)
.range([0, width]);
let tickFormat = x => {
return this.props.unitForward(x).toFixed(0);
};
svg.append("g")
.attr("class", "x axis theme-fill-primary")
.attr("transform", "translate(0," + (height - 5) + ")")
.call(d3.svg.axis().scale(x).tickValues(this.rangeX).orient("bottom"));
.call(d3.svg.axis().scale(x).tickValues(this.rangeX).tickFormat(tickFormat).orient("bottom"));
// add the y Axis
let y = d3.scale.linear()
@ -246,11 +254,27 @@ export default class Histogram extends React.Component {
componentDidMount(){
this.redraw();
onUnitSystemChanged(this.handleUnitSystemChanged);
}
componentWillUnmount(){
offUnitSystemChanged(this.handleUnitSystemChanged);
}
handleUnitSystemChanged = e => {
this.redraw();
this.setState({
minInput: this.props.unitForward(this.state.min).toFixed(3),
maxInput: this.props.unitForward(this.state.max).toFixed(3)
});
}
componentDidUpdate(prevProps, prevState){
if (prevState.min !== this.state.min || prevState.max !== this.state.max){
this.setState({minInput: this.state.min.toFixed(3), maxInput: this.state.max.toFixed(3)});
this.setState({
minInput: this.props.unitForward(this.state.min).toFixed(3),
maxInput: this.props.unitForward(this.state.max).toFixed(3)
});
}
if (prevState.min !== this.state.min ||
@ -295,6 +319,7 @@ export default class Histogram extends React.Component {
handleMaxBlur = (e) => {
let val = parseFloat(e.target.value);
if (!isNaN(val)){
val = this.props.unitBackward(val);
val = Math.max(this.state.min, Math.min(this.rangeX[1], val));
this.setState({max: val, maxInput: val.toFixed(3)});
}
@ -311,6 +336,7 @@ export default class Histogram extends React.Component {
handleMinBlur = (e) => {
let val = parseFloat(e.target.value);
if (!isNaN(val)){
val = this.props.unitBackward(val);
val = Math.max(this.rangeX[0], Math.min(this.state.max, val));
this.setState({min: val, minInput: val.toFixed(3)});
}

Wyświetl plik

@ -295,6 +295,8 @@ export default class LayersControlLayer extends React.Component {
<Histogram width={274}
loading={histogramLoading}
statistics={tmeta.statistics}
unitForward={meta.unitForward}
unitBackward={meta.unitBackward}
colorMap={cmapValues}
min={hmin}
max={hmax}

Wyświetl plik

@ -4,8 +4,6 @@ import '../css/Map.scss';
import 'leaflet/dist/leaflet.css';
import Leaflet from 'leaflet';
import async from 'async';
import '../vendor/leaflet/L.Control.MousePosition.css';
import '../vendor/leaflet/L.Control.MousePosition';
import '../vendor/leaflet/Leaflet.Autolayers/css/leaflet.auto-layers.css';
import '../vendor/leaflet/Leaflet.Autolayers/leaflet-autolayers';
// import '../vendor/leaflet/L.TileLayer.NoGap';
@ -29,6 +27,8 @@ import '../vendor/leaflet/Leaflet.Ajax';
import 'rbush';
import '../vendor/leaflet/leaflet-markers-canvas';
import { _ } from '../classes/gettext';
import UnitSelector from './UnitSelector';
import { unitSystem, toMetric } from '../classes/Units';
class Map extends React.Component {
static defaultProps = {
@ -135,6 +135,8 @@ class Map extends React.Component {
const { url, meta, type } = tile;
let metaUrl = url + "metadata";
let unitForward = value => value;
let unitBackward = value => value;
if (type == "plant"){
if (meta.task && meta.task.orthophoto_bands && meta.task.orthophoto_bands.length === 2){
@ -150,6 +152,12 @@ class Map extends React.Component {
}
}else if (type == "dsm" || type == "dtm"){
metaUrl += "?hillshade=6&color_map=viridis";
unitForward = value => {
return unitSystem().length(value, { fixedUnit: true }).value;
};
unitBackward = value => {
return toMetric(value).value;
};
}
this.tileJsonRequests.push($.getJSON(metaUrl)
@ -210,6 +218,8 @@ class Map extends React.Component {
// Associate metadata with this layer
meta.name = name + ` (${this.typeToHuman(type)})`;
meta.metaUrl = metaUrl;
meta.unitForward = unitForward;
meta.unitBackward = unitBackward;
layer[Symbol.for("meta")] = meta;
layer[Symbol.for("tile-meta")] = mres;
@ -385,7 +395,7 @@ class Map extends React.Component {
this.map = Leaflet.map(this.container, {
scrollWheelZoom: true,
positionControl: true,
positionControl: false,
zoomControl: false,
minZoom: 0,
maxZoom: 24
@ -397,12 +407,23 @@ class Map extends React.Component {
PluginsAPI.Map.triggerWillAddControls({
map: this.map,
tiles
tiles,
mapView: this
});
let scaleControl = Leaflet.control.scale({
maxWidth: 250,
}).addTo(this.map);
const UnitsCtrl = Leaflet.Control.extend({
options: {
position: 'bottomleft'
},
onAdd: function () {
this.container = Leaflet.DomUtil.create('div', 'leaflet-control-units-selection leaflet-control');
Leaflet.DomEvent.disableClickPropagation(this.container);
ReactDOM.render(<UnitSelector />, this.container);
return this.container;
}
});
new UnitsCtrl().addTo(this.map);
//add zoom control with your options
let zoomControl = Leaflet.control.zoom({
@ -524,6 +545,8 @@ _('Example:'),
this.map.fitBounds(this.mapBounds);
this.map.on('click', e => {
if (PluginsAPI.Map.handleClick(e)) return;
// Find first tile layer at the selected coordinates
for (let layer of this.state.imageryLayers){
if (layer._map && layer.options.bounds.contains(e.latlng)){
@ -577,7 +600,6 @@ _('Example:'),
tiles: tiles,
controls:{
autolayers: this.autolayers,
scale: scaleControl,
zoom: zoomControl
}
});
@ -627,7 +649,7 @@ _('Example:'),
<div style={{height: "100%"}} className="map">
<ErrorMessage bind={[this, 'error']} />
<div className="opacity-slider theme-secondary hidden-xs">
{_("Opacity:")} <input type="range" step="1" value={this.state.opacity} onChange={this.updateOpacity} />
<div className="opacity-slider-label">{_("Opacity:")}</div> <input type="range" step="1" value={this.state.opacity} onChange={this.updateOpacity} />
</div>
<Standby

Wyświetl plik

@ -193,7 +193,7 @@ class ProjectListItem extends React.Component {
.on("complete", (file) => {
// Retry
const retry = () => {
const MAX_RETRIES = 10;
const MAX_RETRIES = 20;
if (file.retries < MAX_RETRIES){
// Update progress
@ -219,7 +219,7 @@ class ProjectListItem extends React.Component {
try{
if (file.status === "error"){
if ((file.size / 1024) > this.dz.options.maxFilesize) {
if ((file.size / 1024 / 1024) > this.dz.options.maxFilesize) {
// Delete from upload queue
this.setUploadState({
totalCount: this.state.upload.totalCount - 1,
@ -231,7 +231,7 @@ class ProjectListItem extends React.Component {
}else{
// Check response
let response = JSON.parse(file.xhr.response);
if (response.success){
if (response.success && response.uploaded && response.uploaded[file.name] === file.size){
// Update progress by removing the tracked progress and
// use the file size as the true number of bytes
let totalBytesSent = this.state.upload.totalBytesSent + file.size;
@ -284,7 +284,6 @@ class ProjectListItem extends React.Component {
}else if (this.dz.getQueuedFiles() === 0){
// Done but didn't upload all?
this.setUploadState({
totalCount: this.state.upload.totalCount - remainingFilesCount,
uploading: false,
error: interpolate(_('%(count)s files cannot be uploaded. As a reminder, only images (.jpg, .tif, .png) and GCP files (.txt) can be uploaded. Try again.'), { count: remainingFilesCount })
});

Wyświetl plik

@ -13,7 +13,7 @@ class ShareButton extends React.Component {
task: PropTypes.object.isRequired,
linksTarget: PropTypes.oneOf(['map', '3d']).isRequired,
popupPlacement: PropTypes.string,
queryParams: PropTypes.string
queryParams: PropTypes.object
}
constructor(props){

Wyświetl plik

@ -606,7 +606,7 @@ class TaskListItem extends React.Component {
/> : ""}
{showOrthophotoMissingWarning ?
<div className="task-warning"><i className="fa fa-warning"></i> <span>{_("An orthophoto could not be generated. To generate one, make sure GPS information is embedded in the EXIF tags of your images, or use a Ground Control Points (GCP) file.")}</span></div> : ""}
<div className="task-warning"><i className="fa fa-exclamation-triangle"></i> <span>{_("An orthophoto could not be generated. To generate one, make sure GPS information is embedded in the EXIF tags of your images, or use a Ground Control Points (GCP) file.")}</span></div> : ""}
{showMemoryErrorWarning ?
<div className="task-warning"><i className="fa fa-support"></i> <Trans params={{ memlink: `<a href="${memoryErrorLink}" target='_blank'>${_("enough RAM allocated")}</a>`, cloudlink: `<a href='https://webodm.net' target='_blank'>${_("cloud processing node")}</a>` }}>{_("It looks like your processing node ran out of memory. If you are using docker, make sure that your docker environment has %(memlink)s. Alternatively, make sure you have enough physical RAM, reduce the number of images, make your images smaller, or reduce the max-concurrency parameter from the task's options. You can also try to use a %(cloudlink)s.")}</Trans></div> : ""}

Wyświetl plik

@ -0,0 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import { systems, getUnitSystem, setUnitSystem } from '../classes/Units';
import '../css/UnitSelector.scss';
class UnitSelector extends React.Component {
static propTypes = {
}
constructor(props){
super(props);
this.state = {
system: getUnitSystem()
}
}
handleChange = e => {
this.setState({system: e.target.value});
setUnitSystem(e.target.value);
};
render() {
return (
<select className="unit-selector" value={this.state.system} onChange={this.handleChange}>
{Object.keys(systems).map(k =>
<option value={k} key={k}>{systems[k].getName()}</option>)}
</select>
);
}
}
export default UnitSelector;

Wyświetl plik

@ -0,0 +1,10 @@
import React from 'react';
import { shallow } from 'enzyme';
import UnitSelector from '../UnitSelector';
describe('<UnitSelector />', () => {
it('renders without exploding', () => {
const wrapper = shallow(<UnitSelector />);
expect(wrapper.exists()).toBe(true);
})
});

Wyświetl plik

@ -0,0 +1,122 @@
import { systems, toMetric } from '../../classes/Units';
describe('Metric system', () => {
it('it should display units properly', () => {
const { metric } = systems;
const lengths = [
[1, "1 m"],
[0.01, "1 cm"],
[0.0154, "1.5 cm"],
[0.99, "99 cm"],
[0.995555, "99.6 cm"],
[1.01, "1.01 m"],
[999, "999 m"],
[1000, "1 km"],
[1001, "1.001 km"],
[1000010, "1,000.01 km"],
[1000012.349, "1,000.01235 km"],
];
lengths.forEach(l => {
expect(metric.length(l[0]).toString()).toBe(l[1]);
});
const areas = [
[1, "1 m²"],
[9999, "9,999 m²"],
[10000, "1 ha"],
[11005, "1.1005 ha"],
[11005, "1.1005 ha"],
[999999, "99.9999 ha"],
[1000000, "1 km²"],
[1000000000, "1,000 km²"],
[1000255558, "1,000.25556 km²"]
];
areas.forEach(a => {
expect(metric.area(a[0]).toString()).toBe(a[1]);
});
const volumes = [
[1, "1 m³"],
[9000, "9,000 m³"],
[9000.25559, "9,000.2556 m³"],
];
volumes.forEach(v => {
expect(metric.volume(v[0]).toString()).toBe(v[1]);
});
expect(metric.area(11005.09, { fixedUnit: true }).toString({precision: 1})).toBe("11,005.1 m²");
})
});
describe('Imperial systems', () => {
it('it should display units properly', () => {
const { imperial, imperialUS } = systems;
const lengths = [
[1, "3.2808 ft", "3.2808 ft (US)"],
[0.01, "0.0328 ft", "0.0328 ft (US)"],
[0.0154, "0.0505 ft", "0.0505 ft (US)"],
[1609, "5,278.8714 ft", "5,278.8608 ft (US)"],
[1609.344, "1 mi", "5,279.9894 ft (US)"],
[1609.3472187, "1 mi", "1 mi (US)"],
[3218.69, "2 mi", "2 mi (US)"]
];
lengths.forEach(l => {
expect(imperial.length(l[0]).toString()).toBe(l[1]);
expect(imperialUS.length(l[0]).toString()).toBe(l[2]);
});
const areas = [
[1, "10.76 ft²", "10.76 ft² (US)"],
[9999, "2.47081 ac", "2.4708 ac (US)"],
[4046.86, "1 ac", "43,559.86 ft² (US)"],
[4046.87261, "1 ac", "1 ac (US)"],
[2587398.1, "639.35999 ac", "639.35744 ac (US)"],
[2.59e+6, "1 mi²", "1 mi² (US)"]
];
areas.forEach(a => {
expect(imperial.area(a[0]).toString()).toBe(a[1]);
expect(imperialUS.area(a[0]).toString()).toBe(a[2]);
});
const volumes = [
[1, "1.308 yd³", "1.3079 yd³ (US)"],
[1000, "1,307.9506 yd³", "1,307.9428 yd³ (US)"]
];
volumes.forEach(v => {
expect(imperial.volume(v[0]).toString()).toBe(v[1]);
expect(imperialUS.volume(v[0]).toString()).toBe(v[2]);
});
expect(imperial.area(9999, { fixedUnit: true }).toString({precision: 1})).toBe("107,628.3 ft²");
});
});
describe('Metric conversion', () => {
it('it should convert units properly', () => {
const { metric, imperial } = systems;
const km = metric.length(2000);
const mi = imperial.length(3220);
expect(km.unit.abbr).toBe("km");
expect(km.value).toBe(2);
expect(mi.unit.abbr).toBe("mi");
expect(Math.round(mi.value)).toBe(2)
expect(toMetric(km).toString()).toBe("2,000 m");
expect(toMetric(mi).toString()).toBe("3,220 m");
expect(toMetric(km).value).toBe(2000);
expect(toMetric(mi).value).toBe(3220);
});
});

Wyświetl plik

@ -11,6 +11,11 @@
margin-left: -100px;
z-index: 999;
padding-bottom: 6px;
.opacity-slider-label{
display: inline-block;
position: relative;
top: 2px;
}
}
.leaflet-touch .leaflet-control-layers-toggle, .leaflet-control-layers-toggle{

Wyświetl plik

@ -0,0 +1,4 @@
.unit-selector{
font-size: 14px;
padding: 5px;
}

Wyświetl plik

@ -8,6 +8,16 @@ import { setLocale } from './translations/functions';
// Main is always executed first in the page
// Silence annoying React deprecation notice of useful functionality
const originalError = console.error;
console.error = function(...args) {
let message = args[0];
if (typeof message === 'string' && message.indexOf('Warning: A future version of React will block javascript:') !== -1) {
return;
}
originalError.apply(console, args);
};
// We share some objects to avoid having to include them
// as a dependency in each component (adds too much space overhead)
window.ReactDOM = ReactDOM;

Wyświetl plik

@ -1,94 +1,93 @@
// Auto-generated with extract_odm_strings.py, do not edit!
_("DSM/DTM resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. Default: %(default)s");
_("Path to a GeoTIFF DEM or a LAS/LAZ point cloud that the reconstruction outputs should be automatically aligned to. Experimental. Default: %(default)s");
_("Delete heavy intermediate files to optimize disk space usage. This affects the ability to restart the pipeline from an intermediate stage, but allows datasets to be processed on machines that don't have sufficient disk space available. Default: %(default)s");
_("Copy output results to this folder after processing.");
_("URL to a ClusterODM instance for distributing a split-merge workflow on multiple nodes in parallel. Default: %(default)s");
_("Perform image matching with the nearest images based on GPS exif data. Set to 0 to match by triangulation. Default: %(default)s");
_("show this help message and exit");
_("Choose what to merge in the merge step in a split dataset. By default all available outputs are merged. Options: %(choices)s. Default: %(default)s");
_("Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s");
_("Turn off camera parameter optimization during bundle adjustment. This can be sometimes useful for improving results that exhibit doming/bowling or when images are taken with a rolling shutter camera. Default: %(default)s");
_("Generate static tiles for orthophotos and DEMs that are suitable for viewers like Leaflet or OpenLayers. Default: %(default)s");
_("Computes an euclidean raster map for each DEM. The map reports the distance from each cell to the nearest NODATA value (before any hole filling takes place). This can be useful to isolate the areas that have been filled. Default: %(default)s");
_("Perform ground rectification on the point cloud. This means that wrongly classified ground points will be re-classified and gaps will be filled. Useful for generating DTMs. Default: %(default)s");
_("Simple Morphological Filter slope parameter (rise over run). Default: %(default)s");
_("Classify the point cloud outputs. You can control the behavior of this option by tweaking the --dem-* parameters. Default: %(default)s");
_("Permanently delete all previous results and rerun the processing pipeline.");
_("Do not attempt to merge partial reconstructions. This can happen when images do not have sufficient overlap or are isolated. Default: %(default)s");
_("Save the georeferenced point cloud in Cloud Optimized Point Cloud (COPC) format. Default: %(default)s");
_("Export the georeferenced point cloud in Entwine Point Tile (EPT) format. Default: %(default)s");
_("Skip generation of PDF report. This can save time if you don't need a report. Default: %(default)s");
_("Choose the structure from motion algorithm. For aerial datasets, if camera GPS positions and angles are available, triangulation can generate better results. For planar scenes captured at fixed altitude with nadir-only images, planar can be much faster. Can be one of: %(choices)s. Default: %(default)s");
_("Generates a polygon around the cropping area that cuts the orthophoto around the edges of features. This polygon can be useful for stitching seamless mosaics with multiple overlapping orthophotos. Default: %(default)s");
_("Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas. Default: %(default)s");
_("Path to the file containing the ground control points used for georeferencing. The file needs to use the following format: EPSG:<code> or <+proj definition>geo_x geo_y geo_z im_x im_y image_name [gcp_name] [extra1] [extra2]Default: %(default)s");
_("Skip the blending of colors near seams. Default: %(default)s");
_("Keep faces in the mesh that are not seen in any camera. Default: %(default)s");
_("When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. It's recommended to choose a band which has sharp details and is in focus. Default: %(default)s");
_("Maximum number of frames to extract from video files for processing. Set to 0 for no limit. Default: %(default)s");
_("Generate single file Binary glTF (GLB) textured models. Default: %(default)s");
_("Set a value in meters for the GPS Dilution of Precision (DOP) information for all images. If your images are tagged with high precision GPS information (RTK), this value will be automatically set accordingly. You can use this option to manually set it in case the reconstruction fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s");
_("Set this parameter if you want a striped GeoTIFF. Default: %(default)s");
_("Generate OBJs that have a single material and a single texture file instead of multiple ones. Default: %(default)s");
_("Choose the algorithm for extracting keypoints and computing descriptors. Can be one of: %(choices)s. Default: %(default)s");
_("Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s");
_("Set point cloud quality. Higher quality generates better, denser point clouds, but requires more memory and takes longer. Each step up in quality increases processing time roughly by a factor of 4x.Can be one of: %(choices)s. Default: %(default)s");
_("Name of dataset (i.e subfolder name within project folder). Default: %(default)s");
_("Override the rolling shutter readout time for your camera sensor (in milliseconds), instead of using the rolling shutter readout database. Note that not all cameras are present in the database. Set to 0 to use the database value. Default: %(default)s");
_("Path to the image geolocation file containing the camera center coordinates used for georeferencing. If you don't have values for yaw/pitch/roll you can set them to 0. The file needs to use the following format: EPSG:<code> or <+proj definition>image_name geo_x geo_y geo_z [yaw (degrees)] [pitch (degrees)] [roll (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]Default: %(default)s");
_("Set the compression to use for orthophotos. Can be one of: %(choices)s. Default: %(default)s");
_("The maximum number of processes to use in various processes. Peak memory requirement is ~1GB per thread and 2 megapixel image resolution. Default: %(default)s");
_("Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. Can be one of: %(choices)s. Default: %(default)s");
_("Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. Starting with a radius equal to the output resolution, N different DEMs are generated with progressively bigger radius using the inverse distance weighted (IDW) algorithm and merged together. Remaining gaps are then merged using nearest neighbor interpolation. Default: %(default)s");
_("End processing at this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Decimate the points before generating the DEM. 1 is no decimation (full quality). 100 decimates ~99%% of the points. Useful for speeding up generation of DEM results in very large datasets. Default: %(default)s");
_("Ignore Ground Sampling Distance (GSD).A memory and processor hungry change relative to the default behavior if set to true. Ordinarily, GSD estimates are used to cap the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. Never set --ignore-gsd to true unless you are positive you need it, and even then: do not use it. Default: %(default)s");
_("Simple Morphological Filter window radius parameter (meters). Default: %(default)s");
_("Generate OGC 3D Tiles outputs. Default: %(default)s");
_("Rerun this stage only and stop. Can be one of: %(choices)s. Default: %(default)s");
_("Skip alignment of submodels in split-merge. Useful if GPS is good enough on very large datasets. Default: %(default)s");
_("Set the radiometric calibration to perform on images. When processing multispectral and thermal images you should set this option to obtain reflectance/temperature values (otherwise you will get digital number values). [camera] applies black level, vignetting, row gradient gain/exposure compensation (if appropriate EXIF tags are found) and computes absolute temperature values. [camera+sun] is experimental, applies all the corrections of [camera], plus compensates for spectral radiance registered via a downwelling light sensor (DLS) taking in consideration the angle of the sun. Can be one of: %(choices)s. Default: %(default)s");
_("Export the georeferenced point cloud in LAS format. Default: %(default)s");
_("Use this tag to build a DTM (Digital Terrain Model, ground only) using a simple morphological filter. Check the --dem* and --smrf* parameters for finer tuning. Default: %(default)s");
_("Minimum number of features to extract per image. More features can be useful for finding more matches between images, potentially allowing the reconstruction of areas with little overlap or insufficient features. More features also slow down processing. Default: %(default)s");
_("Perform image matching with the nearest N images based on image filename order. Can speed up processing of sequential images, such as those extracted from video. It is applied only on non-georeferenced datasets. Set to 0 to disable. Default: %(default)s");
_("Skips dense reconstruction and 3D model generation. It generates an orthophoto directly from the sparse reconstruction. If you just need an orthophoto and do not need a full 3D model, turn on this option. Default: %(default)s");
_("Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive morphological filter. Check the --dem* parameters for finer tuning. Default: %(default)s");
_("Turn on rolling shutter correction. If the camera has a rolling shutter and the images were taken in motion, you can turn on this option to improve the accuracy of the results. See also --rolling-shutter-readout. Default: %(default)s");
_("Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. Default: %(default)s");
_("Automatically crop image outputs by creating a smooth buffer around the dataset boundaries, shrunk by N meters. Use 0 to disable cropping. Default: %(default)s");
_("Path to the image groups file that controls how images should be split into groups. The file needs to use the following format: image_name group_nameDefault: %(default)s");
_("GeoJSON polygon limiting the area of the reconstruction. Can be specified either as path to a GeoJSON file or as a JSON string representing the contents of a GeoJSON file. Default: %(default)s");
_("Specify the distance between camera shot locations and the outer edge of the boundary when computing the boundary with --auto-boundary. Set to 0 to automatically choose a value. Default: %(default)s");
_("Automatically set a boundary using camera shot locations to limit the area of the reconstruction. This can help remove far away background artifacts (sky, background landscapes, etc.). See also --boundary. Default: %(default)s");
_("When processing multispectral datasets, ODM will automatically align the images for each band. If the images have been postprocessed and are already aligned, use this option. Default: %(default)s");
_("Orthophoto resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate.Default: %(default)s");
_("Use the camera parameters computed from another dataset instead of calculating them. Can be specified either as path to a cameras.json file or as a JSON string representing the contents of a cameras.json file. Default: %(default)s");
_("Displays version number and exits. ");
_("The maximum vertex count of the output mesh. Default: %(default)s");
_("Simple Morphological Filter elevation threshold parameter (meters). Default: %(default)s");
_("Matcher algorithm, Fast Library for Approximate Nearest Neighbors or Bag of Words. FLANN is slower, but more stable. BOW is faster, but can sometimes miss valid matches. BRUTEFORCE is very slow but robust.Can be one of: %(choices)s. Default: %(default)s");
_("Run local bundle adjustment for every image added to the reconstruction and a global adjustment every 100 images. Speeds up reconstruction for very large datasets. Default: %(default)s");
_("Automatically compute image masks using AI to remove the background. Experimental. Default: %(default)s");
_("Average number of images per submodel. When splitting a large dataset into smaller submodels, images are grouped into clusters. This value regulates the number of images that each cluster should have on average. Default: %(default)s");
_("Simple Morphological Filter elevation scalar parameter. Default: %(default)s");
_("Set a camera projection type. Manually setting a value can help improve geometric undistortion. By default the application tries to determine a lens type from the images metadata. Can be one of: %(choices)s. Default: %(default)s");
_("Path to the project folder. Your project folder should contain subfolders for each dataset. Each dataset should have an \"images\" folder.");
_("Skip normalization of colors across all images. Useful when processing radiometric data. Default: %(default)s");
_("Set this parameter if you want to generate a PNG rendering of the orthophoto. Default: %(default)s");
_("The maximum output resolution of extracted video frames in pixels. Default: %(default)s");
_("Radius of the overlap between submodels. After grouping images into clusters, images that are closer than this radius to a cluster are added to the cluster. This is done to ensure that neighboring submodels overlap. Default: %(default)s");
_("Rerun processing from this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Build orthophoto overviews for faster display in programs such as QGIS. Default: %(default)s");
_("Classify the point cloud outputs. You can control the behavior of this option by tweaking the --dem-* parameters. Default: %(default)s");
_("Path to a GeoTIFF DEM or a LAS/LAZ point cloud that the reconstruction outputs should be automatically aligned to. Experimental. Default: %(default)s");
_("Skip generation of the orthophoto. This can save time if you only need 3D results or DEMs. Default: %(default)s");
_("Automatically compute image masks using AI to remove the sky. Experimental. Default: %(default)s");
_("Create Cloud-Optimized GeoTIFFs instead of normal GeoTIFFs. Default: %(default)s");
_("Generate static tiles for orthophotos and DEMs that are suitable for viewers like Leaflet or OpenLayers. Default: %(default)s");
_("Path to the project folder. Your project folder should contain subfolders for each dataset. Each dataset should have an \"images\" folder.");
_("Rerun processing from this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Decimate the points before generating the DEM. 1 is no decimation (full quality). 100 decimates ~99%% of the points. Useful for speeding up generation of DEM results in very large datasets. Default: %(default)s");
_("Generates a polygon around the cropping area that cuts the orthophoto around the edges of features. This polygon can be useful for stitching seamless mosaics with multiple overlapping orthophotos. Default: %(default)s");
_("Set a value in meters for the GPS Dilution of Precision (DOP) information for all images. If your images are tagged with high precision GPS information (RTK), this value will be automatically set accordingly. You can use this option to manually set it in case the reconstruction fails. Lowering this option can sometimes help control bowling-effects over large areas. Default: %(default)s");
_("Keep faces in the mesh that are not seen in any camera. Default: %(default)s");
_("Simple Morphological Filter slope parameter (rise over run). Default: %(default)s");
_("Set point cloud quality. Higher quality generates better, denser point clouds, but requires more memory and takes longer. Each step up in quality increases processing time roughly by a factor of 4x.Can be one of: %(choices)s. Default: %(default)s");
_("Set the compression to use for orthophotos. Can be one of: %(choices)s. Default: %(default)s");
_("Use this tag to build a DTM (Digital Terrain Model, ground only) using a simple morphological filter. Check the --dem* and --smrf* parameters for finer tuning. Default: %(default)s");
_("URL to a ClusterODM instance for distributing a split-merge workflow on multiple nodes in parallel. Default: %(default)s");
_("Rerun this stage only and stop. Can be one of: %(choices)s. Default: %(default)s");
_("Save the georeferenced point cloud in Cloud Optimized Point Cloud (COPC) format. Default: %(default)s");
_("Skip generation of a full 3D model. This can save time if you only need 2D results such as orthophotos and DEMs. Default: %(default)s");
_("Ignore Ground Sampling Distance (GSD).A memory and processor hungry change relative to the default behavior if set to true. Ordinarily, GSD estimates are used to cap the maximum resolution of image outputs and resizes images when necessary, resulting in faster processing and lower memory usage. Since GSD is an estimate, sometimes ignoring it can result in slightly better image output quality. Never set --ignore-gsd to true unless you are positive you need it, and even then: do not use it. Default: %(default)s");
_("Generate single file Binary glTF (GLB) textured models. Default: %(default)s");
_("Displays version number and exits. ");
_("Average number of images per submodel. When splitting a large dataset into smaller submodels, images are grouped into clusters. This value regulates the number of images that each cluster should have on average. Default: %(default)s");
_("Perform ground rectification on the point cloud. This means that wrongly classified ground points will be re-classified and gaps will be filled. Useful for generating DTMs. Default: %(default)s");
_("Automatically crop image outputs by creating a smooth buffer around the dataset boundaries, shrunk by N meters. Use 0 to disable cropping. Default: %(default)s");
_("Use images' GPS exif data for reconstruction, even if there are GCPs present.This flag is useful if you have high precision GPS measurements. If there are no GCPs, this flag does nothing. Default: %(default)s");
_("Export the georeferenced point cloud in LAS format. Default: %(default)s");
_("Choose the structure from motion algorithm. For aerial datasets, if camera GPS positions and angles are available, triangulation can generate better results. For planar scenes captured at fixed altitude with nadir-only images, planar can be much faster. Can be one of: %(choices)s. Default: %(default)s");
_("Skip generation of PDF report. This can save time if you don't need a report. Default: %(default)s");
_("Turn off camera parameter optimization during bundle adjustment. This can be sometimes useful for improving results that exhibit doming/bowling or when images are taken with a rolling shutter camera. Default: %(default)s");
_("Filters the point cloud by keeping only a single point around a radius N (in meters). This can be useful to limit the output resolution of the point cloud and remove duplicate points. Set to 0 to disable sampling. Default: %(default)s");
_("Name of dataset (i.e subfolder name within project folder). Default: %(default)s");
_("Use this tag to build a DSM (Digital Surface Model, ground + objects) using a progressive morphological filter. Check the --dem* parameters for finer tuning. Default: %(default)s");
_("GeoJSON polygon limiting the area of the reconstruction. Can be specified either as path to a GeoJSON file or as a JSON string representing the contents of a GeoJSON file. Default: %(default)s");
_("When processing multispectral datasets, you can specify the name of the primary band that will be used for reconstruction. It's recommended to choose a band which has sharp details and is in focus. Default: %(default)s");
_("Path to the image geolocation file containing the camera center coordinates used for georeferencing. If you don't have values for yaw/pitch/roll you can set them to 0. The file needs to use the following format: EPSG:<code> or <+proj definition>image_name geo_x geo_y geo_z [yaw (degrees)] [pitch (degrees)] [roll (degrees)] [horz accuracy (meters)] [vert accuracy (meters)]Default: %(default)s");
_("Do not use GPU acceleration, even if it's available. Default: %(default)s");
_("Geometric estimates improve the accuracy of the point cloud by computing geometrically consistent depthmaps but may not be usable in larger datasets. This flag disables geometric estimates. Default: %(default)s");
_("Build orthophoto overviews for faster display in programs such as QGIS. Default: %(default)s");
_("Use this tag if you have a GCP File but want to use the EXIF information for georeferencing instead. Default: %(default)s");
_("Filters the point cloud by keeping only a single point around a radius N (in meters). This can be useful to limit the output resolution of the point cloud and remove duplicate points. Set to 0 to disable sampling. Default: %(default)s");
_("Skip generation of the orthophoto. This can save time if you only need 3D results or DEMs. Default: %(default)s");
_("Filters the point cloud by removing points that deviate more than N standard deviations from the local mean. Set to 0 to disable filtering. Default: %(default)s");
_("Set the radiometric calibration to perform on images. When processing multispectral and thermal images you should set this option to obtain reflectance/temperature values (otherwise you will get digital number values). [camera] applies black level, vignetting, row gradient gain/exposure compensation (if appropriate EXIF tags are found) and computes absolute temperature values. [camera+sun] is experimental, applies all the corrections of [camera], plus compensates for spectral radiance registered via a downwelling light sensor (DLS) taking in consideration the angle of the sun. Can be one of: %(choices)s. Default: %(default)s");
_("Octree depth used in the mesh reconstruction, increase to get more vertices, recommended values are 8-12. Default: %(default)s");
_("Generate OGC 3D Tiles outputs. Default: %(default)s");
_("Minimum number of features to extract per image. More features can be useful for finding more matches between images, potentially allowing the reconstruction of areas with little overlap or insufficient features. More features also slow down processing. Default: %(default)s");
_("Run local bundle adjustment for every image added to the reconstruction and a global adjustment every 100 images. Speeds up reconstruction for very large datasets. Default: %(default)s");
_("Skips dense reconstruction and 3D model generation. It generates an orthophoto directly from the sparse reconstruction. If you just need an orthophoto and do not need a full 3D model, turn on this option. Default: %(default)s");
_("Create Cloud-Optimized GeoTIFFs instead of normal GeoTIFFs. Default: %(default)s");
_("Choose what to merge in the merge step in a split dataset. By default all available outputs are merged. Options: %(choices)s. Default: %(default)s");
_("Export the georeferenced point cloud in Entwine Point Tile (EPT) format. Default: %(default)s");
_("Delete heavy intermediate files to optimize disk space usage. This affects the ability to restart the pipeline from an intermediate stage, but allows datasets to be processed on machines that don't have sufficient disk space available. Default: %(default)s");
_("Matcher algorithm, Fast Library for Approximate Nearest Neighbors or Bag of Words. FLANN is slower, but more stable. BOW is faster, but can sometimes miss valid matches. BRUTEFORCE is very slow but robust.Can be one of: %(choices)s. Default: %(default)s");
_("Automatically set a boundary using camera shot locations to limit the area of the reconstruction. This can help remove far away background artifacts (sky, background landscapes, etc.). See also --boundary. Default: %(default)s");
_("Skip normalization of colors across all images. Useful when processing radiometric data. Default: %(default)s");
_("Simple Morphological Filter window radius parameter (meters). Default: %(default)s");
_("Turn on rolling shutter correction. If the camera has a rolling shutter and the images were taken in motion, you can turn on this option to improve the accuracy of the results. See also --rolling-shutter-readout. Default: %(default)s");
_("When processing multispectral datasets, ODM will automatically align the images for each band. If the images have been postprocessed and are already aligned, use this option. Default: %(default)s");
_("The maximum vertex count of the output mesh. Default: %(default)s");
_("Permanently delete all previous results and rerun the processing pipeline.");
_("Export the georeferenced point cloud in CSV format. Default: %(default)s");
_("Simple Morphological Filter elevation threshold parameter (meters). Default: %(default)s");
_("Maximum number of frames to extract from video files for processing. Set to 0 for no limit. Default: %(default)s");
_("Specify the distance between camera shot locations and the outer edge of the boundary when computing the boundary with --auto-boundary. Set to 0 to automatically choose a value. Default: %(default)s");
_("Set this parameter if you want to generate a Google Earth (KMZ) rendering of the orthophoto. Default: %(default)s");
_("Path to the image groups file that controls how images should be split into groups. The file needs to use the following format: image_name group_nameDefault: %(default)s");
_("Radius of the overlap between submodels. After grouping images into clusters, images that are closer than this radius to a cluster are added to the cluster. This is done to ensure that neighboring submodels overlap. Default: %(default)s");
_("Override the rolling shutter readout time for your camera sensor (in milliseconds), instead of using the rolling shutter readout database. Note that not all cameras are present in the database. Set to 0 to use the database value. Default: %(default)s");
_("Choose the algorithm for extracting keypoints and computing descriptors. Can be one of: %(choices)s. Default: %(default)s");
_("Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. Starting with a radius equal to the output resolution, N different DEMs are generated with progressively bigger radius using the inverse distance weighted (IDW) algorithm and merged together. Remaining gaps are then merged using nearest neighbor interpolation. Default: %(default)s");
_("Automatically compute image masks using AI to remove the background. Experimental. Default: %(default)s");
_("Set feature extraction quality. Higher quality generates better features, but requires more memory and takes longer. Can be one of: %(choices)s. Default: %(default)s");
_("Simple Morphological Filter elevation scalar parameter. Default: %(default)s");
_("Perform image matching with the nearest images based on GPS exif data. Set to 0 to match by triangulation. Default: %(default)s");
_("Computes an euclidean raster map for each DEM. The map reports the distance from each cell to the nearest NODATA value (before any hole filling takes place). This can be useful to isolate the areas that have been filled. Default: %(default)s");
_("Skip alignment of submodels in split-merge. Useful if GPS is good enough on very large datasets. Default: %(default)s");
_("Path to the file containing the ground control points used for georeferencing. The file needs to use the following format: EPSG:<code> or <+proj definition>geo_x geo_y geo_z im_x im_y image_name [gcp_name] [extra1] [extra2]Default: %(default)s");
_("DSM/DTM resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate. Default: %(default)s");
_("End processing at this stage. Can be one of: %(choices)s. Default: %(default)s");
_("Set this parameter if you want a striped GeoTIFF. Default: %(default)s");
_("Set this parameter if you want to generate a PNG rendering of the orthophoto. Default: %(default)s");
_("Generate OBJs that have a single material and a single texture file instead of multiple ones. Default: %(default)s");
_("Do not attempt to merge partial reconstructions. This can happen when images do not have sufficient overlap or are isolated. Default: %(default)s");
_("Use the camera parameters computed from another dataset instead of calculating them. Can be specified either as path to a cameras.json file or as a JSON string representing the contents of a cameras.json file. Default: %(default)s");
_("show this help message and exit");
_("Use this tag if you have a GCP File but want to use the EXIF information for georeferencing instead. Default: %(default)s");
_("The maximum output resolution of extracted video frames in pixels. Default: %(default)s");
_("Orthophoto resolution in cm / pixel. Note that this value is capped by a ground sampling distance (GSD) estimate.Default: %(default)s");
_("Perform image matching with the nearest N images based on image filename order. Can speed up processing of sequential images, such as those extracted from video. It is applied only on non-georeferenced datasets. Set to 0 to disable. Default: %(default)s");
_("Use a full 3D mesh to compute the orthophoto instead of a 2.5D mesh. This option is a bit faster and provides similar results in planar areas. Default: %(default)s");
_("Set a camera projection type. Manually setting a value can help improve geometric undistortion. By default the application tries to determine a lens type from the images metadata. Can be one of: %(choices)s. Default: %(default)s");

Wyświetl plik

@ -45,6 +45,7 @@ _("Navigation cube");
_("Remove all clipping volumes");
_("Compass");
_("Camera Animation");
_("Remove last camera animation");
_("Point budget");
_("Point size");
_("Minimum size");

Wyświetl plik

@ -1,9 +0,0 @@
.leaflet-container .leaflet-control-mouseposition {
background-color: rgba(255, 255, 255, 0.7);
box-shadow: 0 0 5px #bbb;
padding: 0 5px;
margin:0;
color: #333;
font: 11px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}

Wyświetl plik

@ -1,48 +0,0 @@
L.Control.MousePosition = L.Control.extend({
options: {
position: 'bottomleft',
separator: ' : ',
emptyString: 'Unavailable',
lngFirst: false,
numDigits: 5,
lngFormatter: undefined,
latFormatter: undefined,
prefix: ""
},
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'leaflet-control-mouseposition');
L.DomEvent.disableClickPropagation(this._container);
map.on('mousemove', this._onMouseMove, this);
this._container.innerHTML=this.options.emptyString;
return this._container;
},
onRemove: function (map) {
map.off('mousemove', this._onMouseMove)
},
_onMouseMove: function (e) {
var lng = this.options.lngFormatter ? this.options.lngFormatter(e.latlng.lng) : L.Util.formatNum(e.latlng.lng, this.options.numDigits);
var lat = this.options.latFormatter ? this.options.latFormatter(e.latlng.lat) : L.Util.formatNum(e.latlng.lat, this.options.numDigits);
var value = this.options.lngFirst ? lng + this.options.separator + lat : lat + this.options.separator + lng;
var prefixAndValue = this.options.prefix + ' ' + value;
this._container.innerHTML = prefixAndValue;
}
});
L.Map.mergeOptions({
positionControl: false
});
L.Map.addInitHook(function () {
if (this.options.positionControl) {
this.positionControl = new L.Control.MousePosition();
this.addControl(this.positionControl);
}
});
L.control.mousePosition = function (options) {
return new L.Control.MousePosition(options);
};

Wyświetl plik

@ -20,6 +20,12 @@
{% for field in form %}
{% include 'registration/form_field.html' %}
{% endfor %}
<input type="hidden" name="next" value="" id="loginNext" />
<script>
var loginNext = document.getElementById("loginNext");
var value = new URLSearchParams(new URL(window.location.href).search).get('next');
if (value) loginNext.value = value;
</script>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">{% trans 'Log in' %}</button>

Wyświetl plik

@ -7,7 +7,7 @@ _("Display program version, memory and disk space usage statistics")
_("Integrate WebODM with DroneDB: import images and share results")
_("Create editable short links when sharing task URLs")
_("Add a fullscreen button to the 2D map view")
_("Sync accounts from webodm.net")
_("Process in the cloud with webodm.net")
_("Compute volume, area and length measurements on Leaflet")
_("A plugin to upload orthophotos to OpenAerialMap")
_("A plugin to add a button for quickly opening OpenStreetMap's iD editor and setup a TMS basemap.")
@ -16,3 +16,4 @@ _("A plugin to show charts of projects and tasks")
_("Create short links when sharing task URLs")
_("Get notified when a task has finished processing, has been removed or has failed")
_("A plugin to create GCP files from images")
_("Annotate and measure on 2D maps with ease")

Wyświetl plik

@ -9,7 +9,7 @@ from django.utils.translation import gettext_lazy as _
class ContoursException(Exception):
pass
def calc_contours(dem, epsg, interval, output_format, simplify):
def calc_contours(dem, epsg, interval, output_format, simplify, zfactor = 1):
import os
import subprocess
import tempfile
@ -50,7 +50,7 @@ def calc_contours(dem, epsg, interval, output_format, simplify):
outfile = os.path.join(tmpdir, f"output.{ext}")
p = subprocess.Popen([ogr2ogr_bin, outfile, contours_file, "-simplify", str(simplify), "-f", output_format, "-t_srs", f"EPSG:{epsg}", "-nln", "contours",
"-dialect", "sqlite", "-sql", f"SELECT * FROM contour WHERE ST_Length(GEOM) >= {MIN_CONTOUR_LENGTH}"], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
"-dialect", "sqlite", "-sql", f"SELECT ID, ROUND(level * {zfactor}, 5) AS level, GeomFromGML(AsGML(ATM_Transform(GEOM, ATM_Scale(ATM_Create(), 1, 1, {zfactor})), 10)) as GEOM FROM contour WHERE ST_Length(GEOM) >= {MIN_CONTOUR_LENGTH}"], cwd=tmpdir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
out = out.decode('utf-8').strip()
@ -102,8 +102,9 @@ class TaskContoursGenerate(TaskView):
if not format in supported_formats:
raise ContoursException("Invalid format {} (must be one of: {})".format(format, ",".join(supported_formats)))
simplify = float(request.data.get('simplify', 0.01))
zfactor = float(request.data.get('zfactor', 1))
celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify).task_id
celery_task_id = run_function_async(calc_contours, dem, epsg, interval, format, simplify, zfactor).task_id
return Response({'celery_task_id': celery_task_id}, status=status.HTTP_200_OK)
except ContoursException as e:
return Response({'error': str(e)}, status=status.HTTP_200_OK)

Wyświetl plik

@ -1,8 +1,8 @@
{
"name": "Contours",
"webodmMinVersion": "0.9.0",
"webodmMinVersion": "2.5.0",
"description": "Compute, preview and export contours from DEMs",
"version": "1.0.0",
"version": "1.1.0",
"author": "Piero Toffanin",
"email": "pt@masseranolabs.com",
"repository": "https://github.com/OpenDroneMap/WebODM",

Wyświetl plik

@ -6,6 +6,7 @@ import './ContoursPanel.scss';
import ErrorMessage from 'webodm/components/ErrorMessage';
import Workers from 'webodm/classes/Workers';
import { _ } from 'webodm/classes/gettext';
import { systems, getUnitSystem, onUnitSystemChanged, offUnitSystemChanged, toMetric } from 'webodm/classes/Units';
export default class ContoursPanel extends React.Component {
static defaultProps = {
@ -20,13 +21,23 @@ export default class ContoursPanel extends React.Component {
constructor(props){
super(props);
const unitSystem = getUnitSystem();
const defaultInterval = unitSystem === "metric" ? "1" : "4";
const defaultSimplify = unitSystem === "metric" ? "0.2" : "0.6";
// Remove legacy parameters
Storage.removeItem("last_contours_interval");
Storage.removeItem("last_contours_custom_interval");
Storage.removeItem("last_contours_simplify");
Storage.removeItem("last_contours_custom_simplify");
this.state = {
error: "",
permanentError: "",
interval: Storage.getItem("last_contours_interval") || "1",
customInterval: Storage.getItem("last_contours_custom_interval") || "1",
simplify: Storage.getItem("last_contours_simplify") || "0.2",
customSimplify: Storage.getItem("last_contours_custom_simplify") || "0.2",
interval: Storage.getItem("last_contours_interval_" + unitSystem) || defaultInterval,
customInterval: Storage.getItem("last_contours_custom_interval_" + unitSystem) || defaultInterval,
simplify: Storage.getItem("last_contours_simplify_" + unitSystem) || defaultSimplify,
customSimplify: Storage.getItem("last_contours_custom_simplify_" + unitSystem) || defaultSimplify,
layer: "",
epsg: Storage.getItem("last_contours_epsg") || "4326",
customEpsg: Storage.getItem("last_contours_custom_epsg") || "4326",
@ -36,13 +47,18 @@ export default class ContoursPanel extends React.Component {
previewLoading: false,
exportLoading: false,
previewLayer: null,
unitSystem
};
}
componentDidMount(){
onUnitSystemChanged(this.unitsChanged);
}
componentDidUpdate(){
if (this.props.isShowed && this.state.loading){
const {id, project} = this.state.task;
this.loadingReq = $.getJSON(`/api/projects/${project}/tasks/${id}/`)
.done(res => {
const { available_assets } = res;
@ -76,6 +92,24 @@ export default class ContoursPanel extends React.Component {
this.generateReq.abort();
this.generateReq = null;
}
offUnitSystemChanged(this.unitsChanged);
}
unitsChanged = e => {
this.saveInputValues();
const unitSystem = e.detail;
const defaultInterval = unitSystem === "metric" ? "1" : "4";
const defaultSimplify = unitSystem === "metric" ? "0.2" : "0.5";
const interval = Storage.getItem("last_contours_interval_" + unitSystem) || defaultInterval;
const customInterval = Storage.getItem("last_contours_custom_interval_" + unitSystem) || defaultInterval;
const simplify = Storage.getItem("last_contours_simplify_" + unitSystem) || defaultSimplify;
const customSimplify = Storage.getItem("last_contours_custom_simplify_" + unitSystem) || defaultSimplify;
this.setState({unitSystem, interval, customInterval, simplify, customSimplify });
}
handleSelectInterval = e => {
@ -106,19 +140,31 @@ export default class ContoursPanel extends React.Component {
this.setState({customEpsg: e.target.value});
}
getFormValues = () => {
getFormValues = (preview) => {
const { interval, customInterval, epsg, customEpsg,
simplify, customSimplify, layer } = this.state;
simplify, customSimplify, layer, unitSystem } = this.state;
const su = systems[unitSystem];
let meterInterval = interval !== "custom" ? interval : customInterval;
let meterSimplify = simplify !== "custom" ? simplify : customSimplify;
meterInterval = toMetric(meterInterval, su.lengthUnit(1)).value;
meterSimplify = toMetric(meterSimplify, su.lengthUnit(1)).value;
const zfactor = preview ? 1 : su.lengthUnit(1).factor;
return {
interval: interval !== "custom" ? interval : customInterval,
interval: meterInterval,
epsg: epsg !== "custom" ? epsg : customEpsg,
simplify: simplify !== "custom" ? simplify : customSimplify,
simplify: meterSimplify,
zfactor,
layer
};
}
addGeoJSONFromURL = (url, cb) => {
const { map } = this.props;
const us = systems[this.state.unitSystem];
$.getJSON(url)
.done((geojson) => {
@ -128,7 +174,7 @@ export default class ContoursPanel extends React.Component {
this.setState({previewLayer: L.geoJSON(geojson, {
onEachFeature: (feature, layer) => {
if (feature.properties && feature.properties.level !== undefined) {
layer.bindPopup(`<b>${_("Elevation:")}</b> ${feature.properties.level} ${_("meters")}`);
layer.bindPopup(`<div style="margin-right: 32px;"><b>${_("Elevation:")}</b> ${us.length(feature.properties.level)}</div>`);
}
},
style: feature => {
@ -155,18 +201,23 @@ export default class ContoursPanel extends React.Component {
}
}
saveInputValues = () => {
const us = this.state.unitSystem;
// Save settings
Storage.setItem("last_contours_interval_" + us, this.state.interval);
Storage.setItem("last_contours_custom_interval_" + us, this.state.customInterval);
Storage.setItem("last_contours_simplify_" + us, this.state.simplify);
Storage.setItem("last_contours_custom_simplify_" + us, this.state.customSimplify);
Storage.setItem("last_contours_epsg", this.state.epsg);
Storage.setItem("last_contours_custom_epsg", this.state.customEpsg);
}
generateContours = (data, loadingProp, isPreview) => {
this.setState({[loadingProp]: true, error: ""});
const taskId = this.state.task.id;
this.saveInputValues();
// Save settings for next time
Storage.setItem("last_contours_interval", this.state.interval);
Storage.setItem("last_contours_custom_interval", this.state.customInterval);
Storage.setItem("last_contours_simplify", this.state.simplify);
Storage.setItem("last_contours_custom_simplify", this.state.customSimplify);
Storage.setItem("last_contours_epsg", this.state.epsg);
Storage.setItem("last_contours_custom_epsg", this.state.customEpsg);
this.generateReq = $.ajax({
type: 'POST',
url: `/api/plugins/contours/task/${taskId}/contours/generate`,
@ -203,7 +254,7 @@ export default class ContoursPanel extends React.Component {
handleExport = (format) => {
return () => {
const data = this.getFormValues();
const data = this.getFormValues(false);
data.format = format;
this.generateContours(data, 'exportLoading', false);
};
@ -212,7 +263,7 @@ export default class ContoursPanel extends React.Component {
handleShowPreview = () => {
this.setState({previewLoading: true});
const data = this.getFormValues();
const data = this.getFormValues(true);
data.epsg = 4326;
data.format = "GeoJSON";
this.generateContours(data, 'previewLoading', true);
@ -222,11 +273,15 @@ export default class ContoursPanel extends React.Component {
const { loading, task, layers, error, permanentError, interval, customInterval, layer,
epsg, customEpsg, exportLoading,
simplify, customSimplify,
previewLoading, previewLayer } = this.state;
const intervalValues = [0.25, 0.5, 1, 1.5, 2];
previewLoading, previewLayer, unitSystem } = this.state;
const us = systems[unitSystem];
const lengthUnit = us.lengthUnit(1);
const intervalStart = unitSystem === "metric" ? 1 : 4;
const intervalValues = [intervalStart / 4, intervalStart / 2, intervalStart, intervalStart * 2, intervalStart * 4];
const simplifyValues = [{label: _('Do not simplify'), value: 0},
{label: _('Normal'), value: 0.2},
{label: _('Aggressive'), value: 1}];
{label: _('Normal'), value: unitSystem === "metric" ? 0.2 : 0.5},
{label: _('Aggressive'), value: unitSystem === "metric" ? 1 : 4}];
const disabled = (interval === "custom" && !customInterval) ||
(epsg === "custom" && !customEpsg) ||
@ -242,7 +297,7 @@ export default class ContoursPanel extends React.Component {
<label className="col-sm-3 control-label">{_("Interval:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={interval} onChange={this.handleSelectInterval}>
{intervalValues.map(iv => <option value={iv}>{iv} {_("meter")}</option>)}
{intervalValues.map(iv => <option value={iv}>{iv} {lengthUnit.label}</option>)}
<option value="custom">{_("Custom")}</option>
</select>
</div>
@ -251,7 +306,7 @@ export default class ContoursPanel extends React.Component {
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 ">
<input type="number" className="form-control custom-interval" value={customInterval} onChange={this.handleChangeCustomInterval} /><span> {_("meter")}</span>
<input type="number" className="form-control custom-interval" value={customInterval} onChange={this.handleChangeCustomInterval} /><span> {lengthUnit.label}</span>
</div>
</div>
: ""}
@ -269,7 +324,7 @@ export default class ContoursPanel extends React.Component {
<label className="col-sm-3 control-label">{_("Simplify:")}</label>
<div className="col-sm-9 ">
<select className="form-control" value={simplify} onChange={this.handleSelectSimplify}>
{simplifyValues.map(sv => <option value={sv.value}>{sv.label} ({sv.value} {_("meter")})</option>)}
{simplifyValues.map(sv => <option value={sv.value}>{sv.label} ({sv.value} {lengthUnit.label})</option>)}
<option value="custom">{_("Custom")}</option>
</select>
</div>
@ -278,7 +333,7 @@ export default class ContoursPanel extends React.Component {
<div className="row form-group form-inline">
<label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 ">
<input type="number" className="form-control custom-interval" value={customSimplify} onChange={this.handleChangeCustomSimplify} /><span> {_("meter")}</span>
<input type="number" className="form-control custom-interval" value={customSimplify} onChange={this.handleChangeCustomSimplify} /><span> {lengthUnit.label}</span>
</div>
</div>
: ""}

Wyświetl plik

@ -343,7 +343,7 @@ class ShareTaskView(TaskView):
settings = get_settings(request)
available_assets = [task.get_asset_file_or_zipstream(f)[0] for f in list(set(task.available_assets) & set(DRONEDB_ASSETS))]
available_assets = [task.get_asset_file_or_stream(f) for f in list(set(task.available_assets) & set(DRONEDB_ASSETS))]
if 'textured_model.zip' in task.available_assets:
texture_files = [join(task.assets_path('odm_texturing'), f) for f in listdir(task.assets_path('odm_texturing')) if isfile(join(task.assets_path('odm_texturing'), f))]

Wyświetl plik

@ -1,7 +1,7 @@
{
"name": "Lightning Network Bridge",
"name": "Lightning",
"webodmMinVersion": "0.7.1",
"description": "Sync accounts from webodm.net",
"description": "Process in the cloud with webodm.net",
"version": "0.9.0",
"author": "Piero Toffanin",
"email": "pt@masseranolabs.com",

Wyświetl plik

@ -22,7 +22,7 @@ def JsonResponse(dict):
class Plugin(PluginBase):
def main_menu(self):
return [Menu(_("Lightning Network"), self.public_url(""), "fa fa-bolt fa-fw")]
return [Menu(_("Lightning"), self.public_url(""), "fa fa-bolt fa-fw")]
def include_js_files(self):
return ['add_cost_estimate.js']
@ -36,7 +36,7 @@ class Plugin(PluginBase):
uds = UserDataStore('lightning', request.user)
return render(request, self.template_path("index.html"), {
'title': _('Lightning Network'),
'title': _('Lightning'),
'api_key': uds.get_string("api_key")
})

Wyświetl plik

@ -36,7 +36,7 @@ export default class LightningPanel extends React.Component {
return (<div className="plugin-lightning">
{ !apiKey ?
<div>
<h4><i className="fa fa-bolt"/> {_("Lightning Network")}</h4>
<h4><i className="fa fa-bolt"/> {_("Lightning")}</h4>
{_("Lightning is a service that allows you to quickly process small and large datasets using high performance servers in the cloud.")}
<Trans params={{ link: '<a href="https://webodm.net" target="_blank">webodm.net</a>', register: `<a href="https://webodm.net/register" target="_blank">${_("register")}</a>`}}>
{_("Below you can enter your %(link)s credentials to sync your account and automatically setup a new processing node. If you don't have an account, you can %(register)s for free.")}</Trans>

Wyświetl plik

@ -1,8 +1,8 @@
{
"name": "Volume/Area/Length Measurements",
"webodmMinVersion": "0.9.0",
"webodmMinVersion": "2.5.0",
"description": "Compute volume, area and length measurements on Leaflet",
"version": "1.0.0",
"version": "1.1.0",
"author": "Abdelkoddouss Izem, Piero Toffanin",
"email": "pt@masseranolabs.com",
"repository": "https://github.com/OpenDroneMap/WebODM",

Wyświetl plik

@ -4,7 +4,7 @@ import './MeasurePopup.scss';
import Utils from 'webodm/classes/Utils';
import Workers from 'webodm/classes/Workers';
import { _, interpolate } from 'webodm/classes/gettext';
import { systems, unitSystem, getUnitSystem } from 'webodm/classes/Units';
import $ from 'jquery';
import L from 'leaflet';
@ -50,15 +50,19 @@ export default class MeasurePopup extends React.Component {
}
getProperties(){
const result = {
Length: this.props.model.length,
Area: this.props.model.area
};
const us = systems[this.lastUnitSystem];
const result = {
Length: us.length(this.props.model.length).value,
Area: us.area(this.props.model.area).value
};
if (this.state.volume !== null && this.state.volume !== false){
result.Volume = this.state.volume;
result.Volume = us.volume(this.state.volume).value;
result.BaseSurface = this.state.baseMethod;
}
result.UnitSystem = this.lastUnitSystem;
return result;
}
@ -167,6 +171,9 @@ export default class MeasurePopup extends React.Component {
render(){
const { volume, error, featureType } = this.state;
const us = unitSystem();
this.lastUnitSystem = getUnitSystem();
const baseMethods = [
{label: _("Triangulate"), method: 'triangulate'},
{label: _("Plane"), method: 'plane'},
@ -175,12 +182,12 @@ export default class MeasurePopup extends React.Component {
{label: _("Lowest"), method: 'lowest'}];
return (<div className="plugin-measure popup">
{featureType == "Polygon" && <p>{_("Area:")} {this.props.model.areaDisplay}</p>}
{featureType == "Polygon" && <p>{_("Perimeter:")} {this.props.model.lengthDisplay}</p>}
{featureType == "Polygon" && <p>{_("Area:")} {this.props.model.areaDisplay}</p>}
{featureType == "Polygon" && volume === null && !error && <p>{_("Volume:")} <i>{_("computing…")}</i> <i className="fa fa-cog fa-spin fa-fw" /></p>}
{typeof volume === "number" ?
[
<p>{_("Volume:")} {volume.toFixed("2")} {_("Cubic Meters")} ({(volume * 35.3147).toFixed(2)} {_("Cubic Feet")})</p>,
<p>{_("Volume:")} {us.volume(volume).toString()}</p>,
<p className="base-control">{_("Base surface:")}
<select className="form-control" value={this.state.baseMethod} onChange={this.handleBaseMethodChange}>
{baseMethods.map(bm =>

Wyświetl plik

@ -8,6 +8,7 @@ import ReactDOM from 'ReactDOM';
import React from 'React';
import $ from 'jquery';
import { _, get_format } from 'webodm/classes/gettext';
import { unitSystem } from 'webodm/classes/Units';
export default class App{
constructor(map){
@ -52,6 +53,15 @@ export default class App{
// measure.options.labels.
measure._getMeasurementDisplayStrings = measurement => {
const us = unitSystem();
return {
lengthDisplay: us.length(measurement.length).toString(),
areaDisplay: us.area(measurement.area).toString()
};
};
const $btnExport = $(`<br/><a href='#' class='js-start start'>${_("Export Measurements")}</a>`);
$btnExport.appendTo($(measure.$startPrompt).children("ul.tasks"));
$btnExport.on('click', () => {

2
locale

@ -1 +1 @@
Subproject commit 700e3aea27ab919bc27c35d994bf62e232428de3
Subproject commit cbb06f369b4538d2e4b43708bb4919620a897169

Wyświetl plik

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