first commit
This commit is contained in:
commit
3efb239b23
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
</profile>
|
||||
</component>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/geo.iml" filepath="$PROJECT_DIR$/.idea/geo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="7bd3c14e-34ef-4905-8343-493f8e7480c1" name="Changes" comment="" />
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo"><![CDATA[{
|
||||
"associatedIndex": 7
|
||||
}]]></component>
|
||||
<component name="ProjectId" id="30dF6TjQdioNRSFt2AwM8C8kXXK" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
<option name="showMembers" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"git-widget-placeholder": "main",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-js-predefined-d6986cc7102b-09060db00ec0-JavaScript-WS-251.27812.50" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="7bd3c14e-34ef-4905-8343-493f8e7480c1" name="Changes" comment="" />
|
||||
<created>1753949367505</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1753949367505</updated>
|
||||
<workItem from="1753949368756" duration="155000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,186 @@
|
|||
# Vue 3 + ArcGIS + 天地图分屏对比项目
|
||||
|
||||
这是一个基于Vue 3和ArcGIS API for JavaScript的天地图分屏对比项目。项目实现了同时显示矢量地图和影像地图的分屏功能,支持多种交互和动画效果。
|
||||
|
||||
## ✨ 功能特点
|
||||
|
||||
### 🗺️ 地图功能
|
||||
- ✅ 天地图矢量地图和影像地图加载
|
||||
- ✅ 普通地图模式:支持图层切换
|
||||
- ✅ **分屏对比模式**:同时显示两种地图类型
|
||||
- ✅ 垂直/水平分割方向切换
|
||||
- ✅ 可拖拽调整分割比例
|
||||
|
||||
### 🎮 交互控制
|
||||
- ✅ 分割线位置实时显示
|
||||
- ✅ 一键重置到中间位置
|
||||
- ✅ 预设布局快速切换
|
||||
- ✅ 平滑动画过渡效果
|
||||
- ✅ 摆动演示动画
|
||||
|
||||
### 🎨 界面设计
|
||||
- ✅ 响应式设计,适配移动端
|
||||
- ✅ 现代化UI控件
|
||||
- ✅ 实时状态显示
|
||||
- ✅ 直观的操作反馈
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 安装依赖
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### 2. 配置天地图API Key
|
||||
**重要:需要天地图API Key才能正常显示地图**
|
||||
|
||||
编辑 `src/config/tianditu.js`:
|
||||
```javascript
|
||||
export const TIANDITU_CONFIG = {
|
||||
API_KEY: '你的天地图API_KEY', // 替换这里
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
获取API Key:
|
||||
1. 访问 [天地图开发者控制台](https://console.tianditu.gov.cn/)
|
||||
2. 注册并创建应用
|
||||
3. 获取API Key
|
||||
|
||||
详细配置说明:[TIANDITU_SETUP.md](./TIANDITU_SETUP.md)
|
||||
|
||||
### 3. 启动开发服务器
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
访问 http://localhost:5173
|
||||
|
||||
### 4. 构建生产版本
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
geo/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── TiandituMap.vue # 普通天地图组件
|
||||
│ │ ├── SplitScreenMap.vue # 分屏对比组件
|
||||
│ │ └── HelloWorld.vue # 示例组件
|
||||
│ ├── config/
|
||||
│ │ └── tianditu.js # 天地图配置
|
||||
│ ├── utils/
|
||||
│ │ └── splitScreen.js # 分屏工具类 ⭐
|
||||
│ ├── App.vue # 主应用(模式切换)
|
||||
│ └── main.js # 应用入口
|
||||
├── TIANDITU_SETUP.md # 天地图配置指南
|
||||
├── SPLIT_SCREEN_GUIDE.md # 分屏功能使用指南 ⭐
|
||||
├── vite.config.js # Vite配置(ArcGIS支持)
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## 🎯 核心功能说明
|
||||
|
||||
### 普通地图模式
|
||||
- 矢量地图:道路、建筑、行政区划
|
||||
- 影像地图:高清卫星影像
|
||||
- 图层切换:右上角按钮切换
|
||||
|
||||
### 分屏对比模式 ⭐
|
||||
- **同时显示**:左侧矢量,右侧影像(或上下分布)
|
||||
- **实时对比**:相同区域不同图层的直观对比
|
||||
- **交互同步**:缩放、平移操作同步到两个图层
|
||||
- **分割线控制**:拖拽调整显示比例
|
||||
|
||||
### 高级功能
|
||||
- **动画过渡**:平滑的位置变化动画
|
||||
- **预设布局**:影像主导/矢量主导快速切换
|
||||
- **摆动演示**:分割线摆动效果展示
|
||||
- **实时反馈**:显示当前分割位置和方向
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
- **前端框架**:Vue 3 (Composition API)
|
||||
- **构建工具**:Vite
|
||||
- **地图引擎**:ArcGIS API for JavaScript
|
||||
- **地图服务**:天地图 (国家地理信息公共服务平台)
|
||||
- **核心功能**:ArcGIS Swipe Widget
|
||||
|
||||
## 📖 使用指南
|
||||
|
||||
### 基础操作
|
||||
1. 页面右上角切换"普通地图"/"分屏对比"
|
||||
2. 分屏模式下,左上角控制面板进行操作
|
||||
3. 拖拽分割线手动调整比例
|
||||
|
||||
### 控制面板功能
|
||||
- **重置分割线**:动画回到50%中间位置
|
||||
- **方向切换**:垂直分割 ↔ 水平分割
|
||||
- **影像主导**:影像占70%显示区域
|
||||
- **矢量主导**:矢量占70%显示区域
|
||||
- **摆动演示**:分割线摆动动画效果
|
||||
|
||||
### 高级定制
|
||||
详细的自定义配置和扩展功能请查看:
|
||||
- [分屏功能使用指南](./SPLIT_SCREEN_GUIDE.md)
|
||||
- [天地图配置指南](./TIANDITU_SETUP.md)
|
||||
|
||||
## 🎨 界面预览
|
||||
|
||||
### 普通地图模式
|
||||
- 右上角图层切换按钮
|
||||
- 支持矢量/影像地图切换
|
||||
|
||||
### 分屏对比模式
|
||||
- 左上角功能丰富的控制面板
|
||||
- 实时显示分割状态信息
|
||||
- 多种预设布局和动画效果
|
||||
|
||||
## 🔧 开发说明
|
||||
|
||||
### 核心文件
|
||||
- `splitScreen.js`:分屏功能核心工具类
|
||||
- `SplitScreenMap.vue`:分屏地图组件
|
||||
- `tianditu.js`:天地图服务配置
|
||||
|
||||
### 自定义开发
|
||||
```javascript
|
||||
// 使用分屏管理器
|
||||
import { SplitScreenManager } from './utils/splitScreen.js'
|
||||
|
||||
const manager = new SplitScreenManager(view)
|
||||
manager.createSwipe(leadingLayers, trailingLayers)
|
||||
|
||||
// 使用动画器
|
||||
import { SplitScreenAnimator } from './utils/splitScreen.js'
|
||||
|
||||
const animator = new SplitScreenAnimator(manager)
|
||||
await animator.animateToPosition(75, 1000)
|
||||
```
|
||||
|
||||
## 🐛 常见问题
|
||||
|
||||
### Q: 地图显示空白?
|
||||
A: 检查天地图API Key是否正确配置
|
||||
|
||||
### Q: 分屏功能不工作?
|
||||
A: 确认ArcGIS API版本支持Swipe小部件
|
||||
|
||||
### Q: 动画效果卡顿?
|
||||
A: 检查浏览器性能,可调整动画时长
|
||||
|
||||
详细问题解答请查看 [SPLIT_SCREEN_GUIDE.md](./SPLIT_SCREEN_GUIDE.md)
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [ArcGIS API for JavaScript](https://developers.arcgis.com/javascript/latest/)
|
||||
- [ArcGIS Swipe Widget](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Swipe.html)
|
||||
- [天地图官网](https://www.tianditu.gov.cn/)
|
||||
- [Vue 3 官方文档](https://v3.vuejs.org/)
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
MIT License
|
|
@ -0,0 +1,205 @@
|
|||
# 分屏地图使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目实现了基于ArcGIS API for JavaScript的分屏地图功能,可以同时显示天地图的矢量地图和影像地图,支持多种交互和动画效果。
|
||||
|
||||
## 功能特点
|
||||
|
||||
### 🎯 核心功能
|
||||
- **分屏对比**:同时显示矢量地图和影像地图
|
||||
- **方向切换**:支持垂直分割和水平分割
|
||||
- **位置调节**:可拖拽分割线调整显示比例
|
||||
- **动画效果**:平滑的过渡动画和演示效果
|
||||
|
||||
### 🎮 交互控制
|
||||
- **重置分割线**:一键回到50%中间位置
|
||||
- **方向切换**:在垂直和水平分割间切换
|
||||
- **预设布局**:快速切换到预定义布局
|
||||
- **动画演示**:摆动效果展示分屏功能
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 启动项目
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. 切换到分屏模式
|
||||
在页面右上角点击"分屏对比"按钮
|
||||
|
||||
### 3. 控制面板操作
|
||||
分屏模式下,左上角会显示控制面板,包含以下功能:
|
||||
|
||||
#### 基础控制
|
||||
- **重置分割线**:将分割线动画回到中间位置(50%)
|
||||
- **水平分割/垂直分割**:切换分割方向
|
||||
|
||||
#### 预设布局
|
||||
- **影像主导**:影像地图占70%,矢量地图占30%
|
||||
- **矢量主导**:矢量地图占70%,影像地图占30%
|
||||
|
||||
#### 动画演示
|
||||
- **摆动演示**:分割线摆动动画,展示分屏效果
|
||||
|
||||
### 4. 手动调节
|
||||
- 拖拽分割线可以手动调整两种地图的显示比例
|
||||
- 实时显示当前分割位置百分比
|
||||
|
||||
## 技术实现
|
||||
|
||||
### splitScreen.js 工具类
|
||||
|
||||
#### SplitScreenManager
|
||||
分屏管理器,负责创建和管理分屏小部件:
|
||||
|
||||
```javascript
|
||||
import { SplitScreenManager } from './utils/splitScreen.js'
|
||||
|
||||
// 创建管理器
|
||||
const manager = new SplitScreenManager(view)
|
||||
|
||||
// 创建分屏
|
||||
manager.createSwipe(leadingLayers, trailingLayers, options)
|
||||
|
||||
// 切换方向
|
||||
manager.toggleDirection()
|
||||
|
||||
// 设置位置
|
||||
manager.setPosition(75) // 75%
|
||||
```
|
||||
|
||||
#### SplitScreenAnimator
|
||||
动画控制器,提供平滑的动画效果:
|
||||
|
||||
```javascript
|
||||
import { SplitScreenAnimator } from './utils/splitScreen.js'
|
||||
|
||||
// 创建动画器
|
||||
const animator = new SplitScreenAnimator(manager)
|
||||
|
||||
// 动画到指定位置
|
||||
await animator.animateToPosition(75, 1000) // 1秒动画到75%
|
||||
|
||||
// 摆动动画
|
||||
await animator.wiggle(15, 3, 2000) // 摆动幅度15,3个周期,2秒
|
||||
```
|
||||
|
||||
#### 预设配置
|
||||
```javascript
|
||||
import { SPLIT_PRESETS } from './utils/splitScreen.js'
|
||||
|
||||
// 可用预设
|
||||
SPLIT_PRESETS.VERTICAL_SATELLITE_RIGHT // 垂直分割,影像在右
|
||||
SPLIT_PRESETS.HORIZONTAL_SATELLITE_BOTTOM // 水平分割,影像在下
|
||||
SPLIT_PRESETS.SATELLITE_DOMINANT // 影像占主导(70%)
|
||||
SPLIT_PRESETS.VECTOR_DOMINANT // 矢量占主导(70%)
|
||||
```
|
||||
|
||||
## 自定义配置
|
||||
|
||||
### 修改默认分割方向
|
||||
在 `SplitScreenMap.vue` 中:
|
||||
```javascript
|
||||
const isVertical = ref(false) // 改为false使用水平分割
|
||||
```
|
||||
|
||||
### 修改默认分割位置
|
||||
```javascript
|
||||
const currentPosition = ref(30) // 改为30%
|
||||
```
|
||||
|
||||
### 添加新的预设
|
||||
在 `splitScreen.js` 中添加:
|
||||
```javascript
|
||||
export const SPLIT_PRESETS = {
|
||||
// 现有预设...
|
||||
|
||||
// 新预设
|
||||
CUSTOM_LAYOUT: {
|
||||
direction: 'vertical',
|
||||
position: 25
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 自定义动画效果
|
||||
```javascript
|
||||
// 修改动画时长
|
||||
await animator.animateToPosition(50, 2000) // 2秒动画
|
||||
|
||||
// 修改摆动参数
|
||||
await animator.wiggle(20, 4, 3000) // 摆动幅度20,4个周期,3秒
|
||||
```
|
||||
|
||||
## 样式定制
|
||||
|
||||
### 分割线样式
|
||||
在 `SplitScreenMap.vue` 的全局样式中:
|
||||
```css
|
||||
:global(.esri-swipe__divider) {
|
||||
background-color: #ff0000 !important; /* 红色分割线 */
|
||||
width: 6px !important; /* 更粗的分割线 */
|
||||
}
|
||||
```
|
||||
|
||||
### 控制面板样式
|
||||
修改 `.control-panel` 类的样式:
|
||||
```css
|
||||
.control-panel {
|
||||
background: rgba(0, 0, 0, 0.8); /* 深色背景 */
|
||||
color: white;
|
||||
/* 其他样式... */
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 图层管理
|
||||
- 分屏时只加载必要的图层
|
||||
- 避免重复创建相同的图层
|
||||
- 及时销毁不需要的小部件
|
||||
|
||||
### 动画优化
|
||||
- 使用 `requestAnimationFrame` 确保流畅动画
|
||||
- 缓动函数提供自然的动画效果
|
||||
- 避免在动画过程中创建新的分屏小部件
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 分屏功能不显示?
|
||||
A: 检查ArcGIS API版本,确保支持Swipe小部件
|
||||
|
||||
### Q: 动画卡顿?
|
||||
A: 检查浏览器性能,降低动画复杂度或时长
|
||||
|
||||
### Q: 自定义样式不生效?
|
||||
A: 使用 `:global()` 选择器覆盖ArcGIS默认样式
|
||||
|
||||
### Q: 如何添加更多地图类型?
|
||||
A: 在天地图配置中添加新的服务URL,然后在分屏组件中使用
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 三分屏对比
|
||||
可以扩展为三个或更多地图的对比:
|
||||
```javascript
|
||||
// 创建多个分屏小部件
|
||||
const swipe1 = new Swipe({ /* 配置1 */ })
|
||||
const swipe2 = new Swipe({ /* 配置2 */ })
|
||||
```
|
||||
|
||||
### 时间轴对比
|
||||
结合时间滑块,对比不同时间的地图数据
|
||||
|
||||
### 测量工具
|
||||
在分屏模式下添加距离和面积测量功能
|
||||
|
||||
### 图层透明度
|
||||
添加图层透明度控制,实现更细致的对比效果
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [ArcGIS Swipe Widget 文档](https://developers.arcgis.com/javascript/latest/api-reference/esri-widgets-Swipe.html)
|
||||
- [天地图API文档](https://lbs.tianditu.gov.cn/server/MapService.html)
|
||||
- [Vue 3 Composition API](https://v3.vuejs.org/guide/composition-api-introduction.html)
|
|
@ -0,0 +1,67 @@
|
|||
# 天地图配置指南
|
||||
|
||||
## 1. 申请天地图API Key
|
||||
|
||||
要使用天地图服务,你需要先申请API Key:
|
||||
|
||||
1. 访问天地图官网:https://www.tianditu.gov.cn/
|
||||
2. 点击右上角"控制台"进入开发者控制台:https://console.tianditu.gov.cn/
|
||||
3. 注册账号并登录
|
||||
4. 在控制台中创建应用并获取API Key
|
||||
|
||||
## 2. 配置API Key
|
||||
|
||||
获取API Key后,需要在项目中配置:
|
||||
|
||||
### 方法1:修改配置文件
|
||||
编辑 `src/config/tianditu.js` 文件,将 `API_KEY` 替换为你的API Key:
|
||||
|
||||
```javascript
|
||||
export const TIANDITU_CONFIG = {
|
||||
API_KEY: '你的API_KEY', // 替换这里
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
### 方法2:直接修改组件
|
||||
编辑 `src/components/TiandituMap.vue` 文件,找到 `apiKey` 字段并替换:
|
||||
|
||||
```javascript
|
||||
const tiandituConfig = {
|
||||
apiKey: '你的API_KEY', // 替换这里
|
||||
// ... 其他配置
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 启动项目
|
||||
|
||||
配置完成后,启动项目:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 4. 功能说明
|
||||
|
||||
- **矢量地图**:显示道路、建筑等矢量信息
|
||||
- **影像地图**:显示卫星影像
|
||||
- **图层切换**:点击右上角按钮可以切换不同图层
|
||||
- **地图交互**:支持缩放、平移等常用地图操作
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
### Q: 地图显示空白或加载失败
|
||||
A: 请确认API Key是否正确配置,并检查网络连接
|
||||
|
||||
### Q: 如何修改默认地图中心点?
|
||||
A: 在 `TiandituMap.vue` 组件中找到 `center` 属性并修改坐标
|
||||
|
||||
### Q: 如何添加更多图层?
|
||||
A: 天地图还提供地形图等其他图层,可以参考官方文档进行配置
|
||||
|
||||
## 6. 相关链接
|
||||
|
||||
- [天地图官网](https://www.tianditu.gov.cn/)
|
||||
- [天地图开发者控制台](https://console.tianditu.gov.cn/)
|
||||
- [天地图API文档](https://lbs.tianditu.gov.cn/server/MapService.html)
|
||||
- [ArcGIS API for JavaScript](https://developers.arcgis.com/javascript/latest/)
|
|
@ -0,0 +1,13 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + Vue</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "geo-vue-app",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arcgis/core": "^4.33.11",
|
||||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.0",
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<!-- 地图模式切换按钮 -->
|
||||
<div class="map-mode-selector">
|
||||
<button
|
||||
@click="switchMapMode('normal')"
|
||||
:class="{ active: currentMode === 'normal' }"
|
||||
class="mode-btn"
|
||||
>
|
||||
普通地图
|
||||
</button>
|
||||
<button
|
||||
@click="switchMapMode('split')"
|
||||
:class="{ active: currentMode === 'split' }"
|
||||
class="mode-btn"
|
||||
>
|
||||
分屏对比
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 根据模式显示不同的地图组件 -->
|
||||
<TiandituMap v-if="currentMode === 'normal'" />
|
||||
<SplitScreenMap v-if="currentMode === 'split'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import TiandituMap from './components/TiandituMap.vue'
|
||||
import SplitScreenMap from './components/SplitScreenMap.vue'
|
||||
|
||||
const currentMode = ref('normal')
|
||||
|
||||
const switchMapMode = (mode) => {
|
||||
currentMode.value = mode
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.map-mode-selector {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 125px;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
padding: 10px 16px;
|
||||
background: transparent;
|
||||
color: #333;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mode-btn:hover {
|
||||
background: rgba(0, 122, 200, 0.1);
|
||||
border-color: #007ac8;
|
||||
color: #007ac8;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: linear-gradient(135deg, #007ac8, #005a96);
|
||||
color: white;
|
||||
border-color: #005a96;
|
||||
box-shadow: 0 2px 8px rgba(0, 122, 200, 0.3);
|
||||
}
|
||||
|
||||
.mode-btn.active:hover {
|
||||
background: linear-gradient(135deg, #005a96, #004070);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.map-mode-selector {
|
||||
top: 10px;
|
||||
right: 125px;
|
||||
left: 10px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="map-container">
|
||||
<div id="mapDiv" class="map-div"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
// 定义 props
|
||||
const props = defineProps({
|
||||
msg: String
|
||||
})
|
||||
|
||||
// 地图实例引用
|
||||
const mapInstance = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
// 确保天地图 API 已加载
|
||||
if (typeof T !== 'undefined') {
|
||||
mapInstance.value = new T.Map('mapDiv')
|
||||
|
||||
|
||||
// 添加地图类型控件
|
||||
let ctrl = new T.Control.MapType([
|
||||
{
|
||||
title: "卫星混合",
|
||||
icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/satellitepoi.png",
|
||||
layer: TMAP_HYBRID_MAP,
|
||||
},
|
||||
{
|
||||
title: "地图",
|
||||
icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/vector.png",
|
||||
layer: TMAP_NORMAL_MAP,
|
||||
},
|
||||
]);
|
||||
|
||||
// 设置控件位置
|
||||
ctrl.setPosition(window.T_ANCHOR_TOP_RIGHT);
|
||||
mapInstance.value.addControl(ctrl);
|
||||
|
||||
|
||||
// 设置地图中心点和缩放级别
|
||||
mapInstance.value.centerAndZoom(new T.LngLat(121.4737, 31.2304), 10);
|
||||
|
||||
console.log('地图初始化成功')
|
||||
} else {
|
||||
console.error('天地图 API 未加载')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.map-div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,331 @@
|
|||
<template>
|
||||
<div class="split-screen-container">
|
||||
<div ref="mapDiv" class="split-map-view"></div>
|
||||
<div class="split-controls">
|
||||
<div class="control-panel">
|
||||
<h3>分屏地图对比</h3>
|
||||
<div class="control-buttons">
|
||||
<button @click="resetSplit" class="control-btn">
|
||||
重置分割线
|
||||
</button>
|
||||
<button @click="toggleSplitDirection" class="control-btn">
|
||||
{{ isVertical ? '水平分割' : '垂直分割' }}
|
||||
</button>
|
||||
<button @click="animateToPreset('SATELLITE_DOMINANT')" class="control-btn preset-btn">
|
||||
影像主导
|
||||
</button>
|
||||
<button @click="animateToPreset('VECTOR_DOMINANT')" class="control-btn preset-btn">
|
||||
矢量主导
|
||||
</button>
|
||||
<button @click="wiggleAnimation" class="control-btn animation-btn">
|
||||
摆动演示
|
||||
</button>
|
||||
</div>
|
||||
<div class="map-info">
|
||||
<div class="info-item">
|
||||
<span class="label">左侧/上方:</span>
|
||||
<span class="value">矢量地图</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">右侧/下方:</span>
|
||||
<span class="value">影像地图</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">分割位置:</span>
|
||||
<span class="value">{{ Math.round(currentPosition) }}%</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">分割方向:</span>
|
||||
<span class="value">{{ isVertical ? '垂直' : '水平' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import Map from '@arcgis/core/Map'
|
||||
import MapView from '@arcgis/core/views/MapView'
|
||||
import WebTileLayer from '@arcgis/core/layers/WebTileLayer'
|
||||
import { TIANDITU_CONFIG } from '../config/tianditu.js'
|
||||
import { SplitScreenManager, SplitScreenAnimator, SPLIT_PRESETS } from '../utils/splitScreen.js'
|
||||
|
||||
const mapDiv = ref()
|
||||
let view = null
|
||||
let splitManager = null
|
||||
let animator = null
|
||||
const isVertical = ref(true)
|
||||
const currentPosition = ref(50)
|
||||
|
||||
// 创建天地图图层
|
||||
const createTiandituLayer = (serviceUrl, id) => {
|
||||
return new WebTileLayer({
|
||||
id: id,
|
||||
urlTemplate: serviceUrl.replace('{key}', TIANDITU_CONFIG.API_KEY),
|
||||
subDomains: TIANDITU_CONFIG.SUB_DOMAINS,
|
||||
copyright: '天地图'
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化分屏地图
|
||||
const initSplitScreenMap = () => {
|
||||
// 创建矢量图层
|
||||
const vectorLayer = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR, 'vector')
|
||||
const vectorAnnotation = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR_ANNOTATION, 'vector-annotation')
|
||||
|
||||
// 创建影像图层
|
||||
const satelliteLayer = createTiandituLayer(TIANDITU_CONFIG.SERVICES.SATELLITE, 'satellite')
|
||||
const satelliteAnnotation = createTiandituLayer(TIANDITU_CONFIG.SERVICES.SATELLITE_ANNOTATION, 'satellite-annotation')
|
||||
|
||||
// 创建地图,添加所有图层
|
||||
const map = new Map({
|
||||
layers: [
|
||||
vectorLayer,
|
||||
vectorAnnotation,
|
||||
satelliteLayer,
|
||||
satelliteAnnotation
|
||||
]
|
||||
})
|
||||
|
||||
// 创建地图视图
|
||||
view = new MapView({
|
||||
container: mapDiv.value,
|
||||
map: map,
|
||||
center: TIANDITU_CONFIG.DEFAULT_CENTER,
|
||||
zoom: TIANDITU_CONFIG.DEFAULT_ZOOM
|
||||
})
|
||||
|
||||
// 等待视图加载完成后创建分屏管理器
|
||||
view.when(() => {
|
||||
// 创建分屏管理器
|
||||
splitManager = new SplitScreenManager(view)
|
||||
animator = new SplitScreenAnimator(splitManager)
|
||||
|
||||
// 创建分屏效果
|
||||
const leadingLayers = [satelliteLayer, satelliteAnnotation]
|
||||
const trailingLayers = [vectorLayer, vectorAnnotation]
|
||||
|
||||
splitManager.createSwipe(leadingLayers, trailingLayers, {
|
||||
direction: isVertical.value ? 'vertical' : 'horizontal',
|
||||
position: currentPosition.value
|
||||
})
|
||||
|
||||
// 监听位置变化
|
||||
if (splitManager.swipeWidget) {
|
||||
splitManager.swipeWidget.watch('position', (newPosition) => {
|
||||
currentPosition.value = newPosition
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重置分割线位置
|
||||
const resetSplit = async () => {
|
||||
if (splitManager) {
|
||||
await animator.animateToPosition(50, 800)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换分割方向
|
||||
const toggleSplitDirection = () => {
|
||||
isVertical.value = !isVertical.value
|
||||
|
||||
if (splitManager) {
|
||||
splitManager.toggleDirection()
|
||||
}
|
||||
}
|
||||
|
||||
// 动画到预设位置
|
||||
const animateToPreset = async (presetName) => {
|
||||
if (!splitManager || !animator) return
|
||||
|
||||
const preset = SPLIT_PRESETS[presetName]
|
||||
if (preset) {
|
||||
// 如果需要改变方向
|
||||
if ((preset.direction === 'vertical') !== isVertical.value) {
|
||||
toggleSplitDirection()
|
||||
}
|
||||
|
||||
// 动画到目标位置
|
||||
await animator.animateToPosition(preset.position, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
// 摆动动画演示
|
||||
const wiggleAnimation = async () => {
|
||||
if (animator) {
|
||||
await animator.wiggle(15, 2, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initSplitScreenMap()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (splitManager) {
|
||||
splitManager.destroySwipe()
|
||||
}
|
||||
if (view) {
|
||||
view.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.split-screen-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.split-map-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.split-controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
min-width: 250px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.control-panel h3 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid #007ac8;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px 15px;
|
||||
background: linear-gradient(135deg, #007ac8, #005a96);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 122, 200, 0.3);
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
background: linear-gradient(135deg, #005a96, #004070);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 122, 200, 0.4);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.preset-btn {
|
||||
background: linear-gradient(135deg, #28a745, #20c997);
|
||||
border-color: #1e7e34;
|
||||
}
|
||||
|
||||
.preset-btn:hover {
|
||||
background: linear-gradient(135deg, #1e7e34, #17a2b8);
|
||||
}
|
||||
|
||||
.animation-btn {
|
||||
background: linear-gradient(135deg, #ffc107, #fd7e14);
|
||||
border-color: #e0a800;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.animation-btn:hover {
|
||||
background: linear-gradient(135deg, #e0a800, #dc3545);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.map-info {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
background: #f0f8ff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #007ac8;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.split-controls {
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
padding: 15px;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.control-panel h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 分屏小部件样式定制 */
|
||||
:global(.esri-swipe__container) {
|
||||
border: 2px solid #007ac8 !important;
|
||||
}
|
||||
|
||||
:global(.esri-swipe__divider) {
|
||||
background-color: #007ac8 !important;
|
||||
width: 4px !important;
|
||||
}
|
||||
|
||||
:global(.esri-swipe__divider::before) {
|
||||
background-color: #005a96 !important;
|
||||
border: 2px solid white !important;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="map-container">
|
||||
<div ref="mapDiv" class="map-view"></div>
|
||||
<div class="map-controls">
|
||||
<button
|
||||
@click="switchLayer('vector')"
|
||||
:class="{ active: currentLayer === 'vector' }"
|
||||
class="layer-btn"
|
||||
>
|
||||
矢量地图
|
||||
</button>
|
||||
<button
|
||||
@click="switchLayer('satellite')"
|
||||
:class="{ active: currentLayer === 'satellite' }"
|
||||
class="layer-btn"
|
||||
>
|
||||
影像地图
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import Map from '@arcgis/core/Map'
|
||||
import MapView from '@arcgis/core/views/MapView'
|
||||
import WebTileLayer from '@arcgis/core/layers/WebTileLayer'
|
||||
import { TIANDITU_CONFIG } from '../config/tianditu.js'
|
||||
|
||||
const mapDiv = ref()
|
||||
let view = null
|
||||
const currentLayer = ref('vector')
|
||||
|
||||
// 创建天地图图层
|
||||
const createTiandituLayer = (serviceUrl, id) => {
|
||||
return new WebTileLayer({
|
||||
id: id,
|
||||
urlTemplate: serviceUrl.replace('{key}', TIANDITU_CONFIG.API_KEY),
|
||||
subDomains: TIANDITU_CONFIG.SUB_DOMAINS,
|
||||
copyright: '天地图'
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化地图
|
||||
const initMap = () => {
|
||||
// 创建初始图层(矢量地图)
|
||||
const vectorLayer = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR, 'vector')
|
||||
const vectorAnnotation = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR_ANNOTATION, 'vector-annotation')
|
||||
|
||||
const map = new Map({
|
||||
layers: [vectorLayer, vectorAnnotation]
|
||||
})
|
||||
|
||||
view = new MapView({
|
||||
container: mapDiv.value,
|
||||
map: map,
|
||||
center: TIANDITU_CONFIG.DEFAULT_CENTER,
|
||||
zoom: TIANDITU_CONFIG.DEFAULT_ZOOM
|
||||
})
|
||||
}
|
||||
|
||||
// 切换图层
|
||||
const switchLayer = (layerType) => {
|
||||
if (!view) return
|
||||
|
||||
currentLayer.value = layerType
|
||||
view.map.removeAll()
|
||||
|
||||
if (layerType === 'vector') {
|
||||
// 矢量地图
|
||||
const vectorLayer = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR, 'vector')
|
||||
const vectorAnnotation = createTiandituLayer(TIANDITU_CONFIG.SERVICES.VECTOR_ANNOTATION, 'vector-annotation')
|
||||
view.map.add(vectorLayer)
|
||||
view.map.add(vectorAnnotation)
|
||||
} else if (layerType === 'satellite') {
|
||||
// 影像地图
|
||||
const satelliteLayer = createTiandituLayer(TIANDITU_CONFIG.SERVICES.SATELLITE, 'satellite')
|
||||
const satelliteAnnotation = createTiandituLayer(TIANDITU_CONFIG.SERVICES.SATELLITE_ANNOTATION, 'satellite-annotation')
|
||||
view.map.add(satelliteLayer)
|
||||
view.map.add(satelliteAnnotation)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initMap()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (view) {
|
||||
view.destroy()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.map-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.map-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.map-controls {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.layer-btn {
|
||||
padding: 10px 15px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border: 2px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.layer-btn:hover {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-color: #007ac8;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.layer-btn.active {
|
||||
background: #007ac8;
|
||||
color: white;
|
||||
border-color: #005a96;
|
||||
}
|
||||
|
||||
.layer-btn.active:hover {
|
||||
background: #005a96;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
// 天地图配置文件
|
||||
// 请在这里配置你的天地图API Key
|
||||
|
||||
export const TIANDITU_CONFIG = {
|
||||
// 请将下面的字符串替换为你从天地图官网申请的API Key
|
||||
// 申请地址:https://console.tianditu.gov.cn/
|
||||
// 如果没有API Key,地图将无法正常显示
|
||||
API_KEY: '4559200347e75489c4474f5b11846368',
|
||||
|
||||
// 天地图服务URL配置
|
||||
SERVICES: {
|
||||
// 矢量底图
|
||||
VECTOR: 'http://t{subDomain}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk={key}',
|
||||
|
||||
// 矢量注记
|
||||
VECTOR_ANNOTATION: 'http://t{subDomain}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk={key}',
|
||||
|
||||
// 影像底图
|
||||
SATELLITE: 'http://t{subDomain}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk={key}',
|
||||
|
||||
// 影像注记
|
||||
SATELLITE_ANNOTATION: 'http://t{subDomain}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={level}&TILEROW={row}&TILECOL={col}&tk={key}'
|
||||
},
|
||||
|
||||
// 服务器子域名
|
||||
SUB_DOMAINS: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
|
||||
// 默认地图中心点(北京)
|
||||
DEFAULT_CENTER: [116.4074, 39.9042],
|
||||
|
||||
// 默认缩放级别
|
||||
DEFAULT_ZOOM: 10
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
|
@ -0,0 +1,289 @@
|
|||
// splitScreen.js - 分屏地图工具类
|
||||
// 提供分屏地图的创建、管理和控制功能
|
||||
|
||||
import Swipe from '@arcgis/core/widgets/Swipe'
|
||||
|
||||
/**
|
||||
* 分屏地图管理器
|
||||
*/
|
||||
export class SplitScreenManager {
|
||||
constructor(view) {
|
||||
this.view = view
|
||||
this.swipeWidget = null
|
||||
this.isVertical = true
|
||||
this.position = 50
|
||||
this.leadingLayers = []
|
||||
this.trailingLayers = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分屏小部件
|
||||
* @param {Array} leadingLayers - 前景图层(右侧/下方显示)
|
||||
* @param {Array} trailingLayers - 背景图层(左侧/上方显示)
|
||||
* @param {Object} options - 配置选项
|
||||
*/
|
||||
createSwipe(leadingLayers = [], trailingLayers = [], options = {}) {
|
||||
// 如果已存在分屏小部件,先销毁
|
||||
this.destroySwipe()
|
||||
|
||||
// 保存图层引用
|
||||
this.leadingLayers = leadingLayers
|
||||
this.trailingLayers = trailingLayers
|
||||
|
||||
// 合并默认配置
|
||||
const config = {
|
||||
view: this.view,
|
||||
leadingLayers: leadingLayers,
|
||||
trailingLayers: trailingLayers,
|
||||
direction: options.direction || (this.isVertical ? 'vertical' : 'horizontal'),
|
||||
position: options.position || this.position,
|
||||
...options
|
||||
}
|
||||
|
||||
// 创建分屏小部件
|
||||
this.swipeWidget = new Swipe(config)
|
||||
|
||||
// 添加到视图
|
||||
this.view.ui.add(this.swipeWidget)
|
||||
|
||||
// 监听位置变化
|
||||
this.swipeWidget.watch('position', (newPosition) => {
|
||||
this.position = newPosition
|
||||
})
|
||||
|
||||
return this.swipeWidget
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁分屏小部件
|
||||
*/
|
||||
destroySwipe() {
|
||||
if (this.swipeWidget) {
|
||||
this.view.ui.remove(this.swipeWidget)
|
||||
this.swipeWidget.destroy()
|
||||
this.swipeWidget = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换分割方向
|
||||
*/
|
||||
toggleDirection() {
|
||||
this.isVertical = !this.isVertical
|
||||
|
||||
if (this.swipeWidget) {
|
||||
// 重新创建分屏小部件
|
||||
this.createSwipe(this.leadingLayers, this.trailingLayers, {
|
||||
direction: this.isVertical ? 'vertical' : 'horizontal',
|
||||
position: this.position
|
||||
})
|
||||
}
|
||||
|
||||
return this.isVertical
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分割线位置
|
||||
* @param {number} position - 位置百分比 (0-100)
|
||||
*/
|
||||
setPosition(position) {
|
||||
this.position = Math.max(0, Math.min(100, position))
|
||||
|
||||
if (this.swipeWidget) {
|
||||
this.swipeWidget.position = this.position
|
||||
}
|
||||
|
||||
return this.position
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置分割线到中间位置
|
||||
*/
|
||||
resetPosition() {
|
||||
return this.setPosition(50)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前分割方向
|
||||
*/
|
||||
getDirection() {
|
||||
return this.isVertical ? 'vertical' : 'horizontal'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前位置
|
||||
*/
|
||||
getPosition() {
|
||||
return this.position
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新前景图层
|
||||
* @param {Array} layers - 新的前景图层
|
||||
*/
|
||||
updateLeadingLayers(layers) {
|
||||
this.leadingLayers = layers
|
||||
if (this.swipeWidget) {
|
||||
this.swipeWidget.leadingLayers = layers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新背景图层
|
||||
* @param {Array} layers - 新的背景图层
|
||||
*/
|
||||
updateTrailingLayers(layers) {
|
||||
this.trailingLayers = layers
|
||||
if (this.swipeWidget) {
|
||||
this.swipeWidget.trailingLayers = layers
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查分屏小部件是否存在
|
||||
*/
|
||||
isActive() {
|
||||
return this.swipeWidget !== null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建天地图分屏对比
|
||||
* @param {MapView} view - 地图视图
|
||||
* @param {Object} vectorLayers - 矢量图层 {base, annotation}
|
||||
* @param {Object} satelliteLayers - 影像图层 {base, annotation}
|
||||
* @param {Object} options - 配置选项
|
||||
*/
|
||||
export function createTiandituSplitScreen(view, vectorLayers, satelliteLayers, options = {}) {
|
||||
const manager = new SplitScreenManager(view)
|
||||
|
||||
// 设置影像图层为前景(右侧/下方)
|
||||
const leadingLayers = [satelliteLayers.base, satelliteLayers.annotation].filter(Boolean)
|
||||
|
||||
// 矢量图层作为背景(左侧/上方)
|
||||
const trailingLayers = [vectorLayers.base, vectorLayers.annotation].filter(Boolean)
|
||||
|
||||
// 创建分屏
|
||||
manager.createSwipe(leadingLayers, trailingLayers, options)
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
/**
|
||||
* 分屏预设配置
|
||||
*/
|
||||
export const SPLIT_PRESETS = {
|
||||
// 垂直分割,影像在右
|
||||
VERTICAL_SATELLITE_RIGHT: {
|
||||
direction: 'vertical',
|
||||
position: 50
|
||||
},
|
||||
|
||||
// 水平分割,影像在下
|
||||
HORIZONTAL_SATELLITE_BOTTOM: {
|
||||
direction: 'horizontal',
|
||||
position: 50
|
||||
},
|
||||
|
||||
// 影像占大部分(70%)
|
||||
SATELLITE_DOMINANT: {
|
||||
direction: 'vertical',
|
||||
position: 30
|
||||
},
|
||||
|
||||
// 矢量占大部分(70%)
|
||||
VECTOR_DOMINANT: {
|
||||
direction: 'vertical',
|
||||
position: 70
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分屏动画效果
|
||||
*/
|
||||
export class SplitScreenAnimator {
|
||||
constructor(manager) {
|
||||
this.manager = manager
|
||||
this.isAnimating = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画移动分割线
|
||||
* @param {number} targetPosition - 目标位置
|
||||
* @param {number} duration - 动画时长(ms)
|
||||
*/
|
||||
async animateToPosition(targetPosition, duration = 1000) {
|
||||
if (this.isAnimating || !this.manager.isActive()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.isAnimating = true
|
||||
const startPosition = this.manager.getPosition()
|
||||
const distance = targetPosition - startPosition
|
||||
const startTime = Date.now()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const animate = () => {
|
||||
const elapsed = Date.now() - startTime
|
||||
const progress = Math.min(elapsed / duration, 1)
|
||||
|
||||
// 使用缓动函数
|
||||
const easeProgress = this.easeInOutCubic(progress)
|
||||
const currentPosition = startPosition + (distance * easeProgress)
|
||||
|
||||
this.manager.setPosition(currentPosition)
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate)
|
||||
} else {
|
||||
this.isAnimating = false
|
||||
resolve(true)
|
||||
}
|
||||
}
|
||||
|
||||
animate()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓动函数
|
||||
*/
|
||||
easeInOutCubic(t) {
|
||||
return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 摆动动画
|
||||
*/
|
||||
async wiggle(amplitude = 10, cycles = 3, duration = 1000) {
|
||||
if (this.isAnimating || !this.manager.isActive()) {
|
||||
return false
|
||||
}
|
||||
|
||||
this.isAnimating = true
|
||||
const centerPosition = this.manager.getPosition()
|
||||
const startTime = Date.now()
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const animate = () => {
|
||||
const elapsed = Date.now() - startTime
|
||||
const progress = elapsed / duration
|
||||
|
||||
if (progress >= 1) {
|
||||
this.manager.setPosition(centerPosition)
|
||||
this.isAnimating = false
|
||||
resolve(true)
|
||||
return
|
||||
}
|
||||
|
||||
// 正弦波摆动
|
||||
const wiggleOffset = Math.sin(progress * Math.PI * 2 * cycles) * amplitude * (1 - progress)
|
||||
this.manager.setPosition(centerPosition + wiggleOffset)
|
||||
|
||||
requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
animate()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
define: {
|
||||
// 解决ArcGIS API中的process.env问题
|
||||
'process.env': {}
|
||||
},
|
||||
optimizeDeps: {
|
||||
// 排除ArcGIS模块从预构建中,因为它们已经是优化的
|
||||
exclude: ['@arcgis/core']
|
||||
},
|
||||
server: {
|
||||
// 允许从外部域加载资源
|
||||
cors: true
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue