{"spec_id":"map-projections","library":"makie","language":"julia","code":"# anyplot.ai\n# map-projections: World Map with Different Projections\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 86/100 | Created: 2026-05-23\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst BRAND       = colorant\"#009E73\"\n\nd2r(x) = x * π / 180.0\n\n# --- Projection functions ---\n\nfunction proj_mercator(lon_deg, lat_deg)\n    lat = clamp(lat_deg, -85.0, 85.0)\n    return d2r(lon_deg), log(tan(π / 4 + d2r(lat) / 2))\nend\n\nfunction mollweide_theta(lat_deg)\n    abs(lat_deg) >= 89.9 && return sign(lat_deg) * (π / 2)\n    phi   = d2r(lat_deg)\n    theta = phi\n    rhs   = π * sin(phi)\n    for _ in 1:60\n        denom = 2.0 + 2.0 * cos(2theta)\n        abs(denom) < 1e-14 && break\n        theta -= (2theta + sin(2theta) - rhs) / denom\n    end\n    return theta\nend\n\nfunction proj_mollweide(lon_deg, lat_deg)\n    theta = mollweide_theta(lat_deg)\n    return (2sqrt(2) / π) * d2r(lon_deg) * cos(theta), sqrt(2) * sin(theta)\nend\n\nfunction proj_orthographic(lon_deg, lat_deg)\n    lam = d2r(lon_deg)\n    phi = d2r(lat_deg)\n    cos(phi) * cos(lam) < 0.0 && return (NaN, NaN)\n    return cos(phi) * sin(lam), sin(phi)\nend\n\nconst EE_M  = sqrt(3.0) / 2.0\nconst EE_A1 = 1.340264\nconst EE_A2 = -0.081106\nconst EE_A3 = 0.000893\nconst EE_A4 = 0.003796\n\nfunction proj_equal_earth(lon_deg, lat_deg)\n    lam    = d2r(lon_deg)\n    phi    = d2r(lat_deg)\n    theta  = asin(clamp(EE_M * sin(phi), -1.0, 1.0))\n    theta2 = theta * theta\n    P      = EE_A1 + theta2 * (EE_A2 + theta2 * (EE_A3 + theta2 * EE_A4))\n    return lam * cos(theta) / (EE_M * P), theta * P\nend\n\n# --- Simplified continent outlines (lon, lat) in degrees, roughly clockwise ---\n\nconst CONTINENT_POLYS = [\n    # Africa\n    [(-5.4,36.0),(10.0,37.3),(12.0,33.0),(25.0,31.3),(32.5,31.0),\n     (36.5,22.0),(43.0,14.8),(50.0,11.8),(42.5,11.5),(42.0,0.0),\n     (40.5,-10.0),(35.5,-20.0),(33.0,-27.0),(26.5,-30.8),(18.8,-34.8),\n     (16.5,-29.5),(12.5,-17.0),(9.5,-5.5),(8.5,1.0),(2.5,4.8),\n     (-3.0,4.8),(-8.5,4.5),(-15.0,11.0),(-17.5,14.7),(-17.0,21.0),\n     (-13.0,27.7),(-5.4,36.0)],\n    # Eurasia (simplified — Malay Peninsula omitted for clean outline)\n    [(-9.0,37.0),(-4.5,48.5),(2.0,51.0),(8.0,57.5),(17.5,57.5),\n     (22.0,59.5),(28.0,65.5),(32.0,70.0),(50.0,74.0),(80.0,74.0),\n     (110.0,73.5),(130.0,72.5),(143.0,70.0),(141.0,52.0),(135.5,43.0),\n     (130.5,35.5),(121.5,29.5),(114.0,21.5),(105.0,10.0),(80.0,9.0),\n     (77.5,8.5),(80.0,22.0),(68.0,22.0),(61.0,24.0),(55.5,23.0),\n     (44.0,12.0),(43.5,11.5),(45.0,15.0),(37.0,22.0),(32.5,29.0),\n     (34.5,36.5),(36.0,36.5),(29.5,41.0),(26.5,41.5),(22.0,40.0),\n     (14.5,40.5),(13.0,38.0),(13.0,44.5),(7.5,44.0),(1.5,43.5),\n     (-2.5,44.0),(-9.0,37.0)],\n    # North America\n    [(-168.0,71.0),(-140.0,72.5),(-100.0,73.0),(-80.0,73.0),\n     (-65.0,63.0),(-53.0,47.0),(-66.5,44.5),(-70.5,43.0),\n     (-75.5,35.0),(-81.0,25.0),(-87.0,16.0),(-83.5,10.5),\n     (-77.5,8.0),(-84.0,10.0),(-90.0,14.0),(-97.0,19.0),\n     (-104.0,19.0),(-110.0,23.5),(-118.0,32.0),(-120.5,34.5),\n     (-124.0,46.0),(-130.0,55.0),(-137.0,60.0),(-152.0,60.0),\n     (-165.0,62.0),(-168.0,65.0),(-168.0,71.0)],\n    # South America\n    [(-80.0,8.0),(-75.0,10.5),(-63.0,10.5),(-60.0,6.5),\n     (-51.0,4.0),(-50.0,0.0),(-51.0,-3.0),(-36.0,-5.0),\n     (-35.0,-8.0),(-37.5,-12.0),(-39.0,-23.0),(-43.0,-23.0),\n     (-48.0,-28.5),(-52.0,-34.0),(-58.0,-34.0),(-62.0,-38.0),\n     (-65.0,-40.0),(-65.5,-45.0),(-65.5,-55.0),(-68.5,-54.5),\n     (-73.0,-50.0),(-75.0,-42.0),(-72.0,-36.0),(-72.0,-30.0),\n     (-70.0,-18.0),(-76.0,-2.0),(-80.0,0.0),(-80.0,8.0)],\n    # Australia\n    [(114.0,-22.0),(114.0,-34.0),(117.0,-35.5),(122.0,-34.0),\n     (126.0,-34.0),(131.0,-31.0),(132.0,-29.0),(125.0,-14.0),\n     (130.0,-12.0),(136.0,-12.0),(137.0,-16.0),(141.0,-16.0),\n     (145.0,-14.5),(146.5,-19.0),(149.0,-21.0),(153.5,-28.0),\n     (152.0,-33.0),(150.5,-37.0),(148.0,-38.5),(146.0,-39.0),\n     (143.5,-39.0),(141.0,-38.5),(132.0,-33.0),(126.0,-34.0),\n     (122.0,-34.0),(117.0,-35.5),(114.0,-34.0),(114.0,-22.0)],\n    # Antarctica — coastal ring then south-pole closure\n    [(-180.0,-70.0),(-170.0,-72.0),(-160.0,-70.0),(-150.0,-68.0),\n     (-140.0,-70.0),(-130.0,-72.0),(-120.0,-70.0),(-110.0,-68.0),\n     (-100.0,-70.0),(-90.0,-72.0),(-80.0,-70.0),(-70.0,-68.0),\n     (-60.0,-70.0),(-50.0,-72.0),(-40.0,-70.0),(-30.0,-68.0),\n     (-20.0,-70.0),(-10.0,-72.0),(0.0,-70.0),(10.0,-68.0),\n     (20.0,-70.0),(30.0,-72.0),(40.0,-70.0),(50.0,-68.0),\n     (60.0,-70.0),(70.0,-72.0),(80.0,-70.0),(90.0,-68.0),\n     (100.0,-70.0),(110.0,-72.0),(120.0,-70.0),(130.0,-68.0),\n     (140.0,-70.0),(150.0,-72.0),(160.0,-70.0),(170.0,-68.0),\n     (180.0,-70.0),(90.0,-89.9),(0.0,-89.9),(-90.0,-89.9),\n     (-180.0,-70.0)],\n    # Greenland (illustrates Mercator distortion)\n    [(-44.0,83.0),(-18.0,77.0),(-12.0,76.0),(-18.0,73.0),\n     (-27.5,71.0),(-25.0,69.0),(-17.0,67.5),(-22.0,65.0),\n     (-25.0,65.0),(-38.0,65.5),(-45.0,60.5),(-52.0,66.0),\n     (-57.0,68.0),(-60.0,72.0),(-67.0,77.0),(-68.0,80.0),\n     (-60.0,83.0),(-44.0,83.0)],\n]\n\n# --- Drawing helpers ---\n\nfunction draw_lines_nan!(ax, xs, ys; color, linewidth)\n    n = length(xs)\n    i = 1\n    while i <= n\n        if !isnan(xs[i]) && !isnan(ys[i])\n            j = i + 1\n            while j <= n && !isnan(xs[j]) && !isnan(ys[j])\n                j += 1\n            end\n            j > i + 1 && lines!(ax, xs[i:j-1], ys[i:j-1]; color=color, linewidth=linewidth)\n            i = j\n        else\n            i += 1\n        end\n    end\nend\n\nfunction draw_continents!(ax, proj_fn; fill_color, stroke_color)\n    for poly_ll in CONTINENT_POLYS\n        pts = Point2f[]\n        for (lon, lat) in poly_ll\n            x, y = proj_fn(lon, lat)\n            !isnan(x) && !isnan(y) && push!(pts, Point2f(x, y))\n        end\n        length(pts) >= 3 && poly!(ax, pts;\n            color=fill_color,\n            strokecolor=stroke_color,\n            strokewidth=0.7)\n    end\nend\n\nfunction draw_graticule!(ax, proj_fn; lon_step=30, lat_step=30, n_pts=200,\n                         color=BRAND, linewidth=0.7)\n    for lon in -180:lon_step:180\n        lats = range(-89.9, 89.9; length=n_pts)\n        xs = Float64[]; ys = Float64[]\n        for lat in lats\n            x, y = proj_fn(lon, lat)\n            push!(xs, x); push!(ys, y)\n        end\n        draw_lines_nan!(ax, xs, ys; color=color, linewidth=linewidth)\n    end\n    for lat in -90:lat_step:90\n        lons = range(-180, 180; length=n_pts)\n        xs = Float64[]; ys = Float64[]\n        for lon in lons\n            x, y = proj_fn(lon, lat)\n            push!(xs, x); push!(ys, y)\n        end\n        draw_lines_nan!(ax, xs, ys; color=color, linewidth=linewidth)\n    end\nend\n\nfunction draw_tissot!(ax, proj_fn; fill_color=BRAND, r_deg=7.0, n_pts=60)\n    for lon in -150:60:150, lat in -60:30:60\n        cos_lat = max(cosd(lat), 0.01)\n        pts     = Point2f[]\n        any_nan = false\n        for k in 0:n_pts-1\n            ang    = 2π * k / n_pts\n            x, y   = proj_fn(lon + r_deg * sin(ang) / cos_lat,\n                             clamp(lat + r_deg * cos(ang), -89.0, 89.0))\n            if isnan(x) || isnan(y)\n                any_nan = true\n            else\n                push!(pts, Point2f(x, y))\n            end\n        end\n        !any_nan && length(pts) >= 3 && poly!(ax, pts;\n            color=(fill_color, 0.20f0),\n            strokecolor=(fill_color, 0.85f0),\n            strokewidth=0.9)\n    end\nend\n\n# --- Projection boundary shapes ---\n\nfunction boundary_rect(x0, x1, y0, y1)\n    [Point2f(x0, y0), Point2f(x1, y0), Point2f(x1, y1), Point2f(x0, y1)]\nend\n\nfunction boundary_ellipse(a, b; n=300)\n    ts = range(0, 2π; length=n)\n    [Point2f(a * cos(t), b * sin(t)) for t in ts]\nend\n\nfunction boundary_equal_earth(; n=200)\n    lats      = collect(range(-90, 90; length=n))\n    right     = [Point2f(proj_equal_earth(180, lat)...)  for lat in lats]\n    top_pole  = [Point2f(proj_equal_earth(lon, 90)...)   for lon in range(180, -180; length=60)]\n    left      = [Point2f(proj_equal_earth(-180, lat)...) for lat in reverse(lats)]\n    bot_pole  = [Point2f(proj_equal_earth(lon, -90)...)  for lon in range(-180, 180; length=60)]\n    vcat(right, top_pole, left, bot_pole)\nend\n\n# --- Layout ---\n\nconst merc_y85   = proj_mercator(0, 85.0)[2]\nconst grat_alpha = THEME == \"light\" ? 0.55f0 : 0.70f0\nconst grat_color = RGBAf(Float32(BRAND.r), Float32(BRAND.g), Float32(BRAND.b), grat_alpha)\nconst bord_color = RGBAf(Float32(INK_SOFT.r), Float32(INK_SOFT.g), Float32(INK_SOFT.b), 1.0f0)\nconst land_stroke = RGBAf(Float32(INK_SOFT.r), Float32(INK_SOFT.g), Float32(INK_SOFT.b), 0.8f0)\n\nfig = Figure(size=(1600, 900), fontsize=14, backgroundcolor=PAGE_BG)\n\nLabel(fig[0, 1:2];\n    text=\"map-projections · julia · makie · anyplot.ai\",\n    fontsize=20, color=INK, tellwidth=false, padding=(0, 0, 6, 0))\n\nspecs = [\n    (proj_mercator,     \"Mercator — Conformal\",       1, 1,\n     boundary_rect(-π, π, -merc_y85, merc_y85),\n     (-π * 1.06, π * 1.06, -merc_y85 * 1.08, merc_y85 * 1.08)),\n    (proj_mollweide,    \"Mollweide — Equal-Area\",      1, 2,\n     boundary_ellipse(2sqrt(2), sqrt(2)),\n     (-3.05, 3.05, -1.52, 1.52)),\n    (proj_orthographic, \"Orthographic — Perspective\",  2, 1,\n     boundary_ellipse(1.0, 1.0),\n     (-1.12, 1.12, -1.12, 1.12)),\n    (proj_equal_earth,  \"Equal Earth — Equal-Area\",    2, 2,\n     boundary_equal_earth(),\n     (-2.9, 2.9, -1.48, 1.48)),\n]\n\nfor (proj_fn, title, row, col, bnd_pts, (xlo, xhi, ylo, yhi)) in specs\n    ax = Axis(fig[row, col];\n        title              = title,\n        titlesize          = 16,\n        titlecolor         = INK,\n        backgroundcolor    = PAGE_BG,\n        topspinevisible    = false,\n        rightspinevisible  = false,\n        leftspinevisible   = false,\n        bottomspinevisible = false,\n        xticksvisible      = false,\n        yticksvisible      = false,\n        xticklabelsvisible = false,\n        yticklabelsvisible = false,\n        xgridvisible       = false,\n        ygridvisible       = false,\n    )\n    xlims!(ax, xlo, xhi)\n    ylims!(ax, ylo, yhi)\n    poly!(ax, bnd_pts; color=ELEVATED_BG, strokewidth=0)\n    draw_graticule!(ax, proj_fn; color=grat_color, linewidth=0.7)\n    draw_continents!(ax, proj_fn; fill_color=ELEVATED_BG, stroke_color=land_stroke)\n    draw_tissot!(ax, proj_fn; fill_color=BRAND)\n    poly!(ax, bnd_pts; color=(:white, 0.0f0), strokecolor=bord_color, strokewidth=1.5)\nend\n\nrowgap!(fig.layout, 8)\ncolgap!(fig.layout, 8)\n\nsave(\"plot-$(THEME).png\", fig; px_per_unit=2)\n"}