Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a formatUnit method to the scaleWidget #1048

Merged
merged 1 commit into from
Jun 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- The mapInteractor cancelOnMove option can now take a movement threshold (#1058)
- GCS can now be specified in pointSearch, boxSearch, and polygonSearch (#1051)
- Added a track feature (#1040)
- Added a geo.gui.scaleWidget.formatUnit utility function (#1048)

## Version 0.19.8

Expand Down
89 changes: 9 additions & 80 deletions examples/measure/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,36 +49,6 @@ geo.osmLayer.tileSources['false'] = {
url: '/data/white.jpg',
attribution: ''
};
// Define a decimal miles unit
geo.gui.scaleWidget.unitsTable['decmiles'] = [
{unit: 'mi', scale: 1609.344}
];
// Define area units
var areaUnitsTable = {
si: [
{unit: 'nm\xB2', scale: 1e-18},
{unit: '\u03BCm\xB2', scale: 1e-12},
{unit: 'mm\xB2', scale: 1e-6},
{unit: 'm\xB2', scale: 1},
{unit: 'km\xB2', scale: 1e6}
],
hectares: [
{unit: 'ha', scale: 1e4}
],
decmiles: [
{unit: 'mi\xB2', scale: 1609.344 * 1609.344}
],
miles: [
{unit: 'in\xB2', scale: 0.0254 * 0.0254},
{unit: 'ft\xB2', scale: 0.3048 * 0.3048},
{unit: 'mi\xB2', scale: 1609.344 * 1609.344}
],
acres: [
{unit: 'pl', scale: 0.3048 * 0.3048 * 16.5 * 16.5},
{unit: 'rd', scale: 1609.344 * 1609.344 / 640 / 4},
{unit: 'ac', scale: 1609.344 * 1609.344 / 640}
]
};

var map, mapLayer, layer, fromButtonSelect, fromGeojsonUpdate;

Expand Down Expand Up @@ -418,47 +388,6 @@ function annotationArea(annotation) {
return Math.abs(area);
}

/**
* Format a unit with at least three siginfiicant figures.
*
* @param {number} val The value. A valance or area in base units.
* @param {string} unit The name of the unit system.
* @param {object[]} [table] The table of the unit system.
* @returns {string} A formatted string or `undefined`.
*/
function formatUnit(val, unit, table) {
if (val === undefined || val === null) {
return;
}
table = table || geo.gui.scaleWidget.unitsTable;
if (!table || !table[unit]) {
return;
}
unit = table[unit];
let pos;
for (pos = 0; pos < unit.length - 1; pos += 1) {
if (val < unit[pos + 1].scale) {
break;
}
}
unit = unit[pos];
val /= unit.scale;
let digits = Math.max(0, -Math.ceil(Math.log10(val)) + 3);
if (digits > 10) {
return;
}
let result = val.toFixed(digits);
if (digits) {
while (result.substr(result.length - 1) === '0') {
result = result.substr(0, result.length - 1);
}
if (result.substr(result.length - 1) === '.') {
result = result.substr(0, result.length - 1);
}
}
return result + ' ' + unit.unit;
}

/**
* Add the units to the name of the annotation if appropriate.
*
Expand All @@ -477,11 +406,11 @@ function modifyAnnotation(annotation) {
area = annotationArea(annotation);
var result = s_labelRecord();
if (result) {
dist = formatUnit(dist, query.distunit || 'decmiles');
dist = geo.gui.scaleWidget.formatUnit(dist, query.distunit || 'decmiles');
if (dist) {
result.text += ' - ' + dist;
}
area = formatUnit(area, query.areaunit || 'decmiles', areaUnitsTable);
area = geo.gui.scaleWidget.formatUnit(area, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
result.text += ' - ' + area;
}
Expand Down Expand Up @@ -527,20 +456,20 @@ function labelVertices(annotation, annotationIndex, labels) {
pts.forEach((p, i) => {
if (i) {
labels.push({
text: formatUnit(tally[i], query.distunit || 'decmiles') || '',
text: geo.gui.scaleWidget.formatUnit(tally[i], query.distunit || 'decmiles') || '',
position: p,
style: Object.assign({}, style, {offset: {x: 12, y: 0}, textAlign: 'left'})
});
}
if (i !== pts.length - 1) {
labels.push({
text: formatUnit(tally[tally.length - 1] - tally[i], query.distunit || 'decmiles') || '',
text: geo.gui.scaleWidget.formatUnit(tally[tally.length - 1] - tally[i], query.distunit || 'decmiles') || '',
position: p,
style: Object.assign({}, style, {offset: {x: -12, y: 0}, textAlign: 'right'})
});
let p1 = {x: (pts[i + 1].x + p.x) / 2, y: (pts[i + 1].y + p.y) / 2};
labels.push({
text: formatUnit(dist[i], query.distunit || 'decmiles') || '',
text: geo.gui.scaleWidget.formatUnit(dist[i], query.distunit || 'decmiles') || '',
position: p1,
style: Object.assign({}, style, {offset: {x: 10, y: 0}, textAlign: 'left'})
});
Expand Down Expand Up @@ -582,8 +511,8 @@ function handleAnnotationChange(evt) {
area = annotationArea(annotation);
if (dist) { totaldist += dist; }
if (area) { totalarea += area; }
dist = formatUnit(dist, query.distunit || 'decmiles') || '';
area = formatUnit(area, query.areaunit || 'decmiles', areaUnitsTable);
dist = geo.gui.scaleWidget.formatUnit(dist, query.distunit || 'decmiles') || '';
area = geo.gui.scaleWidget.formatUnit(area, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
dist = (dist ? dist + ' - ' : '') + area;
}
Expand All @@ -603,8 +532,8 @@ function handleAnnotationChange(evt) {
entry.find('.entry-dist').text(dist);
$('#annotationlist').append(entry);
});
let dist = formatUnit(totaldist || undefined, query.distunit || 'decmiles') || '';
let area = formatUnit(totalarea || undefined, query.areaunit || 'decmiles', areaUnitsTable);
let dist = geo.gui.scaleWidget.formatUnit(totaldist || undefined, query.distunit || 'decmiles') || '';
let area = geo.gui.scaleWidget.formatUnit(totalarea || undefined, query.areaunit || 'decmiles', geo.gui.scaleWidget.areaUnitsTable);
if (area) {
dist = (dist ? dist + ' - ' : '') + area;
}
Expand Down
123 changes: 120 additions & 3 deletions src/ui/scaleWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ require('./scaleWidget.styl');
* `mutliple`.
*/

/**
* For a unit table, the records are ordered smallest scale to largest scale.
* The smallest unit can be repeated to have different rounding behavior for
* values less than 1 and values greater than or equal to 1.
*
* @typedef {object} geo.gui.scaleWidget.unitTableRecord
* @property {string} unit The display name of the unit.
* @property {number} scale The size of the unit in base unit.
* @property {number} [basis=10] The number of units in the next greater unit
* if not a power of 10.
* @property {object[]} [multiples] A list of multiplier values to round to
* when rounding is used. The list should probably include a multiple of 1.
* Default is 1, 1.5, 2, 3, 5, 8.
* @property {number} multiples.multiple A factor to round to.
* @property {number} multiples.digit The number of digits to preserve when
* rounding.
*/

/**
* Create a new instance of class geo.gui.scaleWidget.
*
Expand Down Expand Up @@ -314,12 +332,21 @@ var scaleWidget = function (arg) {

inherit(scaleWidget, svgWidget);

/* The unitsTable has predefined unit sets. Each entry is an array that must
* be in ascending order. */
/**
* The unitsTable has predefined unit sets for a base unit of one meter. Each
* entry is an array that must be in ascending order. Use unicode in strings,
* not html entities. It makes it more reusable.
* @name unitsTable
* @property unitsTable {object} The key names are the names of unit systems,
* such as `si`.
* @property unitsTable.unit {geo.gui.scaleWidget.unitTableRecord[]} A list of
* units within the unit system from smallest to largest.
* @memberof geo.gui.scaleWidget
*/
scaleWidget.unitsTable = {
si: [
{unit: 'nm', scale: 1e-9},
{unit: '&mu;m', scale: 1e-6},
{unit: '\u03BCm', scale: 1e-6},
{unit: 'mm', scale: 0.001},
{unit: 'm', scale: 1},
{unit: 'km', scale: 1000}
Expand All @@ -346,8 +373,98 @@ scaleWidget.unitsTable = {
},
{unit: 'ft', scale: 0.3048},
{unit: 'mi', scale: 1609.344}
],
decmiles: [ // decimal miles
{unit: 'mi', scale: 1609.344}
]
};

/**
* The areaUnitsTable has predefined unit sets for a base unit of one square
* meter. Each entry is an array that must be in ascending order. This table
* can be passed to formatUnit.
* @name areaUnitsTable
* @property areaUnitsTable {object} The key names are the names of unit
* systems, such as `si`.
* @property areaUnitsTable.unit {geo.gui.scaleWidget.unitTableRecord[]} A list
* of units within the unit system from smallest to largest.
* @memberof geo.gui.scaleWidget
*/
scaleWidget.areaUnitsTable = {
si: [
{unit: 'nm\xB2', scale: 1e-18},
{unit: '\u03BCm\xB2', scale: 1e-12},
{unit: 'mm\xB2', scale: 1e-6},
{unit: 'm\xB2', scale: 1},
{unit: 'km\xB2', scale: 1e6}
],
hectares: [
{unit: 'ha', scale: 1e4}
],
decmiles: [ // decimal square miles
{unit: 'mi\xB2', scale: 1609.344 * 1609.344}
],
miles: [
{unit: 'in\xB2', scale: 0.0254 * 0.0254},
{unit: 'ft\xB2', scale: 0.3048 * 0.3048},
{unit: 'mi\xB2', scale: 1609.344 * 1609.344}
],
acres: [
{unit: 'pl', scale: 0.3048 * 0.3048 * 16.5 * 16.5},
{unit: 'rd', scale: 1609.344 * 1609.344 / 640 / 4},
{unit: 'ac', scale: 1609.344 * 1609.344 / 640}
]
};

/**
* Format a unit with a specified number of significant figures. Given a value
* in base units, such as meters, this will return a string with appropriate
* units. For instance, `formatUnit(0.345)` will return `345 mm`.
*
* @param {number} val The value. A length or area in base units. With the
* default unit table, this is in meters. With the `areaUnitsTable`, this
* is square meters.
* @param {string|object[]} [unit='si'] The name of the unit system or a unit
* table.
* @param {object} [table=unitTable] The table of the unit system. Ignored if
* `unit` is a unit table.
* @param {number} [digits=3] The minimum number of significant figures.
* @returns {string} A formatted string or `undefined`.
*/
scaleWidget.formatUnit = function (val, unit, table, digits) {
if (val === undefined || val === null) {
return;
}
if (!Array.isArray(unit)) {
table = table || scaleWidget.unitsTable;
if (!table || !table[unit || 'si']) {
return;
}
unit = table[unit || 'si'];
}
let pos;
for (pos = 0; pos < unit.length - 1; pos += 1) {
if (val < unit[pos + 1].scale) {
break;
}
}
unit = unit[pos];
val /= unit.scale;
digits = Math.max(0, -Math.ceil(Math.log10(val)) + (digits === undefined || digits < 0 ? 3 : digits));
if (digits > 10) {
return;
}
let result = val.toFixed(digits);
if (digits) {
while (result.substr(result.length - 1) === '0') {
result = result.substr(0, result.length - 1);
}
if (result.substr(result.length - 1) === '.') {
result = result.substr(0, result.length - 1);
}
}
return result + ' ' + unit.unit;
};

registerWidget('dom', 'scale', scaleWidget);
module.exports = scaleWidget;
2 changes: 1 addition & 1 deletion src/util/color.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ var colorUtils = {
* standard, `rgb` and `rgba` are aliases of each other, as are `hsl` and
* `hsla`.
* @name cssColorConversions
* @property cssColorConversions
* @property cssColorConversions {geo.util.cssColorConversionRecord[]}
* @memberof geo.util
*/
colorUtils.cssColorConversions = [{
Expand Down
26 changes: 25 additions & 1 deletion tests/cases/scaleWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe('geo.gui.scaleWidget', function () {
expect(result.pixels).toBe(150);
result = widget._scaleValue(1.6e-5, 200);
expect(result.value).toBe(1.5e-5);
expect(result.html).toBe('15 &mu;m');
expect(result.html).toBe('15 \u03BCm');
expect(widget._scaleValue(1, 200, 'miles').html).toBe('3 ft');
expect(widget._scaleValue(0.2, 200, 'miles').html).toBe('6 in');
expect(widget._scaleValue(0.01, 200, 'miles').html).toBe('0.3 in');
Expand Down Expand Up @@ -132,4 +132,28 @@ describe('geo.gui.scaleWidget', function () {
expect(widget.options('orientation')).toBe('right');
});
});

describe('Check class utility methods', function () {
it('formatUnit', function () {
var formatUnit = geo.gui.scaleWidget.formatUnit,
areaUnitsTable = geo.gui.scaleWidget.areaUnitsTable;
expect(formatUnit(3, 'si')).toBe('3 m');
expect(formatUnit(3e-4, 'si')).toBe('300 \u03BCm');
expect(formatUnit(3.4567, 'si')).toBe('3.46 m');
expect(formatUnit(3.4567)).toBe('3.46 m');
expect(formatUnit('3.4567', 'si')).toBe('3.46 m');
expect(formatUnit(3.4567, 'si', undefined, 2)).toBe('3.5 m');
expect(formatUnit(3.4567, 'si', undefined, 0)).toBe('3 m');
expect(formatUnit(3.4567, 'si', undefined, -2)).toBe('3.46 m');
expect(formatUnit(34567, 'si')).toBe('34.6 km');
expect(formatUnit(3.4567, 'si', areaUnitsTable)).toBe('3.46 m\xB2');
expect(formatUnit(null, 'si')).toBe(undefined);
expect(formatUnit(3e-100, 'si')).toBe(undefined);
expect(formatUnit(3.4567, 'nosuchunits')).toBe(undefined);
expect(formatUnit(3.4567, 'miles')).toBe('11.3 ft');
expect(formatUnit(3.4567e-3, 'miles')).toBe('0.136 in');
expect(formatUnit(3.4567e3, 'miles')).toBe('2.15 mi');
expect(formatUnit(3.4567e3, [{unit: 'NM', scale: 1852}])).toBe('1.87 NM');
});
});
});