252 lines
8.6 KiB
TypeScript
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;
|