Contours imperial preview working

pull/1496/head
Piero Toffanin 2024-05-09 15:46:30 -04:00
rodzic 75678b7a84
commit d76eacabd3
6 zmienionych plików z 242 dodań i 62 usunięć

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

@ -1,112 +1,156 @@
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
round: 5,
label: _("Acres"),
type: types.AREA
},
acres_us: {
factor: Math.pow(3937 / 1200, 2) / 43560,
abbr: 'ac (US)',
round: 5
round: 5,
label: _("Acres"),
type: types.AREA
},
feet: {
factor: 1 / 0.3048,
abbr: 'ft',
round: 4
round: 4,
label: _("Feet"),
type: types.LENGTH
},
feet_us:{
factor: 3937 / 1200,
abbr: 'ft (US)',
round: 4
round: 4,
label: _("Feet"),
type: types.LENGTH
},
hectares: {
factor: 0.0001,
abbr: 'ha',
round: 4
round: 4,
label: _("Hectares"),
type: types.AREA
},
meters: {
factor: 1,
abbr: 'm',
round: 3
round: 3,
label: _("Meters"),
type: types.LENGTH
},
kilometers: {
factor: 0.001,
abbr: 'km',
round: 5
round: 5,
label: _("Kilometers"),
type: types.LENGTH
},
centimeters: {
factor: 100,
abbr: 'cm',
round: 1
round: 1,
label: _("Centimeters"),
type: types.LENGTH
},
miles: {
factor: (1 / 0.3048) / 5280,
abbr: 'mi',
round: 5
},
round: 5,
label: _("Miles"),
type: types.LENGTH
},
miles_us: {
factor: (3937 / 1200) / 5280,
abbr: 'mi (US)',
round: 5
round: 5,
label: _("Miles"),
type: types.LENGTH
},
sqfeet: {
factor: 1 / (0.3048 * 0.3048),
abbr: 'ft²',
round: 2
round: 2,
label: _("Squared Feet"),
type: types.AREA
},
sqfeet_us: {
factor: Math.pow(3937 / 1200, 2),
abbr: 'ft² (US)',
round: 2
round: 2,
label: _("Squared Feet"),
type: types.AREA
},
sqmeters: {
factor: 1,
abbr: 'm²',
round: 2
round: 2,
label: _("Squared Meters"),
type: types.AREA
},
sqkilometers: {
factor: 0.000001,
abbr: 'km²',
round: 5
round: 5,
label: _("Squared Kilometers"),
type: types.AREA
},
sqmiles: {
factor: Math.pow((1 / 0.3048) / 5280, 2),
abbr: 'mi²',
round: 5
round: 5,
label: _("Squared Miles"),
type: types.AREA
},
sqmiles_us: {
factor: Math.pow((3937 / 1200) / 5280, 2),
abbr: 'mi² (US)',
round: 5
round: 5,
label: _("Squared Miles"),
type: types.AREA
},
cbmeters:{
factor: 1,
abbr: 'm³',
round: 4
round: 4,
label: _("Cubic Meters"),
type: types.VOLUME
},
cbyards:{
factor: Math.pow(1/(0.3048*3), 3),
abbr: 'yd³',
round: 4
round: 4,
label: _("Cubic Yards"),
type: types.VOLUME
},
cbyards_us:{
factor: Math.pow(3937/3600, 3),
abbr: 'yd³ (US)',
round: 4
round: 4,
label: _("Cubic Yards"),
type: types.VOLUME
}
};
class ValueUnit{
constructor(val, unit){
this.val = val;
constructor(value, unit){
this.value = value;
this.unit = unit;
}
toString(){
const mul = Math.pow(10, this.unit.round);
const rounded = (Math.round(this.val * mul) / mul).toString();
const rounded = (Math.round(this.value * mul) / mul).toString();
let withCommas = "";
let parts = rounded.split(".");
@ -117,6 +161,12 @@ class ValueUnit{
}
}
class NanUnit{
toString(){
return "NaN";
}
}
class UnitSystem{
lengthUnit(meters){ throw new Error("Not implemented"); }
areaUnit(sqmeters){ throw new Error("Not implemented"); }
@ -125,24 +175,55 @@ class UnitSystem{
getName(){ throw new Error("Not implemented"); }
area(sqmeters){
sqmeters = parseFloat(sqmeters);
if (isNaN(sqmeters)) return NanUnit();
const unit = this.areaUnit(sqmeters);
const val = unit.factor * sqmeters;
return new ValueUnit(val, unit);
}
length(meters){
meters = parseFloat(meters);
if (isNaN(meters)) return NanUnit();
const unit = this.lengthUnit(meters);
const val = unit.factor * meters;
return new ValueUnit(val, unit);
}
volume(cbmeters){
cbmeters = parseFloat(cbmeters);
if (isNaN(cbmeters)) return NanUnit();
const unit = this.volumeUnit(cbmeters);
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");
@ -249,16 +330,32 @@ const systems = {
}
// Expose to allow every part of the app to access this information
function getPreferredUnitSystem(){
return localStorage.getItem("preferred_unit_system") || "metric";
function getUnitSystem(){
return localStorage.getItem("_unit_system") || "metric";
}
function setPreferredUnitSystem(system){
localStorage.setItem("preferred_unit_system", system);
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);
}
export {
systems,
getPreferredUnitSystem,
setPreferredUnitSystem
types,
toMetric,
getUnitSystem,
setUnitSystem,
onUnitSystemChanged,
offUnitSystemChanged
};

Wyświetl plik

@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { systems, getPreferredUnitSystem, setPreferredUnitSystem } from '../classes/Units';
import { systems, getUnitSystem, setUnitSystem } from '../classes/Units';
import '../css/UnitSelector.scss';
class UnitSelector extends React.Component {
@ -11,13 +11,13 @@ class UnitSelector extends React.Component {
super(props);
this.state = {
system: getPreferredUnitSystem()
system: getUnitSystem()
}
}
handleChange = e => {
this.setState({system: e.target.value});
setPreferredUnitSystem(e.target.value);
setUnitSystem(e.target.value);
};
render() {

Wyświetl plik

@ -1,4 +1,4 @@
import { systems } from '../../classes/Units';
import { systems, toMetric } from '../../classes/Units';
describe('Metric system', () => {
it('it should display units properly', () => {
@ -94,5 +94,25 @@ describe('Imperial systems', () => {
expect(imperial.volume(v[0]).toString()).toBe(v[1]);
expect(imperialUS.volume(v[0]).toString()).toBe(v[2]);
});
})
});
});
});
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

@ -1,8 +1,8 @@
{
"name": "Contours",
"webodmMinVersion": "0.9.0",
"webodmMinVersion": "2.4.3",
"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 => {
@ -108,17 +142,29 @@ export default class ContoursPanel extends React.Component {
getFormValues = () => {
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 zExportFactor = su.lengthUnit(1).factor;
return {
interval: interval !== "custom" ? interval : customInterval,
interval: meterInterval,
epsg: epsg !== "custom" ? epsg : customEpsg,
simplify: simplify !== "custom" ? simplify : customSimplify,
simplify: meterSimplify,
zExportFactor,
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`,
@ -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>
: ""}