init project
This commit is contained in:
commit
85e53bcfb1
|
|
@ -0,0 +1,18 @@
|
|||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
# https://github.com/jokeyrhyme/standard-editorconfig
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# defaults
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
if (process.env.VITE_APP_VERSION === undefined) {
|
||||
const now = new Date;
|
||||
process.env.VITE_APP_VERSION = `${now.getUTCFullYear() - 2000}.${now.getUTCMonth() + 1}.${now.getUTCDate()}-${now.getUTCHours() * 60 + now.getUTCMinutes()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {import('electron-builder').Configuration}
|
||||
* @see https://www.electron.build/configuration/configuration
|
||||
*/
|
||||
const config = {
|
||||
directories: {
|
||||
output: 'dist',
|
||||
buildResources: 'buildResources',
|
||||
},
|
||||
files: [
|
||||
'packages/**/dist/**',
|
||||
],
|
||||
extraMetadata: {
|
||||
version: process.env.VITE_APP_VERSION,
|
||||
},
|
||||
nsis: {
|
||||
oneClick: false,
|
||||
allowToChangeInstallationDirectory: true
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"chrome": "96",
|
||||
"node": "16"
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"es2021": true,
|
||||
"node": true,
|
||||
"browser": false
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"node_modules/**",
|
||||
"**/dist/**"
|
||||
],
|
||||
"rules": {
|
||||
|
||||
/**
|
||||
* Having a semicolon helps the optimizer interpret your code correctly.
|
||||
* This avoids rare errors in optimized code.
|
||||
* @see https://twitter.com/alex_kozack/status/1364210394328408066
|
||||
*/
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
/**
|
||||
* This will make the history of changes in the hit a little cleaner
|
||||
*/
|
||||
"comma-dangle": [
|
||||
"warn",
|
||||
"always-multiline"
|
||||
],
|
||||
/**
|
||||
* Just for beauty
|
||||
*/
|
||||
"quotes": [
|
||||
"warn", "single"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.github/actions/**/*.js linguist-detectable=false
|
||||
scripts/*.js linguist-detectable=false
|
||||
*.config.js linguist-detectable=false
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
*.local
|
||||
thumbs.db
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
.idea/artifacts
|
||||
.idea/compiler.xml
|
||||
.idea/jarRepositories.xml
|
||||
.idea/modules.xml
|
||||
.idea/*.iml
|
||||
.idea/modules
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
/.idea/csv-plugin.xml
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Alex Kozack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
[](https://nodejs.org/about/releases/)
|
||||
[](https://github.com/npm/cli/releases)
|
||||
# map-download
|
||||
|
||||
> 基于electron和maptalks实现高德地图、百度地图(包括百度自定义地图 !!!百度个性化地图午夜蓝、清新蓝、黑夜等等链接已经失效!!!)、腾讯地图、OpenStreetMap、CartoDb、ArcGIS在线地图、天地图、MapBox的下载
|
||||
|
||||
> 支持卫星遥感影像和标注合并
|
||||
|
||||
> 支持行政区划瓦片下载,裁切边界
|
||||
|
||||
> 支持下载瓦片格式jpeg、png、webp
|
||||
|
||||
> 软件下载地址:https://github.com/Hxy1992/MapDownload/releases
|
||||
|
||||
> V0.42版本win-unpacked压缩包 百度网盘链接:https://pan.baidu.com/s/1M12KnC8bIvyHo3ik3hxy9A 提取码:9986
|
||||
|
||||

|
||||
|
||||
|
||||
## Build Setup
|
||||
|
||||
``` bash
|
||||
# 安装依赖(依赖较大,使用国内镜像)
|
||||
npm install
|
||||
|
||||
# 热更新服务
|
||||
npm run dev / npm run watch
|
||||
|
||||
# 构建web
|
||||
npm run build
|
||||
|
||||
# 构建应用
|
||||
npm run compile
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
## 下载瓦片加载方式
|
||||
|
||||
### Cesium
|
||||
|
||||
```javascript
|
||||
// 非百度地图
|
||||
viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
}))
|
||||
// 百度地图(需自定义BaiduImageryProvider),可参考cesium-helper目录下代码
|
||||
import BaiduImageryProvider from './cesium-helper/BaiduImageryProvider/BaiduImageryProvider.js'
|
||||
viewer.imageryLayers.addImageryProvider(new BaiduImageryProvider({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
}))
|
||||
|
||||
```
|
||||
|
||||
### openlayers
|
||||
|
||||
```javascript
|
||||
// 非百度地图
|
||||
const baseMap = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png',
|
||||
projection: 'EPSG:3857',
|
||||
}),
|
||||
});
|
||||
const map = new ol.Map({
|
||||
layers: [baseMap],
|
||||
target: 'map',
|
||||
view: new ol.View({
|
||||
center: ol.proj.transform([105.08052356963802, 36.04231948670001], 'EPSG:4326', 'EPSG:3857'),
|
||||
zoom: 5,
|
||||
}),
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
### maptalks
|
||||
|
||||
```javascript
|
||||
// 非百度地图
|
||||
var map = new maptalks.Map('map', {
|
||||
center: [105.08052356963802, 36.04231948670001],
|
||||
zoom: 5,
|
||||
minZoom:1,
|
||||
maxZoom:19,
|
||||
baseLayer: new maptalks.TileLayer('base', {
|
||||
'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
})
|
||||
});
|
||||
//百度地图
|
||||
var map = new maptalks.Map('map', {
|
||||
center: [105.08052356963802, 36.04231948670001],
|
||||
zoom: 5,
|
||||
minZoom:1,
|
||||
maxZoom:19,
|
||||
spatialReference:{
|
||||
projection : 'baidu',
|
||||
},
|
||||
baseLayer: new maptalks.TileLayer('base', {
|
||||
'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
# TODO
|
||||
+ 自定义图层加载、下载,支持上传geojson作为下载范围
|
||||
+ 瓦片拼接大图
|
||||
|
||||
如果该项目对你有帮助,麻烦给个star!
|
||||
|
||||
声明:本软件仅供个人学习与科研使用,所下载的数据版权归各个地图服务商所有,任何组织或个人因数据使用不当造成的问题,软件作者不负责任。
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"name": "map-download",
|
||||
"description": "高德地图、百度地图(包括百度自定义地图)、腾讯地图、OpenStreetMap、CartoDb、ArcGIS在线地图、天地图、MapBox的下载",
|
||||
"author": "Hxy1992<https://github.com/Hxy1992>",
|
||||
"repository": "https://github.com/Hxy1992/MapDownload",
|
||||
"version": "v1.0.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=v16.13",
|
||||
"npm": ">=8.1"
|
||||
},
|
||||
"main": "packages/main/dist/index.cjs",
|
||||
"scripts": {
|
||||
"build": "node --max_old_space_size=8192 scripts/build.js",
|
||||
"precompile": "cross-env MODE=production npm run build",
|
||||
"compile": "electron-builder build --config .electron-builder.config.js --dir --config.asar=false",
|
||||
"pretest": "npm run build",
|
||||
"test": "node tests/app.spec.js",
|
||||
"watch": "node scripts/watch.js",
|
||||
"lint": "eslint . --ext js,ts,vue",
|
||||
"dev": "npm run watch"
|
||||
},
|
||||
"browserslist": [
|
||||
"Chrome 96"
|
||||
],
|
||||
"lint-staged": {
|
||||
"*.{js,ts,vue}": "eslint --cache --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vitejs/plugin-vue": "1.10.0",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "16.0.1",
|
||||
"electron-builder": "22.14.5",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
"eslint": "8.3.0",
|
||||
"eslint-plugin-vue": "8.1.1",
|
||||
"naive-ui": "^2.28.0",
|
||||
"playwright": "1.16.3",
|
||||
"sass": "^1.45.0",
|
||||
"vfonts": "^0.0.3",
|
||||
"vite": "2.6.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@turf/boolean-contains": "^6.5.0",
|
||||
"@turf/boolean-crosses": "^6.5.0",
|
||||
"@turf/boolean-disjoint": "^6.5.0",
|
||||
"@turf/helpers": "^6.5.0",
|
||||
"@turf/intersect": "^6.5.0",
|
||||
"electron-updater": "4.6.2",
|
||||
"fs-extra": "^10.0.0",
|
||||
"maptalks": "^1.0.0-rc.33",
|
||||
"marked": "^4.0.8",
|
||||
"sharp": "^0.29.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vue": "3.2.22",
|
||||
"vue-router": "4.0.12"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
function createWindow() {
|
||||
// Your window creation code
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const mainWindow = createWindow();
|
||||
|
||||
// Add IPC listeners here
|
||||
ipcMain.on('get-path-join', (event, ...args) => {
|
||||
event.returnValue = path.join(...args);
|
||||
});
|
||||
|
||||
ipcMain.handle('fs-exists', (_, filePath) => fs.existsSync(filePath));
|
||||
ipcMain.handle('fs-read', (_, filePath) => fs.readFileSync(filePath, 'utf-8'));
|
||||
ipcMain.handle('fs-write', (_, filePath, content) => fs.writeFileSync(filePath, content));
|
||||
});
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import {app, BrowserWindow, shell} from 'electron';
|
||||
import {join} from 'path';
|
||||
import {URL} from 'url';
|
||||
import { ipcHandle } from './ipcMain';
|
||||
|
||||
|
||||
const isSingleInstance = app.requestSingleInstanceLock();
|
||||
const isDevelopment = import.meta.env.MODE === 'development';
|
||||
|
||||
if (!isSingleInstance) {
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
// Install "Vue.js devtools"
|
||||
if (isDevelopment) {
|
||||
app.whenReady()
|
||||
.then(() => import('electron-devtools-installer'))
|
||||
.then(({default: installExtension, VUEJS3_DEVTOOLS}) => installExtension(VUEJS3_DEVTOOLS, {
|
||||
loadExtensionOptions: {
|
||||
allowFileAccess: true,
|
||||
},
|
||||
}))
|
||||
.catch(e => console.error('Failed install extension:', e));
|
||||
}
|
||||
|
||||
let mainWindow = null;
|
||||
|
||||
const createWindow = async () => {
|
||||
mainWindow = new BrowserWindow({
|
||||
show: false, // Use 'ready-to-show' event to show window
|
||||
webPreferences: {
|
||||
nativeWindowOpen: true,
|
||||
// nodeIntegration: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
preload: join(__dirname, '../../preload/dist/index.cjs'),
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* If you install `show: true` then it can cause issues when trying to close the window.
|
||||
* Use `show: false` and listener events `ready-to-show` to fix these issues.
|
||||
*
|
||||
* @see https://github.com/electron/electron/issues/25012
|
||||
*/
|
||||
mainWindow.on('ready-to-show', () => {
|
||||
mainWindow?.show();
|
||||
|
||||
if (isDevelopment) {
|
||||
mainWindow?.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* URL for main window.
|
||||
* Vite dev server for development.
|
||||
* `file://../renderer/index.html` for production and test
|
||||
*/
|
||||
const pageUrl = isDevelopment && import.meta.env.VITE_DEV_SERVER_URL !== undefined
|
||||
? import.meta.env.VITE_DEV_SERVER_URL
|
||||
: new URL('../renderer/dist/index.html', 'file://' + __dirname).toString();
|
||||
|
||||
|
||||
await mainWindow.loadURL(pageUrl, {
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
});
|
||||
|
||||
ipcHandle(mainWindow);
|
||||
};
|
||||
|
||||
app.on('web-contents-created', (_event, contents) => {
|
||||
|
||||
/**
|
||||
* Block navigation to origins not on the allowlist.
|
||||
*
|
||||
* Navigation is a common attack vector. If an attacker can convince the app to navigate away
|
||||
* from its current page, they can possibly force the app to open web sites on the Internet.
|
||||
*
|
||||
* @see https://www.electronjs.org/docs/latest/tutorial/security#13-disable-or-limit-navigation
|
||||
*/
|
||||
contents.on('will-navigate', (event, url) => {
|
||||
const allowedOrigins =
|
||||
new Set(); // Do not use insecure protocols like HTTP. https://www.electronjs.org/docs/latest/tutorial/security#1-only-load-secure-content
|
||||
const { origin, hostname } = new URL(url);
|
||||
const isDevLocalhost = isDevelopment && hostname === 'localhost'; // permit live reload of index.html
|
||||
if (!allowedOrigins.has(origin) && !isDevLocalhost){
|
||||
console.warn('Blocked navigating to an unallowed origin:', origin);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Hyperlinks to allowed sites open in the default browser.
|
||||
*
|
||||
* The creation of new `webContents` is a common attack vector. Attackers attempt to convince the app to create new windows,
|
||||
* frames, or other renderer processes with more privileges than they had before; or with pages opened that they couldn't open before.
|
||||
* You should deny any unexpected window creation.
|
||||
*
|
||||
* @see https://www.electronjs.org/docs/latest/tutorial/security#14-disable-or-limit-creation-of-new-windows
|
||||
* @see https://www.electronjs.org/docs/latest/tutorial/security#15-do-not-use-openexternal-with-untrusted-content
|
||||
*/
|
||||
contents.setWindowOpenHandler(({ url }) => {
|
||||
const allowedOrigins =
|
||||
new Set([ // Do not use insecure protocols like HTTP. https://www.electronjs.org/docs/latest/tutorial/security#1-only-load-secure-content
|
||||
'https://vitejs.dev',
|
||||
'https://github.com',
|
||||
'https://v3.vuejs.org']);
|
||||
const { origin } = new URL(url);
|
||||
if (allowedOrigins.has(origin)){
|
||||
shell.openExternal(url);
|
||||
} else {
|
||||
console.warn('Blocked the opening of an unallowed origin:', origin);
|
||||
}
|
||||
return { action: 'deny' };
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.on('second-instance', () => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.whenReady()
|
||||
.then(createWindow)
|
||||
.catch((e) => console.error('Failed create window:', e));
|
||||
|
||||
|
||||
// Auto-updates
|
||||
if (import.meta.env.PROD) {
|
||||
app.whenReady()
|
||||
.then(() => import('electron-updater'))
|
||||
.then(({autoUpdater}) => autoUpdater.checkForUpdatesAndNotify())
|
||||
.catch((e) => console.error('Failed check updates:', e));
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
|
||||
/**
|
||||
* 设置请求头
|
||||
*/
|
||||
export function getHeader() {
|
||||
return {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
// 'X-Forwarded-For': returnIp(),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
// 在主进程中.
|
||||
const { ipcMain } = require('electron');
|
||||
const { dialog } = require('electron');
|
||||
const fse = require('fs-extra');
|
||||
const fs = require('fs');
|
||||
const sharp = require('sharp');
|
||||
const request = require('superagent');
|
||||
const path = require('path');
|
||||
import { getHeader } from './ipHandle';
|
||||
|
||||
ipcMain.handle('show-dialog', async () => {
|
||||
const result = await dialog.showOpenDialog({ properties: ['openFile', 'openDirectory'] });
|
||||
return result;
|
||||
});
|
||||
// 确保目录存在,不存在则创建
|
||||
ipcMain.on('ensure-dir', (event, args) => {
|
||||
fse.ensureDirSync(args);
|
||||
});
|
||||
|
||||
|
||||
// 下载事件
|
||||
export function ipcHandle(win) {
|
||||
|
||||
// superagent & sharp 下载图片
|
||||
ipcMain.on('save-image', (event, args) => {
|
||||
// sharp(base64Data).composite 反过来试试
|
||||
const savePath = path.normalize(args.savePath);
|
||||
const sharpStream = sharp({
|
||||
failOnError: false,
|
||||
});
|
||||
const promises = [];
|
||||
if (args.imageBuffer) {
|
||||
const base64Data = args.imageBuffer.replace(/^data:image\/\w+;base64,/, '');
|
||||
const dataBuffer = Buffer.from(base64Data, 'base64');
|
||||
promises.push(
|
||||
sharpStream
|
||||
.composite([{ input: dataBuffer, gravity: 'centre', blend: 'dest-in' }])
|
||||
.toFile(savePath),
|
||||
);
|
||||
} else {
|
||||
promises.push(
|
||||
sharpStream
|
||||
// .ensureAlpha()
|
||||
.toFile(savePath),
|
||||
);
|
||||
}
|
||||
|
||||
request.get(args.url).set(getHeader()).pipe(sharpStream);
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
win.webContents.send('imageDownloadDone', {
|
||||
state: 'completed',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('错误', err);
|
||||
try {
|
||||
fs.unlinkSync(savePath);
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
win.webContents.send('imageDownloadDone', {
|
||||
state: 'error',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// superagent & sharp 下载、合并图片
|
||||
ipcMain.on('save-image-merge', (event, args) => {
|
||||
try {
|
||||
const savePath = path.normalize(args.savePath);
|
||||
let imgBack;
|
||||
const imgBuffer = [];
|
||||
args.layers.forEach(async (item, index) => {
|
||||
const sharpStream = sharp({
|
||||
failOnError: false,
|
||||
});
|
||||
request.get(item.url).set(getHeader()).pipe(sharpStream);
|
||||
const bff = await sharpStream.toBuffer();
|
||||
if (item.isLabel) {
|
||||
imgBack = bff;
|
||||
} else {
|
||||
imgBuffer.push(bff);
|
||||
}
|
||||
// 结束保存
|
||||
if (index === args.layers.length - 1) {
|
||||
let opration;
|
||||
if (args.imageBuffer) {
|
||||
const base64Data = args.imageBuffer.replace(/^data:image\/\w+;base64,/, '');
|
||||
const dataBuffer = Buffer.from(base64Data, 'base64');
|
||||
sharp(imgBack)
|
||||
.composite(imgBuffer.map(input => {
|
||||
return { input, gravity: 'centre', blend: 'saturate' };
|
||||
}))
|
||||
.composite([{ input: dataBuffer, gravity: 'centre', blend: 'dest-in' }]);
|
||||
} else {
|
||||
opration = sharp(imgBack)
|
||||
.composite(imgBuffer.map(input => {
|
||||
return { input, gravity: 'centre', blend: 'saturate' };
|
||||
}));
|
||||
}
|
||||
opration
|
||||
.toFile(savePath)
|
||||
.then(() => {
|
||||
win.webContents.send('imageDownloadDone', {
|
||||
state: 'completed',
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('错误', err);
|
||||
try {
|
||||
fs.unlinkSync(savePath);
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
win.webContents.send('imageDownloadDone', {
|
||||
state: 'error',
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import {node} from '../../.electron-vendors.cache.json';
|
||||
import {join} from 'path';
|
||||
import {builtinModules} from 'module';
|
||||
|
||||
const PACKAGE_ROOT = __dirname;
|
||||
|
||||
|
||||
const config = {
|
||||
mode: process.env.MODE,
|
||||
root: PACKAGE_ROOT,
|
||||
envDir: process.cwd(),
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/': join(PACKAGE_ROOT, 'src') + '/',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: 'inline',
|
||||
target: `node${node}`,
|
||||
outDir: 'dist',
|
||||
assetsDir: '.',
|
||||
minify: process.env.MODE !== 'development',
|
||||
lib: {
|
||||
entry: 'src/index.js',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'electron',
|
||||
'electron-devtools-installer',
|
||||
...builtinModules,
|
||||
],
|
||||
output: {
|
||||
entryFileNames: '[name].cjs',
|
||||
},
|
||||
},
|
||||
emptyOutDir: true,
|
||||
brotliSize: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import {contextBridge} from 'electron';
|
||||
const { ipcRenderer } = require('electron');
|
||||
|
||||
const apiKey = 'electron';
|
||||
let imageDownloadhandle;
|
||||
ipcRenderer.on('imageDownloadDone', (event, state) => {
|
||||
imageDownloadhandle && imageDownloadhandle(state);
|
||||
});
|
||||
/**
|
||||
* @see https://github.com/electron/electron/issues/21437#issuecomment-573522360
|
||||
*/
|
||||
const api = {
|
||||
versions: process.versions,
|
||||
ipcRenderer: { ...ipcRenderer },
|
||||
imageDownloadDone: (callback) => {
|
||||
imageDownloadhandle = callback;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* The "Main World" is the JavaScript context that your main renderer code runs in.
|
||||
* By default, the page you load in your renderer executes code in this world.
|
||||
*
|
||||
* @see https://www.electronjs.org/docs/api/context-bridge
|
||||
*/
|
||||
contextBridge.exposeInMainWorld(apiKey, api);
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import {chrome} from '../../.electron-vendors.cache.json';
|
||||
import {join} from 'path';
|
||||
import {builtinModules} from 'module';
|
||||
|
||||
const PACKAGE_ROOT = __dirname;
|
||||
|
||||
const config = {
|
||||
mode: process.env.MODE,
|
||||
root: PACKAGE_ROOT,
|
||||
envDir: process.cwd(),
|
||||
resolve: {
|
||||
alias: {
|
||||
'/@/': join(PACKAGE_ROOT, 'src') + '/',
|
||||
},
|
||||
},
|
||||
build: {
|
||||
sourcemap: 'inline',
|
||||
target: `chrome${chrome}`,
|
||||
outDir: 'dist',
|
||||
assetsDir: '.',
|
||||
minify: process.env.MODE !== 'development',
|
||||
lib: {
|
||||
entry: 'src/index.js',
|
||||
formats: ['cjs'],
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [
|
||||
'electron',
|
||||
...builtinModules,
|
||||
],
|
||||
output: {
|
||||
entryFileNames: '[name].cjs',
|
||||
},
|
||||
},
|
||||
emptyOutDir: true,
|
||||
brotliSize: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": false
|
||||
},
|
||||
"extends": [
|
||||
/** @see https://eslint.vuejs.org/rules/ */
|
||||
"plugin:vue/vue3-recommended"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<!-- 内容安全策略:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy -->
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' blob: 'unsafe-eval' 'unsafe-inline' 'unsafe-hashes'">
|
||||
<title>MapDownload</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./src/index.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<template>
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<router-view />
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
setup() {
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1650436523116" class="icon" viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2577" xmlns:xlink="http://www.w3.org/1999/xlink" width="200.5859375" height="200"><defs><style type="text/css">@font-face { font-family: feedback-iconfont; src: url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff2?t=1630033759944") format("woff2"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.woff?t=1630033759944") format("woff"), url("//at.alicdn.com/t/font_1031158_u69w8yhxdu.ttf?t=1630033759944") format("truetype"); }
|
||||
</style></defs><path d="M977.6 555.2c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2h-84.8v-256h84.8c25.6 0 43.2-17.6 43.2-43.2S1003.2 128 977.6 128h-84.8V43.2c0-25.6-17.6-43.2-43.2-43.2s-43.2 17.6-43.2 43.2V128h-256V43.2c1.6-25.6-16-43.2-41.6-43.2s-43.2 17.6-43.2 43.2V128h-256V43.2C209.6 17.6 192 0 166.4 0S128 17.6 128 43.2V128H43.2C17.6 128 0 145.6 0 171.2s17.6 43.2 43.2 43.2H128v256H43.2C17.6 468.8 0 486.4 0 512s17.6 43.2 43.2 43.2H128v256H43.2c-25.6 0-43.2 17.6-43.2 43.2C0 878.4 17.6 896 43.2 896H128v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h256v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h256v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h84.8c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2H896v-256h84.8v1.6zM808 212.8v256h-256v-256h256z m-595.2 0h256v256h-256v-256z m0 598.4v-256h256v256h-256z m595.2 0h-256v-256h256v256z" p-id="2578"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#41D1FF"/>
|
||||
<stop offset="1" stop-color="#BD34FE"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#FFEA83"/>
|
||||
<stop offset="0.0833333" stop-color="#FFDD35"/>
|
||||
<stop offset="1" stop-color="#FFA800"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,175 @@
|
|||
import * as Cesium from 'cesium';
|
||||
import ImageryType from './ImageryType';
|
||||
import BaiduMercatorTilingScheme from './BaiduMercatorTilingScheme';
|
||||
|
||||
const IMG_URL =
|
||||
'https://shangetu{s}.map.bdimg.com/it/u=x={x};y={y};z={z};v=009;type=sate&fm=46';
|
||||
|
||||
const VEC_URL =
|
||||
'https://online{s}.map.bdimg.com/tile/?qt=tile&x={x}&y={y}&z={z}&styles=sl&v=020';
|
||||
|
||||
const CUSTOM_URL =
|
||||
'https://api{s}.map.bdimg.com/customimage/tile?&x={x}&y={y}&z={z}&scale=1&customid=midnight';
|
||||
|
||||
const TRAFFIC_URL =
|
||||
'https://its.map.baidu.com:8002/traffic/TrafficTileService?time={time}&label={labelStyle}&v=016&level={z}&x={x}&y={y}&scaler=2';
|
||||
|
||||
class BaiduImageryProvider {
|
||||
constructor(options = {}) {
|
||||
this._url =
|
||||
options.style === 'img'
|
||||
? IMG_URL
|
||||
: options.style === 'vec'
|
||||
? VEC_URL
|
||||
: options.style === 'traffic'
|
||||
? TRAFFIC_URL
|
||||
: CUSTOM_URL;
|
||||
const baiduLocal = options.url;
|
||||
if (baiduLocal) {
|
||||
this._url = `${baiduLocal}/{z}/{x}/{y}.png`;
|
||||
}
|
||||
this._labelStyle = options.labelStyle || 'web2D';
|
||||
this._tileWidth = 256;
|
||||
this._tileHeight = 256;
|
||||
this._maximumLevel = 18;
|
||||
this._crs = options.crs || 'BD09';
|
||||
if (options.crs === 'WGS84') {
|
||||
const resolutions = [];
|
||||
for (let i = 0; i < 19; i++) {
|
||||
resolutions[i] = 256 * Math.pow(2, 18 - i);
|
||||
}
|
||||
this._tilingScheme = new BaiduMercatorTilingScheme({
|
||||
resolutions,
|
||||
rectangleSouthwestInMeters: new Cesium.Cartesian2(
|
||||
-20037726.37,
|
||||
-12474104.17,
|
||||
),
|
||||
rectangleNortheastInMeters: new Cesium.Cartesian2(
|
||||
20037726.37,
|
||||
12474104.17,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this._tilingScheme = new Cesium.WebMercatorTilingScheme({
|
||||
rectangleSouthwestInMeters: new Cesium.Cartesian2(-33554054, -33746824),
|
||||
rectangleNortheastInMeters: new Cesium.Cartesian2(33554054, 33746824),
|
||||
});
|
||||
}
|
||||
this._rectangle = this._tilingScheme.rectangle;
|
||||
this._credit = undefined;
|
||||
this._token = undefined;
|
||||
this._style = options.style || 'normal';
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
get token() {
|
||||
return this._token;
|
||||
}
|
||||
|
||||
get tileWidth() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'tileWidth must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return this._tileWidth;
|
||||
}
|
||||
|
||||
get tileHeight() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'tileHeight must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return this._tileHeight;
|
||||
}
|
||||
|
||||
get maximumLevel() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'maximumLevel must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return this._maximumLevel;
|
||||
}
|
||||
|
||||
get minimumLevel() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'minimumLevel must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
get tilingScheme() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'tilingScheme must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return this._tilingScheme;
|
||||
}
|
||||
|
||||
get rectangle() {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'rectangle must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
return this._rectangle;
|
||||
}
|
||||
|
||||
get ready() {
|
||||
return !!this._url;
|
||||
}
|
||||
|
||||
get credit() {
|
||||
return this._credit;
|
||||
}
|
||||
|
||||
get hasAlphaChannel() {
|
||||
return true;
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
getTileCredits(x, y, level) {}
|
||||
|
||||
/**
|
||||
* Request Image
|
||||
* @param x
|
||||
* @param y
|
||||
* @param level
|
||||
* @returns {Promise<HTMLImageElement | HTMLCanvasElement>}
|
||||
*/
|
||||
requestImage(x, y, level) {
|
||||
if (!this.ready) {
|
||||
throw new Cesium.DeveloperError(
|
||||
'requestImage must not be called before the imagery provider is ready.',
|
||||
);
|
||||
}
|
||||
const xTiles = this._tilingScheme.getNumberOfXTilesAtLevel(level);
|
||||
const yTiles = this._tilingScheme.getNumberOfYTilesAtLevel(level);
|
||||
let url = this._url
|
||||
.replace('{z}', level)
|
||||
.replace('{s}', String(1))
|
||||
.replace('{style}', this._style)
|
||||
.replace('{labelStyle}', this._labelStyle)
|
||||
.replace('{time}', String(new Date().getTime()));
|
||||
|
||||
if (this._crs === 'WGS84') {
|
||||
url = url.replace('{x}', String(x)).replace('{y}', String(-y));
|
||||
} else {
|
||||
url = url
|
||||
.replace('{x}', String(x - xTiles / 2))
|
||||
.replace('{y}', String(yTiles / 2 - y - 1));
|
||||
}
|
||||
return Cesium.ImageryProvider.loadImage(this, url);
|
||||
}
|
||||
}
|
||||
|
||||
ImageryType.BAIDU = 'baidu';
|
||||
|
||||
export default BaiduImageryProvider;
|
||||
|
|
@ -0,0 +1,494 @@
|
|||
|
||||
const EARTH_RADIUS = 6370996.81;
|
||||
const MC_BAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0];
|
||||
const LL_BAND = [75, 60, 45, 30, 15, 0];
|
||||
const MC2LL = [
|
||||
[
|
||||
1.410526172116255e-8,
|
||||
8.98305509648872e-6,
|
||||
-1.9939833816331,
|
||||
2.009824383106796e2,
|
||||
-1.872403703815547e2,
|
||||
91.6087516669843,
|
||||
-23.38765649603339,
|
||||
2.57121317296198,
|
||||
-0.03801003308653,
|
||||
1.73379812e7,
|
||||
],
|
||||
[
|
||||
-7.435856389565537e-9,
|
||||
8.983055097726239e-6,
|
||||
-0.78625201886289,
|
||||
96.32687599759846,
|
||||
-1.85204757529826,
|
||||
-59.36935905485877,
|
||||
47.40033549296737,
|
||||
-16.50741931063887,
|
||||
2.28786674699375,
|
||||
1.026014486e7,
|
||||
],
|
||||
[
|
||||
-3.030883460898826e-8,
|
||||
8.98305509983578e-6,
|
||||
0.30071316287616,
|
||||
59.74293618442277,
|
||||
7.357984074871,
|
||||
-25.38371002664745,
|
||||
13.45380521110908,
|
||||
-3.29883767235584,
|
||||
0.32710905363475,
|
||||
6.85681737e6,
|
||||
],
|
||||
[
|
||||
-1.981981304930552e-8,
|
||||
8.983055099779535e-6,
|
||||
0.03278182852591,
|
||||
40.31678527705744,
|
||||
0.65659298677277,
|
||||
-4.44255534477492,
|
||||
0.85341911805263,
|
||||
0.12923347998204,
|
||||
-0.04625736007561,
|
||||
4.48277706e6,
|
||||
],
|
||||
[
|
||||
3.09191371068437e-9,
|
||||
8.983055096812155e-6,
|
||||
0.00006995724062,
|
||||
23.10934304144901,
|
||||
-0.00023663490511,
|
||||
-0.6321817810242,
|
||||
-0.00663494467273,
|
||||
0.03430082397953,
|
||||
-0.00466043876332,
|
||||
2.5551644e6,
|
||||
],
|
||||
[
|
||||
2.890871144776878e-9,
|
||||
8.983055095805407e-6,
|
||||
-0.00000003068298,
|
||||
7.47137025468032,
|
||||
-0.00000353937994,
|
||||
-0.02145144861037,
|
||||
-0.00001234426596,
|
||||
0.00010322952773,
|
||||
-0.00000323890364,
|
||||
8.260885e5,
|
||||
],
|
||||
];
|
||||
const LL2MC = [
|
||||
[
|
||||
-0.0015702102444,
|
||||
1.113207020616939e5,
|
||||
1.704480524535203e15,
|
||||
-1.033898737604234e16,
|
||||
2.611266785660388e16,
|
||||
-3.51496691766537e16,
|
||||
2.659570071840392e16,
|
||||
-1.072501245418824e16,
|
||||
1.800819912950474e15,
|
||||
82.5,
|
||||
],
|
||||
[
|
||||
8.277824516172526e-4,
|
||||
1.113207020463578e5,
|
||||
6.477955746671608e8,
|
||||
-4.082003173641316e9,
|
||||
1.077490566351142e10,
|
||||
-1.517187553151559e10,
|
||||
1.205306533862167e10,
|
||||
-5.124939663577472e9,
|
||||
9.133119359512032e8,
|
||||
67.5,
|
||||
],
|
||||
[
|
||||
0.00337398766765,
|
||||
1.113207020202162e5,
|
||||
4.481351045890365e6,
|
||||
-2.339375119931662e7,
|
||||
7.968221547186455e7,
|
||||
-1.159649932797253e8,
|
||||
9.723671115602145e7,
|
||||
-4.366194633752821e7,
|
||||
8.477230501135234e6,
|
||||
52.5,
|
||||
],
|
||||
[
|
||||
0.00220636496208,
|
||||
1.113207020209128e5,
|
||||
5.175186112841131e4,
|
||||
3.796837749470245e6,
|
||||
9.920137397791013e5,
|
||||
-1.22195221711287e6,
|
||||
1.340652697009075e6,
|
||||
-6.209436990984312e5,
|
||||
1.444169293806241e5,
|
||||
37.5,
|
||||
],
|
||||
[
|
||||
-3.441963504368392e-4,
|
||||
1.113207020576856e5,
|
||||
2.782353980772752e2,
|
||||
2.485758690035394e6,
|
||||
6.070750963243378e3,
|
||||
5.482118345352118e4,
|
||||
9.540606633304236e3,
|
||||
-2.71055326746645e3,
|
||||
1.405483844121726e3,
|
||||
22.5,
|
||||
],
|
||||
[
|
||||
-3.218135878613132e-4,
|
||||
1.113207020701615e5,
|
||||
0.00369383431289,
|
||||
8.237256402795718e5,
|
||||
0.46104986909093,
|
||||
2.351343141331292e3,
|
||||
1.58060784298199,
|
||||
8.77738589078284,
|
||||
0.37238884252424,
|
||||
7.45,
|
||||
],
|
||||
];
|
||||
|
||||
class BaiduMercatorProjection {
|
||||
constructor() {
|
||||
this.isWgs84 = false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @returns {number}
|
||||
*/
|
||||
getDistanceByMC(point1, point2) {
|
||||
if (!point1 || !point2) {
|
||||
return 0;
|
||||
}
|
||||
point1 = this.convertMC2LL(point1);
|
||||
if (!point1) {
|
||||
return 0;
|
||||
}
|
||||
const x1 = this.toRadians(point1['lng']);
|
||||
const y1 = this.toRadians(point1['lat']);
|
||||
point2 = this.convertMC2LL(point2);
|
||||
if (!point2) {
|
||||
return 0;
|
||||
}
|
||||
const x2 = this.toRadians(point2['lng']);
|
||||
const y2 = this.toRadians(point2['lat']);
|
||||
return this.getDistance(x1, x2, y1, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the distance between two points according to the latitude and longitude coordinates
|
||||
* @param point1
|
||||
* @param point2
|
||||
* @returns {number|*}
|
||||
*/
|
||||
getDistanceByLL(point1, point2) {
|
||||
if (!point1 || !point2) {
|
||||
return 0;
|
||||
}
|
||||
point1['lng'] = this.getLoop(point1['lng'], -180, 180);
|
||||
point1['lat'] = this.getRange(point1['lat'], -74, 74);
|
||||
point2['lng'] = this.getLoop(point2['lng'], -180, 180);
|
||||
point2['lat'] = this.getRange(point2['lat'], -74, 74);
|
||||
const x1 = this.toRadians(point1['lng']);
|
||||
const y1 = this.toRadians(point1['lat']);
|
||||
const x2 = this.toRadians(point2['lng']);
|
||||
const y2 = this.toRadians(point2['lat']);
|
||||
return this.getDistance(x1, x2, y1, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* The plane cartesian coordinates are converted to latitude and longitude coordinates
|
||||
* @param point
|
||||
* @returns {Point|{lng: number, lat: number}}
|
||||
*/
|
||||
convertMC2LL(point) {
|
||||
if (!point) {
|
||||
return { lng: 0, lat: 0 };
|
||||
}
|
||||
let lnglat = {};
|
||||
if (this.isWgs84) {
|
||||
lnglat.lng = (point.lng / 20037508.34) * 180;
|
||||
const mmy = (point.lat / 20037508.34) * 180;
|
||||
lnglat.lat =
|
||||
(180 / Math.PI) *
|
||||
(2 * Math.atan(Math.exp((mmy * Math.PI) / 180)) - Math.PI / 2);
|
||||
return {
|
||||
lng: lnglat['lng'].toFixed(6),
|
||||
lat: lnglat['lat'].toFixed(6),
|
||||
};
|
||||
}
|
||||
|
||||
const temp = {
|
||||
lng: Math.abs(point['lng']),
|
||||
lat: Math.abs(point['lat']),
|
||||
};
|
||||
|
||||
let factor;
|
||||
for (let i = 0; i < MC_BAND.length; i++) {
|
||||
if (temp['lat'] >= MC_BAND[i]) {
|
||||
factor = MC2LL[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
lnglat = this.convertor(point, factor);
|
||||
return {
|
||||
lng: lnglat['lng'].toFixed(6),
|
||||
lat: lnglat['lat'].toFixed(6),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The latitude and longitude coordinates are converted to plane cartesian coordinates
|
||||
* @param point
|
||||
* @returns {{lng: number, lat: number}|*}
|
||||
*/
|
||||
convertLL2MC(point) {
|
||||
if (!point) {
|
||||
return { lng: 0, lat: 0 };
|
||||
}
|
||||
if (
|
||||
point['lng'] > 180 ||
|
||||
point['lng'] < -180 ||
|
||||
point['lat'] > 90 ||
|
||||
point['lat'] < -90
|
||||
) {
|
||||
return point;
|
||||
}
|
||||
|
||||
if (this.isWgs84) {
|
||||
const mercator = {};
|
||||
const earthRad = 6378137.0;
|
||||
mercator.lng = ((point.lng * Math.PI) / 180) * earthRad;
|
||||
const a = (point.lat * Math.PI) / 180;
|
||||
mercator.lat =
|
||||
(earthRad / 2) * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
|
||||
|
||||
return {
|
||||
lng: parseFloat(mercator['lng'].toFixed(2)),
|
||||
lat: parseFloat(mercator['lat'].toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
point['lng'] = this.getLoop(point['lng'], -180, 180);
|
||||
point['lat'] = this.getRange(point['lat'], -74, 74);
|
||||
const temp = { lng: point['lng'], lat: point['lat'] };
|
||||
let factor;
|
||||
for (let i = 0; i < LL_BAND.length; i++) {
|
||||
if (temp['lat'] >= LL_BAND[i]) {
|
||||
factor = LL2MC[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!factor) {
|
||||
for (let i = 0; i < LL_BAND.length; i++) {
|
||||
if (temp['lat'] <= -LL_BAND[i]) {
|
||||
factor = LL2MC[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const mc = this.convertor(point, factor);
|
||||
return {
|
||||
lng: parseFloat(mc['lng'].toFixed(2)),
|
||||
lat: parseFloat(mc['lat'].toFixed(2)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param fromPoint
|
||||
* @param factor
|
||||
* @returns {{lng: *, lat: *}}
|
||||
*/
|
||||
convertor(fromPoint, factor) {
|
||||
if (!fromPoint || !factor) {
|
||||
return { lng: 0, lat: 0 };
|
||||
}
|
||||
let x = factor[0] + factor[1] * Math.abs(fromPoint['lng']);
|
||||
const temp = Math.abs(fromPoint['lat']) / factor[9];
|
||||
let y =
|
||||
factor[2] +
|
||||
factor[3] * temp +
|
||||
factor[4] * temp * temp +
|
||||
factor[5] * temp * temp * temp +
|
||||
factor[6] * temp * temp * temp * temp +
|
||||
factor[7] * temp * temp * temp * temp * temp +
|
||||
factor[8] * temp * temp * temp * temp * temp * temp;
|
||||
x *= fromPoint['lng'] < 0 ? -1 : 1;
|
||||
y *= fromPoint['lat'] < 0 ? -1 : 1;
|
||||
return {
|
||||
lng: x,
|
||||
lat: y,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param x1
|
||||
* @param x2
|
||||
* @param y1
|
||||
* @param y2
|
||||
* @returns {number}
|
||||
*/
|
||||
getDistance(x1, x2, y1, y2) {
|
||||
return (
|
||||
EARTH_RADIUS *
|
||||
Math.acos(
|
||||
Math.sin(y1) * Math.sin(y2) +
|
||||
Math.cos(y1) * Math.cos(y2) * Math.cos(x2 - x1),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deg
|
||||
* @returns {number}
|
||||
*/
|
||||
toRadians(deg) {
|
||||
return (Math.PI * deg) / 180;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param rad
|
||||
* @returns {number}
|
||||
*/
|
||||
toDegrees(rad) {
|
||||
return (180 * rad) / Math.PI;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param v
|
||||
* @param a
|
||||
* @param b
|
||||
* @returns {number}
|
||||
*/
|
||||
getRange(v, a, b) {
|
||||
if (a != null) {
|
||||
v = Math.max(v, a);
|
||||
}
|
||||
if (b != null) {
|
||||
v = Math.min(v, b);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param v
|
||||
* @param a
|
||||
* @param b
|
||||
* @returns {*}
|
||||
*/
|
||||
getLoop(v, a, b) {
|
||||
while (v > b) {
|
||||
v -= b - a;
|
||||
}
|
||||
while (v < a) {
|
||||
v += b - a;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point
|
||||
* @returns {{lng: number, lat: number}|*}
|
||||
*/
|
||||
lngLatToMercator(point) {
|
||||
return this.convertLL2MC(point);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point
|
||||
* @returns {{x: (number|*), y: (number|*)}}
|
||||
*/
|
||||
lngLatToPoint(point) {
|
||||
const mercator = this.convertLL2MC(point);
|
||||
return {
|
||||
x: mercator['lng'],
|
||||
y: mercator['lat'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* WebMercator transforms to latitude and longitude
|
||||
* @param point
|
||||
* @returns {Point|{lng: number, lat: number}}
|
||||
*/
|
||||
mercatorToLngLat(point) {
|
||||
return this.convertMC2LL(point);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param point
|
||||
* @returns {Point|{lng: number, lat: number}}
|
||||
*/
|
||||
pointToLngLat(point) {
|
||||
const mercator = { lng: point.x, lat: point.y };
|
||||
return this.convertMC2LL(mercator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Latitude and longitude coordinates transforms to pixel coordinates
|
||||
* @param point
|
||||
* @param zoom
|
||||
* @param mapCenter
|
||||
* @param mapSize
|
||||
* @returns {{x: number, y: number}}
|
||||
*/
|
||||
pointToPixel(point, zoom, mapCenter, mapSize) {
|
||||
if (!point) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
point = this.lngLatToMercator(point);
|
||||
const zoomUnits = this.getZoomUnits(zoom);
|
||||
const x = Math.round(
|
||||
(point['lng'] - mapCenter['lng']) / zoomUnits + mapSize.width / 2,
|
||||
);
|
||||
const y = Math.round(
|
||||
(mapCenter['lat'] - point['lat']) / zoomUnits + mapSize.height / 2,
|
||||
);
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Pixel coordinates transforms to latitude and longitude coordinates
|
||||
* @param pixel
|
||||
* @param zoom
|
||||
* @param mapCenter
|
||||
* @param mapSize
|
||||
* @returns {Point|{lng: number, lat: number}}
|
||||
*/
|
||||
pixelToPoint(pixel, zoom, mapCenter, mapSize) {
|
||||
if (!pixel) {
|
||||
return { lng: 0, lat: 0 };
|
||||
}
|
||||
const zoomUnits = this.getZoomUnits(zoom);
|
||||
const lng = mapCenter['lng'] + zoomUnits * (pixel.x - mapSize.width / 2);
|
||||
const lat = mapCenter['lat'] - zoomUnits * (pixel.y - mapSize.height / 2);
|
||||
const point = { lng, lat };
|
||||
return this.mercatorToLngLat(point);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param zoom
|
||||
* @returns {number}
|
||||
*/
|
||||
getZoomUnits(zoom) {
|
||||
return Math.pow(2, 18 - zoom);
|
||||
}
|
||||
}
|
||||
|
||||
export default BaiduMercatorProjection;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
|
||||
import * as Cesium from 'cesium';
|
||||
import CoordTransform from './CoordTransform';
|
||||
import BaiduMercatorProjection from './BaiduMercatorProjection';
|
||||
|
||||
class BaiduMercatorTilingScheme extends Cesium.WebMercatorTilingScheme {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const projection = new BaiduMercatorProjection();
|
||||
this._projection.project = function(cartographic, result) {
|
||||
result = result || {};
|
||||
result = CoordTransform.WGS84ToGCJ02(
|
||||
Cesium.Math.toDegrees(cartographic.longitude),
|
||||
Cesium.Math.toDegrees(cartographic.latitude),
|
||||
);
|
||||
result = CoordTransform.GCJ02ToBD09(result[0], result[1]);
|
||||
result[0] = Math.min(result[0], 180);
|
||||
result[0] = Math.max(result[0], -180);
|
||||
result[1] = Math.min(result[1], 74.000022);
|
||||
result[1] = Math.max(result[1], -71.988531);
|
||||
result = projection.lngLatToPoint({
|
||||
lng: result[0],
|
||||
lat: result[1],
|
||||
});
|
||||
return new Cesium.Cartesian2(result.x, result.y);
|
||||
};
|
||||
this._projection.unproject = function(cartesian, result) {
|
||||
result = result || {};
|
||||
result = projection.mercatorToLngLat({
|
||||
lng: cartesian.x,
|
||||
lat: cartesian.y,
|
||||
});
|
||||
result = CoordTransform.BD09ToGCJ02(result.lng, result.lat);
|
||||
result = CoordTransform.GCJ02ToWGS84(result[0], result[1]);
|
||||
return new Cesium.Cartographic(
|
||||
Cesium.Math.toRadians(result[0]),
|
||||
Cesium.Math.toRadians(result[1]),
|
||||
);
|
||||
};
|
||||
this.resolutions = options.resolutions || [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param x
|
||||
* @param y
|
||||
* @param level
|
||||
* @param result
|
||||
* @returns {module:cesium.Rectangle|*}
|
||||
*/
|
||||
tileXYToNativeRectangle(x, y, level, result) {
|
||||
const tileWidth = this.resolutions[level];
|
||||
const west = x * tileWidth;
|
||||
const east = (x + 1) * tileWidth;
|
||||
const north = ((y = -y) + 1) * tileWidth;
|
||||
const south = y * tileWidth;
|
||||
|
||||
if (!Cesium.defined(result)) {
|
||||
return new Cesium.Rectangle(west, south, east, north);
|
||||
}
|
||||
|
||||
result.west = west;
|
||||
result.south = south;
|
||||
result.east = east;
|
||||
result.north = north;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param position
|
||||
* @param level
|
||||
* @param result
|
||||
* @returns {undefined|*}
|
||||
*/
|
||||
positionToTileXY(position, level, result) {
|
||||
const rectangle = this._rectangle;
|
||||
if (!Cesium.Rectangle.contains(rectangle, position)) {
|
||||
return undefined;
|
||||
}
|
||||
const projection = this._projection;
|
||||
const webMercatorPosition = projection.project(position);
|
||||
if (!Cesium.defined(webMercatorPosition)) {
|
||||
return undefined;
|
||||
}
|
||||
const tileWidth = this.resolutions[level];
|
||||
const xTileCoordinate = Math.floor(webMercatorPosition.x / tileWidth);
|
||||
const yTileCoordinate = -Math.floor(webMercatorPosition.y / tileWidth);
|
||||
if (!Cesium.defined(result)) {
|
||||
return new Cesium.Cartesian2(xTileCoordinate, yTileCoordinate);
|
||||
}
|
||||
result.x = xTileCoordinate;
|
||||
result.y = yTileCoordinate;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default BaiduMercatorTilingScheme;
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
|
||||
const BD_FACTOR = (3.14159265358979324 * 3000.0) / 180.0
|
||||
const PI = 3.1415926535897932384626
|
||||
const RADIUS = 6378245.0
|
||||
const EE = 0.00669342162296594323
|
||||
|
||||
class CoordTransform {
|
||||
/**
|
||||
* BD-09 To GCJ-02
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number[]}
|
||||
*/
|
||||
static BD09ToGCJ02(lng, lat) {
|
||||
const x = +lng - 0.0065
|
||||
const y = +lat - 0.006
|
||||
const z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * BD_FACTOR)
|
||||
const theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * BD_FACTOR)
|
||||
const gg_lng = z * Math.cos(theta)
|
||||
const gg_lat = z * Math.sin(theta)
|
||||
return [gg_lng, gg_lat]
|
||||
}
|
||||
|
||||
/**
|
||||
* GCJ-02 To BD-09
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number[]}
|
||||
* @constructor
|
||||
*/
|
||||
static GCJ02ToBD09(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
const z =
|
||||
Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * BD_FACTOR)
|
||||
const theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * BD_FACTOR)
|
||||
const bd_lng = z * Math.cos(theta) + 0.0065
|
||||
const bd_lat = z * Math.sin(theta) + 0.006
|
||||
return [bd_lng, bd_lat]
|
||||
}
|
||||
|
||||
/**
|
||||
* WGS-84 To GCJ-02
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number[]}
|
||||
*/
|
||||
static WGS84ToGCJ02(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
if (this.out_of_china(lng, lat)) {
|
||||
return [lng, lat]
|
||||
} else {
|
||||
const d = this.delta(lng, lat)
|
||||
return [lng + d[0], lat + d[1]]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GCJ-02 To WGS-84
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number[]}
|
||||
* @constructor
|
||||
*/
|
||||
static GCJ02ToWGS84(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
if (this.out_of_china(lng, lat)) {
|
||||
return [lng, lat]
|
||||
} else {
|
||||
const d = this.delta(lng, lat)
|
||||
const mgLng = lng + d[0]
|
||||
const mgLat = lat + d[1]
|
||||
return [lng * 2 - mgLng, lat * 2 - mgLat]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number[]}
|
||||
*/
|
||||
static delta(lng, lat) {
|
||||
let dLng = this.transformLng(lng - 105, lat - 35)
|
||||
let dLat = this.transformLat(lng - 105, lat - 35)
|
||||
const radLat = (lat / 180) * PI
|
||||
let magic = Math.sin(radLat)
|
||||
magic = 1 - EE * magic * magic
|
||||
const sqrtMagic = Math.sqrt(magic)
|
||||
dLng = (dLng * 180) / ((RADIUS / sqrtMagic) * Math.cos(radLat) * PI)
|
||||
dLat = (dLat * 180) / (((RADIUS * (1 - EE)) / (magic * sqrtMagic)) * PI)
|
||||
return [dLng, dLat]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number}
|
||||
*/
|
||||
static transformLng(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
let ret =
|
||||
300.0 +
|
||||
lng +
|
||||
2.0 * lat +
|
||||
0.1 * lng * lng +
|
||||
0.1 * lng * lat +
|
||||
0.1 * Math.sqrt(Math.abs(lng))
|
||||
ret +=
|
||||
((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) *
|
||||
2.0) /
|
||||
3.0
|
||||
ret +=
|
||||
((20.0 * Math.sin(lng * PI) + 40.0 * Math.sin((lng / 3.0) * PI)) * 2.0) /
|
||||
3.0
|
||||
ret +=
|
||||
((150.0 * Math.sin((lng / 12.0) * PI) +
|
||||
300.0 * Math.sin((lng / 30.0) * PI)) *
|
||||
2.0) /
|
||||
3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {number}
|
||||
*/
|
||||
static transformLat(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
let ret =
|
||||
-100.0 +
|
||||
2.0 * lng +
|
||||
3.0 * lat +
|
||||
0.2 * lat * lat +
|
||||
0.1 * lng * lat +
|
||||
0.2 * Math.sqrt(Math.abs(lng))
|
||||
ret +=
|
||||
((20.0 * Math.sin(6.0 * lng * PI) + 20.0 * Math.sin(2.0 * lng * PI)) *
|
||||
2.0) /
|
||||
3.0
|
||||
ret +=
|
||||
((20.0 * Math.sin(lat * PI) + 40.0 * Math.sin((lat / 3.0) * PI)) * 2.0) /
|
||||
3.0
|
||||
ret +=
|
||||
((160.0 * Math.sin((lat / 12.0) * PI) +
|
||||
320 * Math.sin((lat * PI) / 30.0)) *
|
||||
2.0) /
|
||||
3.0
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lng
|
||||
* @param lat
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static out_of_china(lng, lat) {
|
||||
lat = +lat
|
||||
lng = +lng
|
||||
return !(lng > 73.66 && lng < 135.05 && lat > 3.86 && lat < 53.55)
|
||||
}
|
||||
}
|
||||
|
||||
export default CoordTransform
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
const ImageryType = {
|
||||
ARCGIS: 'arcgis',
|
||||
SINGLE_TILE: 'single_tile',
|
||||
WMS: 'wms',
|
||||
WMTS: 'wmts',
|
||||
XYZ: 'xyz',
|
||||
COORD: 'coord'
|
||||
}
|
||||
|
||||
export default ImageryType
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<template>
|
||||
<h2 id="versions">
|
||||
Lib versions
|
||||
</h2>
|
||||
<div>
|
||||
<ul aria-labelledby="versions">
|
||||
<li
|
||||
v-for="(version, lib) in versions"
|
||||
:key="lib"
|
||||
>
|
||||
<strong>{{ lib }}</strong>: v{{ version }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
import {defineComponent} from 'vue';
|
||||
import {useElectron} from '/@/use/electron';
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
setup() {
|
||||
const {versions} = useElectron();
|
||||
// It makes no sense to make "versions" reactive
|
||||
return {versions};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div {
|
||||
text-align: left;
|
||||
display: grid;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<n-popover
|
||||
ref="popover"
|
||||
trigger="click"
|
||||
width="200"
|
||||
>
|
||||
<template #trigger>
|
||||
<span
|
||||
style="cursor: pointer;color: #2080f0;"
|
||||
title="选择下载区域"
|
||||
@click="handleIconClick"
|
||||
>
|
||||
{{ chooseArea || '选择下载区域' }}
|
||||
</span>
|
||||
</template>
|
||||
<n-tree
|
||||
block-line
|
||||
:data="layers"
|
||||
:key-field="'areaCode'"
|
||||
:label-field="'areaName'"
|
||||
:default-expanded-keys="[-1]"
|
||||
:on-update:selected-keys="handleSelect"
|
||||
selectable
|
||||
virtual-scroll
|
||||
style="height: 320px"
|
||||
/>
|
||||
</n-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent, ref} from 'vue';
|
||||
import {getAreaList} from '/@/utils/areaList.js';
|
||||
export default defineComponent({
|
||||
name: 'AreaChoose',
|
||||
components: {
|
||||
},
|
||||
props: {
|
||||
},
|
||||
emits: ['choose'],
|
||||
setup(prop, {emit}) {
|
||||
const layerList = getAreaList();
|
||||
const chooseArea = ref('');
|
||||
const popover = ref();
|
||||
return {
|
||||
chooseArea: chooseArea,
|
||||
popover: popover,
|
||||
layers: layerList,
|
||||
icon: {
|
||||
normal: '#333333',
|
||||
active: '#2080f0',
|
||||
},
|
||||
handleSelect (keys, options) {
|
||||
const option = options[0];
|
||||
chooseArea.value = option.areaName;
|
||||
popover.value.setShow(false);
|
||||
option.fetchLoad().then(geojson => {
|
||||
emit('choose', {option, geojson});
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<n-icon
|
||||
size="20"
|
||||
title="显示瓦片网格"
|
||||
style="cursor: pointer;"
|
||||
:color="gridColor"
|
||||
@click="handleGridClick"
|
||||
>
|
||||
<svg
|
||||
t="1650436523116"
|
||||
class="icon"
|
||||
viewBox="0 0 1027 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="2577"
|
||||
width="200"
|
||||
height="200"
|
||||
><path
|
||||
d="M977.6 555.2c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2h-84.8v-256h84.8c25.6 0 43.2-17.6 43.2-43.2S1003.2 128 977.6 128h-84.8V43.2c0-25.6-17.6-43.2-43.2-43.2s-43.2 17.6-43.2 43.2V128h-256V43.2c1.6-25.6-16-43.2-41.6-43.2s-43.2 17.6-43.2 43.2V128h-256V43.2C209.6 17.6 192 0 166.4 0S128 17.6 128 43.2V128H43.2C17.6 128 0 145.6 0 171.2s17.6 43.2 43.2 43.2H128v256H43.2C17.6 468.8 0 486.4 0 512s17.6 43.2 43.2 43.2H128v256H43.2c-25.6 0-43.2 17.6-43.2 43.2C0 878.4 17.6 896 43.2 896H128v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h256v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h256v84.8c0 25.6 17.6 43.2 43.2 43.2s43.2-17.6 43.2-43.2V896h84.8c25.6 0 43.2-17.6 43.2-43.2s-17.6-43.2-43.2-43.2H896v-256h84.8v1.6zM808 212.8v256h-256v-256h256z m-595.2 0h256v256h-256v-256z m0 598.4v-256h256v256h-256z m595.2 0h-256v-256h256v256z"
|
||||
p-id="2578"
|
||||
/></svg>
|
||||
</n-icon>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
import {defineComponent} from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'GridIcon',
|
||||
emits: ['showGrid'],
|
||||
data() {
|
||||
return {
|
||||
showGrid: false,
|
||||
gridColor: '#333333',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleGridClick() {
|
||||
this.showGrid = !this.showGrid;
|
||||
this.gridColor = this.showGrid ? '#2080f0' : '#333333';
|
||||
this.$emit('showGrid', this.showGrid);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="box-modal"
|
||||
>
|
||||
<div class="dialog">
|
||||
<div class="header">
|
||||
<span class="title">帮助</span>
|
||||
<n-icon
|
||||
class="close"
|
||||
size="20"
|
||||
@click="ok"
|
||||
>
|
||||
<Close />
|
||||
</n-icon>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<b>MapDownload</b> - 基于electron和maptalks实现高德地图、百度地图(包括百度自定义地图)、腾讯地图、OpenStreetMap、CartoDb、ArcGIS在线地图的下载。
|
||||
</div>
|
||||
<div>
|
||||
<b>地址</b> - https://github.com/Hxy1992/MapDownload
|
||||
</div>
|
||||
<div
|
||||
ref="code"
|
||||
class="code"
|
||||
/>
|
||||
</div>
|
||||
<!-- <div class="footer">
|
||||
<button
|
||||
@click="ok"
|
||||
>
|
||||
确定
|
||||
</button>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from 'vue';
|
||||
import { marked } from 'marked';
|
||||
import { Close } from '@vicons/ionicons5';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HelpDialog',
|
||||
components: {
|
||||
Close,
|
||||
},
|
||||
props: {
|
||||
visible: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) this.$nextTick(() => {
|
||||
this.updateCode();
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
updateCode() {
|
||||
const code = `
|
||||
## 下载瓦片加载方式
|
||||
|
||||
### Cesium
|
||||
|
||||
\`\`\`javascript
|
||||
// 非百度地图
|
||||
viewer.imageryLayers.addImageryProvider(new Cesium.UrlTemplateImageryProvider({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
}))
|
||||
// 百度地图(需自定义BaiduImageryProvider),可参考cesium-helper目录下代码
|
||||
import BaiduImageryProvider from './cesium-helper/BaiduImageryProvider/BaiduImageryProvider.js'
|
||||
viewer.imageryLayers.addImageryProvider(new BaiduImageryProvider({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
}))
|
||||
|
||||
\`\`\`
|
||||
|
||||
### openlayers
|
||||
|
||||
\`\`\`javascript
|
||||
// 非百度地图
|
||||
const baseMap = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: 'http://localhost:7099/{z}/{x}/{y}.png',
|
||||
projection: 'EPSG:3857',
|
||||
}),
|
||||
});
|
||||
const map = new ol.Map({
|
||||
layers: [baseMap],
|
||||
target: 'map',
|
||||
view: new ol.View({
|
||||
center: ol.proj.transform([105.08052356963802, 36.04231948670001], 'EPSG:4326', 'EPSG:3857'),
|
||||
zoom: 5,
|
||||
}),
|
||||
});
|
||||
|
||||
\`\`\`
|
||||
|
||||
### maptalks
|
||||
|
||||
\`\`\`javascript
|
||||
// 非百度地图
|
||||
var map = new maptalks.Map('map', {
|
||||
center: [105.08052356963802, 36.04231948670001],
|
||||
zoom: 5,
|
||||
minZoom:1,
|
||||
maxZoom:19,
|
||||
baseLayer: new maptalks.TileLayer('base', {
|
||||
'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
})
|
||||
});
|
||||
//百度地图
|
||||
var map = new maptalks.Map('map', {
|
||||
center: [105.08052356963802, 36.04231948670001],
|
||||
zoom: 5,
|
||||
minZoom:1,
|
||||
maxZoom:19,
|
||||
spatialReference:{
|
||||
projection : 'baidu',
|
||||
},
|
||||
baseLayer: new maptalks.TileLayer('base', {
|
||||
'urlTemplate' : 'http://localhost:7099/{z}/{x}/{y}.png'
|
||||
})
|
||||
});
|
||||
\`\`\`
|
||||
`;
|
||||
this.$refs.code.innerHTML = marked.parse(code);
|
||||
},
|
||||
ok() {
|
||||
// eslint-disable-next-line
|
||||
this.$emit('ok');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-modal{
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 100;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.dialog{
|
||||
width: 850px;
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
box-shadow: 0px 2px 4px 0px rgb(54 58 80 / 30%);
|
||||
border-radius: 3px;
|
||||
.header{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 5px 8px;
|
||||
.title{
|
||||
color: #303133;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.content{
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
.code{
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
background: #aaa9af;
|
||||
}
|
||||
}
|
||||
.footer{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 8px;
|
||||
button{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,307 @@
|
|||
<template>
|
||||
<n-spin
|
||||
class="loadingSpin"
|
||||
:show="mapLoading"
|
||||
>
|
||||
<div id="map" />
|
||||
</n-spin>
|
||||
<div class="box-controls">
|
||||
<layer-control
|
||||
@choose="chooseLayers"
|
||||
/>
|
||||
<div class="splitline" />
|
||||
<n-icon
|
||||
size="20"
|
||||
title="绘制矩形"
|
||||
style="cursor: pointer;"
|
||||
:color="isDrawing ? '#2080f0' : '#333333'"
|
||||
@click="drawRect"
|
||||
>
|
||||
<SquareOutline />
|
||||
</n-icon>
|
||||
<div class="splitline" />
|
||||
<n-icon
|
||||
size="20"
|
||||
title="下载地图"
|
||||
style="cursor: pointer;"
|
||||
@click="showSave"
|
||||
>
|
||||
<CloudDownloadOutline />
|
||||
</n-icon>
|
||||
<div class="splitline" />
|
||||
<GridIcon @show-grid="showGrid" />
|
||||
<div class="splitline" />
|
||||
<n-icon
|
||||
size="20"
|
||||
title="设置"
|
||||
style="cursor: pointer;"
|
||||
@click="showSet(true)"
|
||||
>
|
||||
<SettingsOutline />
|
||||
</n-icon>
|
||||
<div class="splitline" />
|
||||
<n-icon
|
||||
size="20"
|
||||
title="帮助"
|
||||
style="cursor: pointer;"
|
||||
@click="showHelp(true)"
|
||||
>
|
||||
<HelpCircleOutline />
|
||||
</n-icon>
|
||||
<div class="splitline" />
|
||||
<n-icon
|
||||
size="20"
|
||||
title="上传GeoJSON"
|
||||
style="cursor: pointer;"
|
||||
@click="uploadGeoJSON()"
|
||||
>
|
||||
<CloudUploadOutline />
|
||||
</n-icon>
|
||||
<div class="splitline" />
|
||||
<area-choose
|
||||
@choose="chooseArea"
|
||||
/>
|
||||
|
||||
<save-diablog
|
||||
:visible="saveVisible"
|
||||
:download-extent="downloadExtent"
|
||||
:base-layer="saveLayers"
|
||||
:limit-max-zoom="limitMaxZoom"
|
||||
:limit-min-zoom="limitMinZoom"
|
||||
:is-baidu="isBaidu"
|
||||
@ok="save"
|
||||
@cancel="cancelSave"
|
||||
/>
|
||||
<help-diablog
|
||||
:visible="helpVisible"
|
||||
@ok="showHelp(false)"
|
||||
/>
|
||||
<map-key
|
||||
:visible="setVisible"
|
||||
@hide="showSet(false)"
|
||||
/>
|
||||
</div>
|
||||
<ProgressControl />
|
||||
<tips />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from 'vue';
|
||||
import baseMap from '../utils/baseMap.js';
|
||||
import {setMapLoading,getMapLoading} from '../utils/baseMap.js';
|
||||
import LayerControl from './LayerControl.vue';
|
||||
import AreaChoose from './AreaChoose.vue';
|
||||
import SaveDiablog from './Save.vue';
|
||||
import FileSave from '../utils/fileSave.js';
|
||||
import HelpDiablog from './Help.vue';
|
||||
import MapKey from './MapKey.vue';
|
||||
import Tips from './Tips.vue';
|
||||
import {useMessage, useNotification} from 'naive-ui';
|
||||
import GridIcon from './GridIcon.vue';
|
||||
import ProgressControl from './ProgressControl.vue';
|
||||
import { CloudDownloadOutline, HelpCircleOutline, SettingsOutline, SquareOutline,CloudUploadOutline } from '@vicons/ionicons5';
|
||||
// eslint-disable-next-line
|
||||
let map
|
||||
export default defineComponent({
|
||||
name: 'HomeMain',
|
||||
components: {
|
||||
LayerControl,
|
||||
SaveDiablog,
|
||||
HelpDiablog,
|
||||
MapKey,
|
||||
Tips,
|
||||
AreaChoose,
|
||||
GridIcon,
|
||||
ProgressControl,
|
||||
CloudDownloadOutline,
|
||||
HelpCircleOutline,
|
||||
SettingsOutline,
|
||||
SquareOutline,
|
||||
CloudUploadOutline,
|
||||
},
|
||||
setup() {
|
||||
window.$message = useMessage();
|
||||
window.$notification = useNotification();
|
||||
const mapLoading = getMapLoading();
|
||||
return {
|
||||
mapLoading: mapLoading,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isDrawing: false,
|
||||
saveVisible: false,
|
||||
downloadExtent: {},
|
||||
helpVisible: false,
|
||||
setVisible: false,
|
||||
saveLayers: [],
|
||||
limitMinZoom: 1,
|
||||
limitMaxZoom: 18,
|
||||
isBaidu: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mounted() {
|
||||
map = new baseMap('map');
|
||||
this.addMapRightClickHandle();
|
||||
},
|
||||
methods: {
|
||||
chooseLayers(data) {
|
||||
this._currentLayer = data;
|
||||
map.switchBaseLayer(data);
|
||||
},
|
||||
drawRect() {
|
||||
this.isDrawing = !this.isDrawing;
|
||||
if (this.isDrawing) {
|
||||
this.hideDrawTips();
|
||||
this._drawStartInfo = window.$notification.create({
|
||||
content: '已开启矩形绘制,右键下载瓦片',
|
||||
duration: 10000,
|
||||
});
|
||||
map.startDraw();
|
||||
} else {
|
||||
map.endDraw();
|
||||
}
|
||||
},
|
||||
hideDrawTips() {
|
||||
if (this._drawStartInfo) {
|
||||
this._drawStartInfo.destroy();
|
||||
this._drawStartInfo = null;
|
||||
}
|
||||
},
|
||||
// 地图右键下载瓦片
|
||||
addMapRightClickHandle() {
|
||||
map.getMap().addEventListener('contextmenu', () => {
|
||||
if (!this.isDrawing) return;
|
||||
if (!this.showSave(false)) {
|
||||
setTimeout(() => {
|
||||
this.isDrawing = false;
|
||||
map.endDraw();
|
||||
this.hideDrawTips();
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
});
|
||||
},
|
||||
showSave(showMsg = true) {
|
||||
this.downloadExtent = map.getDownloadExtent();
|
||||
if (!this.downloadExtent) {
|
||||
if (showMsg) window.$message.warning('获取下载范围错误,请重新绘制下载范围');
|
||||
return false;
|
||||
}
|
||||
const {tileLayer,maxZoom,minZoom,projection} = map.getBaseMapConfig();
|
||||
this.saveLayers = tileLayer;
|
||||
this.limitMaxZoom = maxZoom;
|
||||
this.limitMinZoom = minZoom;
|
||||
this.isBaidu = projection.code === 'BAIDU';
|
||||
this.saveVisible = true;
|
||||
map.fitExtent();
|
||||
setMapLoading(true);
|
||||
return true;
|
||||
},
|
||||
save(val) {
|
||||
this.saveVisible = false;
|
||||
this.$nextTick(() => {
|
||||
const mapConfig = map.getBaseMapConfig();
|
||||
val.mapConfig = mapConfig;
|
||||
if (val.clipImage) {
|
||||
val.downloadGeometry = map.getDownloadGeometry();
|
||||
}
|
||||
new FileSave(val);
|
||||
});
|
||||
},
|
||||
cancelSave() {
|
||||
setMapLoading(false);
|
||||
this.saveVisible = false;
|
||||
},
|
||||
showHelp(val) {
|
||||
this.helpVisible = val;
|
||||
},
|
||||
showSet(val) {
|
||||
this.setVisible = val;
|
||||
if (!val && this._currentLayer && (this._currentLayer.parent === 'Tdt' || this._currentLayer.parent === 'Mapbox')) {
|
||||
map.switchBaseLayer(this._currentLayer);
|
||||
}
|
||||
},
|
||||
chooseArea(data) {
|
||||
// 结束绘制
|
||||
this.isDrawing = false;
|
||||
map.endDraw();
|
||||
this.hideDrawTips();
|
||||
// 添加区域至地图
|
||||
const {geojson} = data;
|
||||
// console.log(option);
|
||||
map.addGeometry(geojson, true, () => {
|
||||
this.showSave();
|
||||
});
|
||||
map.fitExtent();
|
||||
},
|
||||
uploadGeoJSON() {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = '.geojson';
|
||||
fileInput.click();
|
||||
fileInput.onchange = () => {
|
||||
const file = fileInput.files[0];
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.readAsText(file);
|
||||
reader.onload = () => {
|
||||
const geojson = JSON.parse(reader.result);
|
||||
console.log(geojson)
|
||||
this.chooseArea({geojson});
|
||||
};
|
||||
}
|
||||
},
|
||||
showGrid(val) {
|
||||
map.showTileGrid(val);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
.loadingSpin{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#map{
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.box-controls{
|
||||
position: absolute;
|
||||
left: 70px;
|
||||
top: 10px;
|
||||
background-color: white;
|
||||
box-shadow: 0px 2px 4px 0px rgb(54 58 80 / 30%);
|
||||
// width: 200px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
// .items{
|
||||
// width: 20px;
|
||||
// height: 20px;
|
||||
// background-size: contain;
|
||||
// background-repeat: no-repeat;
|
||||
// background-position: center center;
|
||||
// cursor: pointer;
|
||||
// }
|
||||
.splitline{
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
margin: 0 8px;
|
||||
background-color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<template>
|
||||
<n-dropdown
|
||||
:options="layers"
|
||||
placement="bottom-start"
|
||||
trigger="click"
|
||||
:key-field="'uuid'"
|
||||
:on-clickoutside="onClickoutside"
|
||||
@select="handleSelect"
|
||||
>
|
||||
<n-icon
|
||||
class="sourceLayer"
|
||||
size="20"
|
||||
title="切换地图源"
|
||||
style="cursor: pointer;"
|
||||
:color="layerColor"
|
||||
@click="handleIconClick"
|
||||
>
|
||||
<Layers />
|
||||
</n-icon>
|
||||
</n-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent, ref} from 'vue';
|
||||
import {getMapList} from '/@/utils/layerList.js';
|
||||
import {getKeys} from '/@/utils/mapKey.js';
|
||||
import { Layers } from '@vicons/ionicons5';
|
||||
export default defineComponent({
|
||||
name: 'LayerControl',
|
||||
components: {
|
||||
Layers,
|
||||
},
|
||||
props: {
|
||||
},
|
||||
emits: ['choose'],
|
||||
setup(prop, {emit}) {
|
||||
const showDropdownRef = ref(false);
|
||||
const layerList = getMapList();
|
||||
return {
|
||||
layers: layerList,
|
||||
layersVisible: showDropdownRef,
|
||||
icon: {
|
||||
normal: '#333333',
|
||||
active: '#2080f0',
|
||||
},
|
||||
handleSelect (key, layer) {
|
||||
const parent = layerList.find(item => { return item.uuid === layer.pid; });
|
||||
|
||||
const {mapboxKey, tdtKey} = getKeys();
|
||||
if ((parent.value === 'Mapbox' && !mapboxKey) || (parent.value === 'Tdt' && !tdtKey)) {
|
||||
window.$message.warning(`请设置${parent.label}地图Key`);
|
||||
}
|
||||
emit('choose', { parent: parent.value, layer: layer });
|
||||
|
||||
showDropdownRef.value = false;
|
||||
},
|
||||
handleIconClick (e) {
|
||||
e.preventDefault();
|
||||
showDropdownRef.value = true;
|
||||
},
|
||||
onClickoutside () {
|
||||
showDropdownRef.value = false;
|
||||
},
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
layerColor() {
|
||||
return this.layersVisible ? this.icon.active : this.icon.normal;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<n-modal
|
||||
:show="showModal"
|
||||
:show-icon="false"
|
||||
:on-mask-click="cancel"
|
||||
:on-esc="cancel"
|
||||
:on-close="cancel"
|
||||
preset="dialog"
|
||||
>
|
||||
<template #header>
|
||||
地图Key配置
|
||||
</template>
|
||||
<div class="dialog-content">
|
||||
<div class="item">
|
||||
<span class="label">天地图:</span>
|
||||
<input
|
||||
v-model="tdtKey"
|
||||
class="value"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">MapBox:</span>
|
||||
<input
|
||||
v-model="mapboxKey"
|
||||
class="value"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<template #action>
|
||||
<n-button @click="cancel">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
@click="ok"
|
||||
>
|
||||
确定
|
||||
</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from 'vue';
|
||||
import {getKeys, setKeys} from '/@/utils/mapKey.js';
|
||||
export default defineComponent({
|
||||
name: 'MapKey',
|
||||
props: {
|
||||
visible: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
tdtKey: '',
|
||||
mapboxKey: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
visible() {
|
||||
this.showModal = this.visible;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.showModal = this.visible;
|
||||
},
|
||||
mounted() {
|
||||
this.getKeys();
|
||||
},
|
||||
methods: {
|
||||
getKeys() {
|
||||
const data = getKeys();
|
||||
this.tdtKey = data?.tdtKey;
|
||||
this.mapboxKey = data?.mapboxKey;
|
||||
},
|
||||
cancel() {
|
||||
// eslint-disable-next-line
|
||||
this.$emit('hide');
|
||||
},
|
||||
ok() {
|
||||
setKeys({
|
||||
tdtKey: this.tdtKey || '',
|
||||
mapboxKey: this.mapboxKey || '',
|
||||
});
|
||||
this.cancel();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-content{
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
.item{
|
||||
margin: 3px 0;
|
||||
}
|
||||
.label{
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
.value{
|
||||
display: inline-block;
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
class="box-progress"
|
||||
>
|
||||
<progress
|
||||
ref="progress"
|
||||
class="progress"
|
||||
value="0"
|
||||
max="100"
|
||||
/>
|
||||
<div class="item">
|
||||
已下载:<span
|
||||
ref="progressSuccess"
|
||||
class="success"
|
||||
/>
|
||||
</div>
|
||||
<div class="item">
|
||||
失败:<span
|
||||
ref="progressError"
|
||||
class="error"
|
||||
/>
|
||||
</div>
|
||||
<button @click="closeProgress">
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
import {defineComponent} from 'vue';
|
||||
import { setProgressDom, showProgress } from '../utils/progress';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProgressControl',
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
setProgressDom({
|
||||
success: this.$refs['progressSuccess'],
|
||||
error: this.$refs['progressError'],
|
||||
progress: this.$refs['progress'],
|
||||
container: this.$refs['container'],
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
closeProgress() {
|
||||
showProgress(false);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.box-progress{
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
background-color: white;
|
||||
box-shadow: 0px 2px 4px 0px rgb(54 58 80 / 30%);
|
||||
width: 200px;
|
||||
padding: 8px;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
.progress{
|
||||
width: 100%;
|
||||
}
|
||||
.item{
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
<template>
|
||||
<n-modal
|
||||
:show="showModal"
|
||||
:show-icon="false"
|
||||
:on-mask-click="cancel"
|
||||
:on-esc="cancel"
|
||||
:on-close="cancel"
|
||||
preset="dialog"
|
||||
>
|
||||
<template #header>
|
||||
下载参数配置
|
||||
</template>
|
||||
<div class="dialog-content">
|
||||
<n-descriptions
|
||||
label-placement="left"
|
||||
title="下载范围"
|
||||
size="small"
|
||||
:column="1"
|
||||
class="descriptions"
|
||||
>
|
||||
<n-descriptions-item label="xmin">
|
||||
{{ downloadExtent.xmin }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="xmax">
|
||||
{{ downloadExtent.xmax }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="ymin">
|
||||
{{ downloadExtent.ymin }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="ymax">
|
||||
{{ downloadExtent.ymax }}
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
<div class="item">
|
||||
<span class="label">最小层级:</span>
|
||||
<input
|
||||
v-model="minZoom"
|
||||
class="value"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">最大层级:</span>
|
||||
<input
|
||||
v-model="maxZoom"
|
||||
class="value"
|
||||
type="text"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="showMerge"
|
||||
class="item"
|
||||
>
|
||||
<span class="label">合并标注:</span>
|
||||
<div class="value">
|
||||
<input
|
||||
v-model="mergeLayers"
|
||||
type="checkbox"
|
||||
>是否合并
|
||||
</div>
|
||||
<div
|
||||
v-if="mergeLayers"
|
||||
class="warning-merge"
|
||||
>
|
||||
下载大量瓦片时,合并可能会造成卡死!
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">边界裁切:</span>
|
||||
<div class="value">
|
||||
<input
|
||||
v-model="clipImage"
|
||||
type="checkbox"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">下载路径:</span>
|
||||
<div class="value">
|
||||
<input
|
||||
v-model="savePath"
|
||||
type="text"
|
||||
disabled
|
||||
style="width:215px;"
|
||||
>
|
||||
<button @click="setFolder">
|
||||
选择
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<span class="label">瓦片格式:</span>
|
||||
<div class="value">
|
||||
<n-select
|
||||
v-model:value="imageType"
|
||||
:options="imageTypeList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #action>
|
||||
<n-button @click="cancel">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="info"
|
||||
@click="ok"
|
||||
>
|
||||
确定
|
||||
</n-button>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {defineComponent} from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'SaveDialog',
|
||||
props: {
|
||||
visible: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
downloadExtent: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
baseLayer: {
|
||||
required: true,
|
||||
type: [Object, Array],
|
||||
},
|
||||
limitMinZoom: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
limitMaxZoom: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
isBaidu: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showModal: false,
|
||||
savePath: '',
|
||||
maxZoom: '8',
|
||||
minZoom: '5',
|
||||
mergeLayers: false,
|
||||
clipImage: false,
|
||||
imageType: 'png',
|
||||
imageTypeList: [
|
||||
{
|
||||
label: 'png',
|
||||
value: 'png',
|
||||
},
|
||||
{
|
||||
label: 'jpeg',
|
||||
value: 'jpeg',
|
||||
},
|
||||
{
|
||||
label: 'webp',
|
||||
value: 'webp',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showMerge() {
|
||||
return Array.isArray(this.baseLayer) && this.baseLayer.length > 1;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
visible() {
|
||||
this.showModal = this.visible;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.showModal = this.visible;
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
reset() {
|
||||
this.savePath = '';
|
||||
this.maxZoom = '';
|
||||
this.minZoom = '';
|
||||
},
|
||||
async setFolder() {
|
||||
const result = await window.electron.ipcRenderer.invoke('show-dialog');
|
||||
if (result.canceled) return;
|
||||
this.savePath = result.filePaths[0];
|
||||
},
|
||||
cancel() {
|
||||
this.reset();
|
||||
// eslint-disable-next-line
|
||||
this.$emit('cancel', {});
|
||||
},
|
||||
ok() {
|
||||
if (!this.savePath) {
|
||||
return window.$message.warning('请选择保存目录');
|
||||
}
|
||||
if (!this.maxZoom) {
|
||||
return window.$message.warning('请输入最大层级');
|
||||
}
|
||||
if (!this.minZoom) {
|
||||
return window.$message.warning('请输入最小层级');
|
||||
}
|
||||
const minZoom = parseInt(Number(this.minZoom));
|
||||
const maxZoom = parseInt(Number(this.maxZoom));
|
||||
if (isNaN(minZoom) || isNaN(maxZoom)) {
|
||||
return window.$message.warning('层级格式错误,请输入非负整数');
|
||||
}
|
||||
if (minZoom > maxZoom || minZoom < this.limitMinZoom || maxZoom > this.limitMaxZoom) {
|
||||
return window.$message.warning('层级格式错误');
|
||||
}
|
||||
if (this.isBaidu && this.clipImage) {
|
||||
return window.$message.warning('百度瓦片暂不支持裁切,请取消裁切后重试');
|
||||
}
|
||||
const param = {
|
||||
savePath: this.savePath,
|
||||
minZoom: minZoom,
|
||||
maxZoom: maxZoom,
|
||||
mergeLayers: this.mergeLayers,
|
||||
extent: this.downloadExtent,
|
||||
clipImage: this.clipImage,
|
||||
imageType: this.imageType,
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
this.$emit('ok', param);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-content{
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
.item{
|
||||
margin: 3px 0;
|
||||
}
|
||||
.label{
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
.value{
|
||||
display: inline-block;
|
||||
width: 260px;
|
||||
}
|
||||
}
|
||||
.descriptions{
|
||||
::v-deep .n-descriptions-header{
|
||||
font-size: 14px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
::v-deep .n-descriptions-table-content__label{
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
.warning-merge{
|
||||
width: 100%;
|
||||
color: red;
|
||||
padding-left: 83px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="visible"
|
||||
class="modal"
|
||||
@click="visible = false"
|
||||
>
|
||||
<div class="box-tips">
|
||||
<div class="title">
|
||||
地图下载教程
|
||||
</div>
|
||||
<div class="item">
|
||||
1.选择需要下载的图层(天地图和Mapbox需输入Key,可在设置中自行配置)
|
||||
</div>
|
||||
<div class="item">
|
||||
2.绘制需要下载的地图范围 or 选择下载区域
|
||||
</div>
|
||||
<div class="item">
|
||||
3.点击下载按钮,配置相关信息,点击确定
|
||||
</div>
|
||||
<div class="item">
|
||||
4.等待下载完成即可
|
||||
</div>
|
||||
<div class="opt">
|
||||
<button>确定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
import {defineComponent} from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'TipsInfo',
|
||||
setup() {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const isFirstLogin = localStorage.getItem('is-first-login');
|
||||
if (!isFirstLogin) {
|
||||
this.visible = true;
|
||||
}
|
||||
localStorage.setItem('is-first-login', 1);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
background-color: #0000003b;
|
||||
}
|
||||
.box-tips {
|
||||
position: absolute;
|
||||
left: 70px;
|
||||
top: 55px;
|
||||
width: 360px;
|
||||
background-color: white;
|
||||
box-shadow: 0px 2px 4px 0px rgb(54 58 80 / 30%);
|
||||
padding: 8px;
|
||||
.title{
|
||||
font-weight: bold;
|
||||
}
|
||||
.item{
|
||||
padding-left: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.opt{
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue