Equal Earth GeoJSON
Runnable OpenLayers example: Equal Earth GeoJSON. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.
single HTML file · CDN dependencies onlyinterface may contain Chinese labels
Source
<!--
示例:Equal Earth 投影 — OpenLayers equal-earth-geojson
核心逻辑:用解析公式自建投影,GeoJSON 以 4326 读入并转入该投影,底图随视口重投影
依赖:OpenLayers 10.3.1 · jsDelivr CDN
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Equal Earth 投影</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10.3.1/ol.css">
<style>
html,body{height:100%;margin:0}
body{display:flex;flex-direction:column;overflow:hidden;
font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif}
.bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
display:flex;gap:12px;align-items:center;font-size:12px;color:#58606e}
#map{flex:1 1 auto;min-height:0;background:#eef0f3}
</style>
</head>
<body>
<div class="bar">
<span>投影 EPSG:8857 · Equal Earth</span>
<span id="info">—</span>
</div>
<div id="map"></div>
<script src="https://cdn.jsdelivr.net/npm/ol@10.3.1/dist/ol.js"></script>
<script>
(function () {
'use strict';
if (typeof ol === 'undefined') {
document.getElementById('map').innerHTML =
'<div style="padding:20px;color:#b42318">OpenLayers 加载失败,请检查能否访问 cdn.jsdelivr.net</div>';
return;
}
/* Equal Earth 公式常数,R 取球面半径 */
var A1 = 1.340264, A2 = -0.081106, A3 = 0.000893, A4 = 0.003796;
var M = Math.sqrt(3) / 2;
var R = 6371007.181;
var RAD = Math.PI / 180;
var DEG = 180 / Math.PI;
function forward(coord) {
var lon = coord[0] * RAD;
var lat = coord[1] * RAD;
var theta = Math.asin(M * Math.sin(lat));
var t2 = theta * theta, t3 = t2 * theta, t4 = t3 * theta;
var t5 = t4 * theta, t6 = t5 * theta, t7 = t6 * theta;
return [
R * Math.cos(theta) * (A1 + 3 * A2 * t2 + 7 * A3 * t4 + 9 * A4 * t6) * lon,
R * (A1 * theta + A2 * t3 + A3 * t5 + A4 * t7)
];
}
function inverse(coord) {
var x = coord[0] / R;
var y = coord[1] / R;
var theta = y / A1;
for (var i = 0; i < 12; i++) {
var t2 = theta * theta, t3 = t2 * theta, t4 = t2 * t2, t6 = t4 * t2;
var f = A1 * theta + A2 * t3 + A3 * t4 * theta + A4 * t6 * theta - y;
var fp = A1 + 3 * A2 * t2 + 5 * A3 * t4 + 7 * A4 * t6;
theta -= f / fp;
}
var s2 = theta * theta, s4 = s2 * s2, s6 = s4 * s2;
var lon = x / (Math.cos(theta) * (A1 + 3 * A2 * s2 + 7 * A3 * s4 + 9 * A4 * s6));
var lat = Math.asin(Math.sin(theta) / M);
return [lon * DEG, lat * DEG];
}
var equalEarth = new ol.proj.Projection({
code: 'EPSG:8857',
units: 'm',
extent: [-26830000, -8387000, 26830000, 8387000],
global: true
});
ol.proj.addProjection(equalEarth);
ol.proj.addCoordinateTransforms('EPSG:4326', equalEarth, forward, inverse);
/* 粗略的大陆轮廓,够看出投影的形变特征 */
var geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
properties: { name: '欧亚大陆' },
geometry: { type: 'Polygon', coordinates: [[
[-10, 36], [0, 43], [12, 45], [28, 41], [40, 41], [50, 46],
[60, 44], [75, 40], [88, 30], [100, 22], [110, 20], [120, 24],
[122, 38], [130, 43], [140, 50], [150, 60], [170, 66], [180, 68],
[180, 72], [140, 75], [100, 78], [60, 72], [30, 70], [10, 60],
[-5, 50], [-10, 36]
]] }
},
{
type: 'Feature',
properties: { name: '非洲' },
geometry: { type: 'Polygon', coordinates: [[
[-17, 15], [-5, 5], [8, 4], [10, -2], [13, -12], [20, -20],
[26, -33], [32, -30], [40, -15], [43, 0], [38, 12], [33, 22],
[25, 32], [10, 34], [0, 30], [-10, 25], [-17, 15]
]] }
},
{
type: 'Feature',
properties: { name: '美洲' },
geometry: { type: 'Polygon', coordinates: [[
[-168, 65], [-140, 70], [-110, 72], [-85, 70], [-60, 55],
[-55, 45], [-75, 30], [-85, 20], [-100, 18], [-115, 30],
[-125, 42], [-140, 58], [-168, 65]
], [
[-80, 8], [-60, 10], [-35, -5], [-38, -22], [-55, -35],
[-70, -45], [-75, -20], [-80, 8]
]] }
},
{
type: 'Feature',
properties: { name: '澳大利亚' },
geometry: { type: 'Polygon', coordinates: [[
[114, -22], [122, -18], [135, -15], [145, -18], [153, -28],
[150, -38], [140, -38], [130, -32], [115, -34], [114, -22]
]] }
}
]
};
var vectorSource = new ol.source.Vector({
features: new ol.format.GeoJSON().readFeatures(geojson, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:8857'
})
});
var vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: function (feature) {
return new ol.style.Style({
fill: new ol.style.Fill({ color: 'rgba(31,95,208,0.35)' }),
stroke: new ol.style.Stroke({ color: '#1f5fd0', width: 2 }),
text: new ol.style.Text({
text: feature.get('name'),
font: '500 13px sans-serif',
fill: new ol.style.Fill({ color: '#16181d' }),
stroke: new ol.style.Stroke({ color: '#ffffff', width: 3 })
})
});
}
});
var map = new ol.Map({
target: 'map',
layers: [
/* OSM 是 3857 瓦片,视口为 8857,渲染层负责重投影 */
new ol.layer.Tile({ source: new ol.source.XYZ({url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', attributions: 'Tiles © Esri'}) }),
vectorLayer
],
view: new ol.View({
projection: 'EPSG:8857',
center: [0, 0],
zoom: 1,
minZoom: 0,
maxZoom: 10
}),
controls: ol.control.defaults.defaults({ attribution: false })
});
map.on('moveend', function () {
var v = map.getView();
var r = v.getResolution();
document.getElementById('info').textContent =
'分辨率 ' + Math.round(r / 1000) + ' km/px';
});
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.