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); 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; export default Storage;

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,4 +1,4 @@
import { systems } from '../../classes/Units'; import { systems, toMetric } from '../../classes/Units';
describe('Metric system', () => { describe('Metric system', () => {
it('it should display units properly', () => { it('it should display units properly', () => {
@ -94,5 +94,25 @@ describe('Imperial systems', () => {
expect(imperial.volume(v[0]).toString()).toBe(v[1]); expect(imperial.volume(v[0]).toString()).toBe(v[1]);
expect(imperialUS.volume(v[0]).toString()).toBe(v[2]); 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", "name": "Contours",
"webodmMinVersion": "0.9.0", "webodmMinVersion": "2.4.3",
"description": "Compute, preview and export contours from DEMs", "description": "Compute, preview and export contours from DEMs",
"version": "1.0.0", "version": "1.1.0",
"author": "Piero Toffanin", "author": "Piero Toffanin",
"email": "pt@masseranolabs.com", "email": "pt@masseranolabs.com",
"repository": "https://github.com/OpenDroneMap/WebODM", "repository": "https://github.com/OpenDroneMap/WebODM",

Wyświetl plik

@ -6,6 +6,7 @@ import './ContoursPanel.scss';
import ErrorMessage from 'webodm/components/ErrorMessage'; import ErrorMessage from 'webodm/components/ErrorMessage';
import Workers from 'webodm/classes/Workers'; import Workers from 'webodm/classes/Workers';
import { _ } from 'webodm/classes/gettext'; import { _ } from 'webodm/classes/gettext';
import { systems, getUnitSystem, onUnitSystemChanged, offUnitSystemChanged, toMetric } from 'webodm/classes/Units';
export default class ContoursPanel extends React.Component { export default class ContoursPanel extends React.Component {
static defaultProps = { static defaultProps = {
@ -20,13 +21,23 @@ export default class ContoursPanel extends React.Component {
constructor(props){ constructor(props){
super(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 = { this.state = {
error: "", error: "",
permanentError: "", permanentError: "",
interval: Storage.getItem("last_contours_interval") || "1", interval: Storage.getItem("last_contours_interval_" + unitSystem) || defaultInterval,
customInterval: Storage.getItem("last_contours_custom_interval") || "1", customInterval: Storage.getItem("last_contours_custom_interval_" + unitSystem) || defaultInterval,
simplify: Storage.getItem("last_contours_simplify") || "0.2", simplify: Storage.getItem("last_contours_simplify_" + unitSystem) || defaultSimplify,
customSimplify: Storage.getItem("last_contours_custom_simplify") || "0.2", customSimplify: Storage.getItem("last_contours_custom_simplify_" + unitSystem) || defaultSimplify,
layer: "", layer: "",
epsg: Storage.getItem("last_contours_epsg") || "4326", epsg: Storage.getItem("last_contours_epsg") || "4326",
customEpsg: Storage.getItem("last_contours_custom_epsg") || "4326", customEpsg: Storage.getItem("last_contours_custom_epsg") || "4326",
@ -36,9 +47,14 @@ export default class ContoursPanel extends React.Component {
previewLoading: false, previewLoading: false,
exportLoading: false, exportLoading: false,
previewLayer: null, previewLayer: null,
unitSystem
}; };
} }
componentDidMount(){
onUnitSystemChanged(this.unitsChanged);
}
componentDidUpdate(){ componentDidUpdate(){
if (this.props.isShowed && this.state.loading){ if (this.props.isShowed && this.state.loading){
const {id, project} = this.state.task; const {id, project} = this.state.task;
@ -76,6 +92,24 @@ export default class ContoursPanel extends React.Component {
this.generateReq.abort(); this.generateReq.abort();
this.generateReq = null; 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 => { handleSelectInterval = e => {
@ -108,17 +142,29 @@ export default class ContoursPanel extends React.Component {
getFormValues = () => { getFormValues = () => {
const { interval, customInterval, epsg, customEpsg, 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 { return {
interval: interval !== "custom" ? interval : customInterval, interval: meterInterval,
epsg: epsg !== "custom" ? epsg : customEpsg, epsg: epsg !== "custom" ? epsg : customEpsg,
simplify: simplify !== "custom" ? simplify : customSimplify, simplify: meterSimplify,
zExportFactor,
layer layer
}; };
} }
addGeoJSONFromURL = (url, cb) => { addGeoJSONFromURL = (url, cb) => {
const { map } = this.props; const { map } = this.props;
const us = systems[this.state.unitSystem];
$.getJSON(url) $.getJSON(url)
.done((geojson) => { .done((geojson) => {
@ -128,7 +174,7 @@ export default class ContoursPanel extends React.Component {
this.setState({previewLayer: L.geoJSON(geojson, { this.setState({previewLayer: L.geoJSON(geojson, {
onEachFeature: (feature, layer) => { onEachFeature: (feature, layer) => {
if (feature.properties && feature.properties.level !== undefined) { 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 => { style: feature => {
@ -155,17 +201,22 @@ 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) => { generateContours = (data, loadingProp, isPreview) => {
this.setState({[loadingProp]: true, error: ""}); this.setState({[loadingProp]: true, error: ""});
const taskId = this.state.task.id; 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({ this.generateReq = $.ajax({
type: 'POST', type: 'POST',
@ -222,11 +273,15 @@ export default class ContoursPanel extends React.Component {
const { loading, task, layers, error, permanentError, interval, customInterval, layer, const { loading, task, layers, error, permanentError, interval, customInterval, layer,
epsg, customEpsg, exportLoading, epsg, customEpsg, exportLoading,
simplify, customSimplify, simplify, customSimplify,
previewLoading, previewLayer } = this.state; previewLoading, previewLayer, unitSystem } = this.state;
const intervalValues = [0.25, 0.5, 1, 1.5, 2]; 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}, const simplifyValues = [{label: _('Do not simplify'), value: 0},
{label: _('Normal'), value: 0.2}, {label: _('Normal'), value: unitSystem === "metric" ? 0.2 : 0.5},
{label: _('Aggressive'), value: 1}]; {label: _('Aggressive'), value: unitSystem === "metric" ? 1 : 4}];
const disabled = (interval === "custom" && !customInterval) || const disabled = (interval === "custom" && !customInterval) ||
(epsg === "custom" && !customEpsg) || (epsg === "custom" && !customEpsg) ||
@ -242,7 +297,7 @@ export default class ContoursPanel extends React.Component {
<label className="col-sm-3 control-label">{_("Interval:")}</label> <label className="col-sm-3 control-label">{_("Interval:")}</label>
<div className="col-sm-9 "> <div className="col-sm-9 ">
<select className="form-control" value={interval} onChange={this.handleSelectInterval}> <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> <option value="custom">{_("Custom")}</option>
</select> </select>
</div> </div>
@ -251,7 +306,7 @@ export default class ContoursPanel extends React.Component {
<div className="row form-group form-inline"> <div className="row form-group form-inline">
<label className="col-sm-3 control-label">{_("Value:")}</label> <label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 "> <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>
</div> </div>
: ""} : ""}
@ -269,7 +324,7 @@ export default class ContoursPanel extends React.Component {
<label className="col-sm-3 control-label">{_("Simplify:")}</label> <label className="col-sm-3 control-label">{_("Simplify:")}</label>
<div className="col-sm-9 "> <div className="col-sm-9 ">
<select className="form-control" value={simplify} onChange={this.handleSelectSimplify}> <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> <option value="custom">{_("Custom")}</option>
</select> </select>
</div> </div>
@ -278,7 +333,7 @@ export default class ContoursPanel extends React.Component {
<div className="row form-group form-inline"> <div className="row form-group form-inline">
<label className="col-sm-3 control-label">{_("Value:")}</label> <label className="col-sm-3 control-label">{_("Value:")}</label>
<div className="col-sm-9 "> <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>
</div> </div>
: ""} : ""}