Skip to content

⚡️ Speed up method GlobalMercator.TileLatLonBounds by 45%#18

Open
codeflash-ai[bot] wants to merge 1 commit intomasterfrom
codeflash/optimize-GlobalMercator.TileLatLonBounds-mh4yppof
Open

⚡️ Speed up method GlobalMercator.TileLatLonBounds by 45%#18
codeflash-ai[bot] wants to merge 1 commit intomasterfrom
codeflash/optimize-GlobalMercator.TileLatLonBounds-mh4yppof

Conversation

@codeflash-ai
Copy link
Copy Markdown

@codeflash-ai codeflash-ai Bot commented Oct 24, 2025

📄 45% (0.45x) speedup for GlobalMercator.TileLatLonBounds in opendm/tiles/gdal2tiles.py

⏱️ Runtime : 4.96 milliseconds 3.41 milliseconds (best of 276 runs)

📝 Explanation and details

The optimized code achieves a 45% speedup through two key optimizations:

1. Pre-computed constants in __init__:
The original MetersToLatLon method recalculated expensive values on every call:

  • inv_originShift = 180.0 / self.originShift (division operation)
  • pi = math.pi (attribute lookup)
  • Multiple pi-based calculations in the latitude conversion

The optimized version pre-computes these as instance variables (_inv_originShift, _pi, _deg_per_rad, _pi_per_2) in __init__, eliminating ~26% of the execution time in MetersToLatLon (from 8.29ms to 6.13ms in profiling).

2. Inlined PixelsToMeters logic in TileBounds:
The original TileBounds made two separate calls to PixelsToMeters, each involving function call overhead and repeated calculations. The optimized version inlines this logic directly:

res = self.initialResolution / (2 ** zoom)  # Calculate once
minx = tx * ts * res - self.originShift     # Direct calculation

This reduces TileBounds execution time by ~42% (from 9.81ms to 5.72ms).

Performance benefits scale with usage patterns:

  • Single tile calculations see 26-47% speedups
  • Bulk operations (like the 1000-tile test) see 46% improvements
  • All zoom levels and tile sizes benefit consistently

The optimizations are particularly effective for applications that compute many tile bounds, such as tile servers or mapping applications processing large tile grids.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 2668 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import math

# imports
import pytest
from opendm.tiles.gdal2tiles import GlobalMercator

# unit tests

@pytest.fixture
def mercator():
    # Use default tile size
    return GlobalMercator()

# Helper function for approximate float comparison
def approx_tuple(t1, t2, rel=1e-9, abs=1e-9):
    for a, b in zip(t1, t2):
        if not math.isclose(a, b, rel_tol=rel, abs_tol=abs):
            return False
    return True

# ---- Basic Test Cases ----

def test_tilelatlonbounds_zoom0_origin_tile(mercator):
    # The only tile at zoom 0 should cover the entire world
    codeflash_output = mercator.TileLatLonBounds(0, 0, 0); bounds = codeflash_output # 4.04μs -> 2.99μs (34.9% faster)
    # The bounds should be approximately (-85.05112878, -180, 85.05112878, 180)
    expected = (-85.05112878, -180.0, 85.05112878, 180.0)

def test_tilelatlonbounds_zoom1_tiles(mercator):
    # There are 2x2 tiles at zoom=1
    # Test lower left tile (0,0)
    codeflash_output = mercator.TileLatLonBounds(0, 0, 1); bounds = codeflash_output # 4.50μs -> 3.47μs (29.6% faster)
    expected = (-85.05112878, -180.0, 0.0, 0.0)

    # Test upper right tile (1,1)
    codeflash_output = mercator.TileLatLonBounds(1, 1, 1); bounds = codeflash_output # 2.52μs -> 1.86μs (35.1% faster)
    expected = (0.0, 0.0, 85.05112878, 180.0)

def test_tilelatlonbounds_zoom2_center_tile(mercator):
    # Center tile at zoom=2 (tile 1,1)
    codeflash_output = mercator.TileLatLonBounds(1, 1, 2); bounds = codeflash_output # 4.37μs -> 3.32μs (31.5% faster)
    # The bounds should be roughly (-42.0, -90.0, 0.0, 0.0)
    expected = (-41.979, -90.0, 0.0, 0.0)

def test_tilelatlonbounds_zoom3_specific_tile(mercator):
    # Test tile (4,2) at zoom=3
    codeflash_output = mercator.TileLatLonBounds(4, 2, 3); bounds = codeflash_output # 4.58μs -> 3.18μs (44.2% faster)

