切换echarts 问题1 原先的打标点产生冲突 -- 解决中
parent
ca28c0851e
commit
b0c004eab8
|
@ -13,13 +13,17 @@
|
|||
"@types/leaflet-draw": "^1.0.7",
|
||||
"@types/proj4leaflet": "^1.0.7",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"echarts": "^5.4.3",
|
||||
"geojson": "^0.5.0",
|
||||
"heatmapjs": "^2.0.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet-draw": "^1.0.4",
|
||||
"pinia": "^2.1.6",
|
||||
"proj4": "^2.9.0",
|
||||
"proj4leaflet": "^1.0.2",
|
||||
"quasar": "^2.12.3",
|
||||
"vue": "^3.3.4"
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@quasar/vite-plugin": "^1.4.1",
|
||||
|
|
|
@ -17,15 +17,24 @@ dependencies:
|
|||
'@vueuse/core':
|
||||
specifier: ^10.2.1
|
||||
version: 10.2.1(vue@3.3.4)
|
||||
echarts:
|
||||
specifier: ^5.4.3
|
||||
version: 5.4.3
|
||||
geojson:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
heatmapjs:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
leaflet:
|
||||
specifier: ^1.9.4
|
||||
version: 1.9.4
|
||||
leaflet-draw:
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4
|
||||
pinia:
|
||||
specifier: ^2.1.6
|
||||
version: 2.1.6(typescript@5.1.6)(vue@3.3.4)
|
||||
proj4:
|
||||
specifier: ^2.9.0
|
||||
version: 2.9.0
|
||||
|
@ -38,6 +47,9 @@ dependencies:
|
|||
vue:
|
||||
specifier: ^3.3.4
|
||||
version: 3.3.4
|
||||
vue-router:
|
||||
specifier: '4'
|
||||
version: 4.2.4(vue@3.3.4)
|
||||
|
||||
devDependencies:
|
||||
'@quasar/vite-plugin':
|
||||
|
@ -405,6 +417,10 @@ packages:
|
|||
'@vue/compiler-dom': 3.3.4
|
||||
'@vue/shared': 3.3.4
|
||||
|
||||
/@vue/devtools-api@6.5.0:
|
||||
resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==}
|
||||
dev: false
|
||||
|
||||
/@vue/language-core@1.8.5(typescript@5.1.6):
|
||||
resolution: {integrity: sha512-DKQNiNQzNV7nrkZQujvjfX73zqKdj2+KoM4YeKl+ft3f+crO3JB4ycPnmgaRMNX/ULJootdQPGHKFRl5cXxwaw==}
|
||||
peerDependencies:
|
||||
|
@ -549,6 +565,13 @@ packages:
|
|||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
dev: true
|
||||
|
||||
/echarts@5.4.3:
|
||||
resolution: {integrity: sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==}
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
zrender: 5.4.4
|
||||
dev: false
|
||||
|
||||
/esbuild@0.18.13:
|
||||
resolution: {integrity: sha512-vhg/WR/Oiu4oUIkVhmfcc23G6/zWuEQKFS+yiosSHe4aN6+DQRXIfeloYGibIfVhkr4wyfuVsGNLr+sQU1rWWw==}
|
||||
engines: {node: '>=12'}
|
||||
|
@ -614,6 +637,10 @@ packages:
|
|||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/heatmapjs@2.0.2:
|
||||
resolution: {integrity: sha512-1pO/bbn9G1NYhndvjnzLVAQMGBOCk8abMM7QnLOPlIMoxDfG9Uylb68PlZe/0MCT9GuwGGtchXVPLWfoT6HrCw==}
|
||||
dev: false
|
||||
|
||||
/is-binary-path@2.1.0:
|
||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -692,6 +719,24 @@ packages:
|
|||
engines: {node: '>=8.6'}
|
||||
dev: true
|
||||
|
||||
/pinia@2.1.6(typescript@5.1.6)(vue@3.3.4):
|
||||
resolution: {integrity: sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.4.0
|
||||
typescript: '>=4.4.4'
|
||||
vue: ^2.6.14 || ^3.3.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.5.0
|
||||
typescript: 5.1.6
|
||||
vue: 3.3.4
|
||||
vue-demi: 0.14.5(vue@3.3.4)
|
||||
dev: false
|
||||
|
||||
/postcss@8.4.26:
|
||||
resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
@ -763,11 +808,14 @@ packages:
|
|||
is-number: 7.0.0
|
||||
dev: true
|
||||
|
||||
/tslib@2.3.0:
|
||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||
dev: false
|
||||
|
||||
/typescript@5.1.6:
|
||||
resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/vite@4.4.4(sass@1.32.12):
|
||||
resolution: {integrity: sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==}
|
||||
|
@ -820,6 +868,15 @@ packages:
|
|||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-router@4.2.4(vue@3.3.4):
|
||||
resolution: {integrity: sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.5.0
|
||||
vue: 3.3.4
|
||||
dev: false
|
||||
|
||||
/vue-template-compiler@2.7.14:
|
||||
resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
|
||||
dependencies:
|
||||
|
@ -855,3 +912,9 @@ packages:
|
|||
/yallist@4.0.0:
|
||||
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
|
||||
dev: true
|
||||
|
||||
/zrender@5.4.4:
|
||||
resolution: {integrity: sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==}
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
dev: false
|
||||
|
|
206
src/App.vue
206
src/App.vue
|
@ -1,129 +1,115 @@
|
|||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import Map from './components/Map.vue'
|
||||
import Mark from './components/mapChild/Mark.vue'
|
||||
import useMarks, { markType } from './hook/useMarks.ts'
|
||||
import ThemeSwitch from './components/ThemeSwitch.vue'
|
||||
import MarkMenu from './components/left/MarkMenu.vue'
|
||||
import CtrlMenu from './components/left/CtrlMenu.vue'
|
||||
import { mdiLanConnect } from '@quasar/extras/mdi-v7'
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
import { Config } from './hook/useConfig';
|
||||
import { onBeforeMount, ref, watch } from 'vue';
|
||||
import ThemeSwitch from './components/ThemeSwitch.vue';
|
||||
import MarkMenu from './components/left/MarkMenu.vue';
|
||||
import CtrlMenu from './components/left/CtrlMenu.vue';
|
||||
import { useWindowSize } from '@vueuse/core';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useMarkStore } from './store/useMarkStore';
|
||||
|
||||
const {marks} = useMarkStore();
|
||||
const tab = ref('mark');
|
||||
const sideStyle = ref('20em,logo');
|
||||
const { width } = useWindowSize();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const nowRouter = ref(route.path);
|
||||
|
||||
const map = ref<InstanceType<typeof Map>>(); // 获取地图的实例 纂取实例方法
|
||||
const marks = useMarks([{ latlng: [39.92647400, 116.40328300], title: '北京故宫', opacity: 1 }])
|
||||
const tab = ref('mark')
|
||||
const sideStyle = ref('20em,logo');
|
||||
const { width } = useWindowSize()
|
||||
const mapConfig = ref()
|
||||
watch(nowRouter, (path) => router.push(path));
|
||||
|
||||
onBeforeMount(() => {
|
||||
sideStyle.value = width.value > 600 ? '20em,logo' : '0em,bar'
|
||||
})
|
||||
onBeforeMount(() => {
|
||||
sideStyle.value = width.value > 600 ? '20em,logo' : '0em,bar';
|
||||
});
|
||||
|
||||
function changeSide(data: string) {
|
||||
const _data = data.split(',')
|
||||
document.body.style.setProperty('--side', _data[0])
|
||||
document.body.style.setProperty('--logoOrBar', _data[1])
|
||||
}
|
||||
watch(sideStyle, changeSide)
|
||||
function changeSide(data: string) {
|
||||
const _data = data.split(',');
|
||||
document.body.style.setProperty('--side', _data[0]);
|
||||
document.body.style.setProperty('--logoOrBar', _data[1]);
|
||||
}
|
||||
|
||||
function pushMark(params: any, type: markType) {
|
||||
const { latlng } = params;
|
||||
marks.value.push({ latlng: [latlng.lat, latlng.lng], title: '新标点', opacity: 1, type })
|
||||
}
|
||||
|
||||
function removeMark(index: number) {
|
||||
marks.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function flytoMark(index: number) {
|
||||
mapConfig.value.center = marks.value[index].latlng
|
||||
}
|
||||
|
||||
function ctrlMap(e: Config) {
|
||||
mapConfig.value = e
|
||||
}
|
||||
watch(sideStyle, changeSide);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h5 class="logo">离线地图</h5>
|
||||
<Map ref="map" v-slot="{ contextmenu }" v-model="mapConfig">
|
||||
<q-menu touch-position context-menu>
|
||||
<q-list dense style="min-width: 100px">
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="pushMark(contextmenu, 'localtion')">添加标点</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="pushMark(contextmenu, 'equipment')">添加设备</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
<Mark v-for="(mark, i) in marks" :key="i" :latlng="mark.latlng" :title="mark.title" :opacity="mark.opacity"
|
||||
:ref="`mark-${i}`">
|
||||
<template v-if="mark.type === 'equipment'">
|
||||
<q-icon :name="mdiLanConnect" size="md"></q-icon>
|
||||
</template>
|
||||
</Mark>
|
||||
</Map>
|
||||
<div class="bar">
|
||||
|
||||
<ThemeSwitch></ThemeSwitch>
|
||||
</div>
|
||||
<div class="left ">
|
||||
<q-toggle class="leftctl" v-model="sideStyle" true-value="20em,logo" false-value="0em,bar" />
|
||||
<q-tabs v-model="tab" dense active-color="primary" indicator-color="primary" align="justify" narrow-indicator>
|
||||
<q-tab name="mark" label="标点" />
|
||||
<q-tab name="ctrl" label="控制" />
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="tab" animated keep-alive>
|
||||
<q-tab-panel name="mark">
|
||||
<MarkMenu :marks="marks" @flyTo="flytoMark" @remove="removeMark"></MarkMenu>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="ctrl">
|
||||
<CtrlMenu @change="ctrlMap"></CtrlMenu>
|
||||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
<h5 class="logo">离线地图</h5>
|
||||
<div class="main" ><router-view></router-view></div>
|
||||
<div class="bar">
|
||||
<q-toggle class="leftctl" v-model="sideStyle" true-value="20em,logo" false-value="0em,bar" />
|
||||
<ThemeSwitch></ThemeSwitch>
|
||||
</div>
|
||||
<div class="left">
|
||||
<q-btn-toggle
|
||||
v-model="nowRouter"
|
||||
spread
|
||||
class="my-custom-toggle"
|
||||
no-caps
|
||||
rounded
|
||||
unelevated
|
||||
toggle-color="primary"
|
||||
color="white"
|
||||
text-color="primary"
|
||||
:options="[
|
||||
{ label: '百度', value: '/baidu' },
|
||||
{ label: '高德', value: '/gaode' },
|
||||
{ label: '热力图', value: '/echart' },
|
||||
]" />
|
||||
<q-tabs v-model="tab" glossy>
|
||||
<q-tab name="mark" label="标点" />
|
||||
<q-tab name="ctrl" label="控制" />
|
||||
</q-tabs>
|
||||
<q-tab-panels v-model="tab" animated keep-alive>
|
||||
<!-- <q-tab-panel name="mark">
|
||||
<MarkMenu :marks="marks" @flyTo="flytoMark" @remove="removeMark"></MarkMenu>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="ctrl">
|
||||
<CtrlMenu @change="ctrlMap"></CtrlMenu>
|
||||
</q-tab-panel> -->
|
||||
</q-tab-panels>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.logo {
|
||||
margin: unset;
|
||||
grid-area: var(--logoOrBar, logo);
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
}
|
||||
.logo {
|
||||
margin: unset;
|
||||
grid-area: var(--logoOrBar, logo);
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
align-self: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
.logo.vue:hover {
|
||||
filter: drop-shadow(0 0 2em #42b883aa);
|
||||
}
|
||||
|
||||
.left {
|
||||
grid-area: left;
|
||||
overflow: auto;
|
||||
user-select: none;
|
||||
.left {
|
||||
grid-area: left;
|
||||
overflow: auto;
|
||||
user-select: none;
|
||||
|
||||
.leftctl {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
grid-area: bar;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.leftctl {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 10px;
|
||||
}
|
||||
}
|
||||
.main {
|
||||
grid-area: main;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bar {
|
||||
grid-area: bar;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,70 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, provide, watch } from 'vue'
|
||||
import 'leaflet/dist/leaflet.css'
|
||||
import useMap from '../hook/useMap.ts'
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import { setOptions } from 'leaflet'
|
||||
import { ref, onMounted, provide, watch } from 'vue';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import useMap from '../hook/useMap.ts';
|
||||
import { useResizeObserver } from '@vueuse/core';
|
||||
import { setOptions } from 'leaflet';
|
||||
|
||||
const mapContainer = ref()
|
||||
const { map, tileLayers, props: mapProps } = useMap(mapContainer, { use: 'gaode', zoom: 13 });
|
||||
const mapContainer = ref();
|
||||
const { map, tileLayers, props: mapProps } = useMap(mapContainer, { use: 'gaode', zoom: 13 });
|
||||
|
||||
// const config = defineModel<Object>()
|
||||
// const config = defineModel<Object>()
|
||||
|
||||
const slotProps = ref({
|
||||
contextmenu: {}
|
||||
})
|
||||
const slotProps = ref({
|
||||
contextmenu: {},
|
||||
});
|
||||
|
||||
const mapFilter = ref('invert(1) grayscale(.2) saturate(0.8) brightness(1.8) opacity(1) hue-rotate(184deg) sepia(17%)'); // 地图滤镜
|
||||
const mapFilter = ref('invert(1) grayscale(.2) saturate(0.8) brightness(1.8) opacity(1) hue-rotate(184deg) sepia(17%)'); // 地图滤镜
|
||||
|
||||
provide('map', map); // 注入的内容 mapChild 下
|
||||
|
||||
provide('map', map) // 注入的内容 mapChild 下
|
||||
onMounted(() => {
|
||||
map.value?.addEventListener('contextmenu', (e) => {
|
||||
slotProps.value.contextmenu = e;
|
||||
});
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
map.value?.addEventListener("contextmenu", (e) => {
|
||||
slotProps.value.contextmenu = e
|
||||
})
|
||||
})
|
||||
watch({ center: mapProps.value.center }, ({ center }) => center && map.value?.flyTo(center));
|
||||
watch({ max: mapProps.value.maxZoom || 0 }, ({ max }) => map.value?.setMaxZoom(max));
|
||||
watch({ min: mapProps.value.minZoom || 0 }, ({ min }) => map.value?.setMaxZoom(min));
|
||||
watch({ url: mapProps.value.urlTemplate }, ({ url }) => url && tileLayers.value[0].setUrl(url));
|
||||
watch({ zoomSnap: mapProps.value.zoomSnap }, ({ zoomSnap }) => map.value && (map.value.options.zoomSnap = zoomSnap));
|
||||
|
||||
function changeMapFilter(
|
||||
filterInfo: Record<'blur' | 'brightness' | 'contrast' | 'drop-shadow' | 'grayscale' | 'hue-rotate' | 'invert' | 'opacity' | 'saturate' | 'sepia', string | number>
|
||||
) {
|
||||
mapFilter.value = Object.entries(filterInfo)
|
||||
.map(([key, value]) => `${key}(${value})`)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
useResizeObserver(mapContainer, () => map.value?.invalidateSize(true));
|
||||
|
||||
watch({ center: mapProps.value.center }, ({ center }) => center && map.value?.flyTo(center))
|
||||
watch({ max: mapProps.value.maxZoom || 0 }, ({ max }) => map.value?.setMaxZoom(max))
|
||||
watch({ min: mapProps.value.minZoom || 0 }, ({ min }) => map.value?.setMaxZoom(min))
|
||||
watch({ url: mapProps.value.urlTemplate }, ({ url }) => url && tileLayers.value[0].setUrl(url))
|
||||
watch({ zoomSnap: mapProps.value.zoomSnap }, ({ zoomSnap }) => map.value && (map.value.options.zoomSnap = zoomSnap))
|
||||
|
||||
function changeMapFilter(filterInfo: Record<'blur' |
|
||||
'brightness' |
|
||||
'contrast' |
|
||||
'drop-shadow' |
|
||||
'grayscale' |
|
||||
'hue-rotate' |
|
||||
'invert' |
|
||||
'opacity' |
|
||||
'saturate' |
|
||||
'sepia', string | number>) {
|
||||
mapFilter.value = Object.entries(filterInfo).map(([key, value]) => `${key}(${value})`).join(' ')
|
||||
}
|
||||
|
||||
useResizeObserver(mapContainer, () => map.value?.invalidateSize(true))
|
||||
|
||||
defineExpose({ changeMapFilter, setOptions })
|
||||
defineExpose({ changeMapFilter, setOptions });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mapContainer" ref="mapContainer" :style="{ '--mapFilter': mapFilter }">
|
||||
<slot v-if="map" v-bind="slotProps"></slot>
|
||||
</div>
|
||||
<div class="mapContainer" ref="mapContainer" :style="{ '--mapFilter': mapFilter }">
|
||||
<slot v-if="map" v-bind="slotProps"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.mapContainer {
|
||||
grid-area: map;
|
||||
}
|
||||
<style lang="scss">
|
||||
.mapContainer {
|
||||
grid-area: map;
|
||||
}
|
||||
|
||||
body.body--dark {
|
||||
.mapFilter {
|
||||
filter: var(--mapFilter);
|
||||
}
|
||||
}
|
||||
body.body--dark {
|
||||
.mapFilter {
|
||||
filter: var(--mapFilter);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<q-menu touch-position context-menu>
|
||||
<q-list dense style="min-width: 100px">
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="addPoint">添加标点</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="addDev">添加设备</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, PropType } from 'vue';
|
||||
import { useMarkStore } from '../store/useMarkStore';
|
||||
import { LeafletMouseEventHandlerFn } from 'leaflet';
|
||||
|
||||
const { marks } = useMarkStore();
|
||||
|
||||
const emit = defineEmits(['addPoint', 'addDev']);
|
||||
const props = defineProps({ map: Object as PropType<L.Map> });
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function addPoint() {
|
||||
const { map } = props;
|
||||
if (!map) return;
|
||||
const pushMark: LeafletMouseEventHandlerFn = (e) => {
|
||||
marks.push({ latlng: [e.latlng.alt || 0, e.latlng.lng || 0], type: 'localtion', opacity: 1, title: '新地标' });
|
||||
};
|
||||
map.addEventListener('contextmenu', pushMark);
|
||||
return map.removeEventListener('contextmenu', pushMark);
|
||||
}
|
||||
function addDev() {
|
||||
const { map } = props;
|
||||
if (!map) return;
|
||||
const pushMark: LeafletMouseEventHandlerFn = (e) => {
|
||||
marks.push({ latlng: [e.latlng.alt || 0, e.latlng.lng || 0], type: 'equipment', opacity: 1, title: '新设备' });
|
||||
};
|
||||
map.addEventListener('contextmenu', pushMark);
|
||||
return map.removeEventListener('contextmenu', pushMark);
|
||||
}
|
||||
</script>
|
|
@ -5,15 +5,10 @@ export type DataProps = { zoom: { min: number, max: number }, urlTemplate: strin
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import useConfig from '../../hook/useConfig';
|
||||
import { MapType } from '../../lib/mapType';
|
||||
const _configGetter = useConfig()
|
||||
const maptype = ref<MapType>('gaode')
|
||||
const data = ref(_configGetter('gaode'))
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
watch(maptype, v => data.value = _configGetter(v))
|
||||
watch(data, (v) => emit('change', v), { deep: true })
|
||||
|
||||
</script>
|
||||
<template>
|
||||
|
@ -30,11 +25,11 @@ watch(data, (v) => emit('change', v), { deep: true })
|
|||
<q-item clickable v-close-popup>
|
||||
<q-item-section>
|
||||
<q-item-label>缩放控制</q-item-label>
|
||||
<q-item-label caption>
|
||||
<!-- <q-item-label caption>
|
||||
<q-range :model-value="{ max: data.maxZoom, min: data.minZoom }"
|
||||
@update:model-value="({ max, min }) => { data.maxZoom = max; data.minZoom = min; }" :min="3"
|
||||
:max="19" color="deep-orange" label-always markers switch-label-side />
|
||||
</q-item-label>
|
||||
</q-item-label> -->
|
||||
<!-- TODO 控制多图层 ?? 缩放级别减少 直接控制路线等 -->
|
||||
<!-- TODO 地图不清楚 -->
|
||||
</q-item-section>
|
||||
|
@ -43,7 +38,7 @@ watch(data, (v) => emit('change', v), { deep: true })
|
|||
<q-item-section>
|
||||
<q-item-label>访问地址</q-item-label>
|
||||
<q-item-label caption>
|
||||
<q-input v-model="data.urlTemplate" />
|
||||
<!-- <q-input v-model="data.urlTemplate" /> -->
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
@ -51,7 +46,7 @@ watch(data, (v) => emit('change', v), { deep: true })
|
|||
<q-item-section>
|
||||
<q-item-label>失败图片</q-item-label>
|
||||
<q-item-label caption>
|
||||
<q-input v-model="data.errorTileUrl" />
|
||||
<!-- <q-input v-model="data.errorTileUrl" /> -->
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
@ -59,7 +54,7 @@ watch(data, (v) => emit('change', v), { deep: true })
|
|||
<q-item-section>
|
||||
<q-item-label>缩放倍数</q-item-label>
|
||||
<q-item-label caption>
|
||||
<q-input v-model.number="data.zoomSnap" type="number" />
|
||||
<!-- <q-input v-model.number="data.zoomSnap" type="number" /> -->
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,31 +0,0 @@
|
|||
import { MapType } from "../lib/mapType";
|
||||
|
||||
export type Config = L.TileLayerOptions & { urlTemplate: string, center: L.LatLngExpression, zoomSnap?: number }
|
||||
|
||||
const mapConfig: Record<MapType, Config> = {
|
||||
baidu: {
|
||||
urlTemplate: "http://localhost/baidumaps/roadmap/{z}/{x}/{y}.png",
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
center: [39.926474, 116.403283],
|
||||
maxZoom: 15, minZoom: 3
|
||||
},
|
||||
gaode: {
|
||||
urlTemplate: "http://localhost/mapabc/roadmap/{z}/{x}/{y}.png",
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
center: [-97.00238024827533, 210.7725501856634],
|
||||
maxZoom: 15, minZoom: 3
|
||||
},
|
||||
gaodeLine: {
|
||||
urlTemplate: "http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}",
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
center: [-97.00238024827533, 210.7725501856634],
|
||||
maxZoom: 15, minZoom: 3
|
||||
}
|
||||
}
|
||||
|
||||
function useConfig(): (name: MapType) => Config {
|
||||
return (name) => mapConfig[name]
|
||||
}
|
||||
|
||||
|
||||
export default useConfig
|
|
@ -1,32 +1,29 @@
|
|||
import L from 'leaflet'
|
||||
import 'proj4'
|
||||
import 'proj4leaflet'
|
||||
import L from 'leaflet';
|
||||
import 'proj4';
|
||||
import 'proj4leaflet';
|
||||
import { MapType } from '../lib/mapType';
|
||||
const _level = 19;
|
||||
|
||||
const crss: Record<MapType, L.CRS> = {
|
||||
baidu: new L.Proj.CRS('EPSG:900913',
|
||||
'+proj=merc +a=6378206 +b=6356584.314245179 +lat_ts=0.0 +lon_0=0.0 +x_0=0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs',
|
||||
{
|
||||
resolutions: function () {
|
||||
let level = 19
|
||||
var res = [];
|
||||
res[0] = Math.pow(2, 18);
|
||||
for (var i = 1; i < level; i++) {
|
||||
res[i] = Math.pow(2, (18 - i))
|
||||
}
|
||||
return res;
|
||||
}(),
|
||||
origin: [0, 0],
|
||||
bounds: L.bounds([20037508.342789244, 0], [0, 20037508.342789244])
|
||||
}),
|
||||
gaode: L.CRS.Simple,
|
||||
gaodeLine:L.CRS.EPSG4326
|
||||
}
|
||||
baidu: new L.Proj.CRS('EPSG:900913', '+proj=merc +a=6378206 +b=6356584.314245179 +lat_ts=0.0 +lon_0=0.0 +x_0=0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs', {
|
||||
resolutions: (function () {
|
||||
let level = 19;
|
||||
var res = [];
|
||||
res[0] = Math.pow(2, 18);
|
||||
for (var i = 1; i < level; i++) {
|
||||
res[i] = Math.pow(2, 18 - i);
|
||||
}
|
||||
return res;
|
||||
})(),
|
||||
origin: [0, 0],
|
||||
bounds: L.bounds([20037508.342789244, 0], [0, 20037508.342789244]),
|
||||
}),
|
||||
gaode: L.CRS.Simple,
|
||||
gaodeCharts: L.CRS.Simple,
|
||||
gaodeLine: L.CRS.EPSG4326,
|
||||
};
|
||||
|
||||
function useCrs(name: MapType) {
|
||||
return crss[name]
|
||||
return crss[name];
|
||||
}
|
||||
|
||||
|
||||
export default useCrs
|
||||
export default useCrs;
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
function useDraw(){
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default useDraw
|
|
@ -1,76 +1,32 @@
|
|||
import L, { Layer } from "leaflet"
|
||||
import { Ref, isRef, onMounted, reactive, ref, shallowRef, toRef, triggerRef, watch } from "vue"
|
||||
import useCrs from "./useCrs"
|
||||
import { MapType } from "../lib/mapType"
|
||||
import useConfig, { Config } from "./useConfig"
|
||||
import { use, init, ComposeOption } from 'echarts/core';
|
||||
|
||||
/**
|
||||
* 地图接口 地图类型
|
||||
*/
|
||||
interface MapController {
|
||||
import { ScatterChart, ScatterSeriesOption, EffectScatterChart, EffectScatterSeriesOption, HeatmapChart } from 'echarts/charts';
|
||||
|
||||
import { TooltipComponent, TitleComponentOption, TitleComponent, VisualMapComponent } from 'echarts/components';
|
||||
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
|
||||
import { LeafletComponent, LeafletComponentOption } from '../leafletchart/export';
|
||||
import L, { CRS, MapOptions } from 'leaflet';
|
||||
import { HeatmapSeriesOption } from 'echarts';
|
||||
|
||||
export type EMapOptions = LeafletComponentOption<MapOptions & { getCrs?: () => CRS }>;
|
||||
export type ECOption = ComposeOption<ScatterSeriesOption | EffectScatterSeriesOption | TitleComponentOption | HeatmapSeriesOption> & EMapOptions;
|
||||
|
||||
function useMap(dom: HTMLElement, chartOptions: ECOption) {
|
||||
use([CanvasRenderer, TooltipComponent, LeafletComponent, ScatterChart, EffectScatterChart, TitleComponent, HeatmapChart, VisualMapComponent]);
|
||||
|
||||
const chart = init(dom);
|
||||
chart.setOption(chartOptions);
|
||||
// @ts-ignore
|
||||
const lmapComponent = chart.getModel().getComponent('lmap');
|
||||
// @ts-ignore
|
||||
const lmap: L.Map = lmapComponent.getLeaflet();
|
||||
|
||||
return {
|
||||
map: lmap,
|
||||
chart,
|
||||
};
|
||||
}
|
||||
|
||||
type DefineProps = L.MapOptions & Partial<Config> & { use: MapType }
|
||||
|
||||
interface DefineExport {
|
||||
map: Ref<L.Map | undefined>
|
||||
tileLayers: Ref<L.TileLayer[]>
|
||||
gridLayers: Ref<L.GridLayer[]>
|
||||
geoJsons: Ref<L.GeoJSON[]>
|
||||
props: Ref<DefineProps>
|
||||
triggerProps: (options: Config) => void
|
||||
}
|
||||
|
||||
function useMap(dom: Ref<string | HTMLElement>, props: DefineProps | Ref<DefineProps>): DefineExport {
|
||||
const _props = isRef(props) ? props : shallowRef(props)
|
||||
const _map = ref<L.Map>()
|
||||
const _tileLayers: Ref<L.TileLayer[]> = ref([])
|
||||
const _gridLayers: Ref<L.GridLayer[]> = ref([])
|
||||
const _geos: Ref<L.GeoJSON[]> = ref([])
|
||||
const _configGetter = useConfig()
|
||||
|
||||
onMounted(() => {
|
||||
const _options = _props // FIXME 这里必须要强转嘛
|
||||
if (!_options) return
|
||||
const crs = useCrs(_options.value.use)
|
||||
const _config = _configGetter(_options.value.use)
|
||||
_map.value = L.map(dom.value, { ..._options.value, crs, center: _config.center })
|
||||
const layer = L.tileLayer(_config.urlTemplate, _config);
|
||||
layer.addTo(_map.value)
|
||||
_tileLayers.value.push(layer)
|
||||
})
|
||||
|
||||
watch(_props, (options) => {
|
||||
if (!_map.value) return
|
||||
if (!options) return
|
||||
_map.value.options.crs = useCrs(options.use)
|
||||
})
|
||||
|
||||
watch(_tileLayers, (layers) => {
|
||||
if (!_map.value) return
|
||||
if (layers.length < 1) return
|
||||
const { hasLayer: _has, addLayer: _add } = _map.value
|
||||
layers.forEach(l => !_has(l) && _add(l))
|
||||
})
|
||||
|
||||
watch(_geos, (geos) => {
|
||||
const __map = _map.value
|
||||
if (!__map) return
|
||||
if (geos.length < 1) return
|
||||
geos.forEach(geo => {
|
||||
geo.addTo(__map)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 修改后激发props
|
||||
*/
|
||||
function triggerProps(options: Config) {
|
||||
triggerRef(_props)
|
||||
}
|
||||
|
||||
return { map: _map, props: _props, tileLayers: _tileLayers, gridLayers: _gridLayers, geoJsons: _geos, triggerProps }
|
||||
}
|
||||
|
||||
export default useMap
|
||||
export default useMap;
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import { useLocalStorage } from "@vueuse/core"
|
||||
|
||||
const key = 'map-marks'
|
||||
export type markType = 'localtion' | 'equipment'
|
||||
export type markInfo = { latlng: [number, number], title: string, opacity?: number, type?: markType }
|
||||
|
||||
function useMarks(initData: markInfo[] = []) {
|
||||
const stroked = useLocalStorage<markInfo[]>(key, initData);
|
||||
return stroked;
|
||||
}
|
||||
|
||||
export default useMarks
|
|
@ -0,0 +1,108 @@
|
|||
import { data } from './data';
|
||||
import { ECOption, EMapOptions } from './useMap';
|
||||
|
||||
function useOption() {
|
||||
const heatmap: (mapOptions: EMapOptions) => ECOption = (mapOptions) => {
|
||||
var points = [].concat.apply(
|
||||
[],
|
||||
data.map(function (track: any) {
|
||||
return track.map(function (seg: { coord: number[] }) {
|
||||
const [x, y] = seg.coord;
|
||||
return [x - 3.73, y + 9.7, 1];
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
animation: false,
|
||||
lmap: {
|
||||
center: [10, 60], // [lng, lat]
|
||||
zoom: 4,
|
||||
resizeEnable: true, // automatically handles browser window resize.
|
||||
renderOnMoving: true,
|
||||
echartsLayerInteractive: true, // Default: true
|
||||
largeMode: false, // Default: false
|
||||
|
||||
...mapOptions?.leaflet,
|
||||
},
|
||||
title: {
|
||||
text: '热力图',
|
||||
},
|
||||
visualMap: {
|
||||
show: false,
|
||||
top: 'top',
|
||||
min: 0,
|
||||
max: 5,
|
||||
seriesIndex: 0,
|
||||
calculable: true,
|
||||
inRange: {
|
||||
color: ['blue', 'blue', 'green', 'yellow', 'red'],
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'heatmap',
|
||||
coordinateSystem: 'lmap',
|
||||
data: points,
|
||||
pointSize: 5,
|
||||
blurSize: 6,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const scatter: (mapOptions?: EMapOptions) => ECOption = (mapOptions) => ({
|
||||
lmap: {
|
||||
center: [10, 60], // [lng, lat]
|
||||
zoom: 4,
|
||||
resizeEnable: true, // automatically handles browser window resize.
|
||||
renderOnMoving: true,
|
||||
echartsLayerInteractive: true, // Default: true
|
||||
largeMode: false, // Default: false
|
||||
|
||||
...mapOptions?.leaflet,
|
||||
},
|
||||
title: {
|
||||
text: '散点图',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'scatter',
|
||||
// use `lmap` as the coordinate system
|
||||
coordinateSystem: 'lmap',
|
||||
// data items [[lng, lat, value], [lng, lat, value], ...]
|
||||
data: [
|
||||
[39.926474, 116.403283, 400],
|
||||
[10, 60, 8],
|
||||
[10.1, 60, 20],
|
||||
],
|
||||
encode: {
|
||||
// encode the third element of data item as the `value` dimension
|
||||
value: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'effectScatter',
|
||||
// use `lmap` as the coordinate system
|
||||
coordinateSystem: 'lmap',
|
||||
// data items [[lng, lat, value], [lng, lat, value], ...]
|
||||
data: [
|
||||
[39.926474, 116, 400],
|
||||
[11, 60, 8],
|
||||
[11.1, 60, 20],
|
||||
],
|
||||
encode: {
|
||||
// encode the third element of data item as the `value` dimension
|
||||
value: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
scatter,
|
||||
heatmap,
|
||||
};
|
||||
}
|
||||
|
||||
export default useOption;
|
|
@ -1,69 +0,0 @@
|
|||
type RGBA = Record<'r' | 'g' | 'b' | 'a', number>;
|
||||
|
||||
|
||||
|
||||
function imageChange(img: HTMLImageElement, colorize: (pixel: RGBA) => RGBA) {
|
||||
if (img.getAttribute('data-colorized'))
|
||||
return;
|
||||
img.crossOrigin = 'anonymous'
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
var ctx = canvas.getContext("2d");
|
||||
if (!ctx) return
|
||||
ctx.drawImage(img, 0, 0);
|
||||
var imgd = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
var pix = imgd.data;
|
||||
for (var i = 0, n = pix.length; i < n; i += 4) {
|
||||
|
||||
var pixel = colorize({ r: pix[i], g: pix[i + 1], b: pix[i + 2], a: pix[i + 3] });
|
||||
if (!!!pixel || pixel !== Object(pixel) || Object.prototype.toString.call(pixel) === '[object Array]') {
|
||||
|
||||
if (i === 0) {
|
||||
throw 'The colorize option should return an object with at least one of "r", "g", "b", or "a" properties.';
|
||||
}
|
||||
|
||||
} else {
|
||||
if (pixel.hasOwnProperty('r') && typeof pixel.r === 'number') {
|
||||
pix[i] = pixel.r;
|
||||
}
|
||||
if (pixel.hasOwnProperty('g')) {
|
||||
pix[i + 1] = pixel.g;
|
||||
}
|
||||
if (pixel.hasOwnProperty('b')) {
|
||||
pix[i + 2] = pixel.b;
|
||||
}
|
||||
if (pixel.hasOwnProperty('a')) {
|
||||
pix[i + 3] = pixel.a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx.putImageData(imgd, 0, 0);
|
||||
img.setAttribute('data-colorized', 'true');
|
||||
img.src = canvas.toDataURL();
|
||||
return imgd;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param img 图片
|
||||
* @param colorize 图片rgb颜色修改
|
||||
* @example
|
||||
* const colorize = useRgb()
|
||||
* layer.on('tileload', (e) => {
|
||||
* colorize(e.tile, (pixel) => {
|
||||
* pixel.g += 17;
|
||||
* pixel.b += 90;
|
||||
* return pixel;
|
||||
* })
|
||||
* })
|
||||
* @returns
|
||||
*/
|
||||
function useRgb() {
|
||||
return imageChange;
|
||||
}
|
||||
|
||||
export default useRgb
|
|
@ -0,0 +1,41 @@
|
|||
import L from 'leaflet';
|
||||
import { tileLayer as LtileLayer } from 'leaflet';
|
||||
|
||||
function useTileLayer() {
|
||||
const baidu = LtileLayer('http://localhost/baidumaps/roadmap/{z}/{x}/{y}.png', {
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
maxZoom: 15,
|
||||
tms: true,
|
||||
minZoom: 3,
|
||||
});
|
||||
|
||||
const gaode = LtileLayer('http://localhost/mapabc/roadmap/{z}/{x}/{y}.png', {
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
maxZoom: 15,
|
||||
tms: true,
|
||||
minZoom: 3,
|
||||
});
|
||||
|
||||
const Kitten = L.TileLayer.extend({
|
||||
getTileUrl: function ({ x, y, z }: any) {
|
||||
return `http://localhost/mapabc/roadmap/${z}/${x}/${y}.png`;
|
||||
},
|
||||
options: {
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
maxZoom: 15,
|
||||
minZoom: 3,
|
||||
},
|
||||
});
|
||||
|
||||
const gaodeChart = new Kitten();
|
||||
|
||||
const gaodeLine = LtileLayer('http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', {
|
||||
errorTileUrl: 'http://localhost/baidumaps/roadmap/error.png',
|
||||
maxZoom: 15,
|
||||
minZoom: 3,
|
||||
});
|
||||
|
||||
return { baidu, gaode, gaodeLine, gaodeChart };
|
||||
}
|
||||
|
||||
export default useTileLayer;
|
|
@ -1,85 +0,0 @@
|
|||
import L from "leaflet"
|
||||
import { ref, Ref } from "vue";
|
||||
|
||||
function close(used: number[], close: number): number { // 接近数字下表
|
||||
const closeUsed = used.map((v, i) => ({ v: close - v, i }))
|
||||
const p = closeUsed.shift();
|
||||
if (!p) throw new Error('传入使用数组不能为空');
|
||||
const a = closeUsed.reduce((pre, cur) => {
|
||||
if (pre.v < 0 && cur.v < 0) { // 双方小数
|
||||
return pre.v < cur.v ? cur : pre // 取大
|
||||
} else if (pre.v < 0 || cur.v < 0) { // 有一方是小数
|
||||
return pre.v < 0 ? pre : cur // 取小数
|
||||
} else { // 都不是
|
||||
|
||||
return pre.v < cur.v ? pre : cur // 取小
|
||||
}
|
||||
}, p)
|
||||
|
||||
return a.i
|
||||
}
|
||||
|
||||
function limitZoom(min: number, max: number, zoom: number): number {
|
||||
return Math.max(min, Math.min(max, zoom))
|
||||
}
|
||||
|
||||
function useZoom(mapContainer: Ref<HTMLElement>, map: Ref<L.Map | undefined>, used: number[]) {
|
||||
const _used = used.sort((a, b) => a - b)
|
||||
let _timer = 0;
|
||||
let _delta = 0;
|
||||
let _currentZoom = ref(0);
|
||||
|
||||
const zoomControl = L.Handler.extend({
|
||||
addHooks: function () {
|
||||
console.log('add')
|
||||
L.DomEvent.on(mapContainer.value, 'wheel', this._onWheelScroll)
|
||||
},
|
||||
removeHooks: function () {
|
||||
console.log('remove')
|
||||
L.DomEvent.off(mapContainer.value, 'wheel', this._onWheelScroll)
|
||||
},
|
||||
_onWheelScroll: function (e: WheelEvent) {
|
||||
const _map = map.value;
|
||||
if (!_map) return;
|
||||
let _lastMousePos = _map.mouseEventToContainerPoint(e);
|
||||
let _debounce = _map.options.wheelDebounceTime
|
||||
|
||||
const delta = L.DomEvent.getWheelDelta(e); // 处理高度
|
||||
_delta += delta
|
||||
|
||||
clearTimeout(_timer)
|
||||
_timer = setTimeout(() => {
|
||||
|
||||
let zoom = _map.getZoom(),
|
||||
snap = _map.options.zoomSnap || 0,
|
||||
d2 = _delta / ((_map.options.wheelPxPerZoomLevel || 60) * 4),
|
||||
d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
|
||||
d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
|
||||
min = _map.getMinZoom(),
|
||||
max = _map.getMaxZoom(),
|
||||
limit = limitZoom(min, max, zoom + (_delta > 0 ? d4 : -d4)),
|
||||
delta = limit - zoom;
|
||||
|
||||
if (!delta) return
|
||||
_currentZoom.value = close(_used, limit) + delta
|
||||
const _to = _used[_currentZoom.value];
|
||||
console.log(_used, '当前', zoom, '放大缩小', delta, '接近', close(_used, limit), '转到', _currentZoom.value, '实际', _to)
|
||||
debugger
|
||||
if (_map.options.scrollWheelZoom === 'center') {
|
||||
_map.setZoom(_to);
|
||||
} else {
|
||||
_map.setZoomAround(_lastMousePos, _to);
|
||||
}
|
||||
_delta = 0
|
||||
}, _debounce);
|
||||
|
||||
|
||||
L.DomEvent.stop(e);
|
||||
},
|
||||
})
|
||||
|
||||
return zoomControl
|
||||
}
|
||||
|
||||
|
||||
export default useZoom
|
|
@ -1,11 +0,0 @@
|
|||
import { MapType } from "../lib/mapType";
|
||||
|
||||
|
||||
|
||||
function useConfig<T>( configName:T ,type: Record<MapType, T>) { // TODO 对config监听??
|
||||
|
||||
|
||||
return // TODO 配置
|
||||
}
|
||||
|
||||
export default useConfig
|
|
@ -1,14 +0,0 @@
|
|||
import L from 'leaflet'
|
||||
import { Ref, onMounted, ref } from 'vue'
|
||||
|
||||
function useMap(mapContainer: Ref<string | HTMLElement>, options: L.MapOptions) {
|
||||
const map = ref<L.Map>();
|
||||
|
||||
onMounted(() => {
|
||||
map.value = L.map(mapContainer.value, options)
|
||||
})
|
||||
|
||||
return [map]
|
||||
}
|
||||
|
||||
export default useMap
|
|
@ -1,26 +0,0 @@
|
|||
import L from "leaflet";
|
||||
import { ref, Ref, onMounted } from "vue";
|
||||
|
||||
|
||||
function useTileLayer(map: Ref<L.Map>, url: string, options: L.TileLayerOptions) {
|
||||
const tileLayer = ref<L.TileLayer>()
|
||||
onMounted(() => {
|
||||
tileLayer.value = L.tileLayer(url, options)
|
||||
})
|
||||
|
||||
function open() {
|
||||
if (!tileLayer.value) return;
|
||||
if (!map.value) return;
|
||||
tileLayer.value.addTo(map.value)
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (!tileLayer.value) return;
|
||||
if (!map.value) return;
|
||||
tileLayer.value.removeFrom(map.value)
|
||||
}
|
||||
|
||||
return [tileLayer, open, close]
|
||||
}
|
||||
|
||||
export default useTileLayer
|
|
@ -0,0 +1,246 @@
|
|||
import { util as zrUtil, graphic, matrix } from 'echarts/core';
|
||||
import { DomUtil, LatLng, Layer, Map as LMap, Projection } from 'leaflet';
|
||||
import 'proj4';
|
||||
import projleaflet from 'proj4leaflet';
|
||||
|
||||
function dataToCoordSize(dataSize, dataItem) {
|
||||
dataItem = dataItem || [0, 0];
|
||||
return zrUtil.map(
|
||||
[0, 1],
|
||||
function (dimIdx) {
|
||||
const val = dataItem[dimIdx];
|
||||
const halfSize = dataSize[dimIdx] / 2;
|
||||
const p1 = [];
|
||||
const p2 = [];
|
||||
p1[dimIdx] = val - halfSize;
|
||||
p2[dimIdx] = val + halfSize;
|
||||
p1[1 - dimIdx] = p2[1 - dimIdx] = dataItem[1 - dimIdx];
|
||||
return Math.abs(this.dataToPoint(p1)[dimIdx] - this.dataToPoint(p2)[dimIdx]);
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
// exclude private and unsupported options
|
||||
const excludedOptions = ['echartsLayerInteractive', 'renderOnMoving', 'largeMode', 'layers', 'getCrs'];
|
||||
|
||||
const CustomOverlay = Layer.extend({
|
||||
initialize: function (container) {
|
||||
this._container = container;
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
let pane = map.getPane(this.options.pane);
|
||||
pane.appendChild(this._container);
|
||||
|
||||
// Calculate initial position of container with
|
||||
// `L.Map.latLngToLayerPoint()`, `getPixelOrigin()
|
||||
// and/or `getPixelBounds()`
|
||||
|
||||
// L.DomUtil.setPosition(this._container, point);
|
||||
|
||||
// Add and position children elements if needed
|
||||
|
||||
// map.on('zoomend viewreset', this._update, this);
|
||||
},
|
||||
|
||||
onRemove: function (map) {
|
||||
DomUtil.remove(this._container);
|
||||
},
|
||||
|
||||
_update: function () {
|
||||
// Recalculate position of container
|
||||
// L.DomUtil.setPosition(this._container, point);
|
||||
// Add/remove/reposition children elements if needed
|
||||
},
|
||||
});
|
||||
|
||||
function LeafletCoordSys(lmap, api) {
|
||||
this._lmap = lmap;
|
||||
this._api = api;
|
||||
this._mapOffset = [0, 0];
|
||||
this._projection = Projection.Mercator;
|
||||
// this.dimensions = ['lat', 'lng'] // Is Leaflet default, but incompatible with echarts heatmap since isGeoCoordSys in HeatMapView.ts, checks for [0] === 'lng', [1] === 'lat'
|
||||
}
|
||||
|
||||
const LeafletCoordSysProto = LeafletCoordSys.prototype;
|
||||
|
||||
LeafletCoordSysProto.setZoom = function (zoom) {
|
||||
this._zoom = zoom;
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.setCenter = function (center) {
|
||||
const latlng = this._projection.project(new LatLng(center[1], center[0])); // lng, lat
|
||||
this._center = [latlng.lng, latlng.lat];
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.setMapOffset = function (mapOffset) {
|
||||
this._mapOffset = mapOffset;
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.setLeaflet = function (lmap) {
|
||||
this._lmap = lmap;
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.getLeaflet = function () {
|
||||
return this._lmap;
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.dataToPoint = function (data) {
|
||||
const latlng = new LatLng(data[1], data[0]); // lng, lat
|
||||
const px = this._lmap.latLngToLayerPoint(latlng);
|
||||
const mapOffset = this._mapOffset;
|
||||
return [px.x - mapOffset[0], px.y - mapOffset[1]];
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.pointToData = function (pt) {
|
||||
const mapOffset = this._mapOffset;
|
||||
const coord = this._lmap.layerPointToLatLng({
|
||||
x: pt[0] + mapOffset[0],
|
||||
y: pt[1] + mapOffset[1],
|
||||
});
|
||||
return [coord.lng, coord.lat]; // lng, lat
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.getViewRect = function () {
|
||||
const api = this._api;
|
||||
return new graphic.BoundingRect(0, 0, api.getWidth(), api.getHeight());
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.getRoamTransform = function () {
|
||||
return matrix.create();
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.prepareCustoms = function () {
|
||||
const rect = this.getViewRect();
|
||||
return {
|
||||
coordSys: {
|
||||
// The name exposed to user is always 'cartesian2d' but not 'grid'.
|
||||
type: 'lmap',
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
},
|
||||
api: {
|
||||
coord: zrUtil.bind(this.dataToPoint, this),
|
||||
size: zrUtil.bind(dataToCoordSize, this),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.convertToPixel = function (ecModel, finder, value) {
|
||||
// here we don't use finder as only one amap component is allowed
|
||||
return this.dataToPoint(value);
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.convertFromPixel = function (ecModel, finder, value) {
|
||||
// here we don't use finder as only one amap component is allowed
|
||||
return this.pointToData(value);
|
||||
};
|
||||
|
||||
LeafletCoordSys.create = function (ecModel, api) {
|
||||
let lmapCoordSys;
|
||||
|
||||
ecModel.eachComponent('lmap', function (lmapModel) {
|
||||
if (typeof L === 'undefined') {
|
||||
throw new Error('Leaflet api is not loaded');
|
||||
}
|
||||
if (lmapCoordSys) {
|
||||
throw new Error('Only one lmap echarts component is allowed');
|
||||
}
|
||||
|
||||
let lmap = lmapModel.getLeaflet();
|
||||
const echartsLayerInteractive = lmapModel.get('echartsLayerInteractive');
|
||||
if (!lmap) {
|
||||
const root = api.getDom();
|
||||
const painter = api.getZr().painter;
|
||||
let viewportRoot = painter.getViewportRoot();
|
||||
viewportRoot.className = 'lmap-ec-layer';
|
||||
// Not support IE8
|
||||
let lmapRoot = root.querySelector('.ec-extension-leaflet');
|
||||
if (lmapRoot) {
|
||||
// Reset viewport left and top, which will be changed
|
||||
// in moving handler in Leaflet View
|
||||
viewportRoot.style.left = '0px';
|
||||
viewportRoot.style.top = '0px';
|
||||
|
||||
root.removeChild(lmapRoot);
|
||||
}
|
||||
lmapRoot = document.createElement('div');
|
||||
lmapRoot.className = 'ec-extension-leaflet';
|
||||
lmapRoot.style.cssText = 'position:absolute;top:0;left:0;bottom:0;right:0;';
|
||||
root.appendChild(lmapRoot);
|
||||
|
||||
const options = zrUtil.clone(lmapModel.get());
|
||||
|
||||
const crs = options.getCrs && options.getCrs();
|
||||
|
||||
// delete excluded options
|
||||
zrUtil.each(excludedOptions, function (key) {
|
||||
delete options[key];
|
||||
});
|
||||
if (crs) {
|
||||
lmap = new LMap(lmapRoot, { ...options, crs });
|
||||
} else {
|
||||
lmap = new LMap(lmapRoot, { ...options });
|
||||
}
|
||||
|
||||
/*
|
||||
Encapsulate viewportRoot element into
|
||||
the parent element responsible for moving,
|
||||
avoiding direct manipulation of viewportRoot elements
|
||||
affecting related attributes such as offset.
|
||||
*/
|
||||
let moveContainer = document.createElement('div');
|
||||
moveContainer.style = 'position: relative;';
|
||||
moveContainer.appendChild(viewportRoot);
|
||||
|
||||
new CustomOverlay(moveContainer).addTo(lmap);
|
||||
|
||||
lmapModel.setLeaflet(lmap);
|
||||
lmapModel.setEChartsLayer(viewportRoot);
|
||||
}
|
||||
|
||||
const oldEChartsLayerInteractive = lmapModel.__echartsLayerInteractive;
|
||||
if (oldEChartsLayerInteractive !== echartsLayerInteractive) {
|
||||
lmapModel.setEChartsLayerInteractive(echartsLayerInteractive);
|
||||
lmapModel.__echartsLayerInteractive = echartsLayerInteractive;
|
||||
}
|
||||
|
||||
const center = lmapModel.get('center');
|
||||
const zoom = lmapModel.get('zoom');
|
||||
if (center && zoom) {
|
||||
const lmapCenter = lmap.getCenter(); // leaflet lat lng
|
||||
const lmapZoom = lmap.getZoom();
|
||||
const centerOrZoomChanged = lmapModel.centerOrZoomChanged(
|
||||
[lmapCenter.lng, lmapCenter.lat], // lng, lat
|
||||
lmapZoom
|
||||
);
|
||||
if (centerOrZoomChanged) {
|
||||
lmap.setView(new LatLng(center[1], center[0]), zoom); // lng, lat
|
||||
}
|
||||
}
|
||||
|
||||
lmapCoordSys = new LeafletCoordSys(lmap, api);
|
||||
lmapCoordSys.setMapOffset(lmapModel.__mapOffset || [0, 0]);
|
||||
lmapCoordSys.setZoom(zoom);
|
||||
lmapCoordSys.setCenter(center);
|
||||
|
||||
lmapModel.coordinateSystem = lmapCoordSys;
|
||||
});
|
||||
|
||||
ecModel.eachSeries(function (seriesModel) {
|
||||
if (seriesModel.get('coordinateSystem') === 'lmap') {
|
||||
seriesModel.coordinateSystem = lmapCoordSys;
|
||||
}
|
||||
});
|
||||
|
||||
return lmapCoordSys && [lmapCoordSys];
|
||||
};
|
||||
|
||||
LeafletCoordSysProto.dimensions = LeafletCoordSys.dimensions = ['lng', 'lat']; // lng, lat
|
||||
|
||||
LeafletCoordSysProto.type = 'lmap';
|
||||
|
||||
export default LeafletCoordSys;
|
|
@ -0,0 +1,57 @@
|
|||
import { ComponentModel } from 'echarts/core';
|
||||
import { v2Equal } from './helper';
|
||||
|
||||
const LeafletModel = {
|
||||
type: 'lmap',
|
||||
|
||||
setLeaflet(lmap) {
|
||||
this.__lmap = lmap;
|
||||
},
|
||||
|
||||
getLeaflet() {
|
||||
return this.__lmap;
|
||||
},
|
||||
|
||||
getId() {
|
||||
return this.__lmap._leaflet_id;
|
||||
},
|
||||
|
||||
setEChartsLayer(layer) {
|
||||
this.__echartsLayer = layer;
|
||||
},
|
||||
|
||||
getEChartsLayer() {
|
||||
return this.__echartsLayer;
|
||||
},
|
||||
|
||||
setEChartsLayerVisiblity(visible) {
|
||||
this.__echartsLayer.style.display = visible ? 'block' : 'none';
|
||||
},
|
||||
|
||||
// FIXME: NOT SUPPORT <= IE 10
|
||||
setEChartsLayerInteractive(interactive) {
|
||||
this.option.echartsLayerInteractive = !!interactive;
|
||||
this.__echartsLayer.style.pointerEvents = interactive ? 'auto' : 'none';
|
||||
},
|
||||
|
||||
setCenterAndZoom(center, zoom) {
|
||||
// center received here is in lat, lng, so swap it
|
||||
this.option.center = [center[1], center[0]];
|
||||
this.option.zoom = zoom;
|
||||
},
|
||||
|
||||
centerOrZoomChanged(center, zoom) {
|
||||
const option = this.option;
|
||||
return !(v2Equal(center, option.center) && zoom === option.zoom);
|
||||
},
|
||||
|
||||
defaultOption: {
|
||||
center: [10.39506, 63.43049], // lng, lat
|
||||
zoom: 6,
|
||||
echartsLayerInteractive: true,
|
||||
renderOnMoving: true,
|
||||
largeMode: false
|
||||
}
|
||||
};
|
||||
|
||||
export default ComponentModel.extend(LeafletModel);
|
|
@ -0,0 +1,189 @@
|
|||
import { ComponentView, getInstanceByDom, throttle } from 'echarts/core';
|
||||
import { clearLogMap } from './helper';
|
||||
|
||||
const LeafletView = {
|
||||
type: 'lmap',
|
||||
|
||||
init() {
|
||||
this._isFirstRender = true;
|
||||
},
|
||||
|
||||
render(lmapModel, ecModel, api) {
|
||||
let rendering = true;
|
||||
|
||||
const lmap = lmapModel.getLeaflet();
|
||||
const moveContainer = api.getZr().painter.getViewportRoot().parentNode;
|
||||
const coordSys = lmapModel.coordinateSystem;
|
||||
const offsetEl = lmap._mapPane;
|
||||
|
||||
const renderOnMoving = lmapModel.get('renderOnMoving');
|
||||
const resizeEnable = lmapModel.get('resizeEnable');
|
||||
const largeMode = lmapModel.get('largeMode');
|
||||
|
||||
let moveHandler = function(e) {
|
||||
if (rendering) {
|
||||
return;
|
||||
}
|
||||
let transformStyle = offsetEl.style.transform;
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
if (transformStyle) {
|
||||
transformStyle = transformStyle.replace('translate3d(', '');
|
||||
let parts = transformStyle.split(',');
|
||||
dx = -parseInt(parts[0], 10);
|
||||
dy = -parseInt(parts[1], 10);
|
||||
} else {
|
||||
// browsers that don't support transform: matrix
|
||||
dx = -parseInt(offsetEl.style.left, 10);
|
||||
dy = -parseInt(offsetEl.style.top, 10);
|
||||
}
|
||||
|
||||
const mapOffset = [dx, dy];
|
||||
const offsetLeft = mapOffset[0] + 'px';
|
||||
const offsetTop = mapOffset[1] + 'px';
|
||||
if (moveContainer.style.left !== offsetLeft) {
|
||||
moveContainer.style.left = offsetLeft;
|
||||
}
|
||||
if (moveContainer.style.top !== offsetTop) {
|
||||
moveContainer.style.top = offsetTop;
|
||||
}
|
||||
|
||||
coordSys.setMapOffset(lmapModel.__mapOffset = mapOffset);
|
||||
|
||||
const actionParams = {
|
||||
type: 'lmapRoam',
|
||||
animation: {
|
||||
// compatible with ECharts 5.x
|
||||
// no delay for rendering but remain animation of elements
|
||||
duration: 0
|
||||
}
|
||||
};
|
||||
|
||||
api.dispatchAction(actionParams);
|
||||
};
|
||||
|
||||
if(largeMode) {
|
||||
moveHandler = throttle(moveHandler, 20, true);
|
||||
}
|
||||
|
||||
if(this._isFirstRender){
|
||||
this._moveHandler = moveHandler;
|
||||
}
|
||||
|
||||
let ctrlStartHandler = function(e) {
|
||||
if(e.originalEvent.code === 'ControlLeft') {
|
||||
lmap.dragging.disable();
|
||||
moveHandler(e);
|
||||
}
|
||||
};
|
||||
|
||||
let ctrlEndHandler = function(e) {
|
||||
if(e.originalEvent.code === 'ControlLeft') {
|
||||
lmap.dragging.enable();
|
||||
}
|
||||
};
|
||||
|
||||
this._ctrlStartHandler = ctrlStartHandler;
|
||||
this._ctrlEndHandler = ctrlEndHandler;
|
||||
|
||||
lmap.off('move', this._moveHandler);
|
||||
lmap.off('moveend', this._moveHandler);
|
||||
lmap.off('zoom', this._moveHandler);
|
||||
lmap.off('viewreset', this._moveHandler);
|
||||
lmap.off('zoomend', this._moveHandler);
|
||||
lmap.off('keydown', this._ctrlStartHandler);
|
||||
lmap.off('keyup', this._ctrlEndHandler);
|
||||
|
||||
if(this._ctrlStartHandler) {
|
||||
lmap.off('keydown', this._ctrlStartHandler);
|
||||
}
|
||||
|
||||
if(this._ctrlEndHandler) {
|
||||
lmap.off('keyup', this._ctrlEndHandler);
|
||||
}
|
||||
|
||||
if (this._resizeHandler) {
|
||||
lmap.off('resize', this._resizeHandler);
|
||||
}
|
||||
if (this._moveStartHandler) {
|
||||
lmap.off('move', this._moveStartHandler);
|
||||
lmap.off('zoomstart', this._moveStartHandler);
|
||||
lmap.off('zoom', this._moveStartHandler);
|
||||
}
|
||||
|
||||
lmap.on(renderOnMoving ? 'move' : 'moveend', moveHandler);
|
||||
lmap.on(renderOnMoving ? 'zoom' : 'zoomend', moveHandler);
|
||||
|
||||
if(renderOnMoving){
|
||||
lmap.on('viewreset', moveHandler); // needed?
|
||||
}
|
||||
|
||||
if(!renderOnMoving && !this._moveEndHandler) {
|
||||
const moveEndHandler = function(e) {
|
||||
setTimeout(function() {
|
||||
lmapModel.setEChartsLayerVisiblity(true);
|
||||
}, !largeMode ? 0 : 20);
|
||||
};
|
||||
this._moveEndHandler = moveEndHandler;
|
||||
lmap.on('moveend', moveEndHandler);
|
||||
lmap.on('zoomend', moveEndHandler);
|
||||
}
|
||||
|
||||
lmap.on('keydown', ctrlStartHandler);
|
||||
lmap.on('keyup', ctrlEndHandler);
|
||||
|
||||
this._moveHandler = moveHandler;
|
||||
|
||||
if (!renderOnMoving) {
|
||||
const moveStartHandler = function() {
|
||||
lmapModel.setEChartsLayerVisiblity(false);
|
||||
setTimeout(function() {
|
||||
lmapModel.setEChartsLayerVisiblity(true);
|
||||
}, 500);
|
||||
};
|
||||
this._moveStartHandler = moveStartHandler;
|
||||
lmap.on('move', moveStartHandler); // hide when move occurs
|
||||
lmap.on('zoomstart', moveStartHandler); // hide when zoom starts
|
||||
lmap.on('zoom', moveStartHandler); // hide when zoom occurs
|
||||
}
|
||||
|
||||
if (resizeEnable) {
|
||||
let resizeHandler = function() {
|
||||
getInstanceByDom(api.getDom()).resize();
|
||||
};
|
||||
if (largeMode) {
|
||||
resizeHandler = throttle(resizeHandler, 20, true);
|
||||
}
|
||||
this._resizeHandler = resizeHandler;
|
||||
lmap.on('resize', resizeHandler);
|
||||
}
|
||||
|
||||
this._isFirstRender = rendering = false;
|
||||
},
|
||||
|
||||
dispose() {
|
||||
clearLogMap();
|
||||
const component = this.__model;
|
||||
if (component) {
|
||||
const leaflet = component.getLeaflet();
|
||||
if(leaflet){
|
||||
component.getLeaflet().off();
|
||||
component.getLeaflet().remove();
|
||||
}
|
||||
component.setLeaflet(null);
|
||||
component.setEChartsLayer(null);
|
||||
if (component.coordinateSystem) {
|
||||
component.coordinateSystem.setLeaflet(null);
|
||||
component.coordinateSystem = null;
|
||||
}
|
||||
delete this._moveHandler;
|
||||
delete this._resizeHandler;
|
||||
delete this._moveStartHandler;
|
||||
delete this._moveEndHandler;
|
||||
delete this._ctrlEndHandler;
|
||||
delete this._ctrlStartHandler;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default ComponentView.extend(LeafletView);
|
|
@ -0,0 +1,42 @@
|
|||
import { use } from "echarts/core";
|
||||
import { HeatmapSeriesOption } from "echarts/charts";
|
||||
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||
k: infer I
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
type LastOf<T> = UnionToIntersection<
|
||||
T extends any ? () => T : never
|
||||
> extends () => infer R
|
||||
? R
|
||||
: never;
|
||||
|
||||
type Push<T extends any[], V> = [...T, V];
|
||||
|
||||
type TuplifyUnion<
|
||||
T,
|
||||
L = LastOf<T>,
|
||||
N = [T] extends [never] ? true : false
|
||||
> = true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>>, L>;
|
||||
|
||||
type EChartsExtensionInstallRegisters = Parameters<
|
||||
TuplifyUnion<Parameters<typeof use>[0]>[0]
|
||||
>[0];
|
||||
|
||||
export type EChartsExtensionRegisters = EChartsExtensionInstallRegisters;
|
||||
|
||||
// HeatmapSeriesOption does not support 'lmap'
|
||||
type LeafletHeatmapSeriesOption = HeatmapSeriesOption & {
|
||||
coordinateSystem: "lmap";
|
||||
};
|
||||
|
||||
/**
|
||||
* To install Leaflet component
|
||||
* @param registers registers echarts registers.
|
||||
*/
|
||||
declare function install(registers: EChartsExtensionRegisters): void;
|
||||
|
||||
export * from "./types";
|
||||
export { install as LeafletComponent, LeafletHeatmapSeriesOption };
|
|
@ -0,0 +1 @@
|
|||
export { LeafletComponent } from './lmap'
|
|
@ -0,0 +1,19 @@
|
|||
import { version } from "echarts/core"; //lib/echarts";
|
||||
|
||||
export const ecVer = version.split('.');
|
||||
|
||||
export function v2Equal(a, b) {
|
||||
return a && b && a[0] === b[0] && a[1] === b[1];
|
||||
}
|
||||
|
||||
let logMap = {};
|
||||
|
||||
export function logWarn(tag, msg, once) {
|
||||
const log = `[ECharts][Extension][Leaflet]${tag ? ' ' + tag + ':' : ''} ${msg}`;
|
||||
once && logMap[log] || console.warn(log);
|
||||
once && (logMap[log] = true);
|
||||
}
|
||||
|
||||
export function clearLogMap() {
|
||||
logMap = {};
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./types";
|
|
@ -0,0 +1,4 @@
|
|||
import { use } from 'echarts/core';
|
||||
import { LeafletComponent } from './lmap';
|
||||
|
||||
use(LeafletComponent);
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Leaflet component extension
|
||||
*/
|
||||
|
||||
import LeafletCoordSys from './LeafletCoordSys';
|
||||
import LeafletModel from './LeafletModel';
|
||||
import LeafletView from './LeafletView';
|
||||
|
||||
import { ecVer } from "./helper";
|
||||
|
||||
function install(registers) {
|
||||
|
||||
// add coordinate system support for pie series for ECharts < 5.4.0
|
||||
if ((ecVer[0] == 5 && ecVer[1] < 4)) {
|
||||
registers.registerLayout(function(ecModel, api) {
|
||||
ecModel.eachSeriesByType('pie', function (seriesModel) {
|
||||
const coordSys = seriesModel.coordinateSystem;
|
||||
const data = seriesModel.getData();
|
||||
const valueDim = data.mapDimension('value');
|
||||
if (coordSys && coordSys.type === 'lmap') {
|
||||
const center = seriesModel.get('center');
|
||||
const point = coordSys.dataToPoint(center);
|
||||
const cx = point[0];
|
||||
const cy = point[1];
|
||||
data.each(valueDim, function (value, idx) {
|
||||
const layout = data.getItemLayout(idx);
|
||||
layout.cx = cx;
|
||||
layout.cy = cy;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registers.registerComponentModel(LeafletModel);
|
||||
registers.registerComponentView(LeafletView);
|
||||
registers.registerCoordinateSystem('lmap', LeafletCoordSys);
|
||||
|
||||
// Action
|
||||
registers.registerAction(
|
||||
{
|
||||
type: 'lmapRoam',
|
||||
event: 'lmapRoam',
|
||||
update: 'updateLayout'
|
||||
},
|
||||
function(payload, ecModel) {
|
||||
ecModel.eachComponent('lmap', function(lmapModel) {
|
||||
const lmap = lmapModel.getLeaflet();
|
||||
const center = lmap.getCenter();
|
||||
lmapModel.setCenterAndZoom([center.lat, center.lng], lmap.getZoom());
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export {install as LeafletComponent}
|
|
@ -0,0 +1,36 @@
|
|||
declare const name = "echarts-extension-leaflet";
|
||||
declare const version = "1.0.0";
|
||||
|
||||
|
||||
interface InnerLeafletComponentOption {
|
||||
/**
|
||||
* Whether echarts layer is interactive.
|
||||
* @default true
|
||||
* @since v1.0.0
|
||||
*/
|
||||
echartsLayerInteractive?: Boolean;
|
||||
/**
|
||||
* Whether to enable large mode
|
||||
* @default false
|
||||
* @since v1.0.0
|
||||
*/
|
||||
largeMode?: false;
|
||||
/**
|
||||
* Whether echarts layer should be rendered when the map is moving.
|
||||
* if `false`, it will only be re-rendered after the map `moveend`.
|
||||
* It's better to set this option to false if data is large.
|
||||
* @default true
|
||||
*/
|
||||
renderOnMoving?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended Leaflet component option
|
||||
*/
|
||||
interface LeafletComponentOption<LeafletOption> {
|
||||
leaflet?: LeafletOption extends never
|
||||
? InnerLeafletComponentOption
|
||||
: InnerLeafletComponentOption & LeafletOption;
|
||||
}
|
||||
|
||||
export { name, version, LeafletComponentOption };
|
|
@ -1 +1 @@
|
|||
export type MapType = 'baidu' | 'gaode' | 'gaodeLine'
|
||||
export type MapType = 'baidu' | 'gaode' | 'gaodeLine' | 'gaodeCharts';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
import baidu from '../pages/baidu/index.vue';
|
||||
import gaode from '../pages/gaode/index.vue';
|
||||
import echart from '../pages/echart/index.vue';
|
||||
|
||||
const routes = [
|
||||
{ path: '/', redirect: '/gaode' },
|
||||
{ path: '/gaode', component: gaode },
|
||||
{ path: '/baidu', component: baidu },
|
||||
{ path: '/echart', component: echart },
|
||||
];
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes,
|
||||
});
|
27
src/main.ts
27
src/main.ts
|
@ -1,20 +1,25 @@
|
|||
import { createApp } from 'vue'
|
||||
import './style.scss'
|
||||
import App from './App.vue'
|
||||
import { createApp } from 'vue';
|
||||
import './style.scss';
|
||||
import App from './App.vue';
|
||||
|
||||
import { Quasar } from 'quasar'
|
||||
import { Quasar } from 'quasar';
|
||||
import { router } from './lib/router';
|
||||
|
||||
// Import icon libraries
|
||||
import '@quasar/extras/material-icons/material-icons.css'
|
||||
import '@quasar/extras/material-icons/material-icons.css';
|
||||
|
||||
import '@quasar/extras/mdi-v7/mdi-v7.css'
|
||||
import '@quasar/extras/mdi-v7/mdi-v7.css';
|
||||
|
||||
// Import Quasar css
|
||||
import 'quasar/src/css/index.sass'
|
||||
import 'quasar/src/css/index.sass';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const myApp = createApp(App);
|
||||
|
||||
const myApp = createApp(App)
|
||||
const pinia = createPinia();
|
||||
myApp.use(Quasar, {
|
||||
plugins: {}, // import Quasar plugins and add here
|
||||
})
|
||||
myApp.mount('#app')
|
||||
plugins: {}, // import Quasar plugins and add here
|
||||
});
|
||||
myApp.use(pinia);
|
||||
myApp.use(router);
|
||||
myApp.mount('#app');
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<template>
|
||||
<div ref="mapContainer" style="width: 100%; height: 100%">
|
||||
<MapMenu :map="map"></MapMenu>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useTileLayer from '../../hook/useTileLayer';
|
||||
import useCrs from '../../hook/useCrs';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import MapMenu from '../../components/MapMenu.vue';
|
||||
|
||||
const mapContainer = ref<HTMLElement>();
|
||||
const { baidu } = useTileLayer();
|
||||
const map = ref<L.Map>();
|
||||
|
||||
onMounted(() => {
|
||||
if (!mapContainer.value) return;
|
||||
map.value = L.map(mapContainer.value, {
|
||||
crs: useCrs('baidu'),
|
||||
center: [39.926474, 116.403283],
|
||||
zoom: 10,
|
||||
maxZoom: 15,
|
||||
minZoom: 3,
|
||||
});
|
||||
|
||||
baidu.addTo(map.value);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div ref="mapContainer" style="width: 100%; height: 100%">
|
||||
<q-menu touch-position context-menu>
|
||||
<q-list dense style="min-width: 100px">
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="$emit('flyTo')">跳转</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable v-close-popup>
|
||||
<q-item-section @click="$emit('remove')">删除</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useOption from '../../hook/useOption';
|
||||
import useMap from '../../hook/useMap';
|
||||
import useTileLayer from '../../hook/useTileLayer';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { useResizeObserver } from '@vueuse/core';
|
||||
|
||||
const mapContainer = ref<HTMLElement>();
|
||||
const { heatmap } = useOption();
|
||||
const { gaodeChart } = useTileLayer();
|
||||
|
||||
onMounted(() => {
|
||||
if (!mapContainer.value) return;
|
||||
const { map } = useMap(mapContainer.value, heatmap({ leaflet: { center: [116.396226, 39.914797], zoom: 13 } }));
|
||||
gaodeChart.addTo(map);
|
||||
useResizeObserver(mapContainer, () => map.invalidateSize(true));
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div ref="mapContainer" style="width: 100%; height: 100%" ></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useTileLayer from '../../hook/useTileLayer';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import useCrs from '../../hook/useCrs';
|
||||
|
||||
const mapContainer = ref<HTMLElement>();
|
||||
const { gaode } = useTileLayer();
|
||||
|
||||
onMounted(() => {
|
||||
if (!mapContainer.value) return;
|
||||
const map = L.map(mapContainer.value, {
|
||||
crs: useCrs('gaode'),
|
||||
center: [-97.00238024827533, 210.7725501856634],
|
||||
zoom: 10,
|
||||
maxZoom: 15,
|
||||
minZoom: 3,
|
||||
});
|
||||
gaode.addTo(map);
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,14 @@
|
|||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const key = 'map-marks';
|
||||
export type markType = 'localtion' | 'equipment';
|
||||
export type markInfo = { latlng: [number, number]; title: string; opacity?: number; type?: markType };
|
||||
|
||||
export const useMarkStore = defineStore('mark', () => {
|
||||
const marks = useLocalStorage<markInfo[]>(key, []);
|
||||
|
||||
return {
|
||||
marks,
|
||||
};
|
||||
});
|
|
@ -1,9 +1,10 @@
|
|||
#app {
|
||||
display: grid;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
grid-template-areas: "logo bar"
|
||||
"left map";
|
||||
grid-template-rows: 3em 1fr;
|
||||
grid-template-columns: var(--side, 20em) 1fr;
|
||||
}
|
||||
display: grid;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
grid-template-areas:
|
||||
'logo bar'
|
||||
'left main';
|
||||
grid-template-rows: 3em 1fr;
|
||||
grid-template-columns: var(--side, 20em) 1fr;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue