Files
TJWaterServer/src/app/OlMap/Controls/BaseLayers.tsx
2026-02-11 11:52:06 +08:00

252 lines
8.6 KiB
TypeScript

import React, { useState, useEffect } from "react";
import { useMap } from "../MapComponent";
import TileLayer from "ol/layer/Tile.js";
import XYZ from "ol/source/XYZ.js";
import mapboxOutdoors from "@assets/map/layers/mapbox-outdoors.png";
import mapboxLight from "@assets/map/layers/mapbox-light.png";
import mapboxSatellite from "@assets/map/layers/mapbox-satellite.png";
import mapboxSatelliteStreet from "@assets/map/layers/mapbox-satellite-streets.png";
import mapboxStreets from "@assets/map/layers/mapbox-streets.png";
import clsx from "clsx";
import Group from "ol/layer/Group";
import { MAPBOX_TOKEN } from "@config/config";
import { TIANDITU_TOKEN } from "@config/config";
const INITIAL_LAYER = "mapbox-light";
const streetsLayer = new TileLayer({
source: new XYZ({
url: `https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
tileSize: 512,
maxZoom: 20,
projection: "EPSG:3857",
attributions:
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
}),
});
const lightMapLayer = new TileLayer({
source: new XYZ({
url: `https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
tileSize: 512,
maxZoom: 20,
projection: "EPSG:3857",
attributions:
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
}),
});
const satelliteLayer = new TileLayer({
source: new XYZ({
url: `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
tileSize: 512,
maxZoom: 20,
projection: "EPSG:3857",
attributions:
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
}),
});
const satelliteStreetsLayer = new TileLayer({
source: new XYZ({
url: `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
tileSize: 512,
maxZoom: 20,
projection: "EPSG:3857",
attributions:
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
}),
});
const tiandituVectorLayer = new TileLayer({
source: new XYZ({
url: `https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
projection: "EPSG:3857",
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
}),
});
const tiandituVectorAnnotationLayer = new TileLayer({
source: new XYZ({
url: `https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
projection: "EPSG:3857",
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
}),
});
const tiandituImageLayer = new TileLayer({
source: new XYZ({
url: `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
projection: "EPSG:3857",
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
}),
});
const tiandituImageAnnotationLayer = new TileLayer({
source: new XYZ({
url: `https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
projection: "EPSG:3857",
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
}),
});
const tiandituVectorLayerGroup = new Group({
layers: [tiandituVectorLayer, tiandituVectorAnnotationLayer],
});
const tiandituImageLayerGroup = new Group({
layers: [tiandituImageLayer, tiandituImageAnnotationLayer],
});
const baseLayers = [
{
id: "mapbox-light",
name: "默认地图",
layer: lightMapLayer,
// layer: tiandituVectorLayerGroup,
img: mapboxLight.src,
},
{
id: "mapbox-satellite",
name: "卫星地图",
layer: satelliteLayer,
// layer: tiandituImageLayerGroup,
img: mapboxSatellite.src,
},
{
id: "mapbox-satellite-streets",
name: "卫星街道地图",
layer: satelliteStreetsLayer,
img: mapboxSatelliteStreet.src,
},
{
id: "mapbox-streets",
name: "街道地图",
layer: streetsLayer,
img: mapboxStreets.src,
},
];
const BaseLayers: React.FC = () => {
const map = useMap();
// 切换底图选项展开,控制显示和卸载
const [isShow, setShow] = useState(false);
const [isExpanded, setExpanded] = useState(false);
// 快速切换底图
const [activeId, setActiveId] = useState(INITIAL_LAYER);
// 初始化默认底图
useEffect(() => {
if (!map) return;
// 添加所有底图至地图并根据 activeId 控制可见性
baseLayers.forEach((layerInfo) => {
const layers = map.getLayers().getArray();
if (!layers.includes(layerInfo.layer)) {
map.getLayers().insertAt(0, layerInfo.layer);
}
layerInfo.layer.setVisible(layerInfo.id === activeId);
});
}, [map]);
const changeMapLayers = (id: string) => {
if (map) {
// 根据 id 设置每个图层的可见性
baseLayers.forEach(({ id: lid, layer }) => {
layer.setVisible(lid === id);
});
}
};
const handleQuickSwitch = () => {
const nextId =
activeId === baseLayers[0].id ? baseLayers[1].id : baseLayers[0].id;
setActiveId(nextId);
handleMapLayers(nextId);
};
const handleMapLayers = (id: string) => {
setActiveId(id);
changeMapLayers(id);
};
// 记录定时器,避免多次触发
const hideTimer = React.useRef<NodeJS.Timeout | null>(null);
const handleEnter = () => {
if (hideTimer.current) {
clearTimeout(hideTimer.current);
hideTimer.current = null;
}
setShow(true);
setExpanded(true);
};
const handleLeave = () => {
setShow(false);
hideTimer.current = setTimeout(() => {
setExpanded(false);
}, 300);
};
return (
<div className="absolute right-17 bottom-11 z-1300">
<div
className="w-20 h-20 bg-white rounded-xl drop-shadow-xl shadow-black"
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
>
<div className="w-20 h-20 p-1">
<button onClick={() => handleQuickSwitch()}>
<img
width={240}
height={100}
src={
activeId === baseLayers[0].id
? baseLayers[1].img
: baseLayers[0].img
}
alt={
activeId === baseLayers[0].id
? baseLayers[1].name
: baseLayers[0].name
}
className="object-cover object-left w-18 h-18 rounded-xl"
/>
<div className=" absolute left-1 bottom-1 flex w-18 h-auto items-center justify-center rounded-b-xl text-xs text-white bg-black opacity-80">
<span>
{activeId === baseLayers[0].id
? baseLayers[1].name
: baseLayers[0].name}
</span>
</div>
</button>
</div>
</div>
{isExpanded && (
<div
className={clsx(
"absolute flex right-24 bottom-0 w-90 h-25 bg-white rounded-xl drop-shadow-xl shadow-black transition-all duration-300",
isShow ? "opacity-100" : "opacity-0"
)}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
>
{baseLayers.map((item) => (
<button
key={item.id}
className="flex flex-auto flex-col justify-center items-center text-gray-500 text-xs"
onClick={() => handleMapLayers(item.id)}
>
<img
width={240}
height={100}
src={item.img}
alt={item.name}
className={clsx(
"object-cover object-left w-16 h-16 rounded-md border-2 border-white hover:ring-2 ring-blue-300",
{
"ring-1 ring-blue-300": activeId === item.id,
}
)}
/>
<span className="pt-1">{item.name}</span>
</button>
))}
</div>
)}
</div>
);
};
export default BaseLayers;