Mapping an Aitoff projection onto a sphere
Hammer Projection


Nodes on left, UV map on right

A while back, sh*t like 6 years ago,  I wrote a number of map projections, including Mercator and among them the Hammer, close relation to Aitoff which I've cleaned up for blender 2.8


UVMap result on default UV sphere

To use, run the script in text editor, or install as addon. Adds a Hammer projection entry to the UV mesh edit mode menu.



from numpy import sin, cos, sinc, arccos
from mathutils import Vector, Matrix
from math import degrees, pi, sqrt
import bmesh
import bpy
bl_info = {
    "name": "Hammer Project",
    "author": "batFINGER",
    "version": (1, 0),
    "blender": (2, 80, 0),
    "location": "View3D &gt; Mesh &gt; UV UnWrap &gt; Hammer Project",
    "description": "UV Hammer Projection",
    "warning": "",
    "wiki_url": "",
    "category": "UV",
}


class Spherical:

    def __init__(self, vert, north=(0, 0, 1), long0=(0, -1)):
        self.vert = vert
        self.north, self.long0 = Vector(north), Vector(long0)
        v = vert.co
        R = v.length
        lat = pi / 2 - self.north.angle(v)
        is_pole = v.xy.length &lt; 0.0001

        long = 0 if is_pole else v.xy.angle_signed(long0)

        self.R = R
        self.lat = lat
        self.long = long

    def __repr__(self):
        return "%.3f, %.3f, %.3f" % (self.R,
                                     degrees(self.lat), degrees(self.long))


class HammerUV:
    def x2(self, lat, long):
        a = 2 * sqrt(2) * cos(lat) * sin(long / 2)
        b = sqrt(1 + cos(lat) * cos(long / 2))
        return a / b

    def y2(self, lat, long):
        a = sqrt(2) * sin(lat)
        b = sqrt(1 + cos(lat) * cos(long / 2))
        return a / b

    # Aitoff equations (no joy)
    def x(self, lat, long):
        a = arccos(cos(lat) * cos(long / 2))
        b = 2 * cos(lat) * sin(long / 2)
        return b / sinc(a)

    def y(self, lat, long):
        a = arccos(cos(lat) * cos(long / 2))
        b = sin(lat)
        return b / sinc(a)

    def uv(self, face, uv_layer):
        ''' Map a UV face '''
        # see if face is east / west
        c = face.calc_center_median()

        for l in face.loops:
            luv = l[uv_layer]
            # apply the location of the vertex as a UV
            p = self.pts[l.vert]

            lat, long = p.lat, p.long  # % longfix
            # quick hack for now.
            if l.vert.co.y &gt; 0:
                if c.x &lt; 0 and long &gt; 0:
                    long = -long
                elif c.x &gt; 0 and long &lt; 0:
                    long = -long

            u, v = self.x2(lat, long), self.y2(lat, long)
            uv = self.translate + self.scale @ Vector((u, v))
            luv.uv = uv


    def calc_uv(self):
        bm = self.bm

        uv_layer = bm.loops.layers.uv.verify()

        bm.select_mode = {'FACE', 'VERT'}
        bm.select_flush_mode()
        bm.select_flush(True)
        # adjust UVs
        for f in bm.faces:
            self.uv(f, uv_layer)

    def __init__(self, me, bm):
        def pt(v):
            s = Spherical(v)
            return s

        # add a new uv map
        uv = me.uv_layers.new(name="Hammer")
        me.uv_layers.active = uv
        self.bm = bm
        # spherical coords for verts
        self.pts = {v: pt(v) for v in self.bm.verts}
        # radius average of calc'd s.R
        self.R = sum(s.R for s in self.pts.values()) / len(self.pts)

        # scale UV to [0, 1] make scale matrix
        scale_x = 0.5 / self.x2(0, pi)
        scale_y = 0.5 / self.y2(pi / 2, 0)
        self.scale = Matrix([[scale_x, 0], [0, scale_y]])
        # and transform vector
        self.translate = Vector((0.5, 0.5))


class UV_OT_HammerProject(bpy.types.Operator):
    """Create a Hammer Projection UV Map"""
    bl_idname = "uv.hammer_project"
    bl_label = "Hammer Project"
    bl_options = {'REGISTER', 'UNDO'}

    @classmethod
    def poll(cls, context):
        return (context.mode == 'EDIT_MESH')

    def execute(self, context):
        obj = context.edit_object
        me = obj.data
        bm = bmesh.from_edit_mesh(me)
        merc = HammerUV(me, bm)
        merc.calc_uv()
        bmesh.update_edit_mesh(me)
        return {'FINISHED'}


def unwrapmenu(self, context):
    ''' menu item '''
    self.layout.operator("uv.hammer_project")


def register():
    # add to edit mesh &gt; UV menu
    bpy.types.VIEW3D_MT_uv_map.prepend(unwrapmenu)
    bpy.utils.register_class(UV_OT_HammerProject)


def unregister():
    bpy.types.VIEW3D_MT_uv_map.remove(unwrapmenu)
    bpy.utils.unregister_class(UV_OT_HammerProject)


if __name__ == "__main__":
    register()


Since it is a UV mapping can also use it on other spherical meshes like the icosphere



I added the code for the Aitoff projection, but have not quite managed to get that one "steady", not sure about the sinc(...) method required for that one.

Using Nodes

In https://blender.stackexchange.com/a/159492/15543 dabbling in nodes I came up with a node setup to generate latitude longitude using object texture coordinates.

Basically the equivalent node setup to the script above, generating the UV and feeding into texture node.





Tissot index hammer projection image from wiki by Justin Kunimune CC BY-SA 4.0