553 lines
16 KiB
Vue
553 lines
16 KiB
Vue
<template>
|
||
<div class="show-map-div">
|
||
<div class="select-menu">
|
||
<div class="add-on-map">
|
||
<a-checkbox v-model:checked="addOnMap" @change="(e) => addOnMapChange(e.target.checked)">
|
||
叠加到地图
|
||
</a-checkbox>
|
||
</div>
|
||
<a-radio-group v-model:value="selectType" button-style="solid" size="small">
|
||
<a-radio-button :value="0">专题图</a-radio-button>
|
||
<!-- <a-radio-button :value="1">截图</a-radio-button> -->
|
||
<!-- <a-radio-button :value="2">天地图</a-radio-button> -->
|
||
</a-radio-group>
|
||
</div>
|
||
<div class="slider-menu">
|
||
<!-- <a-slider
|
||
v-if="addOnMap && sliderShow"
|
||
v-model:value="nowSliderValue"
|
||
vertical="true"
|
||
:tooltipOpen="false"
|
||
:tip-formatter="getValue"
|
||
:marks="marks"
|
||
:step="1"
|
||
min="0"
|
||
max="10"
|
||
@afterChange="sliderChange"
|
||
>
|
||
<template #mark="{ label, point }">
|
||
<template v-if="point >= 0">
|
||
<span
|
||
:style="{
|
||
color: 'white',
|
||
position: 'relative',
|
||
top: `${7}px`,
|
||
left: `${3}px`,
|
||
}"
|
||
>{{ label }}</span
|
||
>
|
||
</template>
|
||
</template>
|
||
</a-slider>
|
||
<a-tooltip placement="right">
|
||
<template #title>
|
||
<span>设置叠加到地图的图片的透明度</span>
|
||
</template>
|
||
<PlusCircleOutlined
|
||
v-if="(addOnMap || selectType == 2) && sliderShow"
|
||
:style="{
|
||
position: 'absolute',
|
||
fontSize: '18px',
|
||
bottom: '-28px',
|
||
right: '8px',
|
||
color: 'white',
|
||
}"
|
||
@click="changeSliderShow"
|
||
/>
|
||
<MinusCircleOutlined
|
||
v-if="(addOnMap || selectType == 2) && !sliderShow"
|
||
:style="{
|
||
position: 'absolute',
|
||
fontSize: '18px',
|
||
bottom: '-28px',
|
||
right: '8px',
|
||
color: 'white',
|
||
}"
|
||
@click="changeSliderShow"
|
||
/>
|
||
</a-tooltip> -->
|
||
</div>
|
||
<div class="expandShow-menu">
|
||
<FullscreenOutlined
|
||
v-if="addOnMap && !expandShow"
|
||
:style="{
|
||
position: 'absolute',
|
||
fontSize: '28px',
|
||
bottom: '-65px',
|
||
right: '4px',
|
||
color: 'white',
|
||
zIndex: 11,
|
||
}"
|
||
@click="expand(!expandShow)"
|
||
/>
|
||
<FullscreenExitOutlined
|
||
v-if="addOnMap && expandShow"
|
||
:style="{
|
||
position: 'fixed',
|
||
fontSize: '35px',
|
||
bottom: '20px',
|
||
right: '20px',
|
||
color: 'white',
|
||
zIndex: 1012,
|
||
}"
|
||
@click="expand(!expandShow)"
|
||
/>
|
||
</div>
|
||
<template v-if="selectType === 0">
|
||
<a-image
|
||
v-if="!addOnMap"
|
||
:width="470"
|
||
:height="470"
|
||
:src="convertValidUrl(showData.url)"
|
||
:preview="{
|
||
src: showData.previewUrl,
|
||
}"
|
||
/>
|
||
</template>
|
||
<template v-if="selectType === 1 && !addOnMap">
|
||
<a-image
|
||
v-if="!addOnMap"
|
||
:width="470"
|
||
:height="470"
|
||
:src="convertValidUrl(showData.urlxian)"
|
||
:preview="{
|
||
src: showData.previewUrlXian,
|
||
}"
|
||
/>
|
||
</template>
|
||
<ModalMap
|
||
v-if="addOnMap"
|
||
:width="modalMapWidth"
|
||
:height="modalMapHeight"
|
||
:zIndex="modalMapZIndex"
|
||
:position="modalMapPosition"
|
||
:centerAndZoom="props.data"
|
||
@getMap="getMap"
|
||
/>
|
||
</div>
|
||
<a-tabs v-model:activeKey="activeKey">
|
||
<a-tab-pane key="1" tab="土地分类">
|
||
<ShowTableList
|
||
:columns="landClassificationColumns"
|
||
:data="landClassifyTable"
|
||
:title="'土地利用现状查询结果'"
|
||
/>
|
||
</a-tab-pane>
|
||
<a-tab-pane key="2" tab="耕地占用">
|
||
<ShowTableList
|
||
:columns="landPlanningColumns"
|
||
:data="plowLandOccupyTable"
|
||
:title="'土地规划查询结果'"
|
||
/>
|
||
</a-tab-pane>
|
||
</a-tabs>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, defineProps, computed, watch, onMounted } from 'vue';
|
||
import {
|
||
PlusCircleOutlined,
|
||
MinusCircleOutlined,
|
||
FullscreenOutlined,
|
||
FullscreenExitOutlined,
|
||
} from '@ant-design/icons-vue';
|
||
import ShowTableList from '@/views/dashboard/test/components/ShowTableList/index.vue';
|
||
import ModalMap from './ModalMap/index.vue';
|
||
// 图片路径拼接
|
||
import { getAppEnvConfig } from '@/utils/env';
|
||
const { VITE_GLOB_API_URL } = getAppEnvConfig();
|
||
const VITE_GLOB_API_URL_VAR = ref<String>(VITE_GLOB_API_URL + '/');
|
||
|
||
const props = defineProps(['data']);
|
||
|
||
let map;
|
||
// 土地分类和耕地占用
|
||
const activeKey = ref('1');
|
||
// 是否叠加到地图
|
||
const addOnMap = ref(true);
|
||
// 专题图、截图、天地图
|
||
const selectType = ref(0);
|
||
|
||
watch(
|
||
// 土地分类和耕地占用
|
||
() => activeKey.value,
|
||
() => {
|
||
// 查看结果进入就显示叠加的图
|
||
if (addOnMap.value) {
|
||
addOnMapChange(true);
|
||
}
|
||
},
|
||
);
|
||
|
||
watch(
|
||
// 专题图、截图、天地图
|
||
() => selectType.value,
|
||
() => {
|
||
// 查看结果进入就显示叠加的图
|
||
if (addOnMap.value) {
|
||
addOnMapChange(true);
|
||
}
|
||
},
|
||
);
|
||
|
||
// 表格数据
|
||
const landClassifyTable = computed(() => {
|
||
let data;
|
||
props.data?.forEach((item, index) => {
|
||
if (item.name == '土地分类') {
|
||
data = { ...item };
|
||
}
|
||
// 第一次打开
|
||
if (index == 0) {
|
||
// 移动位置
|
||
let fourpoint = JSON.parse(`[${item.fourpoint}]`);
|
||
setTimeout(() => {
|
||
moveLocation(fourpoint);
|
||
}, 200);
|
||
// 查看结果进入就显示叠加的图
|
||
addOnMapChange(true);
|
||
}
|
||
// 生成白色底色的预览图片
|
||
if (item.url && !item.previewUrl) {
|
||
changeTransparentColorToWhite(convertValidUrl(item.url)).then((resultImageUrl) => {
|
||
item.previewUrl = resultImageUrl;
|
||
});
|
||
}
|
||
if (item.urlxian && !item.previewUrlXian) {
|
||
changeTransparentColorToWhite(convertValidUrl(item.urlxian)).then((resultImageUrl) => {
|
||
item.previewUrlXian = resultImageUrl;
|
||
});
|
||
}
|
||
});
|
||
return data && data.list;
|
||
});
|
||
|
||
const plowLandOccupyTable = computed(() => {
|
||
let data;
|
||
props.data?.forEach((item) => {
|
||
if (item.name == '耕地占用') {
|
||
data = { ...item };
|
||
}
|
||
});
|
||
return data && data.list;
|
||
});
|
||
|
||
const showData = computed(() => {
|
||
let data;
|
||
switch (activeKey.value) {
|
||
case '1':
|
||
props.data?.forEach((item) => {
|
||
if (item.name == '土地分类') {
|
||
data = { ...item };
|
||
}
|
||
});
|
||
return data;
|
||
case '2':
|
||
props.data?.forEach((item) => {
|
||
if (item.name == '耕地占用') {
|
||
data = { ...item };
|
||
}
|
||
});
|
||
return data;
|
||
}
|
||
});
|
||
// 表格结构
|
||
const landClassificationColumns = [
|
||
{
|
||
title: '地类名称',
|
||
dataIndex: 'type',
|
||
key: 'type',
|
||
width: 180,
|
||
sorter: (a, b) => a.landName - b.landName,
|
||
sortDirections: ['descend', 'ascend'],
|
||
},
|
||
{
|
||
title: '联合属性',
|
||
dataIndex: 'stats',
|
||
key: 'stats',
|
||
width: 100,
|
||
sorter: (a, b) => a.stats.length - b.stats.length,
|
||
sortDirections: ['descend', 'ascend'],
|
||
},
|
||
{
|
||
title: '面积(亩)',
|
||
dataIndex: 'area',
|
||
key: 'area',
|
||
width: 150,
|
||
sorter: (a, b) => a.area - b.area,
|
||
sortDirections: ['descend', 'ascend'],
|
||
},
|
||
];
|
||
const landPlanningColumns = [
|
||
{
|
||
title: '类型',
|
||
dataIndex: 'type',
|
||
key: 'type',
|
||
sorter: (a, b) => a.type.length - b.type.length,
|
||
sortDirections: ['descend', 'ascend'],
|
||
},
|
||
{
|
||
title: '面积(亩)',
|
||
dataIndex: 'area',
|
||
key: 'area',
|
||
sorter: (a, b) => a.area - b.area,
|
||
sortDirections: ['descend', 'ascend'],
|
||
},
|
||
];
|
||
const getMap = (value) => {
|
||
map = value;
|
||
};
|
||
|
||
// 定义预加载图片的方法------------------------------------------------------------------------------------------------
|
||
const addOnMapChange = (value) => {
|
||
setTimeout(async () => {
|
||
let url: any = '';
|
||
let fourpoint = '';
|
||
if (value) {
|
||
let data: any = {};
|
||
if (activeKey.value == '1') {
|
||
props.data.forEach((item) => {
|
||
if (item.name == '土地分类') {
|
||
data = { ...item };
|
||
}
|
||
});
|
||
} else if (activeKey.value == '2') {
|
||
props.data.forEach((item) => {
|
||
if (item.name == '耕地占用') {
|
||
data = { ...item };
|
||
}
|
||
});
|
||
}
|
||
if (selectType.value == 0 || selectType.value == 2) {
|
||
url = convertValidUrl(data.url);
|
||
fourpoint = JSON.parse(`[${data.fourpoint}]`);
|
||
}
|
||
if (selectType.value == 1) {
|
||
url = convertValidUrl(data.urlxian);
|
||
fourpoint = JSON.parse(`[${data.fourpoint}]`);
|
||
}
|
||
// 图片预加载
|
||
let image = new Image();
|
||
image.crossOrigin = 'anonymous';
|
||
image.src = url;
|
||
image.onload = () => {
|
||
// 创建Canvas元素
|
||
const canvas = document.createElement('canvas');
|
||
canvas.width = image.width;
|
||
canvas.height = image.height;
|
||
// 获取Canvas绘图上下文
|
||
const ctx = canvas.getContext('2d');
|
||
// 在Canvas上绘制图片
|
||
ctx.drawImage(image, 0, 0, image.width, image.height);
|
||
// 将Canvas内容转换为图片的数据URL
|
||
const dataURL = canvas.toDataURL('image/png');
|
||
// 移除旧的图层和源
|
||
if (map) {
|
||
if (map.getLayer('radar-layer')) {
|
||
map.removeLayer('radar-layer');
|
||
}
|
||
if (map.getSource('radar')) {
|
||
map.removeSource('radar');
|
||
}
|
||
}
|
||
// 添加新的源和图层
|
||
map.addSource('radar', {
|
||
type: 'image',
|
||
url: dataURL,
|
||
coordinates: fourpoint,
|
||
});
|
||
map.addLayer({
|
||
id: 'radar-layer',
|
||
type: 'raster',
|
||
source: 'radar',
|
||
paint: {
|
||
'raster-opacity': parseFloat(getValue(nowSliderValue.value)),
|
||
},
|
||
});
|
||
moveLocation(fourpoint);
|
||
};
|
||
}
|
||
sliderShow.value = false;
|
||
// console.log(337, props.data);
|
||
}, 500);
|
||
};
|
||
|
||
// 移动位置
|
||
function moveLocation(fourpoint) {
|
||
let x = 0;
|
||
let y = 0;
|
||
let length = fourpoint.length;
|
||
for (let i = 0; i < fourpoint.length; i++) {
|
||
x = x + fourpoint[i][0];
|
||
y = y + fourpoint[i][1];
|
||
}
|
||
x = x / length;
|
||
y = y / length;
|
||
if (map) {
|
||
map.jumpTo({
|
||
center: [x, y],
|
||
zoom: calculateZoom(fourpoint),
|
||
});
|
||
}
|
||
return [x, y];
|
||
}
|
||
|
||
// 计算合适的缩放级别
|
||
function calculateZoom(points) {
|
||
// 计算边界框
|
||
const nw = points[0];
|
||
const ne = points[1];
|
||
const se = points[2];
|
||
const latRange = Math.abs(nw[1] - se[1]);
|
||
const lngRange = Math.abs(nw[0] - ne[0]);
|
||
// 计算缩放级别
|
||
const zoomLat = Math.log2(360 / latRange) - 1;
|
||
const zoomLng = Math.log2(360 / lngRange) - 1;
|
||
// 返回最小的缩放级别,并保留小数后两位
|
||
return Math.min(zoomLat * 1.05, zoomLng * 1.05).toFixed(2);
|
||
}
|
||
|
||
// 全屏--------------------------------------------------------------------------------------------------------------
|
||
const modalMapWidth = ref('470px');
|
||
const modalMapHeight = ref('470px');
|
||
const modalMapZIndex = ref(10);
|
||
const modalMapPosition = ref('absolute');
|
||
const expandShow = ref(false);
|
||
// 全屏
|
||
function expand(value) {
|
||
if (value) {
|
||
modalMapWidth.value = '100%';
|
||
modalMapHeight.value = '100%';
|
||
modalMapZIndex.value = 1011;
|
||
modalMapPosition.value = 'fixed';
|
||
} else {
|
||
modalMapWidth.value = '470px';
|
||
modalMapHeight.value = '470px';
|
||
modalMapZIndex.value = 10;
|
||
modalMapPosition.value = 'absolute';
|
||
}
|
||
setTimeout(() => {
|
||
map.resize();
|
||
}, 100);
|
||
expandShow.value = value;
|
||
}
|
||
|
||
// 图片------------------------------------------------------------------------------------------------------------
|
||
// 正则表达式验证URL,转换
|
||
function convertValidUrl(url: string): boolean {
|
||
const regex =
|
||
/^(?:http|ftp)s?:\/\/(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|localhost|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[?[A-F0-9]*:[A-F0-9:]+\]?)(?::\d+)?(?:\/?|[\/?]\S+)$/i;
|
||
let result: any = regex.test(url) ? url : VITE_GLOB_API_URL_VAR.value + url;
|
||
return result;
|
||
}
|
||
// 预览变成白色的底色
|
||
async function changeTransparentColorToWhite(imageUrl) {
|
||
return new Promise((resolve, reject) => {
|
||
// 创建一个新的 Image 对象
|
||
const img = new Image();
|
||
img.crossOrigin = 'Anonymous'; // 处理跨域问题
|
||
img.src = imageUrl;
|
||
img.onload = () => {
|
||
// 创建一个 canvas 元素
|
||
const canvas = document.createElement('canvas');
|
||
const ctx = canvas.getContext('2d');
|
||
// 设置 canvas 的尺寸与图片相同
|
||
canvas.width = img.width;
|
||
canvas.height = img.height;
|
||
// 将图片绘制到 canvas 上
|
||
ctx.drawImage(img, 0, 0);
|
||
// 获取 canvas 的图像数据
|
||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||
const data = imageData.data;
|
||
// 遍历每个像素,将透明部分变成白色
|
||
for (let i = 0; i < data.length; i += 4) {
|
||
if (data[i + 3] === 0) {
|
||
// 如果 alpha 通道为 0(完全透明)
|
||
data[i] = 255; // 红色通道
|
||
data[i + 1] = 255; // 绿色通道
|
||
data[i + 2] = 255; // 蓝色通道
|
||
data[i + 3] = 255; // alpha 通道(不透明)
|
||
}
|
||
}
|
||
// 将修改后的图像数据放回 canvas
|
||
ctx.putImageData(imageData, 0, 0);
|
||
// 将 canvas 内容转换为图片数据 URL
|
||
const resultImageUrl = canvas.toDataURL('image/png');
|
||
// 返回处理后的新图片的 URL
|
||
resolve(resultImageUrl);
|
||
};
|
||
img.onerror = (error) => {
|
||
reject(error);
|
||
};
|
||
});
|
||
}
|
||
|
||
// 透明度-----------------------------------------------------------------------------------------------------------
|
||
const nowSliderValue = ref<number>(10);
|
||
// 标识
|
||
const marks = ref<Record<number, any>>({
|
||
0: 0,
|
||
1: 0.1,
|
||
2: 0.2,
|
||
3: 0.3,
|
||
4: 0.4,
|
||
5: 0.5,
|
||
6: 0.6,
|
||
7: 0.7,
|
||
8: 0.8,
|
||
9: 0.9,
|
||
10: 1,
|
||
});
|
||
// 获取标识值
|
||
const getValue = (key: number) => {
|
||
if (marks.value) {
|
||
return marks.value[key];
|
||
}
|
||
};
|
||
// 发生改变
|
||
const sliderChange = (key: number) => {
|
||
map.setPaintProperty('radar-layer', 'raster-opacity', parseFloat(getValue(key)));
|
||
};
|
||
// 设置叠加到地图的图片的透明度
|
||
const sliderShow = ref(false);
|
||
function changeSliderShow() {
|
||
sliderShow.value = !sliderShow.value;
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.select-menu {
|
||
display: flex;
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
z-index: 100;
|
||
.add-on-map {
|
||
width: 110px;
|
||
background: rgba(0, 0, 0, 0.2);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-top-left-radius: 5px;
|
||
border-bottom-left-radius: 5px;
|
||
}
|
||
}
|
||
.slider-menu {
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 90px;
|
||
height: 300px;
|
||
z-index: 100;
|
||
}
|
||
.show-map-div {
|
||
width: 470px;
|
||
height: 470px;
|
||
position: relative;
|
||
}
|
||
.expandShow-menu {
|
||
position: absolute;
|
||
right: 10px;
|
||
bottom: 80px;
|
||
}
|
||
</style>
|