Fix #1951 by rounding all floats passed to Bounds.initialize() and
LonLat.initialize() to 14 significant figures. Added an OpenLayers.Util.toFloat() function, and changed the LonLat constructor and the Bounds constructor to truncate all float parameters to 14 significant figures to avoid numeric comparison errors caused by floating point precision loss. See the comments around the definition of OpenLayers.Util.DEFAULT_PRECISION for how it works. Also refactored Bounds.intersectBounds() for readability, and added a Bounds.touchesBounds() in the process. After tweaking the tests for Layer.SphericalMercator and Format.WKT (because they were expecting too many significant figures), all tests pass. git-svn-id: http://svn.openlayers.org/trunk/openlayers@9022 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -65,16 +65,16 @@ OpenLayers.Bounds = OpenLayers.Class({
|
||||
*/
|
||||
initialize: function(left, bottom, right, top) {
|
||||
if (left != null) {
|
||||
this.left = parseFloat(left);
|
||||
this.left = OpenLayers.Util.toFloat(left);
|
||||
}
|
||||
if (bottom != null) {
|
||||
this.bottom = parseFloat(bottom);
|
||||
this.bottom = OpenLayers.Util.toFloat(bottom);
|
||||
}
|
||||
if (right != null) {
|
||||
this.right = parseFloat(right);
|
||||
this.right = OpenLayers.Util.toFloat(right);
|
||||
}
|
||||
if (top != null) {
|
||||
this.top = parseFloat(top);
|
||||
this.top = OpenLayers.Util.toFloat(top);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -386,12 +386,18 @@ OpenLayers.Bounds = OpenLayers.Class({
|
||||
* bounds.
|
||||
*/
|
||||
contains:function(x, y, inclusive) {
|
||||
|
||||
//set default
|
||||
if (inclusive == null) {
|
||||
inclusive = true;
|
||||
}
|
||||
|
||||
if (x == null || y == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
x = OpenLayers.Util.toFloat(x);
|
||||
y = OpenLayers.Util.toFloat(y);
|
||||
|
||||
var contains = false;
|
||||
if (inclusive) {
|
||||
contains = ((x >= this.left) && (x <= this.right) &&
|
||||
@@ -403,6 +409,23 @@ OpenLayers.Bounds = OpenLayers.Class({
|
||||
return contains;
|
||||
},
|
||||
|
||||
/**
|
||||
* APIMethod: touchesBounds
|
||||
*
|
||||
* Parameters:
|
||||
* bounds - {<OpenLayers.Bounds>}
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} The passed-in OpenLayers.Bounds object touches this bounds
|
||||
* at a single edge, and therefore do not inclusively intersect.
|
||||
*/
|
||||
touchesBounds:function(bounds) {
|
||||
return (this.left == bounds.right ||
|
||||
this.right == bounds.left ||
|
||||
this.top == bounds.bottom ||
|
||||
this.bottom == bounds.top);
|
||||
},
|
||||
|
||||
/**
|
||||
* APIMethod: intersectsBounds
|
||||
*
|
||||
@@ -417,26 +440,19 @@ OpenLayers.Bounds = OpenLayers.Class({
|
||||
* partial.
|
||||
*/
|
||||
intersectsBounds:function(bounds, inclusive) {
|
||||
|
||||
if (inclusive == null) {
|
||||
inclusive = true;
|
||||
}
|
||||
var inBottom = (bounds.bottom == this.bottom && bounds.top == this.top) ?
|
||||
true : (((bounds.bottom > this.bottom) && (bounds.bottom < this.top)) ||
|
||||
((this.bottom > bounds.bottom) && (this.bottom < bounds.top)));
|
||||
var inTop = (bounds.bottom == this.bottom && bounds.top == this.top) ?
|
||||
true : (((bounds.top > this.bottom) && (bounds.top < this.top)) ||
|
||||
((this.top > bounds.bottom) && (this.top < bounds.top)));
|
||||
var inRight = (bounds.right == this.right && bounds.left == this.left) ?
|
||||
true : (((bounds.right > this.left) && (bounds.right < this.right)) ||
|
||||
((this.right > bounds.left) && (this.right < bounds.right)));
|
||||
var inLeft = (bounds.right == this.right && bounds.left == this.left) ?
|
||||
true : (((bounds.left > this.left) && (bounds.left < this.right)) ||
|
||||
((this.left > bounds.left) && (this.left < bounds.right)));
|
||||
|
||||
return (this.containsBounds(bounds, true, inclusive) ||
|
||||
bounds.containsBounds(this, true, inclusive) ||
|
||||
((inTop || inBottom ) && (inLeft || inRight )));
|
||||
var intersects = false;
|
||||
// if the two bounds only touch at an edge, and inclusive is false,
|
||||
// then the bounds don't *really* intersect.
|
||||
if (inclusive || !this.touchesBounds(bounds)) {
|
||||
// otherwise, if one of the boundaries even partially contains another,
|
||||
// inclusive of the edges, then they do intersect.
|
||||
intersects = this.containsBounds(bounds, true, true) ||
|
||||
bounds.containsBounds(this, true, true);
|
||||
}
|
||||
return intersects;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -453,34 +469,19 @@ OpenLayers.Bounds = OpenLayers.Class({
|
||||
* {Boolean} The passed-in bounds object is contained within this bounds.
|
||||
*/
|
||||
containsBounds:function(bounds, partial, inclusive) {
|
||||
|
||||
//set defaults
|
||||
if (partial == null) {
|
||||
partial = false;
|
||||
}
|
||||
if (inclusive == null) {
|
||||
inclusive = true;
|
||||
}
|
||||
var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
|
||||
var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
|
||||
var topLeft = this.contains(bounds.left, bounds.top, inclusive);
|
||||
var topRight = this.contains(bounds.right, bounds.top, inclusive);
|
||||
|
||||
var inLeft;
|
||||
var inTop;
|
||||
var inRight;
|
||||
var inBottom;
|
||||
|
||||
if (inclusive) {
|
||||
inLeft = (bounds.left >= this.left) && (bounds.left <= this.right);
|
||||
inTop = (bounds.top >= this.bottom) && (bounds.top <= this.top);
|
||||
inRight= (bounds.right >= this.left) && (bounds.right <= this.right);
|
||||
inBottom = (bounds.bottom >= this.bottom) && (bounds.bottom <= this.top);
|
||||
} else {
|
||||
inLeft = (bounds.left > this.left) && (bounds.left < this.right);
|
||||
inTop = (bounds.top > this.bottom) && (bounds.top < this.top);
|
||||
inRight= (bounds.right > this.left) && (bounds.right < this.right);
|
||||
inBottom = (bounds.bottom > this.bottom) && (bounds.bottom < this.top);
|
||||
}
|
||||
|
||||
return (partial) ? (inTop || inBottom ) && (inLeft || inRight )
|
||||
: (inTop && inLeft && inBottom && inRight);
|
||||
return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
|
||||
: (bottomLeft && bottomRight && topLeft && topRight);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,8 +37,8 @@ OpenLayers.LonLat = OpenLayers.Class({
|
||||
* it will be the y coordinate of the map location in your map units.
|
||||
*/
|
||||
initialize: function(lon, lat) {
|
||||
this.lon = parseFloat(lon);
|
||||
this.lat = parseFloat(lat);
|
||||
this.lon = OpenLayers.Util.toFloat(lon);
|
||||
this.lat = OpenLayers.Util.toFloat(lat);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -796,6 +796,51 @@ OpenLayers.Util.mouseLeft = function (evt, div) {
|
||||
return (target != div);
|
||||
};
|
||||
|
||||
/**
|
||||
* Property: precision
|
||||
* {Number} The number of significant digits to retain to avoid
|
||||
* floating point precision errors.
|
||||
*
|
||||
* We use 14 as a "safe" default because, although IEEE 754 double floats
|
||||
* (standard on most modern operating systems) support up to about 16
|
||||
* significant digits, 14 significant digits are sufficient to represent
|
||||
* sub-millimeter accuracy in any coordinate system that anyone is likely to
|
||||
* use with OpenLayers.
|
||||
*
|
||||
* If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
|
||||
* of OpenLayers <2.8 is preserved. Be aware that this will cause problems
|
||||
* with certain projections, e.g. spherical Mercator.
|
||||
*
|
||||
*/
|
||||
OpenLayers.Util.DEFAULT_PRECISION = 14;
|
||||
|
||||
/**
|
||||
* Function: toFloat
|
||||
* Convenience method to cast an object to a Number, rounded to the
|
||||
* desired floating point precision.
|
||||
*
|
||||
* Parameters:
|
||||
* number - {Number} The number to cast and round.
|
||||
* precision - {Number} An integer suitable for use with
|
||||
* Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
|
||||
* If set to 0, no rounding is performed.
|
||||
*
|
||||
* Returns:
|
||||
* {Number} The cast, rounded number.
|
||||
*/
|
||||
OpenLayers.Util.toFloat = function (number, precision) {
|
||||
if (precision == null) {
|
||||
precision = OpenLayers.Util.DEFAULT_PRECISION;
|
||||
}
|
||||
var number;
|
||||
if (precision == 0) {
|
||||
number = parseFloat(number);
|
||||
} else {
|
||||
number = parseFloat(parseFloat(number).toPrecision(precision))
|
||||
}
|
||||
return number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function: rad
|
||||
*
|
||||
@@ -1071,7 +1116,7 @@ OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
|
||||
OpenLayers.DOTS_PER_INCH = 72;
|
||||
|
||||
/**
|
||||
* Function: normalzeScale
|
||||
* Function: normalizeScale
|
||||
*
|
||||
* Parameters:
|
||||
* scale - {float}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<script type="text/javascript">
|
||||
var bounds;
|
||||
function test_Bounds_constructor (t) {
|
||||
t.plan( 17 );
|
||||
t.plan( 21 );
|
||||
|
||||
bounds = new OpenLayers.Bounds();
|
||||
t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
|
||||
@@ -36,6 +36,14 @@
|
||||
var center = new OpenLayers.LonLat(5,3);
|
||||
var boundsCenter = bounds.getCenterLonLat();
|
||||
t.ok( boundsCenter.equals(center), "bounds.getCenterLonLat() has correct value" );
|
||||
|
||||
// This is an actual use case with Mercator projection at global scale
|
||||
bounds = new OpenLayers.Bounds(-40075016.67999999,-20037508.339999992,
|
||||
40075016.67999999,20037508.339999992);
|
||||
t.eq( bounds.left, -40075016.68, "bounds.left adjusted for floating precision");
|
||||
t.eq( bounds.bottom, -20037508.34, "bounds.bottom adjusted for floating precision");
|
||||
t.eq( bounds.right, 40075016.68, "bounds.right adjusted for floating precision");
|
||||
t.eq( bounds.top, 20037508.34, "bounds.top adjusted for floating precision");
|
||||
}
|
||||
|
||||
function test_Bounds_constructorFromStrings(t) {
|
||||
@@ -91,13 +99,13 @@
|
||||
var ring = poly.components[0];
|
||||
t.eq(ring.components.length, 5,
|
||||
"four sided polygon created");
|
||||
t.eq(ring.components[0].x, minx,
|
||||
t.eq(ring.components[0].x, OpenLayers.Util.toFloat(minx),
|
||||
"bounds left preserved");
|
||||
t.eq(ring.components[0].y, miny,
|
||||
t.eq(ring.components[0].y, OpenLayers.Util.toFloat(miny),
|
||||
"bounds bottom preserved");
|
||||
t.eq(ring.components[2].x, maxx,
|
||||
t.eq(ring.components[2].x, OpenLayers.Util.toFloat(maxx),
|
||||
"bounds left preserved");
|
||||
t.eq(ring.components[2].y, maxy,
|
||||
t.eq(ring.components[2].y, OpenLayers.Util.toFloat(maxy),
|
||||
"bounds bottom preserved");
|
||||
}
|
||||
|
||||
@@ -157,7 +165,7 @@
|
||||
}
|
||||
|
||||
function test_Bounds_intersectsBounds(t) {
|
||||
t.plan( 17 );
|
||||
t.plan( 19 );
|
||||
|
||||
var aBounds = new OpenLayers.Bounds(-180, -90, 180, 90);
|
||||
|
||||
@@ -194,6 +202,13 @@
|
||||
t.eq( aBounds.intersectsBounds(bBounds, true), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + "), inclusive is true" );
|
||||
t.eq( aBounds.intersectsBounds(bBounds, false), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + "), inclusive is false" );
|
||||
|
||||
// This is an actual use case with Mercator tiles at global scale
|
||||
var merc_aBounds = new OpenLayers.Bounds(-40075016.67999999,20037508.339999992,
|
||||
-20037508.339999992,40075016.67999999),
|
||||
merc_bBounds = new OpenLayers.Bounds(-20037508.34,-20037508.34,
|
||||
20037508.34,20037508.34);
|
||||
t.eq( merc_aBounds.intersectsBounds(merc_bBounds, true), true, "intersect shouldn't fall prey to floating point errors, inclusive is true");
|
||||
t.eq( merc_aBounds.intersectsBounds(merc_bBounds, false), false, "intersect shouldn't fall prey to floating point errors, inclusive is false");
|
||||
}
|
||||
|
||||
function test_Bounds_containsBounds(t) {
|
||||
|
||||
@@ -6,12 +6,17 @@
|
||||
var lonlat;
|
||||
|
||||
function test_LonLat_constructor (t) {
|
||||
t.plan( 4 );
|
||||
t.plan( 6 );
|
||||
lonlat = new OpenLayers.LonLat(6, 5);
|
||||
t.ok( lonlat instanceof OpenLayers.LonLat, "new OpenLayers.LonLat returns LonLat object" );
|
||||
t.eq( lonlat.CLASS_NAME, "OpenLayers.LonLat", "lonlat.CLASS_NAME is set correctly");
|
||||
t.eq( lonlat.lon, 6, "lonlat.lon is set correctly");
|
||||
t.eq( lonlat.lat, 5, "lonlat.lat is set correctly");
|
||||
|
||||
// possible global Mercator projection values
|
||||
lonlat = new OpenLayers.LonLat(20037508.33999999, -20037508.33999999);
|
||||
t.eq( lonlat.lon, 20037508.34, "lonlat.lon rounds correctly");
|
||||
t.eq( lonlat.lat, -20037508.34, "lonlat.lat rounds correctly");
|
||||
}
|
||||
|
||||
function test_LonLat_constructorFromStrings (t) {
|
||||
|
||||
@@ -226,7 +226,7 @@
|
||||
|
||||
var points = {
|
||||
src: new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-87.9, 41.9)),
|
||||
dest: new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-9784983.239366667, 5146011.678566458))
|
||||
dest: new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-9784983.2393667, 5146011.6785665))
|
||||
};
|
||||
|
||||
var format = new OpenLayers.Format.WKT({
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
t.eq(sw.lon, -180, "Southwest lon correct");
|
||||
t.eq(ne.lon, 180, "Northeast lon correct");
|
||||
|
||||
t.eq(sw.lat, -85.05112877980659, "Southwest lat correct");
|
||||
t.eq(ne.lat, 85.05112877980660, "Northeast lat correct");
|
||||
t.eq(sw.lat, -85.051128779807, "Southwest lat correct");
|
||||
t.eq(ne.lat, 85.051128779807, "Northeast lat correct");
|
||||
}
|
||||
|
||||
function strToFixed(str, dig) {
|
||||
|
||||
@@ -806,6 +806,16 @@
|
||||
|
||||
}
|
||||
|
||||
function test_toFloat(t) {
|
||||
t.plan(2);
|
||||
// actual possible computed Mercator tile coordinates, more or less
|
||||
var a1=40075016.67999999, b1=-20037508.33999999,
|
||||
a2=40075016.68, b2=-20037508.34;
|
||||
t.eq(OpenLayers.Util.toFloat(a1), OpenLayers.Util.toFloat(a2),
|
||||
"toFloat rounds large floats correctly #1");
|
||||
t.eq(OpenLayers.Util.toFloat(b1), OpenLayers.Util.toFloat(b2),
|
||||
"toFloat rounds large floats correctly #2");
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user