Im currently trying to collect some weather data from an API, and to reduce the amount of API calls im trying to batch the calls on 0.5degree longitude and latitude chunks due to its resolution. I had this code
def round_to_grid_center(coordinate,grid_spacing=0.5 ):
offset = grid_spacing / 2
return round(((coordinate - offset) / grid_spacing)) * grid_spacing + offset
but this function rounded values at 0.5 down to 0.25 instead of up to 0.75, so i added this fix. It works for me, but I'm sure there is a better more efficient method to round the coordinates to their closest grid square centre. Please let me know!
def round_to_grid_center(coordinate,grid_spacing=0.5 ):
#temp fix for round down error
if ((coordinate % 0.5)== 0 and (coordinate % 1 )!= 0):
offset = grid_spacing / 2
return round(((coordinate + 0.01 - offset) / grid_spacing)) * grid_spacing + offset
else:
offset = grid_spacing / 2
return round(((coordinate - offset) / grid_spacing)) * grid_spacing + offset
The round()
function uses "round half to even" rounding mode, as mentioned in the Built-in Types doc, section Numeric types (emphasis by me):
Operation Result round(x[, n])
x rounded to n digits, rounding half to even. If n is omitted, it defaults to 0.
"Rounding half to even" means that a floating point number with a decimal part of .5 is rounded towards the closest even integer rather than the closest greater integer. For example, both round(1.5)
and round(2.5)
will produce 2
. In entry 5 (coordinate=38.5
, grid_spacing=0.5
, offset=0.25
), you will consequently get round((38.5-0.25)/0.5))
= round(76.5)
= 76
, and thus a rounded-down result for the part of your calculation before spacing and offset correction.
The Wikipedia article on rounding provides as motivation for this rounding mode:
This function minimizes the expected error when summing over rounded figures, even when the inputs are mostly positive or mostly negative, provided they are neither mostly even nor mostly odd.
If one needs further convincing that this rounding mode makes sense, one might want to have a look at the very detailed answer to this question ("rounding half to even" is called "banker's rounding" there).
In any case, what you want is round half up instead. You can follow the answers to this question for potential solutions, e.g. rather than using round(x)
, you could use int(x + .5)
or float(Decimal(x).to_integral_value(rounding=ROUND_HALF_UP))
.
Altogether, this could look as follows:
from decimal import Decimal, ROUND_HALF_UP
values = [(33.87, 151.21), (33.85, 151.22), ( 38.75, 149.85),
(35.15, 150.85), (38.50, 149.87), (-38.50, 149.95)]
def round_to_grid_center(coordinate, grid_spacing=0.5):
offset = grid_spacing / 2
return round((coordinate - offset) / grid_spacing) * grid_spacing + offset
def round_with_int(coordinate, grid_spacing=0.5):
offset = grid_spacing / 2
return int(.5 + ((coordinate - offset) / grid_spacing)) * grid_spacing + offset
def round_with_decimal(coordinate, grid_spacing=0.5):
offset = grid_spacing / 2
return float(Decimal((coordinate - offset) / grid_spacing).to_integral_value(rounding=ROUND_HALF_UP)) * grid_spacing + offset
for round_current in [round_to_grid_center, round_with_int, round_with_decimal]:
print(f"\n{round_current.__name__}():")
for i, (v1, v2) in enumerate(values):
print(f"{i+1}: {v1}→{round_current(v1)}, {v2}→{round_current(v2)}")
Which prints:
round_to_grid_center():
1: 33.87→33.75, 151.21→151.25
2: 33.85→33.75, 151.22→151.25
3: 38.75→38.75, 149.85→149.75
4: 35.15→35.25, 150.85→150.75
5: 38.5→38.25, 149.87→149.75
6: -38.5→-38.75, 149.95→149.75
round_with_int():
1: 33.87→33.75, 151.21→151.25
2: 33.85→33.75, 151.22→151.25
3: 38.75→38.75, 149.85→149.75
4: 35.15→35.25, 150.85→150.75
5: 38.5→38.75, 149.87→149.75
6: -38.5→-38.25, 149.95→149.75
round_with_decimal():
1: 33.87→33.75, 151.21→151.25
2: 33.85→33.75, 151.22→151.25
3: 38.75→38.75, 149.85→149.75
4: 35.15→35.25, 150.85→150.75
5: 38.5→38.75, 149.87→149.75
6: -38.5→-38.75, 149.95→149.75
Note how the values differ for negative numbers though (which I included as entry 6): with int()
, "up" means "towards positive infinity"; with Decimal
, "up" means "away from zero".
Last but not least – be aware of numerical imprecision in floating point representation and arithmetic: depending on the size of coordinate
and the value of grid_spacing
, round-off error may lead to unexpected results.