CN-1563 feat: 轨迹地图部分代码
This commit is contained in:
50
nginx_server.conf
Normal file
50
nginx_server.conf
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
#access_log /var/log/nginx/host.access.log main;
|
||||||
|
|
||||||
|
client_max_body_size 3072m;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://127.0.0.1:8090/;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
# redirect server error pages to the static page /50x.html
|
||||||
|
#
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
|
||||||
|
#
|
||||||
|
#location ~ \.php$ {
|
||||||
|
# proxy_pass http://127.0.0.1;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
|
||||||
|
#
|
||||||
|
#location ~ \.php$ {
|
||||||
|
# root html;
|
||||||
|
# fastcgi_pass 127.0.0.1:9000;
|
||||||
|
# fastcgi_index index.php;
|
||||||
|
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
|
||||||
|
# include fastcgi_params;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# deny access to .htaccess files, if Apache's document root
|
||||||
|
# concurs with nginx's one
|
||||||
|
#
|
||||||
|
#location ~ /\.ht {
|
||||||
|
# deny all;
|
||||||
|
#}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"dexie": "~3.2.0",
|
"dexie": "~3.2.0",
|
||||||
"echarts": "^5.1.1",
|
"echarts": "^5.1.1",
|
||||||
"element-plus": "~1.0.2-beta.71",
|
"element-plus": "~1.0.2-beta.71",
|
||||||
|
"h3-js": "~3.7.2",
|
||||||
"lib-flexible": "^0.3.2",
|
"lib-flexible": "^0.3.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"maplibre-gl": "3.6.2",
|
"maplibre-gl": "3.6.2",
|
||||||
|
|||||||
@@ -300,4 +300,38 @@ if (openMock) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Mock.mock(new RegExp(BASE_CONFIG.baseUrl + 'v1/locationIntelligence/baseStation.*'), 'get', function (requestObj) {
|
||||||
|
return {
|
||||||
|
msg: 'success',
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
longitude: 116.38,
|
||||||
|
latitude: 39.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
longitude: 116.39,
|
||||||
|
latitude: 39.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
longitude: 116.383,
|
||||||
|
latitude: 39.886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
longitude: 116.378,
|
||||||
|
latitude: 39.902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
longitude: 116.369,
|
||||||
|
latitude: 39.91
|
||||||
|
},
|
||||||
|
{
|
||||||
|
longitude: 116.38,
|
||||||
|
latitude: 39.91
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -356,7 +356,8 @@ export const api = {
|
|||||||
map: apiVersion + '/locationIntelligence/map',
|
map: apiVersion + '/locationIntelligence/map',
|
||||||
density: apiVersion + '/locationIntelligence/population/density',
|
density: apiVersion + '/locationIntelligence/population/density',
|
||||||
trend: apiVersion + '/locationIntelligence/active/trend',
|
trend: apiVersion + '/locationIntelligence/active/trend',
|
||||||
count: apiVersion + '/locationIntelligence/active/count'
|
count: apiVersion + '/locationIntelligence/active/count',
|
||||||
|
baseStation: apiVersion + '/locationIntelligence/baseStation'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,19 +106,19 @@
|
|||||||
<template v-if="tooltip.type === tooltipType.hexagon">
|
<template v-if="tooltip.type === tooltipType.hexagon">
|
||||||
<div class="body__item">
|
<div class="body__item">
|
||||||
<div class="item__label">Number</div>
|
<div class="item__label">Number</div>
|
||||||
<div class="item__value">{{currentPolygon.count}}</div>
|
<div class="item__value">{{currentPolygon.number}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body__item">
|
<div class="body__item">
|
||||||
<div class="item__label">Locals</div>
|
<div class="item__label">Locals</div>
|
||||||
<div class="item__value">{{currentPolygon.count}}</div>
|
<div class="item__value">{{currentPolygon.number}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body__item">
|
<div class="body__item">
|
||||||
<div class="item__label">Visitors</div>
|
<div class="item__label">Visitors</div>
|
||||||
<div class="item__value">{{currentPolygon.count}}</div>
|
<div class="item__value">{{currentPolygon.number}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body__item">
|
<div class="body__item">
|
||||||
<div class="item__label">Roamers</div>
|
<div class="item__label">Roamers</div>
|
||||||
<div class="item__value">{{currentPolygon.count}}</div>
|
<div class="item__value">{{currentPolygon.number}}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="tooltip.type === tooltipType.human">
|
<template v-else-if="tooltip.type === tooltipType.human">
|
||||||
@@ -171,8 +171,6 @@
|
|||||||
import { ref, shallowRef } from 'vue'
|
import { ref, shallowRef } from 'vue'
|
||||||
import { getNowTime, getSecond } from '@/utils/date-util'
|
import { getNowTime, getSecond } from '@/utils/date-util'
|
||||||
import maplibregl from 'maplibre-gl'
|
import maplibregl from 'maplibre-gl'
|
||||||
import { MapboxOverlay } from '@deck.gl/mapbox'
|
|
||||||
import { H3HexagonLayer } from '@deck.gl/geo-layers'
|
|
||||||
import mapStyle from '@/views/charts2/charts/entityDetail/mapStyle'
|
import mapStyle from '@/views/charts2/charts/entityDetail/mapStyle'
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css'
|
import 'maplibre-gl/dist/maplibre-gl.css'
|
||||||
import { valueToRangeValue } from '@/utils/unit-convert'
|
import { valueToRangeValue } from '@/utils/unit-convert'
|
||||||
@@ -185,9 +183,7 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
import { urlParamsHandler, overwriteUrl } from '@/utils/tools'
|
import { urlParamsHandler, overwriteUrl } from '@/utils/tools'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { api } from '@/utils/api'
|
import { api } from '@/utils/api'
|
||||||
import {
|
import { h3ToGeoBoundary } from 'h3-js'
|
||||||
geoToH3
|
|
||||||
} from 'h3-js'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Location',
|
name: 'Location',
|
||||||
@@ -235,41 +231,39 @@ export default {
|
|||||||
// 最先渲染右上角饼图
|
// 最先渲染右上角饼图
|
||||||
await this.renderDensityPie()
|
await this.renderDensityPie()
|
||||||
// 然后渲染地图的色块、基站、人(包括右侧关注列表),最后渲染右上角折线图
|
// 然后渲染地图的色块、基站、人(包括右侧关注列表),最后渲染右上角折线图
|
||||||
map.on('load', function () {
|
map.on('load', async function () {
|
||||||
console.info('map loaded')
|
console.info('map loaded')
|
||||||
// 地图色块
|
/* 地图色块 */
|
||||||
const hexagonData = _this.queryHexagon()
|
const hexagonData = await _this.queryHexagon()
|
||||||
const hexagon = new MapboxOverlay({
|
// 将查到的h3hexagon数据转为geojson
|
||||||
layers: [
|
const polygonSourceData = _this.hexagonDataConverter(hexagonData)
|
||||||
new H3HexagonLayer({
|
map.addSource('hexGrid', {
|
||||||
id: 'hex',
|
type: 'geojson',
|
||||||
data: hexagonData,
|
data: polygonSourceData
|
||||||
elevationScale: 20,
|
|
||||||
extruded: false,
|
|
||||||
filled: true,
|
|
||||||
getFillColor: d => _this.getHexagonFillColor(d.number),
|
|
||||||
getLineColor: d => _this.getHexagonLineColor(d.number),
|
|
||||||
getLineWidth: 10,
|
|
||||||
getHexagon: d => d.hexId,
|
|
||||||
wireframe: false,
|
|
||||||
pickable: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
map.addControl(hexagon)
|
// TODO 六边形边框,考虑加一层line layer
|
||||||
|
map.addLayer({
|
||||||
|
id: 'hexagon',
|
||||||
|
type: 'fill',
|
||||||
|
source: 'hexGrid',
|
||||||
|
layout: {},
|
||||||
|
paint: {
|
||||||
|
'fill-color': ['get', 'color'],
|
||||||
|
'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 1, 0.6]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 六边形的鼠标事件
|
||||||
|
_this.bindHexagonEvents()
|
||||||
|
|
||||||
// 地图上的基站
|
/* 地图上的基站 */
|
||||||
|
const baseStationData = await _this.queryBaseStation()
|
||||||
|
/* 地图上的人 */
|
||||||
|
|
||||||
// 地图上的人
|
/* 右侧关注列表 */
|
||||||
|
|
||||||
// 右侧关注列表
|
/* 右上角折线图 */
|
||||||
|
|
||||||
// 右上角折线图
|
|
||||||
_this.renderActiveSubscribersLine()
|
_this.renderActiveSubscribersLine()
|
||||||
})
|
})
|
||||||
function hoverTrigger (source, id, hover) {
|
|
||||||
map.setFeatureState({ source, id }, { hover })
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async renderDensityPie () {
|
async renderDensityPie () {
|
||||||
const params = {
|
const params = {
|
||||||
@@ -349,6 +343,19 @@ export default {
|
|||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
},
|
},
|
||||||
|
async queryBaseStation () {
|
||||||
|
this.loading.baseStationLoading = true
|
||||||
|
try {
|
||||||
|
const response = await axios.get(api.location.baseStation)
|
||||||
|
return response.data.data.list
|
||||||
|
} catch (e) {
|
||||||
|
this.errorMsgHandler(e)
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
this.loading.hexagonLoading = false
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
// 先使用min=0的等宽分组法,若后续出现特大或特小的异常值导致等宽分组效果不理想,考虑用分位数分组法
|
// 先使用min=0的等宽分组法,若后续出现特大或特小的异常值导致等宽分组效果不理想,考虑用分位数分组法
|
||||||
calculateValueRamp (data) {
|
calculateValueRamp (data) {
|
||||||
const max = _.maxBy(data, d => d.number)
|
const max = _.maxBy(data, d => d.number)
|
||||||
@@ -366,6 +373,27 @@ export default {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
},
|
},
|
||||||
|
hexagonDataConverter (data) {
|
||||||
|
const features = data.map((d, i) => ({
|
||||||
|
id: i + 1,
|
||||||
|
type: 'Feature',
|
||||||
|
geometry: {
|
||||||
|
type: 'Polygon',
|
||||||
|
coordinates: [
|
||||||
|
h3ToGeoBoundary(d.hexId, true)
|
||||||
|
]
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
hexId: d.hexId,
|
||||||
|
number: d.number,
|
||||||
|
color: this.getHexagonFillColor(d.number)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
return {
|
||||||
|
type: 'FeatureCollection',
|
||||||
|
features: features
|
||||||
|
}
|
||||||
|
},
|
||||||
getHexagonFillColor (number) {
|
getHexagonFillColor (number) {
|
||||||
const ramp = this.pieValueRamp.filter(r => number >= r.start && number < r.end)
|
const ramp = this.pieValueRamp.filter(r => number >= r.start && number < r.end)
|
||||||
if (ramp.length > 0) {
|
if (ramp.length > 0) {
|
||||||
@@ -373,12 +401,37 @@ export default {
|
|||||||
}
|
}
|
||||||
return [255, 255, 255]
|
return [255, 255, 255]
|
||||||
},
|
},
|
||||||
getHexagonLineColor (number) {
|
bindHexagonEvents () {
|
||||||
const ramp = this.pieValueRamp.filter(r => number >= r.start && number < r.end)
|
const _this = this
|
||||||
if (ramp.length > 0) {
|
this.mapChart.on('mouseenter', 'hexagon', () => {
|
||||||
return ramp[0].color.split(',').map(n => n / 1.5)
|
_this.tooltip.mouseIsInPolygon = true
|
||||||
|
})
|
||||||
|
this.mapChart.on('mouseleave', 'hexagon', () => {
|
||||||
|
_this.tooltip.showPolygonTooltip = false
|
||||||
|
_this.tooltip.mouseIsInPolygon = false
|
||||||
|
// 去掉上一块的高亮
|
||||||
|
hoverTrigger('hexGrid', _this.currentPolygon.id, false)
|
||||||
|
})
|
||||||
|
this.mapChart.on('mousemove', 'hexagon', ({ point, originalEvent, features }) => {
|
||||||
|
if (!_this.tooltip.mouseIsInMarker) {
|
||||||
|
_this.tooltip.showPolygonTooltip = true
|
||||||
|
_this.tooltip.type = _this.tooltipType.hexagon
|
||||||
|
if (_this.tooltip.type === _this.tooltipType.hexagon && _this.currentPolygon.id && _this.currentPolygon.id !== features[0].id) {
|
||||||
|
// 去掉上一块的高亮
|
||||||
|
hoverTrigger('hexGrid', _this.currentPolygon.id, false)
|
||||||
|
}
|
||||||
|
_this.currentPolygon = features[0].properties
|
||||||
|
_this.currentPolygon.id = features[0].id
|
||||||
|
_this.tooltip.x = originalEvent.clientX + 15
|
||||||
|
_this.tooltip.y = originalEvent.clientY + 5
|
||||||
|
|
||||||
|
// 鼠标滑过高亮
|
||||||
|
hoverTrigger('hexGrid', _this.currentPolygon.id, true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
function hoverTrigger (source, id, hover) {
|
||||||
|
_this.mapChart.setFeatureState({ source, id }, { hover })
|
||||||
}
|
}
|
||||||
return [255, 255, 255]
|
|
||||||
},
|
},
|
||||||
tooltipMouseEnter () {
|
tooltipMouseEnter () {
|
||||||
},
|
},
|
||||||
@@ -418,7 +471,10 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
tooltipHeaderColor () {
|
tooltipHeaderColor () {
|
||||||
if (this.tooltip.type === this.tooltipType.hexagon) {
|
if (this.tooltip.type === this.tooltipType.hexagon) {
|
||||||
return `rgba(${this.pieColorRamp[this.currentPolygon.level]},.8)`
|
const color = this.currentPolygon.color.split(',')
|
||||||
|
color[0] = color[0].split('[')[1]
|
||||||
|
color[2] = color[2].split(']')[0]
|
||||||
|
return `rgba(${color.join(',')},.8)`
|
||||||
} else if (this.tooltip.type === this.tooltipType.human) {
|
} else if (this.tooltip.type === this.tooltipType.human) {
|
||||||
return '#38ACD2'
|
return '#38ACD2'
|
||||||
} else if (this.tooltip.type === this.tooltipType.baseStation) {
|
} else if (this.tooltip.type === this.tooltipType.baseStation) {
|
||||||
@@ -469,7 +525,8 @@ export default {
|
|||||||
type: ''
|
type: ''
|
||||||
})
|
})
|
||||||
// const pieColorRamp = ['186,224,255', '105,177,255', '22,119,255', '0,62,179', '0,29,102']
|
// const pieColorRamp = ['186,224,255', '105,177,255', '22,119,255', '0,62,179', '0,29,102']
|
||||||
const pieColorRamp = ['156,174,29', '241,198,0', '89,202,242', '63,133,186', '37,55,128']
|
// const pieColorRamp = ['156,174,29', '241,198,0', '89,202,242', '63,133,186', '37,55,128']
|
||||||
|
const pieColorRamp = ['196,214,59', '190,230,255', '135,206,250', '63,133,186', '37,55,128']
|
||||||
const pieValueRamp = ref([])
|
const pieValueRamp = ref([])
|
||||||
const boundaryBox = ref({}) // minLongitude、maxLongitude、minLatitude、maxLatitude
|
const boundaryBox = ref({}) // minLongitude、maxLongitude、minLatitude、maxLatitude
|
||||||
const mapChart = shallowRef({})
|
const mapChart = shallowRef({})
|
||||||
@@ -534,6 +591,9 @@ export default {
|
|||||||
padding: 0 20px 20px;
|
padding: 0 20px 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.maplibregl-canvas:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
.geo-tools {
|
.geo-tools {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user