# ---- Edge Test Cases ----

def test_tilelatlonbounds_invalid_negative_tile(mercator):
    # Negative tile indices should produce bounds but may be outside world
    codeflash_output = mercator.TileLatLonBounds(-1, -1, 2); bounds = codeflash_output # 4.66μs -> 3.31μs (40.9% faster)

def test_tilelatlonbounds_large_tile_indices(mercator):
    # Tile indices beyond the normal range (e.g., tx=10, ty=10 at zoom=2)
    codeflash_output = mercator.TileLatLonBounds(10, 10, 2); bounds = codeflash_output # 4.37μs -> 3.15μs (38.4% faster)

def test_tilelatlonbounds_max_latitude(mercator):
    # Tile at the top edge at max zoom, should not exceed max latitude
    zoom = 5
    ty = (2 ** zoom) - 1
    tx = 0
    codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 4.47μs -> 3.27μs (36.5% faster)

def test_tilelatlonbounds_min_latitude(mercator):
    # Tile at the bottom edge at max zoom, should not be below min latitude
    zoom = 5
    ty = 0
    tx = 0
    codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 4.46μs -> 3.30μs (35.2% faster)

def test_tilelatlonbounds_tile_size_variation():
    # Use a non-default tile size
    merc = GlobalMercator(tileSize=512)
    codeflash_output = merc.TileLatLonBounds(0, 0, 0); bounds = codeflash_output # 4.25μs -> 2.89μs (46.8% faster)
    # Bounds should still cover the whole world
    expected = (-85.05112878, -180.0, 85.05112878, 180.0)

def test_tilelatlonbounds_float_tile_indices(mercator):
    # Float tile indices should be handled (Python allows float, but meaning is unclear)
    # Should produce bounds, but not standard tiles
    codeflash_output = mercator.TileLatLonBounds(0.5, 0.5, 1); bounds = codeflash_output # 4.64μs -> 3.48μs (33.3% faster)
    # Should be between the bounds of (0,0) and (1,1)
    codeflash_output = mercator.TileLatLonBounds(0, 0, 1); bounds00 = codeflash_output # 2.84μs -> 1.97μs (43.7% faster)
    codeflash_output = mercator.TileLatLonBounds(1, 1, 1); bounds11 = codeflash_output # 2.27μs -> 1.61μs (41.0% faster)

def test_tilelatlonbounds_extreme_zoom_levels(mercator):
    # Test at very high zoom level (e.g., 22)
    zoom = 22
    tx, ty = 0, 0
    codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 4.54μs -> 3.35μs (35.7% faster)

def test_tilelatlonbounds_zero_zoom_large_tile_indices(mercator):
    # At zoom 0, there is only one tile, but test with large indices
    codeflash_output = mercator.TileLatLonBounds(100, 100, 0); bounds = codeflash_output # 4.10μs -> 3.11μs (31.9% faster)

# ---- Large Scale Test Cases ----

def test_tilelatlonbounds_many_tiles_at_zoom_4(mercator):
    # At zoom 4, there are 16x16 tiles
    zoom = 4
    tile_count = 16
    for tx in range(tile_count):
        for ty in range(tile_count):
            codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output

def test_tilelatlonbounds_tile_grid_consistency(mercator):
    # For a grid at zoom=3, adjacent tiles should have contiguous bounds
    zoom = 3
    tx, ty = 2, 5
    codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 4.29μs -> 3.29μs (30.7% faster)
    codeflash_output = mercator.TileLatLonBounds(tx+1, ty, zoom); bounds_right = codeflash_output # 2.22μs -> 1.62μs (37.1% faster)
    codeflash_output = mercator.TileLatLonBounds(tx, ty+1, zoom); bounds_top = codeflash_output # 1.94μs -> 1.43μs (36.3% faster)

def test_tilelatlonbounds_all_tiles_zoom_5(mercator):
    # Test all tiles at zoom 5 (32x32 = 1024 tiles)
    zoom = 5
    tile_count = 32
    for tx in range(tile_count):
        for ty in range(tile_count):
            codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output

def test_tilelatlonbounds_performance_large_tile_grid():
    # Performance test: compute bounds for 1000 tiles at zoom 10 (no asserts, just run)
    merc = GlobalMercator()
    zoom = 10
    for tx in range(1000):
        codeflash_output = merc.TileLatLonBounds(tx, 0, zoom); bounds = codeflash_output # 1.85ms -> 1.26ms (46.2% faster)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import math

# imports
import pytest  # used for our unit tests
from opendm.tiles.gdal2tiles import GlobalMercator

