```
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt

from sympy import *
from mpmath import degrees, radians

init_printing()
```

This derivation is based on the USGS Professional Paper 1395:
[Map Projections &mdash; A Working Manual](http://pubs.usgs.gov/pp/1395/report.pdf), ch 14, pp 98 &ndash; 103.


```
radius = Symbol('R')
semimajor, ellipticity = symbols('a e', positive=True)
standard_parallel_1, standard_parallel_2 = symbols('phi_1 phi_2')
central_lat, central_lon = symbols('phi_0 lambda_0')
point_lat, point_lon = symbols('phi lambda')

radius, semimajor, ellipticity, \
standard_parallel_1, standard_parallel_2, \
central_lat, central_lon, \
point_lat, point_lon
```


```
def subdict(eq, vals):
    for k, v in vals.items():
        eq = eq.subs(k, v)
    return eq
```

## Formulas for the Sphere

NOTE: Formulas are defined in reverse (mostly) from the paper, so that
they can build on each other.


```
# Eq. 14-6
ns = (sin(standard_parallel_1) + sin(standard_parallel_2)) / 2
ns
```


```
# Eq. 14-5
Cs = cos(standard_parallel_1)**2 + 2 * ns * sin(standard_parallel_1)
Cs
```


```
# Eq. 14-3a
rho0s = radius * (Cs - 2 * ns * sin(central_lat))**0.5 / ns
rho0s
```


```
# Eq. 14-4
thetas = ns * (point_lon - central_lon)
thetas
```


```
# Eq. 14-3
rhos = radius * (Cs - 2 * ns * sin(point_lat))**0.5 / ns
rhos
```


```
# Eq. 14-1
xs = rhos * sin(thetas)
# Eq. 14-2
ys = rho0s - rhos * cos(thetas)
```


```
xs
```


```
ys
```

## Formulas for the Ellipsoid


```
# Eq. 14-15
m = cos(point_lat) / (1 - ellipticity**2 * sin(point_lat)**2)**0.5
m1 = cos(standard_parallel_1) / (1 - ellipticity**2 * sin(standard_parallel_1)**2)**0.5
m2 = cos(standard_parallel_2) / (1 - ellipticity**2 * sin(standard_parallel_2)**2)**0.5
m, m1, m2
```


```
# Eq 3-12
q = (1 - ellipticity**2) * (
    sin(point_lat) / (1 - ellipticity**2 * sin(point_lat)**2) -
    1 / (2 * ellipticity) * log(
        (1 - ellipticity * sin(point_lat)) /
        (1 + ellipticity * sin(point_lat))
        )
    )
q0 = (1 - ellipticity**2) * (
    sin(central_lat) / (1 - ellipticity**2 * sin(central_lat)**2) -
    1 / (2 * ellipticity) * log(
        (1 - ellipticity * sin(central_lat)) /
        (1 + ellipticity * sin(central_lat))
        )
    )
q1 = (1 - ellipticity**2) * (
    sin(standard_parallel_1) / (1 - ellipticity**2 * sin(standard_parallel_1)**2) -
    1 / (2 * ellipticity) * log(
        (1 - ellipticity * sin(standard_parallel_1)) /
        (1 + ellipticity * sin(standard_parallel_1))
        )
    )
q2 = (1 - ellipticity**2) * (
    sin(standard_parallel_2) / (1 - ellipticity**2 * sin(standard_parallel_2)**2) -
    1 / (2 * ellipticity) * log(
        (1 - ellipticity * sin(standard_parallel_2)) /
        (1 + ellipticity * sin(standard_parallel_2))
        )
    )

q, q0, q1, q2
```


```
# Eq. 14-14
ne = (m1**2 - m2**2) / (q2 - q1)
ne
```


```
# Eq. 14-13
Ce = m1**2 + ne * q1
Ce
```


```
# Eq. 14-12a
rho0e = semimajor * (Ce - ne * q0)**0.5 / ne
rho0e
```


```
# Eq. 14-4
thetae = ne * (point_lon - central_lon)
thetae
```


```
# Eq. 14-12
rhoe = semimajor * (Ce - ne * q)**0.5 / ne
rhoe
```


```
xe = rhoe * sin(thetae)
ye = rho0e - rhoe * cos(thetae)
```

I'm not even going to bother printing these two. They're long and it's not important.

# Cartopy Boundaries


```
# Default Globe in Cartopy
WGS84_semimajor = Rational(6378137)
WGS84_flattening = Rational(1, 298.257223563)
# Plotting fails if I leave this bit as exact, thinking there are complex values,
# even though the input to sqrt is positive, and it's supposed to return the positive root.
WGS84_ellipticity = sqrt(2 * WGS84_flattening - WGS84_flattening**2).evalf()

# Default parameters in Cartopy
lat0_default = radians(0)
lon0_default = radians(0)
stp1_default = radians(20)
stp2_default = radians(50)
```

## Ellipsoid

### Default - WGS84


```
ellipsoid_vars = {
    semimajor: WGS84_semimajor,
    ellipticity: WGS84_ellipticity,
    standard_parallel_1: stp1_default,
    standard_parallel_2: stp2_default,
    central_lat: lat0_default,
    central_lon: lon0_default
}
```

I know the outer boundary should be something like the following based on existing plots.


```
lat_min_ellipsoid = radians(lat0_default - 90)
lat_max_ellipsoid = radians(lat0_default + 90)
lon_min_ellipsoid = radians(lon0_default - 180)
lon_max_ellipsoid = radians(lon0_default + 180)

default_xe = subdict(xe, ellipsoid_vars)
eq1x = default_xe.subs(point_lat, lat_max_ellipsoid)
eq2x = default_xe.subs(point_lon, lon_max_ellipsoid)
eq3x = default_xe.subs(point_lat, lat_min_ellipsoid)
eq4x = default_xe.subs(point_lon, lon_min_ellipsoid)

default_ye = subdict(ye, ellipsoid_vars)
eq1y = default_ye.subs(point_lat, lat_max_ellipsoid)
eq2y = default_ye.subs(point_lon, lon_max_ellipsoid)
eq3y = default_ye.subs(point_lat, lat_min_ellipsoid)
eq4y = default_ye.subs(point_lon, lon_min_ellipsoid)

plotting.plot_parametric((eq1x, eq1y, (point_lon, lon_min_ellipsoid, lon_max_ellipsoid)),
                         (eq2x, eq2y, (point_lat, lat_max_ellipsoid, lat_min_ellipsoid)),
                         (eq3x, eq3y, (point_lon, lon_max_ellipsoid, lon_min_ellipsoid)),
                         (eq4x, eq4y, (point_lat, lat_min_ellipsoid, lat_max_ellipsoid)),
                         title='Ellipsoid - WGS84 - Guessed Boundaries')
```


```
lat_magnitude_ellipsoid = subdict((point_lat - central_lat) / radians(90), ellipsoid_vars)
lon_magnitude_ellipsoid = subdict((point_lon - central_lon) / radians(180), ellipsoid_vars)

plotting.plot3d_parametric_surface(default_xe, default_ye, lat_magnitude_ellipsoid,
                                   (point_lat, lat_min_ellipsoid, lat_max_ellipsoid),
                                   (point_lon, lon_min_ellipsoid, lon_max_ellipsoid),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - WGS84 - Latitude Dependence')
plotting.plot3d_parametric_surface(default_xe, default_ye, lon_magnitude_ellipsoid,
                                   (point_lat, lat_min_ellipsoid, lat_max_ellipsoid),
                                   (point_lon, lon_min_ellipsoid, lon_max_ellipsoid),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - WGS84 - Longitude Dependence')
```

From the above plot, it seems pretty clear that the boundaries match what I guessed above.

However, since the extrema in the $x$ direction is _not_ at the longitudinal limit,
we need to take a derivative and find the zeros.


```
xe_deriv = diff(xe, point_lon)
xe_deriv
```


```
xe_ds = subdict(xe_deriv, ellipsoid_vars)
# Known latitude for outer ring
xe_ds = xe_ds.subs(point_lat, radians(-90))
```


```
extrema_ellipsoid = solve(xe_ds, point_lon)
extrema_ellipsoid
```


```
extrema_ellipsoid = [degrees(_) for _ in extrema_ellipsoid]
extrema_ellipsoid
```


```
xmin = subdict(xe, ellipsoid_vars)
# Calculated minimum location
xmin = xmin.subs(point_lat, radians(-90)).subs(point_lon, radians(-extrema_ellipsoid[0]))
xmin.evalf()
```


```
xmax = subdict(xe, ellipsoid_vars)
# Calculated maximum location
xmax = xmax.subs(point_lat, radians(-90)).subs(point_lon, radians(extrema_ellipsoid[0]))
xmax.evalf()
```


```
ymin = subdict(ye, ellipsoid_vars)
# Known minimum location from plot
ymin = ymin.subs(point_lat, radians(-90)).subs(point_lon, radians(0))
ymin.evalf()
```


```
ymax = subdict(ye, ellipsoid_vars)
# Known maximum location from plot
ymax = ymax.subs(point_lat, radians(-90)).subs(point_lon, radians(180))
ymax.evalf()
```

I want to find how many points to split the longitude range into so that
it comes really close to this extrema.


```
f = plt.figure(figsize=(16, 16))
for n in range(1, 181, 2):
    lons = np.linspace(-180, 180, n)
    plt.plot(lons, n * np.ones(n), c='b')
    plt.scatter(lons, n * np.ones(n), c='b')
    index = np.argmin(abs(lons - extrema_ellipsoid[0]))
    plt.scatter(lons[index], n, c='r')
plt.axvline(extrema_ellipsoid[0], c='g', ls='--')
plt.xlim(150, 180)
plt.xlabel('Longitude')
plt.ylim(0, 180)
plt.ylabel('Number of Points')
plt.title('Longitude Sampling for a Stipulated Number of Points')
plt.show()
```

It seems like some good choices are 83, 103, 123, or 165.


```
lons = np.linspace(-180, 180, 103)
index = np.argmin(abs(lons - extrema_ellipsoid[0]))
lons[index-1:index+2], abs(lons[index-1:index+2] - extrema_ellipsoid[0])
```




    (array([ 158.82352941,  162.35294118,  165.88235294]),
     array([3.55657028545752, 0.0271585207516694, 3.50225324395424], dtype=object))



### Eccentric Globe


```
a = Rational(1000)
b = Rational(500)
f = (a - b) / a
eccentric_globe_vars = {
    semimajor: a,
    ellipticity: sqrt(2 * f - f**2).evalf(),
    standard_parallel_1: stp1_default,
    standard_parallel_2: stp2_default,
    central_lat: lat0_default,
    central_lon: lon0_default
}
```


```
lat_min_eccentric_globe = radians(lat0_default - 90)
lat_max_eccentric_globe = radians(lat0_default + 90)
lon_min_eccentric_globe = radians(lon0_default - 180)
lon_max_eccentric_globe = radians(lon0_default + 180)

eccentric_xe = subdict(xe, eccentric_globe_vars)
eq1x = eccentric_xe.subs(point_lat, lat_max_eccentric_globe)
eq2x = eccentric_xe.subs(point_lon, lon_max_eccentric_globe)
eq3x = eccentric_xe.subs(point_lat, lat_min_eccentric_globe)
eq4x = eccentric_xe.subs(point_lon, lon_min_eccentric_globe)

eccentric_ye = subdict(ye, eccentric_globe_vars)
eq1y = eccentric_ye.subs(point_lat, lat_max_eccentric_globe)
eq2y = eccentric_ye.subs(point_lon, lon_max_eccentric_globe)
eq3y = eccentric_ye.subs(point_lat, lat_min_eccentric_globe)
eq4y = eccentric_ye.subs(point_lon, lon_min_eccentric_globe)

plotting.plot_parametric((eq1x, eq1y, (point_lon, lon_min_eccentric_globe, lon_max_eccentric_globe)),
                         (eq2x, eq2y, (point_lat, lat_max_eccentric_globe, lat_min_eccentric_globe)),
                         (eq3x, eq3y, (point_lon, lon_max_eccentric_globe, lon_min_eccentric_globe)),
                         (eq4x, eq4y, (point_lat, lat_min_eccentric_globe, lat_max_eccentric_globe)),
                         title='Ellipsoid - Eccentric Globe - Guessed Boundaries')
```


```
lat_magnitude_eccentric_globe = subdict((point_lat - central_lat) / radians(90), eccentric_globe_vars)
lon_magnitude_eccentric_globe = subdict((point_lon - central_lon) / radians(180), eccentric_globe_vars)

plotting.plot3d_parametric_surface(eccentric_xe, eccentric_ye, lat_magnitude_eccentric_globe,
                                   (point_lat, lat_min_eccentric_globe, lat_max_eccentric_globe),
                                   (point_lon, lon_min_eccentric_globe, lon_max_eccentric_globe),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - Eccentric Globe - Latitude Dependence')
plotting.plot3d_parametric_surface(eccentric_xe, eccentric_ye, lon_magnitude_ellipsoid,
                                   (point_lat, lat_min_eccentric_globe, lat_max_eccentric_globe),
                                   (point_lon, lon_min_eccentric_globe, lon_max_eccentric_globe),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - Eccentric Globe - Longitude Dependence')
```

It appears for the eccentric globe, a very similar boundary will occur.


```
xe_deg = subdict(xe_deriv, ellipsoid_vars)
# Known latitude for outer ring
xe_deg = xe_deg.subs(point_lat, radians(-90))
```


```
extrema_eccentric_globe = solve(xe_deg, point_lon)
extrema_eccentric_globe
```


```
extrema_eccentric_globe = [degrees(_) for _ in extrema_eccentric_globe]
extrema_eccentric_globe
```

These are (unsurprisingly) at the same place!


```
xmin = subdict(xe, eccentric_globe_vars)
# Calculated minimum location
xmin = xmin.subs(point_lat, radians(-90)).subs(point_lon, radians(-extrema_eccentric_globe[0]))
xmin.evalf()
```


```
xmax = subdict(xe, eccentric_globe_vars)
# Calculated maximum location
xmax = xmax.subs(point_lat, radians(-90)).subs(point_lon, radians(extrema_eccentric_globe[0]))
xmax.evalf()
```


```
ymin = subdict(ye, eccentric_globe_vars)
# Known minimum location from plot
ymin = ymin.subs(point_lat, radians(-90)).subs(point_lon, radians(0))
ymin.evalf()
```


```
ymax = subdict(ye, eccentric_globe_vars)
# Known maximum location from plot
ymax = ymax.subs(point_lat, radians(-90)).subs(point_lon, radians(180))
ymax.evalf()
```


```
ymax = subdict(ye, eccentric_globe_vars)
# Known maximum location from plot
ymax = ymax.subs(point_lat, radians(-90)).subs(point_lon, radians(180))
ymax.evalf()
```

### Clarke 1866


```
# See example on pg 292
clarke1866_lat0 = 23
clarke1866_lon0 = -96
clarke1866_vars = {
    semimajor: 6378206.4,
    ellipticity: 0.0822719,
    standard_parallel_1: radians(29 + 30 / 60),
    standard_parallel_2: radians(45 + 30 / 60),
    central_lat: radians(clarke1866_lat0),
    central_lon: radians(clarke1866_lon0)
}
```


```
lat_min_clarke1866 = radians(-90)
lat_max_clarke1866 = radians(+90)
lon_min_clarke1866 = radians(clarke1866_lon0 - 180)
lon_max_clarke1866 = radians(clarke1866_lon0 + 180)

clarke1866_xe = subdict(xe, clarke1866_vars)
eq1x = clarke1866_xe.subs(point_lat, lat_max_clarke1866)
eq2x = clarke1866_xe.subs(point_lon, lon_max_clarke1866)
eq3x = clarke1866_xe.subs(point_lat, lat_min_clarke1866)
eq4x = clarke1866_xe.subs(point_lon, lon_min_clarke1866)

clarke1866_ye = subdict(ye, clarke1866_vars)
eq1y = clarke1866_ye.subs(point_lat, lat_max_clarke1866)
eq2y = clarke1866_ye.subs(point_lon, lon_max_clarke1866)
eq3y = clarke1866_ye.subs(point_lat, lat_min_clarke1866)
eq4y = clarke1866_ye.subs(point_lon, lon_min_clarke1866)

plotting.plot_parametric((eq1x, eq1y, (point_lon, lon_min_clarke1866, lon_max_clarke1866)),
                         (eq2x, eq2y, (point_lat, lat_max_clarke1866, lat_min_clarke1866)),
                         (eq3x, eq3y, (point_lon, lon_max_clarke1866, lon_min_clarke1866)),
                         (eq4x, eq4y, (point_lat, lat_min_clarke1866, lat_max_clarke1866)),
                         title='Ellipsoid - Clarke 1866 - Guessed Boundaries')
```


```
lat_magnitude_clarke1866 = subdict((point_lat - central_lat) / radians(90), clarke1866_vars)
lon_magnitude_clarke1866 = subdict((point_lon - central_lon) / radians(180), clarke1866_vars)

plotting.plot3d_parametric_surface(clarke1866_xe, clarke1866_ye, lat_magnitude_clarke1866,
                                   (point_lat, lat_min_clarke1866, lat_max_clarke1866),
                                   (point_lon, lon_min_clarke1866, lon_max_clarke1866),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - Clarke 1866 - Latitude Dependence')
plotting.plot3d_parametric_surface(clarke1866_xe, clarke1866_ye, lon_magnitude_clarke1866,
                                   (point_lat, lat_min_clarke1866, lat_max_clarke1866),
                                   (point_lon, lon_min_clarke1866, lon_max_clarke1866),
                                   xlabel='X', ylabel='Y',
                                   title='Ellipsoid - Clarke 1866 - Longitude Dependence')
```

It appears for Clarke 1866, a very similar boundary will occur once again.


```
xe_dc = subdict(xe_deriv, clarke1866_vars)
# Known latitude for outer ring
xe_dc = xe_dc.subs(point_lat, radians(-90))
```


```
extrema_clarke1866 = solve(xe_dc, point_lon)
extrema_clarke1866
```


```
extrema_clarke1866 = [degrees(_) for _ in extrema_clarke1866]
extrema_clarke1866
```


```
xmin = subdict(xe, clarke1866_vars)
# Calculated minimum location
xmin = xmin.subs(point_lat, radians(-90)).subs(point_lon, radians(extrema_clarke1866[1]))
xmin.evalf()
```


```
xmax = subdict(xe, clarke1866_vars)
# Calculated maximum location
xmax = xmax.subs(point_lat, radians(-90)).subs(point_lon, radians(extrema_clarke1866[0]))
xmax.evalf()
```


```
ymin = subdict(ye, clarke1866_vars)
# Known minimum location from plot
ymin = ymin.subs(point_lat, radians(-90)).subs(point_lon, radians(clarke1866_lon0))
ymin.evalf()
```


```
ymax = subdict(ye, clarke1866_vars)
# Known maximum location from plot
ymax = ymax.subs(point_lat, radians(-90)).subs(point_lon, lon_max_clarke1866)
ymax.evalf()
```

## Sphere


```
# From numerical examples, pg 291
radius_sphere = 1.0
stp1_sphere = 29 + 30 / 60
stp2_sphere = 45 + 30 / 60
lat0_sphere = 23
lon0_sphere = -96

sphere_vars = {radius: radius_sphere,
               standard_parallel_1: radians(stp1_sphere),
               standard_parallel_2: radians(stp2_sphere),
               central_lat: radians(lat0_sphere),
               central_lon: radians(lon0_sphere)}
```


```
lon_min_sphere = radians(lon0_sphere - 180)
lon_max_sphere = radians(lon0_sphere + 180)
# Interestingly, the boundary does not appear to depend on central latitude!
lat_min_sphere = radians(-90)
lat_max_sphere = radians(+90)

sphere_xs = subdict(xs, sphere_vars)
eq1x = sphere_xs.subs(point_lat, lat_max_sphere)
eq2x = sphere_xs.subs(point_lon, lon_max_sphere)
eq3x = sphere_xs.subs(point_lat, lat_min_sphere)
eq4x = sphere_xs.subs(point_lon, lon_min_sphere)

sphere_ys = subdict(ys, sphere_vars)
eq1y = sphere_ys.subs(point_lat, lat_max_sphere)
eq2y = sphere_ys.subs(point_lon, lon_max_sphere)
eq3y = sphere_ys.subs(point_lat, lat_min_sphere)
eq4y = sphere_ys.subs(point_lon, lon_min_sphere)

plotting.plot_parametric((eq1x, eq1y, (point_lon, lon_min_sphere, lon_max_sphere)),
                         (eq2x, eq2y, (point_lat, lat_max_sphere, lat_min_sphere)),
                         (eq3x, eq3y, (point_lon, lon_max_sphere, lon_min_sphere)),
                         (eq4x, eq4y, (point_lat, lat_min_sphere, lat_max_sphere)),
                         title='Sphere - Guessed Boundaries')
```

Evidently, the boundary does _not_ depend on central latitude.
However, central longitude does seem to make a difference.


```
lat_magnitude = subdict((point_lat - central_lat) / radians(90), sphere_vars)
lon_magnitude = subdict((point_lon - central_lon) / radians(180), sphere_vars)

plotting.plot3d_parametric_surface(sphere_xs, sphere_ys, lat_magnitude,
                                   (point_lat, lat_min_sphere, lat_max_sphere),
                                   (point_lon, lon_min_sphere, lon_max_sphere),
                                   xlabel='X', ylabel='Y',
                                   title='Sphere - Latitude Dependence')

plotting.plot3d_parametric_surface(sphere_xs, sphere_ys, lon_magnitude,
                                   (point_lat, lat_min_sphere, lat_max_sphere),
                                   (point_lon, lon_min_sphere, lon_max_sphere),
                                   xlabel='X', ylabel='Y',
                                   title='Sphere - Longitude Dependence')
```

The first plot above points to the location of the minimum and maximum latitudes.
The second plot is for the longitudes.
Clearly the outer boundary occurs at the minimum of the latitude ($-90^\circ$!), and all along the longitudes.

Since the extrema in the $x$ direction is _not_ at the longitudinal limit,
we need to take a derivative and find the zeros.


```
xs_deriv = diff(xs, point_lon)
xs_deriv
```


```
xs_ds = subdict(xs_deriv, sphere_vars)
# Known latitude for outer ring
xs_ds = xs_ds.subs(point_lat, lat_min_sphere)
xs_ds
```


```
extrema_sphere = solve(xs_ds, point_lon)
extrema_sphere
```


```
extrema_sphere = [degrees(_) for _ in extrema_sphere]
extrema_sphere
```


```
xmin = subdict(xs, sphere_vars)
# Calculated minimum location
xmin = xmin.subs(point_lat, lat_min_sphere).subs(point_lon, radians(-extrema_sphere[0]))
xmin.evalf()
```


```
xmax = subdict(xs, sphere_vars)
# Calculated maximum location
xmax = xmax.subs(point_lat, lat_min_sphere).subs(point_lon, radians(extrema_sphere[0]))
xmax.evalf()
```

Obviously, something has gone wrong here. Maybe by trying the other extrema,
the result will make some sense.


```
xmin = subdict(xs, sphere_vars)
# Calculated minimum location
xmin = xmin.subs(point_lat, lat_min_sphere).subs(point_lon, radians(-extrema_sphere[1]))
xmin.evalf()
```


```
xmax = subdict(xs, sphere_vars)
# Calculated maximum location
xmax = xmax.subs(point_lat, lat_min_sphere).subs(point_lon, radians(extrema_sphere[1]))
xmax.evalf()
```

Unlike the ellipsoid cases above, it appears both roots were needed to see the
full limits. This difference is likely due to the central longitude not being
zero, and therefore the roots are not symmetric.


```
ymin = subdict(ys, sphere_vars)
# Known minimum location from plot
ymin = ymin.subs(point_lat, lat_min_sphere).subs(point_lon, radians(lon0_sphere))
ymin.evalf()
```


```
ymax = subdict(ys, sphere_vars)
# Known maximum location from plot
ymax = ymax.subs(point_lat, lat_min_sphere).subs(point_lon, lon_max_sphere)
ymax.evalf()
```

1683bc288d801613fbdb93c2d268364c82857ee6
ipynb
Jupyter Notebook
Albers Equal Area.ipynb
QuLogic/map-projections
b4fb910bc4fcbf226c50dca19194a96884cd1b7d
MIT
Albers Equal Area.ipynb
QuLogic/map-projections
b4fb910bc4fcbf226c50dca19194a96884cd1b7d
MIT
Albers Equal Area.ipynb
QuLogic/map-projections
b4fb910bc4fcbf226c50dca19194a96884cd1b7d
MIT
Qwen/Qwen-72B
1. YES
2. YES