Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"No non overlapping boxes found" when trying to arrange labels on cartopy map? #12

Closed
nebukadnezar opened this issue Feb 28, 2024 · 7 comments

Comments

@nebukadnezar
Copy link

nebukadnezar commented Feb 28, 2024

This is an amazing tool for "regular" data plots... however I can't seem to get it to work when labelling geographic data on a cartopy plot.

Entertain the code below if you will to illustrate the behaviour. The labels do overlap, and the message "No non overlapping boxes found" is shown.

import textalloc as ta
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt

np.random.seed(0)
x = np.random.rand(200)
x  = 150.5
y = np.random.rand(200)
y -= 34.5

dpi = 72
fig = plt.figure(figsize=(800/dpi, 800/dpi), dpi=dpi)

zoom = 10
sitelon = np.mean(x)
sitelat = np.mean(y)
radius = (np.max(x) - np.min(x) ) / 2
ll_lon = sitelon - radius * (1/np.cos(np.radians(sitelat)))
ll_lat = sitelat - radius
ur_lon = sitelon   radius * (1/np.cos(np.radians(sitelat)))
ur_lat = sitelat   radius
extent = [ll_lon, ur_lon, ll_lat, ur_lat]

request = cimgt.OSM(desired_tile_form="L")
ax = plt.axes(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request, zoom, alpha=0.5, cmap='gray')

ax.scatter(x, y, c='b', transform=ccrs.PlateCarree())
text_list = [f'Text{i}' for i in range(len(x))]
ta.allocate_text(fig,ax,x,y,
                text_list,
                x_scatter=x, y_scatter=y,
                textsize=10,  transform=ccrs.PlateCarree())
plt.show()

@ckjellson
Copy link
Owner

ckjellson commented Feb 28, 2024

I am not familiar with this way of plotting maps, but if there is this sort of built in transform I do not expect it to work with the current version of this package. I am not sure if I should even try to solve this one, or just suggest a workaround, which I believe works somewhat okay by extracting the OSM-image as an array, and then using the standard way of plotting images:

import textalloc as ta
import numpy as np
import matplotlib.pyplot as plt
import cartopy.io.img_tiles as cimgt
import io

np.random.seed(0)
x = np.random.rand(200)
x  = 150.5
y = np.random.rand(200)
y -= 34.5

zoom = 10
sitelon = np.mean(x)
sitelat = np.mean(y)
radius = (np.max(x) - np.min(x) ) / 2
ll_lon = sitelon - radius * (1/np.cos(np.radians(sitelat)))
ll_lat = sitelat - radius
ur_lon = sitelon   radius * (1/np.cos(np.radians(sitelat)))
ur_lat = sitelat   radius
extent = [ll_lon, ur_lon, ll_lat, ur_lat]

request = cimgt.OSM(desired_tile_form="L")

dpi = 100
fig = plt.figure(figsize=(20, 20), dpi=dpi)
ax = plt.axes(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request, zoom, cmap='gray')
io_buf = io.BytesIO()
b = ax.get_window_extent()
fig.savefig(io_buf, format='raw', bbox_inches='tight', pad_inches=0)
io_buf.seek(0)
img_arr = np.reshape(np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
                     newshape=(int(b.height), int(b.width), -1))
io_buf.close()
plt.close()

fig,ax = plt.subplots(figsize=(20, 20), dpi=dpi)
ax.imshow(img_arr,  extent=extent, alpha=0.5)
ax.scatter(x, y, c='b')
ax.set_xlim((ll_lon, ur_lon))
ax.set_ylim((ll_lat, ur_lat))
ax.set_xticks([])
ax.set_yticks([])
text_list = [f'Text{i}' for i in range(len(x))]
ta.allocate_text(fig,ax,x,y,
                text_list,
                x_scatter=x, y_scatter=y,
                textsize=15)
plt.show()

565a34a0-4eac-433f-9a37-944495e36c89

This works right, or what do you think?

@nebukadnezar
Copy link
Author

That's very clever, and it certainly looks right!

However, trying to run your code on my machine errors out:

   img_arr = np.reshape(np.frombuffer(io_buf.getvalue(), dtype=np.uint8),
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/anaconda3/envs/cartopy/lib/python3.11/site-packages/numpy/core/fromnumeric.py", line 285, in reshape
    return _wrapfunc(a, 'reshape', newshape, order=order)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/anaconda3/envs/cartopy/lib/python3.11/site-packages/numpy/core/fromnumeric.py", line 59, in _wrapfunc
    return bound(*args, **kwds)
           ^^^^^^^^^^^^^^^^^^^^
ValueError: cannot reshape array of size 9480240 into shape (3080,3079,newaxis)

Does the code you posted work as is for you?

@nebukadnezar
Copy link
Author

This is really odd. The byte buffer saved to io_buf is one byte too short for the reshape to 3079x3079, but also, the height returned from ax.get_window_extent() is one pixel too many at 3080.

@ckjellson
Copy link
Owner

Yes, it works as is, very strange! Maybe it is some issue with versions of the relevant packages? Here are mine:
Python 3.8.15
matplotlib 3.6.2
numpy 1.23.5
Cartopy 0.21.1

@nebukadnezar
Copy link
Author

Created a new conda env with the exact same versions as you - no luck. Same error. I'm on MacOS. What OS are you on?

@ckjellson
Copy link
Owner

I'm right now on Ubuntu (WSL). I guess there are easier ways to download OSM-images if you don't have to use Cartopy. It would be great if this package could support your original code, but it seems quite complex and I don't have that much time to look at it right now, so don't expect a quick solution this way.

@ckjellson
Copy link
Owner

Hi again, I have made an update which should solve the original issue. To avoid adding cartopy as a dependency I found no better way than to add another argument src_crs for now, which is required together with the transform argument. The value you should use for PlateCarree() is found in the code below.

I am closing this issue with this change, but if this solution is not sufficient it might be necessary to start using display coordinates instead of data coordinates, which will require bigger changes in this repo.

import textalloc as ta
import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.io.img_tiles as cimgt

np.random.seed(1)
x = np.random.rand(200)
x  = 150.5
y = np.random.rand(200)
y -= 34.5

dpi = 72
fig = plt.figure(figsize=(800/dpi, 800/dpi), dpi=dpi)

zoom = 10
sitelon = np.mean(x)
sitelat = np.mean(y)
radius = (np.max(x) - np.min(x) ) / 1.5
ll_lon = sitelon - radius * (1/np.cos(np.radians(sitelat)))
ll_lat = sitelat - radius
ur_lon = sitelon   radius * (1/np.cos(np.radians(sitelat)))
ur_lat = sitelat   radius
extent = [ll_lon, ur_lon, ll_lat, ur_lat]

request = cimgt.OSM(desired_tile_form="L")
ax = plt.axes(projection=request.crs)
ax.set_extent(extent)
ax.add_image(request, zoom, alpha=0.5, cmap='gray')

ax.scatter(x, y, c='b', transform=ccrs.PlateCarree())
text_list = [f'Text{i}' for i in range(len(x))]
ta.allocate_text(fig,ax,x,y,
                text_list,
                x_scatter=x, y_scatter=y,
                textsize=10,
                draw_lines=True,
                linewidth=0.5,
                draw_all=False,
                src_crs=ccrs.TransverseMercator(),
                transform=ccrs.PlateCarree()
                )
plt.show()

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants