Skip to content

Commit

Permalink
Add a formatUnit method to the scaleWidget.
Browse files Browse the repository at this point in the history
Make the formatting units utility available.
  • Loading branch information
manthey committed Jun 1, 2020
1 parent 2136d8d commit bec2964
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 85 deletions.
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');
});
});
});

0 comments on commit bec2964

Please sign in to comment.