admin管理员组

文章数量:1336563

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

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

Share Improve this question asked Nov 20, 2024 at 2:49 SchliemannSchliemann 551 silver badge6 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 3

Given: round half to even

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).

Required: round half up

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.

Try using this:

def round_to_grid_center(coordinate,grid_spacing=0.5 ):
        offset = grid_spacing / 2
        return round(((coordinate - offset) / grid_spacing)) * grid_spacing + offset

the offset grid_spacing / 2 to shift the coordinate so that the rounding happens around the grid center (ex., 0.25, 0.75 for a 0.5 degree grid).

we round to the nearest multiple of grid_spacing

finally, subtract the offset to return the coordinate to its correct center position

When you round (coordinate + offset) / grid_spacing, you ensure the rounding happens at the center of each grid cell. This way will handle all cases better, including those where the coordinate is exactly on a boundary (like 5.0, 5.5), without causing errors or inconsistences.

本文标签: pythonrounding coordinates to centre of grid squareStack Overflow