用Vue + Mapbox 制作太阳系图
以下是使用 Vue + Mapbox 制作太阳系图的详细步骤和代码示例:
一、项目准备
-
创建 Vue 项目
vue create solar-system-map cd solar-system-map npm install mapbox-gl --save
-
引入 Mapbox 样式
在public/index.html
或 Vue 组件中添加:<link href="https://api.mapbox.com/mapbox-gl-js/v2.18.0/mapbox-gl.css" rel="stylesheet" />
二、组件开发 (SolarSystem.vue
)
<template>
<div style="height: 600px;" ref="mapContainer"></div>
</template>
<script>
import mapboxgl from 'mapbox-gl';
// 行星数据(距离太阳的相对距离、颜色、半径)
const planetsData = [
{ name: '太阳', distance: 0, radius: 20, color: '#FFD700' },
{ name: '水星', distance: 0.4, radius: 3, color: '#8A8A8A' },
{ name: '金星', distance: 0.7, radius: 5, color: '#F0B27A' },
{ name: '地球', distance: 1.0, radius: 5, color: '#2E86C1' },
{ name: '火星', distance: 1.5, radius: 4, color: '#E74C3C' },
{ name: '木星', distance: 5.2, radius: 12, color: '#F4D03F' },
{ name: '土星', distance: 9.5, radius: 10, color: '#D4AC0D' },
{ name: '天王星', distance: 19.2, radius: 8, color: '#5DADE2' },
{ name: '海王星', distance: 30.1, radius: 8, color: '#2980B9' },
];
export default {
data() {
return {
map: null,
center: [0, 0], // 太阳中心坐标(经度, 纬度)
zoom: 0, // 初始缩放级别
};
},
mounted() {
this.initMap();
this.addSunAndPlanets();
},
beforeUnmount() {
// 销毁地图实例
if (this.map) {
this.map.remove();
this.map = null;
}
},
methods: {
initMap() {
// 初始化 Mapbox 地图(隐藏默认地图样式,使用黑色背景)
this.map = new mapboxgl.Map({
container: this.$refs.mapContainer,
style: {
version: 8,
sources: {},
layers: [{
id: 'background',
type: 'background',
paint: { 'background-color': '#000' },
}],
},
center: this.center,
zoom: this.zoom,
interactive: false, // 禁用地图交互
});
},
addSunAndPlanets() {
// 添加太阳(中心圆)
this.addCircleLayer('sun', this.center, 20, '#FFD700');
// 添加行星
planetsData.forEach((planet, index) => {
if (planet.name === '太阳') return; // 跳过太阳
// 计算行星位置(极坐标转经纬度,这里简化为环形分布)
const angle = (index * 40) * (Math.PI / 180); // 每个行星间隔 40 度
const distance = planet.distance * 100000; // 相对距离放大(调整视觉效果)
const lon = this.center[0] + (distance * Math.cos(angle)) / (111319.5 * Math.cos(this.center[1] * Math.PI / 180)); // 经度计算(1°≈111km)
const lat = this.center[1] + (distance * Math.sin(angle)) / 111319.5; // 纬度计算
const position = [lon, lat];
this.addCircleLayer(planet.name, position, planet.radius, planet.color);
});
},
addCircleLayer(id, position, radius, color) {
// 添加圆圈图层
this.map.addSource(id, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: { type: 'Point', coordinates: position },
}],
},
});
this.map.addLayer({
id,
type: 'circle',
source: id,
paint: {
'circle-radius': radius,
'circle-color': color,
'circle-stroke-width': 1,
'circle-stroke-color': '#fff',
},
});
},
},
};
</script>
三、关键功能说明
-
地图初始化
- 使用自定义样式(纯黑色背景),禁用地图交互(
interactive: false
),专注于可视化。 - 中心坐标设为
[0, 0]
(太阳位置),缩放级别zoom: 0
确保足够大的显示范围。
- 使用自定义样式(纯黑色背景),禁用地图交互(
-
行星数据结构
distance
表示相对太阳的距离(以地球为 1 单位,按比例缩放)。radius
和color
定义视觉样式。
-
坐标计算
- 极坐标转经纬度:将行星按环形分布,角度间隔
40°
,距离按比例放大以适配地图显示。 - 经纬度转换公式:利用地球表面距离公式(1°纬度≈111km,1°经度≈111km×cos(纬度))。
- 极坐标转经纬度:将行星按环形分布,角度间隔
-
圆圈图层
- 每个天体(太阳和行星)使用独立的 GeoJSON 数据源和圆圈图层,方便后续动画更新。
四、优化与扩展
-
动画效果(行星旋转)
添加定时器,定期更新行星的角度和坐标:mounted() { this.initMap(); this.addSunAndPlanets(); this.startAnimation(); // 启动动画 }, methods: { startAnimation() { setInterval(() => { planetsData.forEach((planet, index) => { if (planet.name === '太阳') return; const angle = (index * 40 + Date.now() * 0.1) * (Math.PI / 180); // 角度随时间变化 // 重新计算坐标并更新数据源 const newPosition = this.calculatePosition(angle, planet.distance); this.updatePlanetPosition(planet.name, newPosition); }); }, 100); }, calculatePosition(angle, distance) { // 同上坐标计算逻辑 }, updatePlanetPosition(id, position) { const source = this.map.getSource(id); source.setData({ type: 'FeatureCollection', features: [{ type: 'Feature', geometry: { type: 'Point', coordinates: position }, }], }); }, },
-
交互增强
- 添加鼠标悬停提示(使用
mapbox-gl
的queryRenderedFeatures
方法)。 - 支持缩放和拖动(取消
interactive: false
,并调整视图逻辑)。
- 添加鼠标悬停提示(使用
-
3D 效果
- 使用 Mapbox 的 3D 模型(
3d-model
图层)或结合 Three.js 实现更真实的 3D 太阳系。
- 使用 Mapbox 的 3D 模型(
五、运行项目
npm run serve
打开浏览器访问 http://localhost:8080
,即可看到太阳系可视化效果,行星按轨道分布并可添加旋转动画。
通过以上步骤,利用 Mapbox 的自定义图层功能,结合 Vue 的组件化开发,实现了太阳系的交互式可视化。可根据需求进一步优化数据精度和视觉效果。
在 Mapbox 中实现太阳和行星的纹理(如真实表面纹理或图片贴图),需要利用 自定义图标(Symbol Layer) 或 Canvas 绘制(因 Mapbox 的 circle-layer
不支持直接纹理贴图)。以下是具体实现方案:
六、如何增加太阳和行星的纹理
Mapbox 的 symbol-layer
支持加载外部图片作为图标,可直接显示纹理。步骤如下:
1. 准备纹理图片
从公开资源(如 NASA、维基百科)下载天体纹理图(建议正方形图片,尺寸如 64x64px
、128x128px
),命名为 sun.png
、earth.png
等,放入项目 public
目录或通过 URL 引用。
示例纹理资源:
2. 修改组件代码(替换 circle-layer
为 symbol-layer
)
<template>
<div style="height: 600px;" ref="mapContainer"></div>
</template>
<script>
import mapboxgl from 'mapbox-gl';
const planetsData = [
{
name: '太阳',
distance: 0,
icon: '/sun.png', // 纹理图片路径
size: 40, // 图标大小(px)
anchor: 'center' // 图标锚点
},
{
name: '地球',
distance: 1.0,
icon: '/earth.png',
size: 10,
anchor: 'center'
},
// 其他行星...
];
export default {
mounted() {
this.initMap();
this.addSunAndPlanets();
},
methods: {
initMap() {
this.map = new mapboxgl.Map({
container: this.$refs.mapContainer,
style: 'mapbox://styles/mapbox/dark-v11', // 深色背景样式
center: [0, 0],
zoom: 0,
interactive: false,
});
},
addSunAndPlanets() {
planetsData.forEach((planet) => {
// 加载图标(需在添加图层前注册)
this.map.loadImage(planet.icon, (error, image) => {
if (error) throw error;
// 注册自定义图标
this.map.addImage(planet.name, image, {
pixelRatio: 2, // 高清显示(可选)
});
// 添加符号图层(显示纹理图标)
this.map.addSource(planet.name, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: this.calculatePosition(planet.distance) // 计算坐标
},
}],
},
});
this.map.addLayer({
id: planet.name,
type: 'symbol',
source: planet.name,
layout: {
'symbol-placement': 'point',
'icon-image': planet.name, // 引用注册的图标
'icon-size': planet.size / 64, // 图标大小(64px 为 Mapbox 图标默认基准)
'icon-anchor': planet.anchor, // 图标中心对齐
'icon-rotation': 0, // 禁止图标随地图旋转
},
});
});
});
},
// 计算行星坐标(极坐标转经纬度,同前)
calculatePosition(distance) {
const angle = Math.random() * 360 * (Math.PI / 180); // 示例角度(可替换为轨道计算)
const distanceScale = distance * 100000; // 距离缩放
const lon = 0 + (distanceScale * Math.cos(angle)) / (111319.5 * Math.cos(0));
const lat = 0 + (distanceScale * Math.sin(angle)) / 111319.5;
return [lon, lat];
},
},
};
</script>
3. 进阶方案:使用 Canvas 绘制纹理(复杂纹理或动态效果)
若需更精细的纹理(如云层动画、环形山等),可通过 Canvas 动态绘制纹理,再作为图标加载:
- 生成 Canvas 纹理
// 生成太阳纹理(示例:带光斑的圆形)
function createSunCanvas() {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(32, 32, 30, 0, 2 * Math.PI);
ctx.fillStyle = '#FFD700';
ctx.shadowColor = 'rgba(255, 215, 0, 0.5)';
ctx.shadowBlur = 10;
ctx.fill();
return canvas;
}
- 注册 Canvas 生成的图标
// 在 addSunAndPlanets 中替换图片加载
const canvas = createSunCanvas();
this.map.addImage('sun', canvas);
4. 关键配置说明
-
图标锚点(
icon-anchor
)- 设置为
center
确保图标中心与坐标点对齐,避免偏移。
- 设置为
-
图标大小(
icon-size
)- Mapbox 图标默认基准为
64px
,icon-size: 1
表示原始大小。若图片为128px
,设置icon-size: 0.5
可缩小为一半。
- Mapbox 图标默认基准为
-
跨域问题
- 本地图片放入
public
目录,通过相对路径(如/textures/earth.png
)引用,避免跨域。
- 本地图片放入
5. 效果优化
-
高清显示
- 使用
pixelRatio: window.devicePixelRatio
适配视网膜屏幕:this.map.addImage(planet.name, image, { pixelRatio: window.devicePixelRatio });
- 使用
-
纹理混合
- 在图标图层下方添加半透明的
circle-layer
作为光晕(如太阳的光芒):// 添加光晕图层 this.map.addLayer({ id: `${planet.name}-glow`, type: 'circle', source: planet.name, paint: { 'circle-radius': planet.size * 1.2, 'circle-color': 'rgba(255, 215, 0, 0.3)', 'circle-blur': 0.5, }, });
- 在图标图层下方添加半透明的
-
动态纹理(如行星自转)
- 通过定时器更新 Canvas 绘制内容,重新注册图标并刷新图层:
setInterval(() => { const newCanvas = drawRotatedEarth(); // 绘制自转后的地球纹理 this.map.removeImage('earth'); this.map.addImage('earth', newCanvas); }, 1000);
- 通过定时器更新 Canvas 绘制内容,重新注册图标并刷新图层:
6.资源推荐
-
免费天体纹理库
-
Mapbox 图标最佳实践
通过上述方法,可在 Mapbox 中实现带纹理的太阳系天体显示,结合 Vue 的响应式特性,还能动态更新纹理或添加交互(如点击显示天体信息)。