2026-09-07 · 原文发布日
墨卡托与等积地球投影的交互动画对比
Simon Willison因联合国刚对Equal Earth投影表决而好奇,用ChatGPT里的GPT-6 Astra(medium)配D3做出一个网页小工具:拖动滑块或点播放,就能看到世界地图在墨卡托投影和等积地球投影之间平滑变形,直观对比两种投影各自牺牲了什么。工具可直接打开使用,也是一句话让AI写可视化demo的示例。
原文要点
Simon Willison于2026年9月7日发布一个小工具:因联合国刚对Equal Earth投影进行表决而好奇,他在ChatGPT里用GPT-6 Astra(medium)让AI基于D3写出一个交互动画,拖动滑块或点播放,世界地图就在墨卡托投影与等积地球投影之间平滑变形,用来直观对比两种投影分别牺牲了什么地理属性(形状还是面积)。工具可直接打开使用,原文篇幅很短,只说明动机、所用模型和效果,没有给出代码或提示词全文。
这次做成什么
复现这个练习思路:用一句话让AI生成一个网页,在两种投影之间做滑块变形动画。原文未公开提示词和代码,本文提供本站独立编写的提示词,以及一个约50行、双击即可运行的简化版HTML练习文件(用圆柱等积投影代替真正的Equal Earth、用经纬网格代替海岸线,降低门槛)。
准备什么
- 任意现代浏览器(Chrome、Edge、Firefox均可)
- 一个文本编辑器(记事本也行)用于保存HTML文件
- 可选:能联网的ChatGPT或其他AI编程助手,用于生成完整D3版本
具体怎么做
先跑通零依赖简化版
把下方材料2的HTML完整复制,存为projection-demo.html,双击用浏览器打开。拖动滑块从0到1,观察经纬网格:0端是墨卡托(高纬度纬线被拉大、间距变宽),1端是等积圆柱投影(纬度越高越被压扁),中间是连续插值变形。点播放可自动往返。
观察对比要点
停在滑块0.5处对比两端:墨卡托端经线平行、方向保角,但60°纬线间距远大于赤道;等积端各纬度面积比例真实,但高纬度形状被横向拉伸。记下这个取舍,这是两种投影各自牺牲的东西。
用AI生成完整D3版
在AI编程助手里粘贴下方材料1的提示词。它会用到D3的geoMercator与geoEqualEarth插值加世界海岸线数据。拿到代码后存为html打开,若AI引用了在线数据文件,需联网加载;加载失败就让它改成内置简化大陆轮廓。
迭代改进
对生成结果追加要求,如“在地图上叠加格陵兰岛的轮廓方便看面积失真”“加一个按钮一键切换两端”。每轮只提一个改动,比一次提多个要求更稳。生成代码不保证一次可用,报错就把错误信息贴回去让AI修。
可直接复制的材料
一句话生成D3投影变形动画的提示词
本站独立编写,非作者原始提示词;涉及的世界地图数据文件需联网加载或按提示词里的备选方案处理。
请用D3.js写一个单文件HTML交互小工具:页面有一个滑块和一个播放按钮,世界地图在墨卡托投影(d3.geoMercator)与Equal Earth投影(d3.geoEqualEarth)之间平滑变形。滑块值t从0到1时对两种投影的输出坐标做线性插值,t=0纯墨卡托,t=1纯Equal Earth;播放按钮让t自动往返。画出经纬网格和大陆轮廓(可用world-atlas的land-110m TopoJSON,失败时给出内置简化轮廓的替代写法)。顶部显示当前混合比例和两端投影名。所有代码放在一个html文件里,双击即可运行。零依赖简化版投影变形HTML
本站独立编写的教学简化版:用圆柱等积投影和经纬网格代替真正的Equal Earth与海岸线,无外部依赖,保存即用。
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="utf-8"><title>墨卡托↔等积投影变形练习</title></head>
<body style="font-family:sans-serif">
<p>0=墨卡托投影 1=等积圆柱投影(简化练习,非真正的Equal Earth)</p>
<input id="t" type="range" min="0" max="1" step="0.01" value="0" style="width:400px">
<button id="play">播放</button>
<canvas id="c" width="800" height="460" style="border:1px solid #ccc"></canvas>
<script>
const ctx=document.getElementById('c').getContext('2d');
const MAXLAT=80*Math.PI/180;
const YMAX=Math.log(Math.tan(Math.PI/4+MAXLAT/2));
function merc(la,ph){return [la,Math.log(Math.tan(Math.PI/4+ph/2))];}
function cea(la,ph){return [la,Math.sin(ph)];}
function px(la,ph,t){
const m=merc(la,ph),e=cea(la,ph);
const X=(m[0]*(1-t)+e[0]*t+Math.PI)/(2*Math.PI)*760+20;
const Y=(1-(m[1]*(1-t)+e[1]*t+YMAX)/(2*YMAX))*420+20;
return [X,Y];
}
function line(a,b,c,d){
ctx.beginPath();
for(let i=0;i<=d;i++){
const p=px(a(b,c,i/d)[0],a(b,c,i/d)[1],window.__t);
i?ctx.lineTo(p[0],p[1]):ctx.moveTo(p[0],p[1]);
}
ctx.stroke();
}
function draw(t){
window.__t=t;
ctx.clearRect(0,0,800,460);
ctx.strokeStyle='#9ab';
for(let la=-180;la<=180;la+=30)
line((b,c,f)=>[b*Math.PI/180,-MAXLAT+2*MAXLAT*f],la,0,100);
for(let ph=-75;ph<=75;ph+=15)
line((b,c,f)=>[-Math.PI+2*Math.PI*f,b*Math.PI/180],ph,0,120);
ctx.fillStyle='#000';
ctx.fillText('t='+t.toFixed(2)+(t<0.5?' 偏墨卡托(保形状,夸大高纬面积)':' 偏等积(保面积,压扁高纬形状)'),10,20);
}
const slider=document.getElementById('t');
slider.oninput=e=>draw(+e.target.value);
let anim=null;
document.getElementById('play').onclick=()=>{
if(anim){clearInterval(anim);anim=null;return;}
let t=+slider.value,dir=t>=1?-1:1;
anim=setInterval(()=>{
t+=dir*0.02;
if(t>=1||t<=0)dir*=-1;
slider.value=t;draw(t);
},50);
};
draw(0);
</script>
</body>
</html>输入与输出示例
本站虚构演示描述,基于简化版代码的几何行为推演,非作者原工具的运行截图。
输入
保存简化版HTML后用浏览器打开,把滑块从0拖到1,观察60°N纬线与赤道的间距变化。对应输出
t=0时60°N纬线明显远离赤道,高纬度纬线间距被纵向拉大;拖到t=1时60°N纬线被压向赤道、间距缩小到正弦比例,各纬度面积占比恢复真实但形状被横向压扁;0.5处为两端坐标的线性平均过渡态。这正是两种投影各自牺牲的直观体现:一个保形状夸大高纬面积,一个保面积牺牲形状。做完检查这些
- 双击HTML后无需联网即可看到经纬网格和滑块
- 滑块拖到0时纬线间距随纬度升高明显变宽,拖到1时间距符合sin纬度规律
- 点播放后滑块自动往返运动,再点一次停止
- 向AI索要完整D3版时,生成代码中同时出现geoMercator和geoEqualEarth
容易卡住的地方
- 简化版用的是圆柱等积投影,不是真正的Equal Earth,形状曲线会有差异,教学够用但别当原工具
- 真正的Equal Earth需要D3的d3-geo-projection支持,AI生成的完整版可能联网加载世界地图数据,离线打不开
- AI生成代码可能一次跑不通,把浏览器控制台报错贴回去迭代即可
- 投影插值是坐标线性混合,中间态不是任何正式投影,只用于演示
本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。
- 原文标题
- Mercator ↔ Equal Earth
- 原作者
- Simon Willison
- 原始发布日期
- 2026-09-07
- 来源
- https://simonwillison.net/2026/Sep/7/equal-earth/