新版本学习机

This commit is contained in:
2023-11-08 15:31:53 +08:00
parent f21a3e6ad3
commit 770ca0df44
688 changed files with 3722 additions and 1249 deletions

View File

@@ -0,0 +1 @@
export { default as CalculationArea } from './index.vue'

View File

@@ -0,0 +1,507 @@
<template>
<n-grid style="width: 96vw;" class="mx-auto" :cols="2">
<n-gi style="padding-top: 0; padding-bottom: 0;" class="w-h-full p">
<n-scrollbar style="max-height: 49vh; border: 1px solid #eee">
<n-dynamic-input
v-model:value="geodata"
:on-create="onCreate"
:on-remove="onRemove"
>
<template #default="{ value }">
<div style="display: flex; align-items: flex-start; width: 100%">
<div style="width: 50%">
<n-input maxlength="13" clearable v-model:value="value.lat" type="text" placeholder="经度" @input="handleInput" :status="value.latStatus" />
<n-gradient-text :size="12" type="error" v-show="value.latCheck">
<div v-html="value.lat == '' ? '经度不能为空' : '数据格式错误'"></div>
</n-gradient-text>
</div>
<div style="width: 50%">
<n-input maxlength="12" clearable v-model:value="value.lng" type="text" placeholder="纬度" @input="handleInput" :status="value.lngStatus" />
<n-gradient-text :size="12" type="error" v-show="value.lngCheck">
<div v-html="value.lng == '' ? '纬度不能为空' : '数据格式错误'"></div>
</n-gradient-text>
</div>
</div>
</template>
</n-dynamic-input>
</n-scrollbar>
<v-chart style="max-height: 39vh; border: 1px solid #eee" class="chart" id="chart" :option="option" />
<div style="margin-top: 1vh;" class="w-full flex flex-1">
<n-button class="mx-auto" :disabled="geodata.length == 0 ? true : false" size="large" type="primary" @click="calculation">计  算</n-button>
<n-button class="mx-auto" :disabled="geodata.length == 0 ? true : false" size="large" type="error" @click="delgeo">清空输入</n-button>
</div>
</n-gi>
<n-gi style="padding-top: 0; padding-bottom: 0;" class="w-h-full p">
<n-data-table :columns="columns" :data="data" :single-line="false" :pagination="pagination" :style="{ height: '88vh' }" flex-height />
<div style="margin-top: 1vh;" class="w-full flex flex-1">
<div style="width: 128px" class="mx-auto">
<n-select v-model:value="unit" :options="options" />
</div>
<n-button class="mx-auto" :disabled="data.length == 0 ? true : false" size="large" type="error" @click="delall">清除数据</n-button>
</div>
</n-gi>
</n-grid>
</template>
<script setup lang="ts">
import { ref, h, watch, reactive } from "vue"
import * as echarts from "echarts"
import { use, registerMap } from "echarts/core"
import { CanvasRenderer } from 'echarts/renderers'
import { MapChart, ScatterChart } from 'echarts/charts'
import VChart from "vue-echarts"
import { NGi, NGrid, NButton, NDynamicInput, NDataTable, NScrollbar, NInput, NGradientText, NSelect, useMessage, useDialog } from "naive-ui"
const message = useMessage()
const dialog = useDialog()
const geodata = ref([
{
lat: "",
latStatus: "",
latCheck: "",
lng: "",
lngStatus: "",
lngCheck: ""
}
])
const onCreate = () => {
return {
lat: "",
latStatus: "",
latCheck: "",
lng: "",
lngStatus: "",
lngCheck: ""
}
}
const unit = ref('平方米/m²')
const options = [
{
label:"平方米/m²",
value:'平方米/m²'
},
{
label:"平方千米/km²",
value:'平方千米/km²'
}]
const columns = ref([
{
title: '经纬度数据',
key: 'latandlng',
render (row) {
const tags = row.latandlng.map((DataTag) => {
return h(
NScrollbar,
{
style: {
whiteSpace: 'pre-wrap'
}
},
{
default: () => `经度:${DataTag[0]}\n纬度:${DataTag[1]}\n\n`
}
)
})
return tags
}
},
{
title: `面积(单位:${ unit.value })`,
key: 'area'
},
{
title: '轮廓',
key: 'operate',
fixed: 'right',
align: 'center',
render (row) {
return h(
NButton,
{
strong: true,
tertiary: true,
size: 'large',
type: 'info',
onClick: () => drawContour(row)
},
{ default: () => '生成轮廓' }
)
}
},
{
title: '操作',
key: 'operate',
fixed: 'right',
align: 'center',
render (row) {
return h(
NButton,
{
strong: true,
tertiary: true,
size: 'large',
type: 'error',
onClick: () => deldata(row)
},
{ default: () => '删除' }
)
}
}])
const data = ref([])
const geoObj = reactive({
"type":"FeatureCollection",
"features":[{
"type":"Feature",
"properties":{
"adcode":0,
"name":"经纬度数据",
"center":[0,0],
"centroid":[0,0],
"cp":[15,12],
"childrenNum":0,
"level":"city",
"acroutes":[0,0],
"parent":{"adcode":0}
},
"geometry":{
"type":"MultiPolygon",
"coordinates":[[[[15,30],[0,0],[15,10],[30,0]]]]
}
}]
})
use([ CanvasRenderer, MapChart, ScatterChart ])
registerMap('nowin',geoObj)
const option = reactive({
geo: [
{
zlevel:3,
map: 'nowin',
label: {
show: true,
color: 'black',
fontSize: 18
},
itemStyle: {
normal: {
areaColor: '#26c4eb',
shadowColor:"#01273F",
shadowOffsetX: 0,
shadowOffsetY: 0,
borderWidth:0.5,
}
}
}
],
series: [{
//设置为分散点
type: 'scatter',
//series坐标系类型
coordinateSystem: 'geo',
//设置图形 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
symbol: 'pin',
// //标记的大小,可以设置成诸如 10 这样单一的数字,也可以用数组分开表示宽和高,例如 [20, 10] 表示标记宽为20,高为10
symbolSize: [40,40],
//气泡字体设置
label: {
normal: {
show: false,//是否显示
textStyle: {
color: '#000',//字体颜色
fontSize: 8,//字体大小
},
//返回气泡数据
formatter (value){
return value.data.value[2]
}
}
},
itemStyle: {
normal: {
color: '#1E90FF', //标志颜色
areaColor: '#26c4eb',
borderWidth: 0.5,
borderColor: "#ffffff",
shadowColor:"#01273F",
shadowBlur: 6
}
},
// //给区域赋值
data: [],
showEffectOn: 'render',//配置何时显示特效。可选:'render' 绘制完成后显示特效。'emphasis' 高亮(hover)的时候显示特效。
rippleEffect: {//涟漪特效相关配置。
brushType: 'stroke'//波纹的绘制方式,可选 'stroke' 和 'fill'
},
hoverAnimation: true,//是否开启鼠标 hover 的提示动画效果。
zlevel: 3//所属图形的 zlevel 值
}]
})
window.addEventListener('resize', () => {
let chart = echarts.init(document.getElementById('chart'))
chart.resize()
})
const rerender = (mapName,mapArea,mapGeo) => {
if(mapGeo){
geoObj.features[0].properties.cp = centerPoint(mapGeo)
geoObj.features[0].geometry.coordinates[0][0] = mapGeo
}
geoObj.features[0].properties.name = mapArea
registerMap(mapName, geoObj)
option.geo[0].map = mapName
}
const onRemove = () => {
if(geodata.value.length < 3){
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
}
}
const inputFilter = (inputData) => {
let infilter = []
let beffilter = [...inputData]
beffilter.forEach(item => {
if(item !== '\u00A0' && item !== '\u0020' && item !== '\u3000'){
if(!isNaN(Number(item))){
infilter.push(item)
}else if(item == '.'){
if(infilter.indexOf('.') == '-1'){
infilter.push(item)
}
}else if(item == '-'){
if(infilter.indexOf('-') == '-1'){
infilter.push(item)
}
}
}
})
return infilter.join('')
}
const handleInput = () => {
geodata.value.forEach((element,index) => {
element.lat = inputFilter(element.lat)
element.lng = inputFilter(element.lng)
if(element.lat == ''){
element.latStatus = 'error'
element.latCheck = true
}
else{
element.latStatus = 'success'
element.latCheck = false
}
if(element.lng == ''){
element.lngStatus = 'error'
element.lngCheck = true
}
else {
element.lngStatus = 'success'
element.lngCheck = false
}
})
}
const inputCheck = (inputValue) => {
if(inputValue.lat == ''){
message.error('经度不能为空')
inputValue.latStatus = 'error'
inputValue.latCheck = true
throw new Error("经度不能为空")
}
if(inputValue.lng == ''){
message.error('纬度不能为空')
inputValue.lngStatus = 'error'
inputValue.lngCheck = true
throw new Error("纬度不能为空")
}
if(inputValue.lat.indexOf('-') !== -1 && inputValue.lat.indexOf('-') !== 0 || inputValue.lat.indexOf('.') == inputValue.lat.length-1 || inputValue.lat.indexOf('-')+1 == inputValue.lat.indexOf('.')){
message.error('数据格式错误')
inputValue.latStatus = 'error'
inputValue.latCheck = true
throw new Error("数据格式错误")
}
if(inputValue.lng.indexOf('-') !== -1 && inputValue.lng.indexOf('-') !== 0 || inputValue.lng.indexOf('.') == inputValue.lng.length-1 || inputValue.lng.indexOf('-')+1 == inputValue.lng.indexOf('.')){
message.error('数据格式错误')
inputValue.lngStatus = 'error'
inputValue.lngCheck = true
throw new Error("数据格式错误")
}
if(Number(inputValue.lat).toFixed(8) > 180 || Number(inputValue.lat).toFixed(8) < -180){
message.error('非法的经度数据')
inputValue.latStatus = 'error'
inputValue.latCheck = true
throw new Error("非法的经度数据")
}
if(Number(inputValue.lng).toFixed(8) > 90 || Number(inputValue.lng).toFixed(8) < -90){
message.error('非法的纬度数据')
inputValue.lngStatus = 'error'
inputValue.lngCheck = true
throw new Error("非法的纬度数据")
}
}
//地球赤道半径 6378.137
//地球平均半径 6371.004
//弧度转角度 Math.PI / 180.0
//角度转弧度 180.0 / Math.PI
//米转角度 2.0 * Math.PI * 6371.004 / 360.0
//需求决定了输入的经纬度距离不会太远,近似为求平面多边形的面积
const calculationArea = (points) => {
let area = 0
//将多边形分解为三角形
points.forEach((item,index) => {
let j = (index + 1) % points.length
let xi = points[index][0] * (2.0 * Math.PI * 6371004.0 / 360.0) * Math.cos(points[index][1] * (Math.PI / 180.0))
let yi = points[index][1] * (2.0 * Math.PI * 6371004.0 / 360.0)
let xj = points[j][0] * (2.0 * Math.PI * 6371004.0 / 360.0) * Math.cos(points[j][1] * (Math.PI / 180.0))
let yj = points[j][1] * (2.0 * Math.PI * 6371004.0 / 360.0)
area += xi * yj - xj * yi
})
//计算结果为平方米
if(unit.value == '平方米/m²'){
return Math.abs(area / 2).toFixed(4) + unit.value
}
else{
return (Math.abs(area / 2) / 1000000).toFixed(9) + unit.value
}
}
const centerPoint = (allPoint) => {
let averagelat = 0
let averagelng = 0
allPoint.forEach(item => {
averagelat = Number(item[0]) + averagelat
averagelng = Number(item[1]) + averagelng
})
return [averagelat/allPoint.length,averagelng/allPoint.length]
}
const diffData = (areaResult) => {
let allowCalaulation = true
data.value.forEach(items => {
if(items.latandlng.join(';') == areaResult.latandlng.join(';')){
allowCalaulation = false
}
})
if(allowCalaulation){
data.value.push(areaResult)
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
rerender('updataGeo',data.value[data.value.length-1].area,data.value[data.value.length-1].latandlng)
}
}
const calculation = () => {
if(geodata.value.length <= 2){
message.error('经纬度数据对的数量不能满足运算')
return
}
let pointArr = []
geodata.value.forEach((element,index) => {
inputCheck(element)
pointArr.push([element.lat.length>8?Number(element.lat).toFixed(8):element.lat, element.lng.length>8?Number(element.lng).toFixed(8):element.lng])
})
let areaData = {
latandlng: pointArr,
area: calculationArea(pointArr)
}
diffData(areaData)
}
const delgeo = () => {
dialog.warning({
title: '警告',
content: '确定要清除全部输入吗?',
positiveText: '确定',
negativeText: '返回',
onPositiveClick: () => {
geodata.value = [
{
lat: "",
latStatus: "",
latCheck: "",
lng: "",
lngStatus: "",
lngCheck: ""
}
]
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
},
onNegativeClick: () => {
message.error('已返回')
}
})
}
const drawContour = (row) => {
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
rerender('updataGeo',row.area,row.latandlng)
}
const deldata = (row) => {
data.value.filter((item,index,array)=>{
if(item == row){
array.splice(index,1)
console.log(data.value.length)
if(data.value.length == 0){
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
}
}
})
}
const delall = () => {
dialog.warning({
title: '警告',
content: '确定要清除全部数据吗?',
positiveText: '确定',
negativeText: '返回',
onPositiveClick: () => {
data.value = []
rerender('nowin','经纬度数据',[[15,30],[0,0],[15,10],[30,0]])
},
onNegativeClick: () => {
message.error('已返回')
}
})
}
watch(unit,(value) => {
columns.value[1].title = `面积(单位:${ value })`
if(data.value.length !== 0){
data.value.forEach(element => {
if(value == '平方米/m²'){
rerender('updataGeo',element.area)
element.area = (element.area.slice(0,element.area.length-8) * 1000000).toFixed(4) + value
rerender('change',element.area)
}else{
rerender('updataGeo',element.area)
element.area = (element.area.slice(0,element.area.length-6) / 1000000).toFixed(9) + value
rerender('change',element.area)
}
})
}
})
</script>
<style scoped lang="less">
.n-grid {
place-items: center
}
.p {
padding: 1%;
padding-top: 2%
}
.mt {
margin-top:2%
}
</style>