Skip to content

Commit

Permalink
Allow to prevent round-off errors, caused by formatNum (#7100)
Browse files Browse the repository at this point in the history
* GeoJSON.latLngToCoords: do not force default precision value

Rely on default value defined in `formatNum` itself.
Specify all related docs.
Document `precision` argument of `latLngsToCoords` function.

* Util.formatNum: allow to skip processing specifying `false` as precision

This is most necessary for cases where `formatNum` is called indirectly,
e.g. by GeoJSON functions.
  • Loading branch information
johnd0e committed Nov 3, 2021
1 parent 69f73ed commit cc9f327
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 22 deletions.
1 change: 1 addition & 0 deletions spec/suites/core/UtilSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('Util', function () {
expect(L.Util.formatNum(13.12325555, 3)).to.eql(13.123);
expect(L.Util.formatNum(13.12325555)).to.eql(13.123256);
expect(L.Util.formatNum(13.12325555, 0)).to.eql(13);
expect(L.Util.formatNum(13.12325555, false)).to.eql(13.12325555);
expect(isNaN(L.Util.formatNum(-7.993322e-10))).to.eql(false);
});
});
Expand Down
11 changes: 7 additions & 4 deletions src/core/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,13 @@ export function wrapNum(x, range, includeMax) {
// Returns a function which always returns `false`.
export function falseFn() { return false; }

// @function formatNum(num: Number, digits?: Number): Number
// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
export function formatNum(num, digits) {
var pow = Math.pow(10, (digits === undefined ? 6 : digits));
// @function formatNum(num: Number, precision?: Number|false): Number
// Returns the number `num` rounded with specified `precision`.
// The default `precision` value is 6 decimal places.
// `false` can be passed to skip any processing (can be useful to avoid round-off errors).
export function formatNum(num, precision) {
if (precision === false) { return num; }
var pow = Math.pow(10, precision === undefined ? 6 : precision);
return Math.round(num * pow) / pow;
}

Expand Down
32 changes: 14 additions & 18 deletions src/layer/GeoJSON.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,18 +253,19 @@ export function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
return latlngs;
}

// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
// @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
export function latLngToCoords(latlng, precision) {
precision = typeof precision === 'number' ? precision : 6;
return latlng.alt !== undefined ?
[Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision), Util.formatNum(latlng.alt, precision)] :
[Util.formatNum(latlng.lng, precision), Util.formatNum(latlng.lat, precision)];
}

// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
export function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
var coords = [];

Expand Down Expand Up @@ -312,25 +313,22 @@ var PointToGeoJSON = {

// @namespace Marker
// @section Other methods
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
Marker.include(PointToGeoJSON);

// @namespace CircleMarker
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
Circle.include(PointToGeoJSON);
CircleMarker.include(PointToGeoJSON);


// @namespace Polyline
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
Polyline.include({
toGeoJSON: function (precision) {
Expand All @@ -346,9 +344,8 @@ Polyline.include({
});

// @namespace Polygon
// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
Polygon.include({
toGeoJSON: function (precision) {
Expand Down Expand Up @@ -384,9 +381,8 @@ LayerGroup.include({
});
},

// @method toGeoJSON(precision?: Number): Object
// `precision` is the number of decimal places for coordinates.
// The default value is 6 places.
// @method toGeoJSON(precision?: Number|false): Object
// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
toGeoJSON: function (precision) {

Expand Down

0 comments on commit cc9f327

Please sign in to comment.