Search code examples
pythonmatplotlib-basemapcartopysatellitefieldofview

How to plot the field of view of an Earth-Observation satellite when close to the poles using basemap?


I am trying to draw the maximum (theoretical) field of view of a satellite along its orbit. I am using Basemap, on which I want to plot different positions along the orbit (with scatter), and I would like to add the whole field of view using the tissot method (or equivalent). The code below works fine until the latitude reaches about 75 degrees North, on a 300km altitude orbit. Beyond which the code outputs a ValueError: "ValueError: undefined inverse geodesic (may be an antipodal point)"

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import math

earth_radius = 6371000.  # m
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', resolution='l',
            llcrnrlat=-90, urcrnrlat=90,
            llcrnrlon=-180, urcrnrlon=180)

# draw the coastlines on the empty map
m.drawcoastlines(color='k')

# define the position of the satellite
position = [300000., 75., 0.]  # altitude, latitude, longitude

# radius needed by the tissot method
radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
m.tissot(position[2], position[1], radius, 100, facecolor='tab:blue', alpha=0.3)
m.scatter(position[2], position[1], marker='*', c='tab:red')

plt.show()

To be noted that the code works fine at the south pole (latitude lower than -75). I know it's a known bug, is there a known workaround for this issue? Thanks for your help!


Solution

  • What you found is some of Basemap's limitations. Let's switch to Cartopy for now. The working code will be different but not much.

    import matplotlib.pyplot as plt
    import cartopy.crs as ccrs
    import math
    
    earth_radius = 6371000.
    position = [300000., 75., 0.]   # altitude (m), lat, long
    radius = math.degrees(math.acos(earth_radius / (earth_radius + position[0])))
    print(radius)  # in subtended degrees??
    
    fig = plt.figure(figsize=(12,8))
    
    img_extent = [-180, 180, -90, 90]
    
    # here, cartopy's' `PlateCarree` is equivalent with Basemap's `cyl` you use
    ax = fig.add_subplot(1, 1, 1, projection = ccrs.PlateCarree(), extent = img_extent)
    
    # for demo purposes, ...
    # let's take 1 subtended degree = 112 km on earth surface (*** you set the value as needed ***)
    ax.tissot(rad_km=radius*112, lons=position[2], lats=position[1], n_samples=64, \
                 facecolor='red', edgecolor='black', linewidth=0.15, alpha = 0.3)
    
    ax.coastlines(linewidth=0.15)
    ax.gridlines(draw_labels=False, linewidth=1, color='blue', alpha=0.3, linestyle='--')
    plt.show()
    

    With the code above, the resulting plot is:

    enter image description here

    Now, if we use Orthographic projection, (replace relevant line of code with this)

    ax = fig.add_subplot(1, 1, 1, projection = ccrs.Orthographic(central_longitude=0.0, central_latitude=60.0))
    

    you get this plot:

    enter image description here