# unit tests

# Helper function to compare bounds with a tolerance
def bounds_close(a, b, tol=1e-7):
    return all(abs(x - y) < tol for x, y in zip(a, b))

@pytest.fixture
def mercator():
    # Default tile size
    return GlobalMercator()

# --- Basic Test Cases ---

def test_tilelatlonbounds_zoom0_origin_tile(mercator):
    # At zoom=0, only one tile (0,0). It should cover the whole world.
    codeflash_output = mercator.TileLatLonBounds(0, 0, 0); bounds = codeflash_output # 4.81μs -> 3.81μs (26.4% faster)
    # Should roughly be (-85.05112878, -180, 85.05112878, 180)
    expected = (-85.0511287798066, -180.0, 85.0511287798066, 180.0)

def test_tilelatlonbounds_zoom1_four_tiles(mercator):
    # At zoom=1, there are 2x2 tiles. Test each.
    # Tile (0,0)
    codeflash_output = mercator.TileLatLonBounds(0, 0, 1); bounds = codeflash_output # 4.65μs -> 3.32μs (39.8% faster)
    expected = (-85.0511287798066, -180.0, 0.0, 0.0)
    # Tile (1,0)
    codeflash_output = mercator.TileLatLonBounds(1, 0, 1); bounds = codeflash_output # 2.38μs -> 1.77μs (34.1% faster)
    expected = (-85.0511287798066, 0.0, 0.0, 180.0)
    # Tile (0,1)
    codeflash_output = mercator.TileLatLonBounds(0, 1, 1); bounds = codeflash_output # 2.31μs -> 1.56μs (47.7% faster)
    expected = (0.0, -180.0, 85.0511287798066, 0.0)
    # Tile (1,1)
    codeflash_output = mercator.TileLatLonBounds(1, 1, 1); bounds = codeflash_output # 2.01μs -> 1.36μs (47.3% faster)
    expected = (0.0, 0.0, 85.0511287798066, 180.0)

def test_tilelatlonbounds_zoom2_corner_tiles(mercator):
    # At zoom=2, test corners
    # Tile (0,0): bottom left
    codeflash_output = mercator.TileLatLonBounds(0, 0, 2); bounds = codeflash_output # 4.41μs -> 3.22μs (37.0% faster)
    expected = (-85.0511287798066, -180.0, -66.51326044311186, -90.0)
    # Tile (3,3): top right
    codeflash_output = mercator.TileLatLonBounds(3, 3, 2); bounds = codeflash_output # 2.49μs -> 1.80μs (37.9% faster)
    expected = (66.51326044311186, 90.0, 85.0511287798066, 180.0)

def test_tilelatlonbounds_middle_tile(mercator):
    # At zoom=2, tile (1,1) should be in the middle of the map
    codeflash_output = mercator.TileLatLonBounds(1, 1, 2); bounds = codeflash_output # 4.25μs -> 3.15μs (34.8% faster)
    expected = (-22.162034980462543, -45.0, 22.162034980462543, 0.0)

# --- Edge Test Cases ---

def test_tilelatlonbounds_max_zoom_tile(mercator):
    # Test a tile at a high zoom level (edge of typical usage)
    zoom = 22
    tx, ty = 0, 0
    codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 4.68μs -> 3.31μs (41.3% faster)
    # Should be very small, near lower left corner
    minLat, minLon, maxLat, maxLon = bounds

def test_tilelatlonbounds_negative_tile_indices(mercator):
    # Negative tile indices should give bounds outside the world
    codeflash_output = mercator.TileLatLonBounds(-1, -1, 2); bounds = codeflash_output # 4.54μs -> 3.37μs (34.6% faster)
    minLat, minLon, maxLat, maxLon = bounds

def test_tilelatlonbounds_large_tile_indices(mercator):
    # Tile indices beyond the range should give bounds outside the world
    codeflash_output = mercator.TileLatLonBounds(5, 5, 2); bounds = codeflash_output # 4.58μs -> 3.23μs (41.4% faster)
    minLat, minLon, maxLat, maxLon = bounds


def test_tilelatlonbounds_custom_tile_size():
    # Custom tile size should change the bounds
    mercator = GlobalMercator(tileSize=512)
    codeflash_output = mercator.TileLatLonBounds(0, 0, 0); bounds = codeflash_output # 6.09μs -> 4.57μs (33.3% faster)
    # Should still cover the whole world
    expected = (-85.0511287798066, -180.0, 85.0511287798066, 180.0)

