cartopy icon indicating copy to clipboard operation
cartopy copied to clipboard

Some cartopy features are missing when using set_box_aspect without set_extent

Open carstrb opened this issue 1 month ago • 3 comments

While I don't think this is necessarily a bug, I'm having trouble finding a simple way to enforce a matplotlib Axes box aspect ratio without losing certain cartopy features from the map, depending on what data I'm plotting. An example is below:

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plot
import pandas as pd

MAP_FIXED_WIDTH = 8
MAP_FIXED_HEIGHT = 6

df = pd.DataFrame(
    {
        "timestamp": 
        [
            pd.Timestamp('2025-01-01 00:00:00 UTC'), 
            pd.Timestamp('2025-01-01 00:30:00 UTC'), 
            pd.Timestamp('2025-01-01 01:00:00 UTC'), 
            pd.Timestamp('2025-01-01 01:30:00 UTC'), 
            pd.Timestamp('2025-01-01 02:00:00 UTC'),
         ],
        "latitude": [-10, -12, -14, -16, -18],
        "longitude": [40, 50, 60, 70, 80],
    },
)

def create_map_with_scatter_points(df: pd.DataFrame) -> None:
    plot.figure(figsize=(MAP_FIXED_WIDTH, MAP_FIXED_HEIGHT))
    axes = plot.axes(
        projection=ccrs.PlateCarree(central_longitude=df["longitude"].median())
    )

    axes.set_box_aspect(MAP_FIXED_HEIGHT / MAP_FIXED_WIDTH)

    axes.scatter(df["longitude"], df["latitude"], color='green')

    axes.set_facecolor(cfeature.COLORS["water"])
    axes.add_feature(cfeature.LAND, color="lightgray")
    axes.add_feature(cfeature.LAKES)
    axes.add_feature(cfeature.RIVERS)
    axes.add_feature(cfeature.COASTLINE)
    axes.add_feature(cfeature.BORDERS, linestyle=":")

    axes.gridlines(
        draw_labels=True, linewidth=1, color="gray", alpha=0.5, linestyle="--"
    )

    plot.show()

create_map_with_scatter_points(df)

Output is as follows:

Image

From the output, you'll see that certain COASTLINE features are not being rendered. Simply setting edgecolor of LAND to "black" would resolve the missing coastlines in this case, however there are other cases where landmasses don't get rendered based on the data extent and area of the world that we're plotting. My understanding is that Cartopy is unaware that it needs to render map features outside of the explicit range of the data being plotted.

I'm aware that axes.set_box_aspect() only controls the physical dimensions of the Axes box, not the limits of data being displayed. I've also tried using axes.set_aspect(adjustable='datalim'), though it comes with the caveat that set_adjustable method "may ignore explicitly defined axis limits" (link)

I'm explicitly trying to avoid having to set Axes extent manually, as the logic required for choosing the correct central longitude for the map when data crosses the 180th meridian is very cumbersome.

It would be great if there was an easier way to adjust the data limits based on the aspect ratio set with set_box_aspect(). Has anyone run into this quirk before and found a workaround?

Dependency versions:

matplotlib==3.10.7
pandas==2.3.3
cartopy==0.25.0

carstrb avatar Nov 12 '25 17:11 carstrb

Hi @carstrb I posted a workaround on your StackOverflow question earlier, but this comes down to the fact that FeatureArtist.draw uses GeoAxes.get_extent to figure out which geometries to use. get_extent calls autoscale_view and returns the autoscaled extent before reverting the limits via the hold_limits context manager. I have not understood why get_extent does that though. It came in at #355, apparently in response to some Matplotlib v1.3 changes.

rcomer avatar Nov 14 '25 21:11 rcomer

The hold_limits() is only used in two places it looks like, so I wonder if we could remove it. I wonder if it could be worked around in other ways if necessary like setting autoscale=False when adding the features if some similar is needed.

greglucas avatar Nov 15 '25 20:11 greglucas

Hi @rcomer , yes I did see your reply in StackOverflow also! Thank you for looking into this. I definitely would not have been able to find the source of this quirk very easily 😄.

carstrb avatar Nov 17 '25 18:11 carstrb