{"spec_id":"map-projections","library":"ggplot2","language":"r","code":"#' anyplot.ai\n#' map-projections: World Map with Different Projections\n#' Library: ggplot2 3.5.1 | R 4.4.1\n#' Quality: 85/100 | Created: 2026-05-23\n\nlibrary(ggplot2)\nlibrary(maps)\nlibrary(ragg)\n\n# Theme tokens\nTHEME       <- Sys.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG     <- if (THEME == \"light\") \"#FAF8F1\" else \"#1A1A17\"\nINK         <- if (THEME == \"light\") \"#1A1A17\" else \"#F0EFE8\"\nINK_SOFT    <- if (THEME == \"light\") \"#4A4A44\" else \"#B8B7B0\"\nOCEAN_BG    <- if (THEME == \"light\") \"#C4DCF0\" else \"#152030\"\nLAND_BG     <- if (THEME == \"light\") \"#D4C9A8\" else \"#2A3020\"\n\nIMPRINT <- c(\n    \"#009E73\", \"#C475FD\", \"#AE3030\", \"#4467A3\",\n    \"#99B314\", \"#954477\", \"#BD8233\"\n)\n\n# --- Mollweide projection math -----------------------------------------------\n# Solve 2*theta + sin(2*theta) = pi*sin(lat) via Newton-Raphson\nsolve_theta <- function(phi_vec) {\n    sapply(phi_vec, function(p) {\n        if (is.na(p)) return(NA_real_)\n        if (abs(p) >= pi / 2 - 1e-9) return(sign(p) * pi / 2)\n        t <- p\n        for (i in seq_len(60)) {\n            delta <- (2 * t + sin(2 * t) - pi * sin(p)) / (2 + 2 * cos(2 * t))\n            t     <- t - delta\n            if (abs(delta) < 1e-11) break\n        }\n        t\n    })\n}\n\nmollweide <- function(lon_deg, lat_deg) {\n    lon   <- lon_deg * pi / 180\n    lat   <- lat_deg * pi / 180\n    theta <- solve_theta(lat)\n    data.frame(\n        x = (2 * sqrt(2) / pi) * lon * cos(theta),\n        y = sqrt(2) * sin(theta)\n    )\n}\n\n# --- World country boundaries (projected) ------------------------------------\nworld      <- map_data(\"world\")\nworld_proj <- mollweide(world$long, world$lat)\nworld$x    <- world_proj$x\nworld$y    <- world_proj$y\n\n# --- Projection boundary (Mollweide ellipse: a=2√2, b=√2) -------------------\nt_ell    <- seq(0, 2 * pi, length.out = 721)\nboundary <- data.frame(\n    x = 2 * sqrt(2) * cos(t_ell),\n    y = sqrt(2) * sin(t_ell)\n)\n\n# --- Graticule (lat/lon grid at 30° intervals) --------------------------------\nlat_dense <- seq(-90,  90,  length.out = 541)\nlon_dense <- seq(-180, 180, length.out = 1081)\n\nmeridians <- do.call(rbind, lapply(seq(-180, 180, by = 30), function(lon0) {\n    pts <- mollweide(rep(lon0, length(lat_dense)), lat_dense)\n    data.frame(x = pts$x, y = pts$y, group = paste0(\"mer_\", lon0))\n}))\n\nparallels <- do.call(rbind, lapply(seq(-90, 90, by = 30), function(lat0) {\n    pts <- mollweide(lon_dense, rep(lat0, length(lon_dense)))\n    data.frame(x = pts$x, y = pts$y, group = paste0(\"par_\", lat0))\n}))\n\ngraticule <- rbind(meridians, parallels)\n\n# --- Tissot indicatrices (small geodesic circles on the sphere) ---------------\n# Radius in degrees; longitude offset corrected for latitude (cos projection)\nr_deg    <- 5\nt_circle <- seq(0, 2 * pi, length.out = 73)   # 73 pts → closed polygon\n\nlat_centers <- seq(-60, 60, by = 30)   # 5 latitude bands\nlon_centers <- seq(-150, 150, by = 60) # 6 longitude positions\n\ntissot <- do.call(rbind, lapply(lat_centers, function(lat0) {\n    cos_lat <- max(cos(lat0 * pi / 180), 0.08)\n    do.call(rbind, lapply(lon_centers, function(lon0) {\n        lon_c <- lon0 + r_deg * cos(t_circle) / cos_lat\n        lat_c <- pmax(pmin(lat0 + r_deg * sin(t_circle), 89.9), -89.9)\n        pts   <- mollweide(lon_c, lat_c)\n        data.frame(\n            x     = pts$x,\n            y     = pts$y,\n            group = paste0(\"t_\", lon0, \"_\", lat0)\n        )\n    }))\n}))\n\n# --- Equator and prime meridian highlights -----------------------------------\nequator <- mollweide(lon_dense, rep(0, length(lon_dense)))\nequator$group <- \"equator\"\n\nprime_merid <- mollweide(rep(0, length(lat_dense)), lat_dense)\nprime_merid$group <- \"prime\"\n\n# --- Plot --------------------------------------------------------------------\np <- ggplot() +\n    # Ocean fill\n    geom_polygon(\n        data = boundary, aes(x = x, y = y),\n        fill = OCEAN_BG, color = NA\n    ) +\n    # Land masses (country polygons)\n    geom_polygon(\n        data = world, aes(x = x, y = y, group = group),\n        fill = LAND_BG, color = NA\n    ) +\n    # Country borders / coastlines\n    geom_path(\n        data = world, aes(x = x, y = y, group = group),\n        color = INK_SOFT, linewidth = 0.10, alpha = 0.55\n    ) +\n    # Standard graticule (30° grid)\n    geom_path(\n        data = graticule, aes(x = x, y = y, group = group),\n        color = INK_SOFT, linewidth = 0.18, alpha = 0.45\n    ) +\n    # Equator and prime meridian — slightly bolder\n    geom_path(\n        data = equator, aes(x = x, y = y, group = group),\n        color = INK_SOFT, linewidth = 0.35, alpha = 0.70\n    ) +\n    geom_path(\n        data = prime_merid, aes(x = x, y = y, group = group),\n        color = INK_SOFT, linewidth = 0.35, alpha = 0.70\n    ) +\n    # Tissot indicatrices — anyplot brand green (position 1)\n    geom_polygon(\n        data = tissot, aes(x = x, y = y, group = group),\n        fill = IMPRINT[1], color = PAGE_BG,\n        linewidth = 0.06, alpha = 0.78\n    ) +\n    # Ellipse outline\n    geom_path(\n        data = boundary, aes(x = x, y = y),\n        color = INK_SOFT, linewidth = 0.40\n    ) +\n    coord_fixed() +\n    labs(\n        title    = \"map-projections · r · ggplot2 · anyplot.ai\",\n        subtitle = \"Mollweide equal-area projection  ·  Tissot indicatrices (green) confirm equal-area: every circle covers identical surface\"\n    ) +\n    theme_void(base_size = 8) +\n    theme(\n        plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),\n        plot.title      = element_text(\n            color  = INK, size = 12, hjust = 0.5,\n            margin = margin(t = 14, b = 6)\n        ),\n        plot.subtitle   = element_text(\n            color  = INK_SOFT, size = 9.5, hjust = 0.5,\n            margin = margin(b = 12)\n        ),\n        plot.margin     = margin(8, 50, 12, 50)\n    )\n\n# --- Save --------------------------------------------------------------------\nggsave(\n    filename = sprintf(\"plot-%s.png\", THEME),\n    plot     = p,\n    device   = ragg::agg_png,\n    width    = 8,\n    height   = 4.5,\n    units    = \"in\",\n    dpi      = 400\n)\n"}