def test_tilelatlonbounds_invalid_input_types(mercator):
    # Non-integer tile indices should raise error
    with pytest.raises(TypeError):
        mercator.TileLatLonBounds('a', 0, 2) # 2.73μs -> 2.31μs (18.2% faster)
    with pytest.raises(TypeError):
        mercator.TileLatLonBounds(0, None, 2) # 1.70μs -> 2.02μs (16.1% slower)

# --- Large Scale Test Cases ---

def test_tilelatlonbounds_many_tiles_at_zoom4(mercator):
    # At zoom=4, there are 16x16 tiles. Test all tiles for monotonicity and bounds.
    zoom = 4
    num_tiles = 2**zoom
    prev_bounds = None
    for tx in range(num_tiles):
        for ty in range(num_tiles):
            codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output
            minLat, minLon, maxLat, maxLon = bounds
            # No overlap with previous tile in same row
            if prev_bounds is not None and tx > 0:
                pass
        prev_bounds = bounds

def test_tilelatlonbounds_full_world_coverage(mercator):
    # At zoom=3, all tiles together should cover the world
    zoom = 3
    num_tiles = 2**zoom
    minLat = 90.0
    minLon = 180.0
    maxLat = -90.0
    maxLon = -180.0
    for tx in range(num_tiles):
        for ty in range(num_tiles):
            codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output
            minLat = min(minLat, bounds[0])
            minLon = min(minLon, bounds[1])
            maxLat = max(maxLat, bounds[2])
            maxLon = max(maxLon, bounds[3])

def test_tilelatlonbounds_performance_large_zoom():
    # Performance test: compute bounds for all tiles at zoom=9 (512x512 = 262144 tiles)
    mercator = GlobalMercator()
    zoom = 9
    num_tiles = 2**zoom
    count = 0
    # Only test the corners and center to keep runtime reasonable
    indices = [
        (0, 0),
        (num_tiles-1, 0),
        (0, num_tiles-1),
        (num_tiles-1, num_tiles-1),
        (num_tiles//2, num_tiles//2)
    ]
    for tx, ty in indices:
        codeflash_output = mercator.TileLatLonBounds(tx, ty, zoom); bounds = codeflash_output # 12.8μs -> 9.18μs (39.9% faster)
        count += 1

# --- Additional Edge Cases ---

def test_tilelatlonbounds_zero_tile_size():
    # Tile size 0 should raise error
    with pytest.raises(ZeroDivisionError):
        GlobalMercator(tileSize=0)

def test_tilelatlonbounds_large_tile_size():
    # Very large tile size should still work
    mercator = GlobalMercator(tileSize=1024)
    codeflash_output = mercator.TileLatLonBounds(0, 0, 0); bounds = codeflash_output # 4.05μs -> 3.06μs (32.3% faster)
    expected = (-85.0511287798066, -180.0, 85.0511287798066, 180.0)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-GlobalMercator.TileLatLonBounds-mh4yppof and push.

Codeflash

The optimized code achieves a **45% speedup** through two key optimizations:

**1. Pre-computed constants in `__init__`:**
The original `MetersToLatLon` method recalculated expensive values on every call:
- `inv_originShift = 180.0 / self.originShift` (division operation)
- `pi = math.pi` (attribute lookup)
- Multiple pi-based calculations in the latitude conversion

The optimized version pre-computes these as instance variables (`_inv_originShift`, `_pi`, `_deg_per_rad`, `_pi_per_2`) in `__init__`, eliminating ~26% of the execution time in `MetersToLatLon` (from 8.29ms to 6.13ms in profiling).

**2. Inlined `PixelsToMeters` logic in `TileBounds`:**
The original `TileBounds` made two separate calls to `PixelsToMeters`, each involving function call overhead and repeated calculations. The optimized version inlines this logic directly:
```python
res = self.initialResolution / (2 ** zoom)  # Calculate once
minx = tx * ts * res - self.originShift     # Direct calculation
```
This reduces `TileBounds` execution time by ~42% (from 9.81ms to 5.72ms).

**Performance benefits scale with usage patterns:**
- Single tile calculations see 26-47% speedups
- Bulk operations (like the 1000-tile test) see 46% improvements
- All zoom levels and tile sizes benefit consistently

The optimizations are particularly effective for applications that compute many tile bounds, such as tile servers or mapping applications processing large tile grids.
@codeflash-ai codeflash-ai Bot requested a review from mashraf-222 October 24, 2025 14:44
@codeflash-ai codeflash-ai Bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash labels Oct 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: High Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants