效果演示
效果截图:
核心实现
本文将介绍如何使用 MapLibre GL 和 React 构建一个交互式的地球地图应用,实现以下功能:
- 定制地图样式:使用自定义的地图样式,打造独特的视觉效果。
- 图例与交互:在地图上显示数据中心和边缘节点的标记,并实现悬停提示信息。
- 边缘过渡遮罩:在地图的边缘添加过渡遮罩,增强视觉体验。
定制地图样式
为了使地图具有独特的风格,我使用了自定义的地图样式文件 map_style.json
。这个样式文件可以通过 Mapbox Studio 或其他工具生成。
步骤:
-
引入自定义样式文件:
import localMapStyle from "@/assets/map_style.json";
-
在
Map
组件中使用自定义样式: -
配置初始视图状态:
const initialViewState = { latitude: 0, longitude: 0, zoom: 1, };
通过以上步骤,地图将使用我们自定义的样式进行渲染。
图例与交互
为了在地图上显示数据中心和边缘节点的信息,我们需要:
- 加载数据:从 JSON 文件中加载数据中心和边缘节点的位置和信息。
- 渲染标记:使用
Marker
组件在地图上标记这些位置。 - 添加交互:在用户悬停或点击标记时显示详细信息。
1. 加载数据:
import dataCenters from "@/assets/data_centers_format.json";
import boundNodeLocations from "@/assets/boundary_node_locations.json";
2. 渲染数据中心标记:
{dataCenters.data_centers.map((center) => (
<Marker
key={center.key}
latitude={center.latitude}
longitude={center.longitude}
anchor="center"
>
<div
className="data-center-marker"
onMouseEnter={() => handleMouseEnter(center)}
onMouseLeave={handleMouseLeave}
/>
</Marker>
))}
3. 渲染边缘节点标记:
{boundNodeLocations.locations.map((node) => (
<Marker
key={node.key}
latitude={node.latitude}
longitude={node.longitude}
anchor="center"
>
<div
className="edge-node-marker"
onMouseEnter={() => handleMouseEnter(node)}
onMouseLeave={handleMouseLeave}
>
<div className="edge-node-ping"></div>
</div>
</Marker>
))}
4. 实现交互效果:
const [hoverInfo, setHoverInfo] = useState(null);
const handleMouseEnter = (info) => {
setHoverInfo({
...info,
longitude: info.longitude,
latitude: info.latitude,
});
};
const handleMouseLeave = () => {
setHoverInfo(null);
};
5. 显示悬停信息 (Popup
):
{hoverInfo && (
<Popup
longitude={hoverInfo.longitude}
latitude={hoverInfo.latitude}
anchor="bottom"
closeButton={false}
closeOnClick={false}
>
<div className="popup-content">
<div className="popup-title">{hoverInfo.name}</div>
{/* 根据类型显示不同的信息 */}
{hoverInfo.type === "dataCenter" ? (
<div className="popup-info">
<div>数据中心</div>
<div>拥有者:{hoverInfo.owner}</div>
<div>活跃节点:{hoverInfo.total_nodes}</div>
<div>节点提供商:{hoverInfo.node_providers}</div>
</div>
) : (
<div className="popup-info">
<div>边界节点</div>
<div>节点数量:{hoverInfo.total_nodes}</div>
</div>
)}
</div>
</Popup>
)}
6. 添加样式:
在 CSS 文件中添加对应的样式,以美化标记和弹出框。
.data-center-marker {
width: 12px;
height: 12px;
background-color: rgba(150, 120, 81, 0.7);
border: 2px solid #f0a03b;
border-radius: 50%;
cursor: pointer;
}
.edge-node-marker {
position: relative;
width: 12px;
height: 12px;
background-color: rgba(0, 122, 255, 0.25);
border: 2px solid #007aff;
border-radius: 50%;
cursor: pointer;
}
.edge-node-ping {
position: absolute;
top: -1px;
left: -1px;
width: 12px;
height: 12px;
background-color: rgba(0, 122, 255, 0.5);
border-radius: 50%;
animation: ping 2s infinite;
}
@keyframes ping {
0% {
transform: scale(1);
opacity: 0.75;
}
100% {
transform: scale(2);
opacity: 0;
}
}
.popup-content {
font-family: Arial, sans-serif;
}
.popup-title {
font-weight: bold;
margin-bottom: 8px;
}
.popup-info div {
margin-bottom: 4px;
}
边缘过渡遮罩
为了使地图的边缘具有过渡效果,我们可以添加一个覆盖层,并使用 CSS 渐变来实现。
1. 添加遮罩层:
在地图组件的底部添加一个 div
:
<div className="map-overlay"></div>
2. 定义遮罩层样式:
.map-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* 确保遮罩层不影响地图交互 */
background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 30%, #020104 70%);
}
通过上述样式,地图的边缘将呈现出由透明到半透明的渐变效果,提升视觉层次感。
相关源码
下面是完整的 EarthMap.tsx
代码:
import React, { useState } from "react";
import Map, { Marker, Popup } from "react-map-gl/maplibre";
import "maplibre-gl/dist/maplibre-gl.css";
import localMapStyle from "@/assets/map_style.json";
import dataCenters from "@/assets/data_centers_format.json";
import boundNodeLocations from "@/assets/boundary_node_locations.json";
export const EarthMap = ({ width = '100vw', height = '100vh' }) => {
const initialViewState = {
latitude: 0,
longitude: 0,
zoom: 1,
};
const [hoverInfo, setHoverInfo] = useState(null);
const handleMouseEnter = (info) => {
setHoverInfo({
...info,
longitude: info.longitude,
latitude: info.latitude,
});
};
const handleMouseLeave = () => {
setHoverInfo(null);
};
return (
<div style={{ height, width, position: "relative" }}>
<Map
initialViewState={initialViewState}
style={{ width, height}}
mapStyle={localMapStyle}
mapLib={import("maplibre-gl")}
attributionControl={true}
scrollZoom={false}
key={`${width}-${height}`}
>
{/* 渲染数据中心标记 */}
{dataCenters.data_centers.map((center) => (
<Marker
key={center.key}
latitude={center.latitude}
longitude={center.longitude}
anchor="center"
>
<div
className="data-center-marker"
onMouseEnter={() => handleMouseEnter({ ...center, type: "dataCenter" })}
onMouseLeave={handleMouseLeave}
/>
</Marker>
))}
{/* 渲染边缘节点标记 */}
{boundNodeLocations.locations.map((node) => (
<Marker
key={node.key}
latitude={node.latitude}
longitude={node.longitude}
anchor="center"
>
<div
className="edge-node-marker"
onMouseEnter={() => handleMouseEnter({ ...node, type: "edgeNode" })}
onMouseLeave={handleMouseLeave}
>
<div className="edge-node-ping"></div>
</div>
</Marker>
))}
{/* 显示悬停信息 */}
{hoverInfo && (
<Popup
longitude={hoverInfo.longitude}
latitude={hoverInfo.latitude}
anchor="bottom"
closeButton={false}
closeOnClick={false}
>
<div className="popup-content">
<div className="popup-title">{hoverInfo.name}</div>
{hoverInfo.type === "dataCenter" ? (
<div className="popup-info">
<div>数据中心</div>
<div>拥有者:{hoverInfo.owner}</div>
<div>活跃节点:{hoverInfo.total_nodes}</div>
<div>节点提供商:{hoverInfo.node_providers}</div>
</div>
) : (
<div className="popup-info">
<div>边界节点</div>
<div>节点数量:{hoverInfo.total_nodes}</div>
</div>
)}
</div>
</Popup>
)}
</Map>
{/* 边缘过渡遮罩 */}
<div className="map-overlay"></div>
</div>
);
};
export default EarthMap;
map_style.json
样式文件暂未公开;data_centers_format.json
等图例数据暂未公开;
相关链接
- MapLibre GL JS 文档:https://maplibre.org/maplibre-gl-js-docs/
- React Map GL 库:https://visgl.github.io/react-map-gl/
- 地图样式设计工具:Mapbox Studio
- 项目源码仓库:暂未公开
Comments NOTHING