This repository has been archived on 2025-09-14. You can view files and clone it, but cannot push or open issues or pull requests.
Files
cyber-narrator-cn-ui/src/views/location/Index.vue

2654 lines
134 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="geo-analysis">
<el-tabs v-model="activeTab" class="location-tabs">
<el-tab-pane :label="$t('location.locationMap')" name="locationMap"></el-tab-pane>
<el-tab-pane :label="$t('location.traceTracking')" name="traceTracking">
<template #label>
<div class="traceTracking-tabs_label">
{{$t('location.traceTracking')}}
<div class="traceTracking-num" :style="`opacity:${opacity};`">
{{trackingSubscribers.length}}
</div>
</div>
</template>
</el-tab-pane>
</el-tabs>
<!-- 右上角工具栏 -->
<div class="geo-tools">
<div class="panel__time">
<date-time-range
class="date-time-range"
:start-time="timeFilter.startTime"
:end-time="timeFilter.endTime"
:date-range="timeFilter.dateRangeValue"
ref="dateTimeRange"
@change="reload"
/>
<time-refresh
class="date-time-range"
@change="timeRefreshChange"
:end-time="timeFilter.endTime"
/>
<div class="divider"><el-divider direction="vertical"></el-divider></div>
<el-button class="follow" @click="add" ><i class="cn-icon cn-icon-follow"></i></el-button>
</div>
</div>
<div class="geo-analysis__container">
<!-- 左侧地图 -->
<div class="analysis-map">
<simple-loading size="small" placement="top-end" :loading="loading.mapLoading" v-if="activeTab === 'locationMap'" :style="rightExpanded ? 'right: 332px;' : ''"></simple-loading>
<simple-loading size="small" placement="top-end" :loading="loading.trackingMapLoading" v-else-if="activeTab === 'traceTracking'"></simple-loading>
<div id="analysisMap"></div>
</div>
<!-- locationMap地图底部的时间轴 -->
<div class="map-time-line">
<time-line v-if="activeTab === 'locationMap'" :timeFilter="timeFilter" @change="mapTimeLineChange"></time-line>
</div>
<!-- 左右侧控制箭头 -->
<el-icon
class="analysis-statistics-arrow analysis-statistics-arrow--left"
:class="leftExpanded ? 'analysis-statistics-arrow--left-expanded' : ''"
@click="leftExpanded = !leftExpanded"
v-show="activeTab === 'locationMap'"
>
<ArrowLeft v-if="leftExpanded"/>
<ArrowRight v-else />
</el-icon>
<el-icon
class="analysis-statistics-arrow analysis-statistics-arrow--right"
:class="rightExpanded ? 'analysis-statistics-arrow--right-expanded' : ''"
@click="rightExpanded = !rightExpanded"
v-show="activeTab === 'locationMap'"
>
<ArrowRight v-if="rightExpanded"/>
<ArrowLeft v-else />
</el-icon>
<!-- 左侧统计栏 -->
<div class="analysis-statistics analysis-statistics--left-drawer"
:class="`analysis-statistics--${leftExpanded ? 'expanded' : 'collapsed'}`"
v-show="activeTab === 'locationMap'">
<!-- 饼图-地图色块统计 -->
<div class="analysis-statistics__chart">
<simple-loading size="small" placement="top-end" :loading="loading.pieLoading"></simple-loading>
<div class="chart__header">{{$t('location.populationDensity')}}</div>
<div class="chart__body">
<div class="chart__drawing" id="populationDensityChart"></div>
<div class="chart__legend">
<div v-for="legend in pieValueRamp" class="legend-item" :key="legend.color">
<div class="legend-icon" :style="`background:rgba(${legend.color},.7);`"></div>
<div class="legend-range" >{{legend.name}}</div>
<div class="legend-count">{{legend.count}}</div>
</div>
</div>
</div>
</div>
<!-- 折线图-活跃subscriber -->
<div class="analysis-statistics__chart">
<simple-loading size="small" placement="top-end" :loading="loading.lineLoading"></simple-loading>
<div class="chart__header">{{$t('location.activeSubscribers')}}</div>
<div class="chart__statistics">
<div class="statistics-number">{{activeCount}}</div>
<div class="statistics-trend">{{activeCountChain === '-' ? '-' : (activeCountChain < 0 ? '-'+valueToRangeValue(Math.abs(activeCountChain), unitTypes.percent).join(' '): valueToRangeValue(activeCountChain, unitTypes.percent).join(' '))}}</div>
</div>
<div class="chart-line__drawing" id="activeSubscribersChart"></div>
</div>
<!-- 单值图-基站数量 -->
<div class="analysis-statistics__chart">
<simple-loading size="small" placement="top-end" :loading="loading.baseStationLoading"></simple-loading>
<div class="chart__header">{{$t('location.totalBaseStation')}}</div>
<div class="chart__value">{{baseStationMarkers.length}}</div>
</div>
</div>
<!-- 右侧数据栏-map -->
<div class="analysis-statistics analysis-statistics--right-drawer"
:class="`analysis-statistics--${rightExpanded ? 'expanded' : 'collapsed'}`"
v-show="activeTab === 'locationMap'"
>
<!-- subscriber list -->
<div class="analysis-statistics__title">
<div class="title__content">{{$t('location.subscribers')}}:&nbsp;{{subscribersTotalCount}}<!--<div class="title__icon">{{subscribersTotalCount}}</div>--></div>
<simple-loading :loading="loading.subscriberLoading" placement="right" size="small"></simple-loading>
</div>
<div class="analysis-statistics__search-list">
<template v-for="search in searchList">
<template v-if="search.active === 1">
<el-input v-if="search.type === 'input'"
:key="search.label"
size="small"
v-model="search.value"
@input="debounceSearch"
clearable
>
<template #prepend>{{search.label}}</template>
</el-input>
<el-input v-if="search.type === 'select'"
class="input-to-select"
:key="search.label"
size="small"
>
<template #prepend>{{search.label}}</template>
<template #append>
<el-select
size="small"
v-model="search.value"
style="width: 100%"
:placeholder="$t('overall.all')"
>
<el-option
v-for="option in search.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-input>
</template>
</template>
<div style="display: flex; justify-content: space-between">
<el-dropdown trigger="click" :hide-on-click="false" ref="searchItemList">
<button class="business-button business-button--light">{{$t('overall.more')}}</button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="search in searchList"
:key="search.label"
>
<el-checkbox
v-model="search.active"
:true-value="1"
:false-value="0"
size="small"
:label="search.label"/>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-checkbox style="margin-right: 15px;" v-model="onlyShowFollowed" @change="showFollowedSubscribers" :label="$t('location.onlyFollowed')" size="small" />
</div>
</div>
<div class="analysis-statistics__subscribers" @scroll="scrollList" id="locationMap-subscriber-scroll" :style="computeListHeight">
<template v-for="item in subscribersList" :key="item.subscriberId">
<div class="analysis-statistics__subscriber"
@click="subscriberListClick(item)"
@mouseenter="subscriberListMouseEnter(item)"
@mouseleave="subscriberListMouseLeave(item)"
:id="`locationMap-subscriberId-${item.subscriberId}`"
:class="highlightSubscriber.subscriberId === item.subscriberId ? 'analysis-statistics__subscriber--active' : ''"
>
<div class="subscriber__header" :class="item.active === 1 ? '' : 'subscriber__header-inactive'" >
<div class="header__icon">
<div class="icon__box">
<svg v-if="item.dataSource.indexOf('Session Record') > -1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M244.784762 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L244.784762 690.468571zM81.92 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571428 8.435809l-99.181715-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634285 193.487238 88.405333 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182857 27.160381c-5.851429 2.048-12.092952 3.120762-18.383239 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L243.712 623.177143a84.894476 84.894476 0 0 1-24.868571-80.700953l34.523428-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L168.228571 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C298.179048 58.465524 351.963429 17.066667 411.745524 23.30819 471.478857 29.549714 514.633143 81.13981 508.099048 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z" fill="#FFFFFF"></path><path d="M734.793143 532.918857l-27.89181-13.897143A308.175238 308.175238 0 0 1 536.380952 243.029333v-73.142857a32.621714 32.621714 0 0 1 18.188191-29.403428l172.958476-86.552381a49.347048 49.347048 0 0 1 43.885714 0l172.958477 86.552381a32.670476 32.670476 0 0 1 18.236952 29.403428v73.142857a308.272762 308.272762 0 0 1-170.520381 275.992381l-27.89181 13.897143a33.304381 33.304381 0 0 1-29.403428 0z" fill="#FA901C"></path><path d="M722.456381 414.232381a27.014095 27.014095 0 1 0 54.02819 0 27.014095 27.014095 0 0 0-54.02819 0zM722.456381 181.735619v151.405714a24.771048 24.771048 0 0 0 27.014095 25.209905 24.771048 24.771048 0 0 0 27.014095-25.209905V181.735619a24.771048 24.771048 0 0 0-27.014095-25.258667 24.771048 24.771048 0 0 0-27.014095 25.258667z" fill="#FFFFFF"></path></svg>
<svg v-else viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>
</div>
</div>
<div class="header__right">
<div class="header-msisdn">
<div class="header__title">
<span>ID</span>
<el-tooltip
effect="light"
trigger="hover"
:content="$t('entity.jumpToEntityDetails')"
placement="right"
popper-class="panel-tooltip"
>
<i class="cn-icon cn-icon-jump-to" @click="jumpEntityDetail(item)" v-show="item.showJumpToEntity"></i>
</el-tooltip>
</div>
<div class="header__content">{{$_.get(item, 'subscriberId', '-') || '-'}}</div>
</div>
<div class="header__operation">
<div class="trajectory-text" @click.stop="addOrRemoveTrackingSubscriber(item)">
<i class="cn-icon" :class="symbolClass(item)"></i>{{$t('location.track')}}
</div>
<div class="cancel-follow">
<i class="cn-icon-follow-fill cn-icon" v-if="item.isFollowed === 1" @click.stop="cancelFollowSubscribers(item)"></i>
<i class="cn-icon-follow cn-icon" v-else @click.stop="followSubscribers(item)"></i>
</div>
</div>
</div>
</div>
<div class="subscriber__body">
<span class="subscriber-tags">
<span v-for="tag in item.tags"
:key="tag.value"
class="subscriber-tag"
:style="getTagColor(tag.color)">
{{ tag.value }}
</span>
</span>
<div class="body__item">
<div class="item__label">MSISDN</div>
<div class="item__value">{{item.phoneNumber || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMEI</div>
<div class="item__value">{{item.imei || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMSI</div>
<div class="item__value">{{item.imsi || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">APN</div>
<div class="item__value">{{item.apn || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('location.location')}}</div>
<div class="item__value">{{locationHandler(item)}}</div>
</div>
</div>
</div>
</template>
<div style="height: 40px;"></div>
</div>
<div style="position: absolute; bottom: 0; height: 40px; width: 100%; background-color: rgba(255,255,255,.8);"></div>
</div>
<!-- 右侧数据栏-trace -->
<div class="analysis-statistics" :style="activeTab === 'traceTracking' ? 'overflow-y: scroll' : ''" id="subscribersBlock" v-show="activeTab === 'traceTracking'">
<div class="analysis-statistics__no-tracking-tip" v-if="trackingSubscribers.length === 0" @click="activeTab = 'locationMap'">{{$t('location.noTrackingYet')}}</div>
<div v-else class="analysis-statistics__search-list">
<el-input class="input-to-select"
size="small"
style="margin-bottom: 0;"
>
<template #prepend>{{searchList[3].label}}</template>
<template #append>
<el-select
size="small"
v-model="searchList[3].value"
style="width: 100%"
:placeholder="$t('overall.all')"
@change="trackingSourceChange"
>
<el-option
v-for="option in searchList[3].options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
</template>
</el-input>
</div>
<div class="analysis-statistics__subscribers">
<template v-for="(subscriber, index) in trackingSubscribers" :key="subscriber.subscriberId">
<div
class="analysis-statistics__subscriber"
:class="currentShowSubscriber && currentShowSubscriber.subscriberId === subscriber.subscriberId ? 'analysis-statistics__subscriber--active' : ''"
@mouseenter="subscriber.showJumpToEntity = true"
@mouseleave="subscriber.showJumpToEntity = false"
@click="changeCurrentShowSubscriber(subscriber)">
<div class="subscriber__header" :class="trackingSubscriberRecordMap[subscriber.subscriberId] && trackingSubscriberRecordMap[subscriber.subscriberId].length > 0 ? '' : 'subscriber__header-inactive'">
<div class="header__icon">
<div class="icon__box">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>
</div>
</div>
<div class="header__right">
<div class="header-msisdn">
<div class="header__title">
<span>ID</span>
<el-tooltip
effect="light"
trigger="hover"
:content="$t('entity.jumpToEntityDetails')"
placement="right"
popper-class="panel-tooltip"
>
<i class="cn-icon cn-icon-jump-to" @click="jumpEntityDetail(subscriber)" v-show="subscriber.showJumpToEntity"></i>
</el-tooltip>
</div>
<div class="header__content">{{$_.get(subscriber, 'subscriberId', '-') || '-'}}</div>
</div>
<div class="header__operation">
<div class="cancel-follow" @click.stop="removeTrackingSubscriber(subscriber)">
<i class="cn-icon-close cn-icon"></i>
</div>
</div>
</div>
</div>
<div class="subscriber__body">
<span class="subscriber-tags">
<span v-for="tag in subscriber.tags"
:key="tag.value"
class="subscriber-tag"
:style="getTagColor(tag.color)">
{{ tag.value }}
</span>
</span>
<div class="body__item">
<div class="item__label">MSISDN</div>
<div class="item__value">{{subscriber.phoneNumber || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMEI</div>
<div class="item__value">{{subscriber.imei || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMSI</div>
<div class="item__value">{{subscriber.imsi || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">APN</div>
<div class="item__value">{{subscriber.apn || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.location')}}</div>
<div class="item__value">{{locationHandler(subscriber)}}</div>
</div>
<div class="body-item-record">
<div class="item-record__header">{{ $t('location.trackRecord') }}</div>
<template v-if="trackingSubscriberRecordMap[subscriber.subscriberId] && trackingSubscriberRecordMap[subscriber.subscriberId].length > 0">
<div class="item-record__info">
<div class="circle"></div>
</div>
<div class="item-record__timeline">
<div class="timeline__info">
<div class="info-icons">
<div class="icon-circle">
<i class="cn-icon cn-icon-events2" v-if="trackingSubscriberRecordMap[subscriber.subscriberId].dataSource === 'Session Record'"></i>
<div class="circle-circle"></div>
</div>
<div class="info-line" v-show="subscriber.showLine"></div>
</div>
<div class="timeline__info--item" @click="timelineClick(subscriber, trackingSubscriberRecordMap[subscriber.subscriberId][0])" @mouseenter="timelineMouseEnter(subscriber, trackingSubscriberRecordMap[subscriber.subscriberId][0])" @mouseleave="timelineMouseLeave(subscriber, trackingSubscriberRecordMap[subscriber.subscriberId][0])">
<div>
<span>{{$t('overall.location')}}: </span><span class="info--item__value">{{locationHandler(trackingSubscriberRecordMap[subscriber.subscriberId][0])}}</span>
</div>
<div>
<span>{{ $t('location.timeOfArrival') }}: </span><span class="info--item__value">{{dateFormatByAppearance(Number(trackingSubscriberRecordMap[subscriber.subscriberId][0].time))}}</span>
</div>
<div>
<span>{{ $t('location.residenceTime') }}: </span><span class="info--item__value">-</span>
</div>
</div>
</div>
<div class="scroll-view" @scroll="onScroll" :id="subscriber.subscriberId" v-show="subscriber.show">
<!-- 虚拟列表 -->
<div class="virtual-scroller" :style="`height: ${subscriber.listHeight}px`"></div>
<div class="scroll-list" :style="`transform: translateY(${subscriber.startOffset}px)`">
<div class="scroll__item"
v-for="(record, index) in trackingSubscriberRecordMap[subscriber.subscriberId].slice(subscriber.scrollStartIndex, subscriber.scrollEndIndex)"
:key="index"
@click="timelineClick(subscriber, record)"
@mouseenter="timelineMouseEnter(subscriber, record)"
@mouseleave="timelineMouseLeave(subscriber, record)">
<div class="item-icons">
<div class="icon-circle">
<i class="cn-icon cn-icon-events2" v-if="record.dataSource === 'Session Record'"></i>
<div class="circle-circle" :class="record.dataSource === highlightTrackingTimeline.dataSource && record.time === highlightTrackingTimeline.time ? 'circle-circle__highlight' : ''"></div>
</div>
<div class="item-line"></div>
</div>
<div class="item-content">
<div style="width: 217px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden">
<span>{{$t('overall.location')}}: </span><span class="item__value" :title="locationHandler(record)">{{locationHandler(record)}}</span>
</div>
<div>
<span>{{ $t('location.timeOfArrival') }}: </span><span class="item__value">{{dateFormatByAppearance(Number(record.time))}}</span>
</div>
<div>
<span>{{ $t('location.residenceTime') }}: </span><span class="item__value">{{record.stayTime}}</span>
</div>
</div>
</div>
</div>
</div>
<div :class="trackingSubscriberRecordMap[subscriber.subscriberId].length === 1 ? 'item-record__btn-disabled' : 'item-record__btn'" @click.stop="clickTrackBlock(index)">
<span v-if="!subscriber.show"><i class="cn-icon cn-icon-down2" :style="trackingSubscriberRecordMap[subscriber.subscriberId].length === 1 ? 'color: var(var(--el-color-info-light-5))' : ''"></i></span>
<span v-if="subscriber.show"><i class="cn-icon cn-icon-up2" :style="trackingSubscriberRecordMap[subscriber.subscriberId].length === 1 ? 'color: var(var(--el-color-info-light-5)' : ''"></i></span>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
<!-- tooltip -->
<div class="geo-analysis__hexagon-tooltip" id="tooltip" :class="`geo-analysis__hexagon-tooltip--${tooltip.type}`" v-if="tooltip.showMarkerTooltip || tooltip.showPolygonTooltip" :style="{'left': `${tooltip.x}px`, 'top': `${tooltip.y}px`}" @mouseenter="tooltipMouseEnter" @mouseleave="tooltipMouseLeave">
<div class="hexagon-tooltip__header" :style="`background-color: ${tooltipHeaderColor}`">
<div class="header__icon">
<svg v-if="tooltip.type === tooltipType.hexagon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="36" height="36"><path d="M747.52 921.088H277.504L42.496 514.048l235.008-407.04H747.52l235.008 407.04-235.008 407.04z m-425.472-76.8h381.44l190.464-330.24-190.464-330.24h-381.44l-190.464 330.24 190.464 330.24z"></path></svg>
<template v-else-if="tooltip.type === tooltipType.human">
<div class="icon__box">
<svg v-if="currentSubscriber.isSessionRecord" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M244.784762 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L244.784762 690.468571zM81.92 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571428 8.435809l-99.181715-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634285 193.487238 88.405333 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182857 27.160381c-5.851429 2.048-12.092952 3.120762-18.383239 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L243.712 623.177143a84.894476 84.894476 0 0 1-24.868571-80.700953l34.523428-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L168.228571 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C298.179048 58.465524 351.963429 17.066667 411.745524 23.30819 471.478857 29.549714 514.633143 81.13981 508.099048 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z" fill="#FFFFFF"></path><path d="M734.793143 532.918857l-27.89181-13.897143A308.175238 308.175238 0 0 1 536.380952 243.029333v-73.142857a32.621714 32.621714 0 0 1 18.188191-29.403428l172.958476-86.552381a49.347048 49.347048 0 0 1 43.885714 0l172.958477 86.552381a32.670476 32.670476 0 0 1 18.236952 29.403428v73.142857a308.272762 308.272762 0 0 1-170.520381 275.992381l-27.89181 13.897143a33.304381 33.304381 0 0 1-29.403428 0z" fill="#FA901C"></path><path d="M722.456381 414.232381a27.014095 27.014095 0 1 0 54.02819 0 27.014095 27.014095 0 0 0-54.02819 0zM722.456381 181.735619v151.405714a24.771048 24.771048 0 0 0 27.014095 25.209905 24.771048 24.771048 0 0 0 27.014095-25.209905V181.735619a24.771048 24.771048 0 0 0-27.014095-25.258667 24.771048 24.771048 0 0 0-27.014095 25.258667z" fill="#FFFFFF"></path></svg>
<svg v-else viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>
</div>
</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">
<div class="icon__box">
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M952.182154 904.113231h-99.800616c-0.118154 0-232.093538-570.683077-232.093538-570.683077 49.427692-31.192615 79.832615-84.361846 79.832615-142.926769A171.047385 171.047385 0 0 0 529.545846 19.692308a170.417231 170.417231 0 0 0-170.574769 170.259692c0 55.768615 26.860308 106.535385 72.231385 138.633846L188.278154 904.113231H107.657846c-29.026462 0-52.499692 21.070769-52.499692 50.097231 0 28.987077 23.512615 50.097231 52.499692 50.09723h844.563692c28.987077 0 52.460308-21.110154 52.460308-50.136615 0-28.987077-23.512615-50.057846-52.460308-50.057846h-0.039384zM524.544 392.664615l33.161846 85.858462h-66.441846l33.28-85.858462z m-91.451077 235.992616h182.744615l38.872616 100.155077H394.259692l38.833231-100.155077z m-106.653538 275.456l29.144615-75.106462h333.351385l28.908307 75.106462H326.4h0.039385z"></path></svg>
</div>
</template>
<template v-else-if="tooltip.type === tooltipType.cell">
<div class="icon__box">
<svg viewBox="0 0 1365 1024" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M186.847004 720.554667a559.104 559.104 0 0 1-43.121777-217.656889c0-75.093333 14.336-150.186667 43.235555-217.656889 21.504-67.584 64.625778-127.658667 114.915556-180.110222L201.183004 0A711.793778 711.793778 0 0 0 50.313671 232.675556 719.303111 719.303111 0 0 0 0.023893 502.897778c0 90.112 14.336 187.733333 50.289778 270.222222 35.953778 82.602667 86.243556 165.205333 150.869333 232.675556l100.693334-105.016889c-50.289778-52.565333-93.411556-112.64-114.915556-180.224z"></path><path d="M316.212338 660.593778c21.617778 52.451556 50.289778 97.507556 86.243555 135.054222l100.579556-105.130667a333.027556 333.027556 0 0 1-57.457778-82.488889c-7.168-37.546667-14.449778-67.584-14.449778-105.130666 0-37.546667 7.281778-67.584 21.617778-97.507556 7.168-37.546667 28.785778-60.074667 50.289778-90.112l-100.579556-105.130666c-35.953778 37.546667-64.739556 82.602667-86.243555 135.168a407.324444 407.324444 0 0 0-28.785778 157.582222c0 52.565333 7.168 105.130667 28.785778 157.696zM574.943004 502.897778c0 62.236444 48.241778 112.64 107.747556 112.64 59.505778 0 107.747556-50.403556 107.747556-112.64 0-62.122667-48.241778-112.64-107.747556-112.64-59.505778 0-107.747556 50.517333-107.747556 112.64zM862.345671 690.631111l100.579556 105.016889c35.953778-37.546667 64.739556-82.602667 86.243555-135.054222a407.324444 407.324444 0 0 0 28.785778-157.696 407.324444 407.324444 0 0 0-28.785778-157.582222 427.690667 427.690667 0 0 0-86.243555-135.168l-100.579556 105.130666c21.617778 22.528 43.121778 52.565333 57.457778 82.602667 14.449778 29.923556 21.617778 67.470222 21.617778 97.507555 0 37.546667-7.168 67.584-21.617778 97.621334-14.336 45.056-35.953778 67.584-57.457778 97.507555z"></path><path d="M1315.067449 232.675556A711.793778 711.793778 0 0 0 1164.198116 0l-100.693334 105.130667c50.289778 52.451556 86.243556 112.64 114.915556 180.110222 28.899556 67.584 43.235556 142.563556 43.235555 217.656889 0 75.093333-14.336 150.186667-43.235555 217.656889-28.672 67.584-64.625778 127.658667-114.915556 180.224l100.693334 105.016889a711.793778 711.793778 0 0 0 150.869333-232.675556A719.303111 719.303111 0 0 0 1365.357227 502.897778c0-90.112-14.336-187.733333-50.289778-270.222222z"></path></svg>
</div>
</template>
</div>
<div class="header__title">
<template v-if="tooltip.type === tooltipType.hexagon">HEX</template>
<template v-else-if="tooltip.type === tooltipType.human">ID</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">NodeB ID</template>
<template v-else-if="tooltip.type === tooltipType.cell">ID</template>
</div>
<div class="header__content">
<template v-if="tooltip.type === tooltipType.hexagon">{{currentPolygon.hexId}}</template>
<template v-else-if="tooltip.type === tooltipType.human">{{currentSubscriber.subscriberId || '-'}}</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">{{currentBaseStation.nodebId}}</template>
<template v-else-if="tooltip.type === tooltipType.cell">{{currentCell.id}}</template>
</div>
</div>
<div class="hexagon-tooltip__body">
<span class="subscriber-tags" v-if="tooltip.type === tooltipType.human">
<span v-for="tag in currentSubscriber.tags"
:key="tag.value"
class="subscriber-tag"
:style="getTagColor(tag.color)">
{{ tag.value }}
</span>
</span>
<template v-if="tooltip.type === tooltipType.hexagon">
<template v-if="activeTab === 'locationMap'">
<div class="body__item">
<div class="item__label">{{ $t('location.number') }}</div>
<div class="item__value">{{currentPolygon.number}}</div>
</div>
</template>
<template v-else>
<span class="body__header">{{locationHandler(JSON.parse(currentPolygon.locations)[0])}}</span>
<template v-for="(item, i) in JSON.parse(currentPolygon.locations)" :key="item.hexId">
<div class="body__timeline" v-if="i < 5">
<div class="timeline-symbol"></div>
<div>
<div class="body__item">
<div class="item__value">{{dateFormatByAppearance(Number(item.time))}}</div>
</div>
</div>
</div>
</template>
<div class="body__timeline" v-if="JSON.parse(currentPolygon.locations).length > 5">...</div>
</template>
</template>
<template v-else-if="tooltip.type === tooltipType.human">
<div class="body__item">
<div class="item__label">MSISDN</div>
<div class="item__value">{{currentSubscriber.phoneNumber || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMEI</div>
<div class="item__value">{{currentSubscriber.imei || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">IMSI</div>
<div class="item__value">{{currentSubscriber.imsi || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">APN</div>
<div class="item__value">{{currentSubscriber.apn || '-'}}</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.location')}}</div>
<div class="item__value">{{locationHandler(currentSubscriber)}}</div>
</div>
<div class="body__tracking" @click="trackSubscriber(currentSubscriber)">{{$t('location.traceTracking')}}</div>
</template>
<template v-else-if="tooltip.type === tooltipType.baseStation">
<div class="body__item" v-if="currentBaseStation.operator">
<div class="item__label">{{$t('location.operator')}}</div>
<div class="item__value">{{currentBaseStation.operator}}</div>
</div>
<div class="body__item" v-if="currentBaseStation.lacTac">
<div class="item__label">LAC/TAC</div>
<div class="item__value">{{currentBaseStation.lacTac}}</div>
</div>
<div class="body__item" v-if="currentBaseStation.mcc">
<div class="item__label">MCC</div>
<div class="item__value">{{currentBaseStation.mcc}}</div>
</div>
<div class="body__item" v-if="currentBaseStation.mnc">
<div class="item__label">MNC</div>
<div class="item__value">{{currentBaseStation.mnc}}</div>
</div>
<div class="body__item" v-if="currentBaseStation.technology">
<div class="item__label">{{$t('overall.technology')}}</div>
<div class="item__value">{{currentBaseStation.technology}}</div>
</div>
<div class="body__item">
<div class="item__label">{{$t('overall.location')}}</div>
<div class="item__value">{{locationHandler(currentBaseStation)}}</div>
</div>
</template>
<template v-else-if="tooltip.type === tooltipType.cell">
<div class="body__item" v-if="currentCell.cellId">
<div class="item__label">Cell ID</div>
<div class="item__value">{{currentCell.cellId}}</div>
</div>
<div class="body__item" v-if="currentCell.operator">
<div class="item__label">{{$t('location.operator')}}</div>
<div class="item__value">{{currentCell.operator}}</div>
</div>
<div class="body__item" v-if="currentCell.azimuth">
<div class="item__label">{{$t('location.azimuth')}}</div>
<div class="item__value">{{currentCell.azimuth}}</div>
</div>
<div class="body__item" v-if="currentCell.coverageRadius">
<div class="item__label">{{$t('location.coverageRadius')}}</div>
<div class="item__value">{{currentCell.coverageRadius}}m</div>
</div>
<div class="body__item" v-if="currentCell.vendor">
<div class="item__label">{{$t('overall.vendor')}}</div>
<div class="item__value">{{currentCell.vendor}}</div>
</div>
<div class="body__item" v-if="currentCell.fddSpectrum">
<div class="item__label">{{$t('location.fddSpectrum')}}</div>
<div class="item__value">{{currentCell.fddSpectrum}}</div>
</div>
</template>
</div>
</div>
<el-drawer
v-model="rightBox.show"
direction="rtl"
class="common-right-box"
:size="700"
:with-header="false"
destroy-on-close
>
<my-follow-box :timeFilter="timeFilter" :level="mapLevel" @close="closeRightBox" @handleFollow="handleFollow" @handleCancelFollowBatch="handleCancelFollowBatch"/>
</el-drawer>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue'
import { dateFormatByAppearance, getNowTime, getSecond } from '@/utils/date-util'
import maplibregl from 'maplibre-gl'
import mapStyle from '@/views/charts2/charts/entityDetail/mapStyle'
import 'maplibre-gl/dist/maplibre-gl.css'
import unitConvert, { valueToRangeValue } from '@/utils/unit-convert'
import {
unitTypes,
storageKey,
defaultMapConfig,
mapLevelField,
intentColor,
entityDefaultColor
} from '@/utils/constants'
import * as echarts from 'echarts'
import { appListChartOption } from '@/views/charts2/charts/options/echartOption'
import { pieOption } from '@/views/location/chartOption'
import _ from 'lodash'
import { useRoute, useRouter } from 'vue-router'
import { urlParamsHandler, overwriteUrl, getTagColor } from '@/utils/tools'
import axios from 'axios'
import { api } from '@/utils/api'
import { h3ToGeo, h3ToGeoBoundary } from 'h3-js'
import TimeLine from '@/components/common/TimeLine.vue'
import SimpleLoading from '@/components/common/SimpleLoading.vue'
import MyFollowBox from '@/components/rightBox/location/MyFollowBox'
import dataListMixin from '@/mixins/data-list'
import * as turf from '@turf/turf'
import { generateRectanglePolygon } from '@/utils/geo-utils'
import i18n from '@/i18n'
// import testData from './test'
const humanSvg = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M366.689524 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L366.689524 690.468571zM203.824762 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571429 8.435809l-99.181714-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634286 193.487238 88.405334 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182858 27.160381c-5.851429 2.048-12.092952 3.120762-18.383238 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L365.616762 623.177143a84.894476 84.894476 0 0 1-24.868572-80.700953l34.523429-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L290.133333 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C420.08381 58.465524 473.86819 17.066667 533.650286 23.30819 593.383619 29.549714 636.537905 81.13981 630.00381 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z"></path></svg>'
const humanLeakedSvg = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M244.784762 690.468571l87.283809 83.821715-75.434666 195.486476c-10.971429 27.794286-43.105524 42.081524-72.265143 32.036571-29.159619-10.24-44.080762-41.252571-33.450667-69.241904L244.784762 690.468571zM81.92 476.306286l51.785143-95.183238a162.279619 162.279619 0 0 1 59.245714-59.977143c119.710476-68.266667 134.777905-67.291429 149.942857-66.218667l80.798476 5.168762c24.868571 0.975238 42.081524 7.314286 125.025524 124.14781a21.26019 21.26019 0 0 0 14.092191 8.289523l99.132952 14.482286c12.873143 1.852952 24.478476 8.582095 32.182857 18.67581a45.494857 45.494857 0 0 1 8.825905 35.108571 46.665143 46.665143 0 0 1-19.504762 30.866286 50.468571 50.468571 0 0 1-36.571428 8.435809l-99.181715-14.433524a119.954286 119.954286 0 0 1-79.774476-47.640381c-4.388571-6.241524-7.558095-11.361524-11.849143-16.579047l-63.634285 193.487238 88.405333 84.845714c12.970667 12.385524 23.698286 29.013333 30.232381 45.494857l67.876571 190.366477c5.022476 13.409524 4.193524 28.233143-2.291809 41.057523a54.613333 54.613333 0 0 1-32.182857 27.160381c-5.851429 2.048-12.092952 3.120762-18.383239 3.169524a58.075429 58.075429 0 0 1-53.930666-36.181333l-67.876572-190.366476c-1.024-2.096762-2.096762-3.120762-3.218285-5.168762L243.712 623.177143a84.894476 84.894476 0 0 1-24.868571-80.700953l34.523428-146.919619c-3.413333 2.340571-7.070476 4.388571-10.776381 6.290286a58.806857 58.806857 0 0 0-22.674286 22.723048L168.228571 519.801905a49.834667 49.834667 0 0 1-43.105523 24.819809 59.977143 59.977143 0 0 1-22.674286-5.12 46.518857 46.518857 0 0 1-20.48-63.146666z m209.67619-360.448C298.179048 58.465524 351.963429 17.066667 411.745524 23.30819 471.478857 29.549714 514.633143 81.13981 508.099048 138.48381c-6.534095 57.392762-60.269714 98.840381-120.05181 92.550095-59.782095-6.241524-102.985143-57.782857-96.451048-115.175619z" fill="#FFFFFF"></path><path d="M734.793143 532.918857l-27.89181-13.897143A308.175238 308.175238 0 0 1 536.380952 243.029333v-73.142857a32.621714 32.621714 0 0 1 18.188191-29.403428l172.958476-86.552381a49.347048 49.347048 0 0 1 43.885714 0l172.958477 86.552381a32.670476 32.670476 0 0 1 18.236952 29.403428v73.142857a308.272762 308.272762 0 0 1-170.520381 275.992381l-27.89181 13.897143a33.304381 33.304381 0 0 1-29.403428 0z" fill="#FA901C"></path><path d="M722.456381 414.232381a27.014095 27.014095 0 1 0 54.02819 0 27.014095 27.014095 0 0 0-54.02819 0zM722.456381 181.735619v151.405714a24.771048 24.771048 0 0 0 27.014095 25.209905 24.771048 24.771048 0 0 0 27.014095-25.209905V181.735619a24.771048 24.771048 0 0 0-27.014095-25.258667 24.771048 24.771048 0 0 0-27.014095 25.258667z"></path></svg>'
const baseStationSvg = '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M952.182154 904.113231h-99.800616c-0.118154 0-232.093538-570.683077-232.093538-570.683077 49.427692-31.192615 79.832615-84.361846 79.832615-142.926769A171.047385 171.047385 0 0 0 529.545846 19.692308a170.417231 170.417231 0 0 0-170.574769 170.259692c0 55.768615 26.860308 106.535385 72.231385 138.633846L188.278154 904.113231H107.657846c-29.026462 0-52.499692 21.070769-52.499692 50.097231 0 28.987077 23.512615 50.097231 52.499692 50.09723h844.563692c28.987077 0 52.460308-21.110154 52.460308-50.136615 0-28.987077-23.512615-50.057846-52.460308-50.057846h-0.039384zM524.544 392.664615l33.161846 85.858462h-66.441846l33.28-85.858462z m-91.451077 235.992616h182.744615l38.872616 100.155077H394.259692l38.833231-100.155077z m-106.653538 275.456l29.144615-75.106462h333.351385l28.908307 75.106462H326.4h0.039385z"></path></svg>'
const cellSvg = '<svg viewBox="0 0 1365 1024" xmlns="http://www.w3.org/2000/svg"><path d="M186.847004 720.554667a559.104 559.104 0 0 1-43.121777-217.656889c0-75.093333 14.336-150.186667 43.235555-217.656889 21.504-67.584 64.625778-127.658667 114.915556-180.110222L201.183004 0A711.793778 711.793778 0 0 0 50.313671 232.675556 719.303111 719.303111 0 0 0 0.023893 502.897778c0 90.112 14.336 187.733333 50.289778 270.222222 35.953778 82.602667 86.243556 165.205333 150.869333 232.675556l100.693334-105.016889c-50.289778-52.565333-93.411556-112.64-114.915556-180.224z"></path><path d="M316.212338 660.593778c21.617778 52.451556 50.289778 97.507556 86.243555 135.054222l100.579556-105.130667a333.027556 333.027556 0 0 1-57.457778-82.488889c-7.168-37.546667-14.449778-67.584-14.449778-105.130666 0-37.546667 7.281778-67.584 21.617778-97.507556 7.168-37.546667 28.785778-60.074667 50.289778-90.112l-100.579556-105.130666c-35.953778 37.546667-64.739556 82.602667-86.243555 135.168a407.324444 407.324444 0 0 0-28.785778 157.582222c0 52.565333 7.168 105.130667 28.785778 157.696zM574.943004 502.897778c0 62.236444 48.241778 112.64 107.747556 112.64 59.505778 0 107.747556-50.403556 107.747556-112.64 0-62.122667-48.241778-112.64-107.747556-112.64-59.505778 0-107.747556 50.517333-107.747556 112.64zM862.345671 690.631111l100.579556 105.016889c35.953778-37.546667 64.739556-82.602667 86.243555-135.054222a407.324444 407.324444 0 0 0 28.785778-157.696 407.324444 407.324444 0 0 0-28.785778-157.582222 427.690667 427.690667 0 0 0-86.243555-135.168l-100.579556 105.130666c21.617778 22.528 43.121778 52.565333 57.457778 82.602667 14.449778 29.923556 21.617778 67.470222 21.617778 97.507555 0 37.546667-7.168 67.584-21.617778 97.621334-14.336 45.056-35.953778 67.584-57.457778 97.507555z"></path><path d="M1315.067449 232.675556A711.793778 711.793778 0 0 0 1164.198116 0l-100.693334 105.130667c50.289778 52.451556 86.243556 112.64 114.915556 180.110222 28.899556 67.584 43.235556 142.563556 43.235555 217.656889 0 75.093333-14.336 150.186667-43.235555 217.656889-28.672 67.584-64.625778 127.658667-114.915556 180.224l100.693334 105.016889a711.793778 711.793778 0 0 0 150.869333-232.675556A719.303111 719.303111 0 0 0 1365.357227 502.897778c0-90.112-14.336-187.733333-50.289778-270.222222z"></path></svg>'
export default {
name: 'Location',
data () {
return {
url: api.location.followedSubscriber,
blankObject: { // 空白对象
id: ''
},
tooltipType: {
hexagon: 'hexagon',
baseStation: 'base-station',
human: 'human',
cell: 'cell'
},
activeCount: '',
activeCountChain: '',
curPageNum: 1,
activeNames: '',
initFlag: true,
emptyTip: '',
opacity: 1,
scrollInfo: {
itemSize: 50, // 一个滚动item高度
containerHeight: 300 // 滚动列表
},
onlyShowFollowed: false,
busy: false,
preScrollTop: 0
}
},
mixins: [dataListMixin],
components: {
TimeLine,
SimpleLoading,
MyFollowBox
},
methods: {
getTagColor,
dateFormatByAppearance,
valueToRangeValue,
async initMap () {
const _this = this
if (!this.mapChart) {
this.mapChart = new maplibregl.Map({
container: 'analysisMap',
style: mapStyle,
center: this.center,
maxZoom: this.maxZoom,
minZoom: this.minZoom,
zoom: this.defaultZoom
})
maplibregl.addProtocol('cn', (params, callback) => { // 切片显示接口 防止跨域的问题
fetch(`${params.url.split('://')[1]}`)
.then(t => {
if (t.status === 200) {
t.arrayBuffer().then(arr => {
callback(null, arr, null, null)
})
} else {
callback(new Error(`Tile fetch error: ${t.statusText}`))
}
})
.catch(e => {
callback(new Error(e))
})
return { cancel: () => { } }
})
}
this.mapChart.on('load', async function () {
// 加载地图上的基站基站不随tab的切换而改变
const baseStationData = await _this.queryBaseStation()
_this.renderMarker(baseStationData, _this.tooltipType.baseStation)
if (_this.activeTab === 'locationMap') {
await _this.initLocationMapTab()
} else if (_this.activeTab === 'traceTracking') {
await _this.initTraceTrackingTab()
}
_this.mapChart.off('moveend', _this.debounceVisualChange)
_this.mapChart.off('zoomend', _this.debounceVisualChange)
_this.mapChart.on('moveend', _this.debounceVisualChange)
_this.mapChart.on('zoomend', _this.debounceVisualChange)
})
},
async initLocationMapTab () {
// 最先渲染右上角饼图
await this.renderDensityPie()
// 然后渲染地图的色块、基站、人(包括右侧关注列表),最后渲染右上角折线图
/* 地图色块 */
this.updateBoundaryBox()
const hexagonData = await this.queryHexagon()
// 将查到的h3hexagon数据转为geojson
const polygonSourceData = this.hexagonDataConverter(hexagonData, 'locationMap')
this.mapChart.addSource('hexGrid', {
type: 'geojson',
data: polygonSourceData
})
this.mapChart.addLayer({
id: 'hexagon',
type: 'fill',
source: 'hexGrid',
layout: {
visibility: this.currentZoom >= 9 ? 'visible' : 'none'
},
paint: {
'fill-color': ['get', 'color'],
'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 0.7, 0.4]
}
})
// 六边形的鼠标事件
this.unbindHexagonEvents()
this.bindHexagonEvents()
/* 右侧默认显示普通列表 */
await this.initSubscriberList()
/* 地图上的人 */
// const mapFollowedSubscriberData = await this.queryMapFollowedSubscriber()
// this.renderMarker(mapFollowedSubscriberData, this.tooltipType.human)
// this.renderMarker(this.subscribersList, this.tooltipType.human)
/* 右上角折线图 */
await this.renderActiveSubscribersLine()
},
async initTraceTrackingTab () {
if (this.idFromUrl) {
const find = this.trackingSubscribers.find(s => s.subscriberId === this.idFromUrl)
if (find) {
this.currentShowSubscriber = find
} else {
const queryResult = await axios.get(`${api.entity.basicInfo}/subscriber?resource=${this.idFromUrl}`)
const newRecord = {
...queryResult.data.data,
subscriberId: this.idFromUrl,
phoneNumber: queryResult.data.data.phone_number,
show: true,
showLine: true,
scrollStartIndex: 1,
scrollEndIndex: 6,
startOffset: 0,
listHeight: 0
}
const queryResult2 = await axios.get(`${api.entity.tags}/subscriber?resource=${this.idFromUrl}`)
if (queryResult2.data.data && queryResult2.data.data.tags) {
newRecord.tags = queryResult2.data.data.tags.map(tag => {
return { value: tag.name, color: intentColor[tag.intent] || entityDefaultColor }
})
}
this.trackingSubscribers.push(newRecord)
this.currentShowSubscriber = newRecord
}
}
await this.queryTraceTracking()
// 如果未指定展示谁的轨迹,则默认取追踪用户中的第一个
if (!this.currentShowSubscriber && this.trackingSubscribers.length > 0) {
this.currentShowSubscriber = this.trackingSubscribers[0]
}
const find = this.trackingSubscribers.find(s => s.subscriberId === this.currentShowSubscriber.subscriberId)
if (find) {
// 滚动条定位到id所在的dom
const findIndex = this.trackingSubscribers.findIndex(s => s.subscriberId === this.currentShowSubscriber.subscriberId)
const dom = document.getElementById('subscribersBlock')
if (findIndex && dom) {
await this.$nextTick(() => {
dom.scrollTop = 207 * findIndex
})
}
}
this.renderTrackingHexagon()
},
async renderDensityPie () {
const params = {
...this.timeFilter,
level: this.mapLevel
}
this.loading.pieLoading = true
try {
const response = await axios.get(api.location.density, { params })
const densityData = response.data.data.filter(d => d.hexId)
// const densityData = testData
// 按值的大小分组,并计算各组数量和颜色
this.pieValueRamp = this.calculateValueRamp(densityData)
const option = _.cloneDeep(pieOption)
option.color = this.pieColorRamp.map(c => `rgb(${c},.7)`)
option.series[0].name = this.$t('location.populationDensity')
option.series[0].data = this.pieValueRamp.map(r => ({
value: r.count
}))
this.pieOption = option
if (!this.pieChart) {
this.pieChart = echarts.init(document.getElementById('populationDensityChart'))
}
this.$nextTick(() => {
this.pieChart.setOption(this.pieOption)
})
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
if (this.pieChart && this.pieChart.clear) {
this.pieChart.clear()
}
this.pieValueRamp = []
console.error(e)
} finally {
this.loading.pieLoading = false
}
},
async renderActiveSubscribersLine () {
const params = {
...this.timeFilter,
level: this.mapLevel
}
this.loading.lineLoading = true
try {
const curCountResponse = await axios.get(api.location.count, { params: { ...params, cycle: 0 } })// 当前周期活跃用户总数
const preCountResponse = await axios.get(api.location.count, { params: { ...params, cycle: 1 } })// 上一周期活跃用户总数
this.activeCount = curCountResponse.data.data.total
const preActiveCount = preCountResponse.data.data.total
if (preActiveCount !== 0) {
this.activeCountChain = (this.activeCount - preActiveCount) / preActiveCount
} else {
this.activeCountChain = '-'
}
const trendResponse = await axios.get(api.location.trend, { params })
const activeSubscribersData = trendResponse.data.data.result
const option = _.cloneDeep(appListChartOption)
option.series[0].data = activeSubscribersData.map(d => {
return [d[0], d[1], unitTypes.number]
})
this.lineOption = option
if (!this.lineChart) {
this.lineChart = echarts.init(document.getElementById('activeSubscribersChart'))
}
this.lineChart.setOption(this.lineOption)
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
if (this.lineChart && this.lineChart.clear) {
this.lineChart.clear()
}
this.activeCount = ''
this.activeCountChain = ''
console.error(e)
} finally {
this.loading.lineLoading = false
}
},
async queryHexagon () {
const params = {
...this.boundaryBox,
...this.timeFilter,
level: this.mapLevel
}
this.loading.hexagonLoading = true
try {
const response = await axios.get(api.location.map, { params })
return response.data.data
// return testData
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
console.error(e)
} finally {
this.loading.hexagonLoading = false
}
return []
},
async queryBaseStation () {
this.loading.baseStationLoading = true
try {
/* 这里涉及如何将cell聚类成基站的问题目前暂时在前端做聚类根据规则同一个运营商中nodeBID是唯一的。因此以nodeBID+operator组合作为聚类依据。
理论上同一个nodeBID下的cell的经纬度都是一样的因此直接取第一个cell的经纬度和地名信息作为基站的经纬度。
同一个nodeBID下的cell除经纬度外还有许多属性值是相同的这里将cell的nodeBID、operator、technology、lacTac、mcc、mnc作为基站的属性来展示
将cell的id、cellId、azimuth、coverageRadius、vendor、fddSpectrum作为cell的属性来展示。 */
const response = await axios.get(api.location.baseStation, { params: { pageSize: -1 } })
const baseStations = []
response.data.data.list.forEach(cell => {
const exist = baseStations.find(bs => bs.nodebId === cell.nodebId && bs.operator === cell.operator)
if (!exist) {
baseStations.push({
nodebId: cell.nodebId,
operator: cell.operator,
technology: cell.technology,
lacTac: cell.lacTac,
mcc: cell.mcc,
mnc: cell.mnc,
latitude: cell.latitude,
longitude: cell.longitude,
superAdministrativeArea: cell.superAdministrativeArea,
administrativeArea: cell.administrativeArea,
country: cell.country,
cells: [cell]
})
} else {
exist.cells.push(cell)
}
})
return baseStations
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
console.error(e)
} finally {
this.loading.baseStationLoading = false
}
return []
},
async queryMapFollowedSubscriber () {
this.loading.timeBarLoading = true
const params = {
...this.minuteTimeFilter,
level: this.mapLevel,
pageSize: -1
}
try {
if (this.subscribersList.length > 0) {
await this.setSubscriberActiveStatus(this.subscribersList)
}
const response = await axios.get(api.location.followedSubscriber, { params })
response.data.data.list.forEach(d => {
d.isFollowed = 1
})
return response.data.data.list
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
console.error(e)
} finally {
this.loading.timeBarLoading = false
}
return []
},
jumpEntityDetail (subscriber) {
const { href } = this.$router.resolve({
path: '/entity/detail',
query: {
entityType: 'subscriber',
entityName: subscriber.subscriberId,
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
range: this.timeFilter.dateRangeValue
}
})
window.open(href, '_blank')
},
async queryTraceTracking () {
if (this.trackingSubscribers.length > 0) {
this.loading.trackingMapLoading = true
const params = {
...this.timeFilter,
subscriberIds: this.trackingSubscribers.map(item => `'${item.subscriberId}'`).join(','),
level: this.mapLevel
}
if (this.searchList[3].value) {
params.params = `data_source='${this.searchList[3].value}'`
}
try {
const response = await axios.get(api.location.tracking, { params })
const trackingSubscribers = _.cloneDeep(this.trackingSubscribers)
if (response.data.data.result) {
// 过滤掉无位置的无效数据
response.data.data.result.forEach(item => {
item.trackRecords = item.trackRecords.filter(t => t.hexId)
})
trackingSubscribers.forEach(s => {
const find = response.data.data.result.find(item => item.subscriberId === s.subscriberId)
if (find) {
this.trackingSubscriberRecordMap[s.subscriberId] = find.trackRecords
} else {
this.trackingSubscriberRecordMap[s.subscriberId] = []
}
s.show = false // 切换到track tracking时就收起时间线
})
this.trackingSubscribers = trackingSubscribers
} else {
Object.keys(this.trackingSubscriberRecordMap).forEach(k => {
this.trackingSubscriberRecordMap[k] = []
})
}
// 计算停留时间
this.trackingSubscribers.forEach((s, index) => {
const trackRecords = this.trackingSubscriberRecordMap[s.subscriberId]
// 初始化时间线可视范围角标
if (trackRecords.length < 6) {
s.scrollStartIndex = 1
s.scrollEndIndex = trackRecords.length
} else {
s.scrollStartIndex = 1
s.scrollEndIndex = 6
}
if (trackRecords && trackRecords.length > 0) {
for (let i = 0; i < trackRecords.length; i++) {
if (i > 0) {
if ((trackRecords[i - 1].subscriberLongitude === trackRecords[i].subscriberLongitude) && (trackRecords[i - 1].subscriberLatitude === trackRecords[i].subscriberLatitude)) {
// 如果连续两条地址重复,则将时间累加,并将上一条删除,键值-1继续循环
if (i > 1 && trackRecords[i - 2]) {
const stayTime = unitConvert(trackRecords[i - 2].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
} else {
// 数据只有2条或者第1条和第2条地点重复删除1合并时间
const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
}
trackRecords.splice(i - 1, 1)
i = i - 1
} else {
const stayTime = unitConvert(trackRecords[i - 1].time - trackRecords[i].time, unitTypes.time, 's')
if (Number(stayTime[0]) === Number(Number(stayTime[0]).toFixed(0))) {
stayTime[0] = Number(stayTime[0]).toFixed(0)
}
trackRecords[i].stayTime = stayTime.join(' ')
}
if (i === trackRecords.length - 1) {
// 初始化数据时,重置偏移量和列表高度
s.startOffset = 0
s.listHeight = i * this.scrollInfo.itemSize
}
}
}
}
})
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
console.error(e)
} finally {
this.loading.trackingMapLoading = false
}
} else {
this.loading.trackingMapLoading = false
}
},
moveToCenter (records) {
const maxLongitude = _.maxBy(records, d => Number(d.subscriberLongitude))
const maxLatitude = _.maxBy(records, d => Number(d.subscriberLatitude))
const minLongitude = _.minBy(records, d => Number(d.subscriberLongitude))
const minLatitude = _.minBy(records, d => Number(d.subscriberLatitude))
const center = [(maxLongitude.subscriberLongitude + minLongitude.subscriberLongitude) / 2, (maxLatitude.subscriberLatitude + minLatitude.subscriberLatitude) / 2]
this.mapChart.panTo(center, { duration: 500 })
},
renderTrackingHexagon () {
this.bindTrackingHexagonEvents()
if (!this.currentShowSubscriber) {
return true
}
const currentShowSubscriberRecords = this.trackingSubscriberRecordMap[this.currentShowSubscriber.subscriberId]
if (currentShowSubscriberRecords && currentShowSubscriberRecords.length > 0) {
// 六边形
this.trackingPolygonSourceData = this.hexagonDataConverter(this.trackingSubscriberRecordMap[this.currentShowSubscriber.subscriberId], 'traceTracking')
this.mapChart.addSource('trackingHexGrid', {
type: 'geojson',
data: this.trackingPolygonSourceData
})
this.mapChart.addLayer({
id: 'trackingHexagon',
type: 'fill',
source: 'trackingHexGrid',
layout: {},
paint: {
'fill-color': ['get', 'color'],
'fill-opacity': ['case', ['boolean', ['feature-state', 'hover'], false], 0.7, 0.4]
}
})
// 轨迹线
const mapLineSourceData = this.mapLineDataConverter()
this.mapChart.addSource('trackingLineSource', {
type: 'geojson',
data: mapLineSourceData
})
this.mapChart.addLayer({
id: 'trackingLine',
type: 'line',
source: 'trackingLineSource',
paint: {
'line-color': 'rgba(222, 52, 52, .8)',
'line-width': 3
}
})
this.moveToCenter(currentShowSubscriberRecords)
// 最后所在地的图标
const coordinate = h3ToGeo(currentShowSubscriberRecords[0].hexId)
this.renderTrackingMarker([coordinate[1], coordinate[0]])
}
},
renderMarker (data, type) {
try {
data.forEach(marker => {
if (type === this.tooltipType.human && (marker.sessionRecordPoint || marker.sdPoint)) {
if (marker.sessionRecordPoint) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
if (marker.subscriberId === this.highlightSubscriber.subscriberId) {
el.classList.add('map-marker--highlight')
}
// 非关注的用户,默认隐藏图标,鼠标移到列表后才显示
if (this.currentZoom && this.currentZoom < 6) {
el.classList.add('map-marker--hidden')
}
if (!marker.isFollowed && this.onlyShowFollowed) {
el.classList.add('map-marker--unfollowed')
}
el.innerHTML = humanLeakedSvg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type, true)
const mapMarker = new maplibregl.Marker({ element: el })
.setLngLat(marker.sessionRecordPoint.coordinate)
.addTo(this.mapChart)
mapMarker.subscriberId = marker.subscriberId
mapMarker.isFollowed = marker.isFollowed
mapMarker.active = marker.active
this.humanMarkers.push(mapMarker)
}
if (marker.sdPoint) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
if (marker.subscriberId === this.highlightSubscriber.subscriberId) {
el.classList.add('map-marker--highlight')
}
// 非关注的用户,默认隐藏图标,鼠标移到列表后才显示
if (this.currentZoom && this.currentZoom < 6) {
el.classList.add('map-marker--hidden')
}
if (!marker.isFollowed && this.onlyShowFollowed) {
el.classList.add('map-marker--unfollowed')
}
el.innerHTML = humanSvg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type)
const mapMarker = new maplibregl.Marker({ element: el })
.setLngLat(marker.sdPoint.coordinate)
.addTo(this.mapChart)
mapMarker.subscriberId = marker.subscriberId
mapMarker.isFollowed = marker.isFollowed
mapMarker.active = marker.active
this.humanMarkers.push(mapMarker)
}
} else if (type === this.tooltipType.baseStation) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
if (this.currentZoom && this.currentZoom < 6) {
el.classList.add('map-marker--hidden')
}
el.innerHTML = baseStationSvg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type)
const mapMarker = new maplibregl.Marker({ element: el })
.setLngLat([marker.longitude, marker.latitude])
.addTo(this.mapChart)
this.baseStationMarkers.push(mapMarker)
} else if (type === this.tooltipType.cell) {
const el = document.createElement('div')
el.className = `map-marker map-marker--${type}`
if (this.currentZoom && this.currentZoom < 6) {
el.classList.add('map-marker--hidden')
}
el.innerHTML = cellSvg
// 鼠标事件控制tooltip显示和marker尺寸
this.bindMarkerEvent(el, marker, type)
const mapMarker = new maplibregl.Marker({ element: el })
.setLngLat([marker.longitude, marker.latitude])
.addTo(this.mapChart)
this.cellMarkers.push(mapMarker)
}
})
} catch (e) {
console.error(e)
}
},
renderTrackingMarker (coordinates) {
const el = document.createElement('div')
el.className = 'map-tracking-marker'
el.innerHTML = `<div class="tracking-marker__inner-circle">${humanSvg}</div>`
this.trackingHumanMarker = new maplibregl.Marker({ element: el })
.setLngLat(coordinates)
.addTo(this.mapChart)
},
updateHumanMarker () {
},
updateBoundaryBox () {
const boundaryBox = this.mapChart.getBounds()
this.boundaryBox = {
maxLongitude: boundaryBox.getEast(),
maxLatitude: boundaryBox.getNorth(),
minLongitude: boundaryBox.getWest(),
minLatitude: boundaryBox.getSouth()
}
let needUpdateData = false
if (!this.boundaryBoxExtreme) {
this.boundaryBoxExtreme = generateRectanglePolygon(this.boundaryBox.minLongitude, this.boundaryBox.minLatitude, this.boundaryBox.maxLongitude, this.boundaryBox.maxLatitude)
needUpdateData = true
} else {
const polygon = generateRectanglePolygon(this.boundaryBox.minLongitude, this.boundaryBox.minLatitude, this.boundaryBox.maxLongitude, this.boundaryBox.maxLatitude)
const union = turf.union(turf.featureCollection([this.boundaryBoxExtreme, polygon]))
// 如果合并后面积变大了,说明有新的区域,需要渲染
if (turf.area(union) > turf.area(this.boundaryBoxExtreme)) {
this.boundaryBoxExtreme = union
needUpdateData = true
}
}
return needUpdateData
},
// 先使用min=0的等宽分组法若后续出现特大或特小的异常值导致等宽分组效果不理想考虑用分位数分组法
calculateValueRamp (data) {
const max = _.maxBy(data, d => Number(d.number))
const result = []
if (max) {
let step
if (max.number <= 1000) {
step = 20
} else {
step = 100
}
const maxLegend = Math.ceil((max.number) / step) * step
for (let i = 1; i <= this.pieColorRamp.length; i++) {
const item = {
start: maxLegend * (i - 1) / this.pieColorRamp.length + 1,
end: maxLegend * i / this.pieColorRamp.length,
color: this.pieColorRamp[i - 1]
}
item.count = data.filter(d => d.number >= item.start && d.number <= item.end).length
if (i === this.pieColorRamp.length) {
item.name = `>${item.start - 1}`
} else {
item.name = `${item.start}~${item.end}`
}
result.push(item)
}
}
return result
},
hexagonDataConverter (data, tab) {
const featureCollection = { type: 'FeatureCollection' }
if (tab === 'locationMap') {
featureCollection.features = data.map(d => ({
id: parseInt(d.hexId, 16),
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
h3ToGeoBoundary(d.hexId, true)
]
},
properties: {
hexId: d.hexId,
number: d.number,
color: this.getHexagonFillColor(d.number)
}
}))
} else if (tab === 'traceTracking') {
// 对hexId去重将重复的hexId的时间合并
const hexagons = []
data.forEach(d => {
const find = hexagons.find(h => h.hexId === d.hexId)
if (!find) {
hexagons.push({ hexId: d.hexId, locations: [{ time: d.time, longitude: d.subscriberLongitude, latitude: d.subscriberLatitude, country: d.country, superAdministrativeArea: d.superAdministrativeArea, administrativeArea: d.administrativeArea }] })
} else {
if (find.locations.length < 6) {
find.locations.push({ time: d.time, longitude: d.subscriberLongitude, latitude: d.subscriberLatitude })
}
}
})
featureCollection.features = hexagons.map((d, i) => ({
id: i + 100000,
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
h3ToGeoBoundary(d.hexId, true)
]
},
properties: {
hexId: d.hexId,
number: d.number,
locations: d.locations,
color: [37, 55, 128]
}
}))
}
return featureCollection
},
getHexagonFillColor (number) {
const ramp = this.pieValueRamp.filter((r, i) => {
if (i < this.pieValueRamp.length - 1) {
return Number(number) >= r.start && Number(number) <= r.end
} else {
return Number(number) >= r.start
}
})
if (ramp.length > 0) {
return ramp[0].color.split(',').map(n => Number(n))
}
return [229, 229, 229]
},
mapLineDataConverter () {
const records = this.trackingSubscriberRecordMap[this.currentShowSubscriber.subscriberId]
const feature = {
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: records.map(d => {
const cs = h3ToGeo(d.hexId)
return [cs[1], cs[0]]
})
}
}
return {
type: 'FeatureCollection',
features: [feature]
}
},
hoverTrigger (source, id, hover) {
this.mapChart.setFeatureState({ source, id }, { hover })
},
bindHexagonEvents () {
// 地图可视范围变化move和zoom的时候获取新的地图边界加载新数据
this.mapChart.on('mouseenter', 'hexagon', this.hexagonMouseEnter)
this.mapChart.on('mouseleave', 'hexagon', this.hexagonMouseLeave)
this.mapChart.on('mousemove', 'hexagon', this.hexagonMouseMove)
this.mapChart.on('click', 'hexagon', this.hexagonClick)
},
unbindHexagonEvents () {
this.mapChart.off('mouseenter', 'hexagon', this.hexagonMouseEnter)
this.mapChart.off('mouseleave', 'hexagon', this.hexagonMouseLeave)
this.mapChart.off('mousemove', 'hexagon', this.hexagonMouseMove)
this.mapChart.off('click', 'hexagon', this.hexagonClick)
},
bindTrackingHexagonEvents () {
this.mapChart.on('mouseenter', 'trackingHexagon', this.trackingHexagonMouseEnter)
this.mapChart.on('mouseleave', 'trackingHexagon', this.trackingHexagonMouseLeave)
this.mapChart.on('mousemove', 'trackingHexagon', this.trackingHexagonMouseMove)
},
unbindTrackingHexagonEvents () {
this.mapChart.off('mouseenter', 'trackingHexagon', this.trackingHexagonMouseEnter)
this.mapChart.off('mouseleave', 'trackingHexagon', this.trackingHexagonMouseLeave)
this.mapChart.off('mousemove', 'trackingHexagon', this.trackingHexagonMouseMove)
},
hexagonMouseEnter () {
this.tooltip.mouseIsInPolygon = true
},
hexagonMouseLeave () {
this.tooltip.showPolygonTooltip = false
this.tooltip.mouseIsInPolygon = false
// 去掉上一块的高亮
this.hoverTrigger('hexGrid', this.currentPolygon.id, false)
},
hexagonMouseMove (e) {
const { originalEvent, features } = e
if (!this.tooltip.mouseInMarkerOrTooltip) {
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) {
// 去掉上一块的高亮
this.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 + this.tooltipDomHeight.hexagon) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.hexagon) : (originalEvent.clientY + 5)
// 鼠标滑过高亮
this.hoverTrigger('hexGrid', this.currentPolygon.id, true)
}
},
async hexagonVisualRangeChange (e) {
this.currentZoom = this.mapChart.getZoom()
console.info(`current zoom: ${this.currentZoom}`)
if (this.activeTab === 'traceTracking') {
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
} else {
this.showBaseStation()
}
} else if (this.activeTab === 'locationMap') {
if (this.currentZoom >= 9 && this.mapChart.getSource('hexGrid') && this.updateBoundaryBox()) {
const oldSourceData = this.mapChart.getSource('hexGrid')._data
const hexagonData = await this.queryHexagon()
// 将查到的h3hexagon数据转为geojson
const polygonSourceData = this.hexagonDataConverter(hexagonData, 'locationMap')
// 对比新旧数据同个hexId新数据number大于旧数据的将旧数据覆盖新hexId直接添加
const newSourceData = this.compareSourceData(oldSourceData, polygonSourceData)
this.mapChart.getSource('hexGrid').setData(newSourceData)
}
}
},
compareSourceData (oldData, newData) {
// 创建一个哈希表来存储oldData中每个feature的hexId和它的索引位置
const oldDataMap = new Map()
oldData.features.forEach((feature, index) => {
oldDataMap.set(feature.properties.hexId, { feature, index })
})
newData.features.forEach(n => {
const hexId = n.properties.hexId
if (oldDataMap.has(hexId)) {
// 如果在oldData中找到匹配的hexId
const { feature: oldFeature } = oldDataMap.get(hexId)
if (Number(n.properties.number) > Number(oldFeature.properties.number)) {
// 更新number和color属性
oldFeature.properties.number = n.properties.number
oldFeature.properties.color = n.properties.color
}
} else {
// 如果没有找到添加新的feature到oldData
oldData.features.push({ ...n })
}
})
return oldData
},
trackingHexagonMouseEnter () {
this.tooltip.mouseIsInPolygon = true
},
trackingHexagonMouseLeave () {
this.tooltip.showPolygonTooltip = false
this.tooltip.mouseIsInPolygon = false
// 去掉上一块的高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, false)
},
trackingHexagonZoomEnd () {
this.currentZoom = this.mapChart.getZoom()
console.info(`current zoom: ${this.currentZoom}`)
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
} else {
this.showBaseStation()
}
},
trackingHexagonMouseMove (e) {
const { originalEvent, features } = e
if (!this.tooltip.mouseInMarkerOrTooltip) {
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) {
// 去掉上一块的高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, false)
}
this.currentPolygon = features[0].properties
this.currentPolygon.id = features[0].id
this.currentPolygon.location = `${h3ToGeo(this.currentPolygon.hexId)[1]}, ${h3ToGeo(this.currentPolygon.hexId)[0]}`
// this.tooltip.x = originalEvent.clientX + 15
// this.tooltip.y = originalEvent.clientY + 5
this.$nextTick(() => {
const tooltipDom = document.getElementById('tooltip')
const tooltipDomHeight = tooltipDom.offsetHeight
this.tooltip.x = originalEvent.clientX + 15
this.tooltip.y = originalEvent.clientY + 5
this.tooltip.y = (originalEvent.clientY + 5 + tooltipDomHeight) > this.mapDomHeight ? (this.mapDomHeight - tooltipDomHeight) : (originalEvent.clientY + 5)
})
// 鼠标滑过高亮
this.hoverTrigger('trackingHexGrid', this.currentPolygon.id, true)
}
},
trackingSourceChange () {
this.timeRefreshChange()
},
bindMarkerEvent (el, markerData, type, isSessionRecord) {
el.addEventListener('mouseenter', e => {
this.currentMarkerDom = el
if (type === this.tooltipType.human) {
this.currentSubscriber = markerData
if (isSessionRecord) {
this.currentSubscriber.isSessionRecord = true
}
const toHoverMarkers = this.humanMarkers.filter(m => m.subscriberId === markerData.subscriberId)
toHoverMarkers.forEach(m => {
m.getElement().classList.add('map-marker--hover')
})
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.x = e.clientX + 15 - e.offsetX
this.tooltip.y = (e.clientY + 15 - e.offsetY + this.tooltipDomHeight.human) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.human) : (e.clientY + 15 - e.offsetY)
}
} else if (type === this.tooltipType.baseStation) {
this.currentBaseStation = markerData
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.x = e.clientX + 15 - e.offsetX
this.tooltip.y = (e.clientY + 15 - e.offsetY + this.tooltipDomHeight.baseStation) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.baseStation) : (e.clientY + 15 - e.offsetY)
}
} else if (type === this.tooltipType.cell) {
this.currentCell = markerData
if (!this.tooltip.mouseInMarkerOrTooltip) {
this.tooltip.x = e.clientX + 15 - e.offsetX
this.tooltip.y = (e.clientY + 15 - e.offsetY + this.tooltipDomHeight.baseStation) > this.mapDomHeight ? (this.mapDomHeight - this.tooltipDomHeight.baseStation) : (e.clientY + 15 - e.offsetY)
}
}
this.tooltip.mouseInMarkerOrTooltip = true
this.tooltip.type = type
this.tooltip.showMarkerTooltip = true
el.classList.add('map-marker--hover')
})
el.addEventListener('mouseleave', event => {
const tooltipDom = document.getElementById('tooltip')
if (!tooltipDom.contains(event.relatedTarget)) {
el.classList.remove('map-marker--hover')
this.tooltip.mouseInMarkerOrTooltip = false
this.tooltip.showMarkerTooltip = false
if (type === this.tooltipType.human) {
const toCancelHoverMarkers = this.humanMarkers.filter(m => m.subscriberId === markerData.subscriberId)
toCancelHoverMarkers.forEach(m => {
m.getElement().classList.remove('map-marker--hover')
})
}
}
})
if (type === this.tooltipType.human) {
el.addEventListener('click', e => {
// 先去掉所有高亮marker
this.humanMarkers.forEach(m => {
m.getElement().classList.remove('map-marker--highlight')
})
// 再判断点击前是否高亮。是则去掉highlightSubscriber的引用否则查找两种dataSource的marker并高亮
if (this.highlightSubscriber.subscriberId !== markerData.subscriberId) {
this.rightExpanded = true
this.highlightSubscriber = markerData
const toHighlightMarkers = this.humanMarkers.filter(m => m.subscriberId === markerData.subscriberId)
toHighlightMarkers.forEach(m => {
m.getElement().classList.add('map-marker--highlight')
})
// 将滚动条跳转到对应位置
document.querySelector(`#locationMap-subscriberId-${markerData.subscriberId}`).scrollIntoView({ behavior: 'smooth', block: 'center' })
} else {
this.highlightSubscriber = {}
}
})
}
if (type === this.tooltipType.baseStation) {
// 点击基站后展开各个方向的cell
el.addEventListener('click', e => {
// 首先将基站图标高亮
this.baseStationMarkers.forEach(m => {
m.getElement().classList.remove('map-marker--highlight')
})
this.cleanCellsAndLines()
if (this.highlightBaseStation.nodebId !== markerData.nodebId || this.highlightBaseStation.operator !== markerData.operator) {
el.classList.add('map-marker--highlight')
this.highlightBaseStation = markerData
// 然后根据cells的数据生成各个方位角的cell
// 先将zoom调到12然后以基站为中心点计算指定角度指定距离的点的坐标并绘制点以及连线
this.mapChart.flyTo({
center: [markerData.longitude, markerData.latitude],
zoom: 12.5,
speed: 0.6
})
const points = []
// 先判断cell的方位角有没重合重合的话在老cell的基础上往右偏移一些
markerData.cells.forEach(cell => {
const sameAzimuthCells = points.filter(p => p.azimuth === cell.azimuth)
const sourceCoordinate = []
let coordinate
if (sameAzimuthCells.length > 0) {
const lastSameAzimuthCell = sameAzimuthCells[sameAzimuthCells.length - 1]
coordinate = turf.destination([lastSameAzimuthCell.longitude, lastSameAzimuthCell.latitude], 0.15, 90).geometry.coordinates
sourceCoordinate.push(lastSameAzimuthCell.longitude)
sourceCoordinate.push(lastSameAzimuthCell.latitude)
} else {
coordinate = turf.destination([markerData.longitude, markerData.latitude], 0.8, cell.azimuth).geometry.coordinates
sourceCoordinate.push(markerData.longitude)
sourceCoordinate.push(markerData.latitude)
}
points.push({
...cell,
longitude: coordinate[0],
latitude: coordinate[1],
sourceCoordinate
})
})
this.drawCellsAndLines(points)
} else {
this.highlightBaseStation = {}
}
})
}
},
tooltipMouseEnter () {
this.tooltip.mouseInMarkerOrTooltip = true
},
tooltipMouseLeave (event) {
if (this.currentMarkerDom && !this.currentMarkerDom.contains(event.relatedTarget)) {
this.tooltip.mouseInMarkerOrTooltip = false
this.tooltip.showMarkerTooltip = false
this.currentMarkerDom.classList.remove('map-marker--hover')
}
},
reload (startTime, endTime, dateRangeValue) {
this.curPageNum = 1
this.timeFilter = { startTime: getSecond(startTime), endTime: getSecond(endTime), dateRangeValue: dateRangeValue }
const { query } = this.$route
this.$store.commit('setTimeRangeArray', [this.timeFilter.startTime, this.timeFilter.endTime])
this.$store.commit('setTimeRangeFlag', dateRangeValue.value)
const newUrl = urlParamsHandler(window.location.href, query, {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
range: dateRangeValue.value
})
overwriteUrl(newUrl)
},
timelineMouseEnter (subscriber, record) {
this.trackingPolygonSourceData.features.forEach(f => {
this.hoverTrigger('trackingHexGrid', f.id, false)
})
if (this.currentShowSubscriber.subscriberId === subscriber.subscriberId) {
const find = this.trackingPolygonSourceData.features.find(d => d.properties.hexId === record.hexId)
if (find) {
this.hoverTrigger('trackingHexGrid', find.id, true)
}
}
},
timelineMouseLeave (subscriber, record) {
if (this.currentShowSubscriber.subscriberId === subscriber.subscriberId) {
const find = this.trackingPolygonSourceData.features.find(d => d.properties.hexId === record.hexId)
if (find) {
this.hoverTrigger('trackingHexGrid', find.id, false)
}
}
},
timelineClick (subscriber, record) {
// 点击timeline时将地图中心移动到该记录的位置并高亮左侧小红圈
this.mapChart.panTo([record.subscriberLongitude, record.subscriberLatitude], { duration: 500 })
this.highlightTrackingTimeline = record
},
// subscriber列表点击后将地图上的人图标保持特殊高亮并移动地图中心至图标点
subscriberListClick (subscriber) {
// 先删除当前所有高亮的
this.humanMarkers.forEach(m => {
m.getElement().classList.remove('map-marker--highlight')
})
if (this.highlightSubscriber.subscriberId === subscriber.subscriberId) {
this.highlightSubscriber = {}
} else {
this.highlightSubscriber = subscriber
const targets = this.humanMarkers.filter(m => subscriber.subscriberId === m.subscriberId)
if (targets.length > 0) {
targets.forEach(target => {
target.getElement().classList.add('map-marker--highlight')
})
this.mapChart.panTo(targets[0].getLngLat(), { duration: 500 })
}
}
},
subscriberListMouseEnter (subscriber) {
subscriber.showJumpToEntity = true
const targets = this.humanMarkers.filter(m => subscriber.subscriberId === m.subscriberId)
if (targets.length > 0) {
targets.forEach(target => {
target.addClassName && target.addClassName('map-marker--hover')
})
}
},
subscriberListMouseLeave (subscriber) {
subscriber.showJumpToEntity = false
const targets = this.humanMarkers.filter(m => subscriber.subscriberId === m.subscriberId)
if (targets.length > 0) {
targets.forEach(target => {
target.removeClassName && target.removeClassName('map-marker--hover')
})
}
},
async scrollList (e) {
if (!this.onlyShowFollowed) {
const dom = document.getElementById('locationMap-subscriber-scroll')
if (dom.scrollTop + dom.clientHeight >= dom.scrollHeight && !this.loading.subscriberLoading) {
this.curPageNum++
await this.initSubscriberList()
}
}
},
timeRefreshChange () {
this.curPageNum = 1
// 不是自选时间
if (this.$refs.dateTimeRange) {
if (!this.$refs.dateTimeRange.isCustom) {
const value = this.timeFilter.dateRangeValue
this.$refs.dateTimeRange.quickChange(value)
} else {
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
}
} else {
this.timeFilter = JSON.parse(JSON.stringify(this.timeFilter))
}
},
clickTrackBlock (i) {
const length = this.trackingSubscriberRecordMap[this.trackingSubscribers[i].subscriberId].length
if (length > 1) {
this.trackingSubscribers[i].show = !this.trackingSubscribers[i].show
if (this.trackingSubscribers[i].show) {
this.trackingSubscribers[i].showLine = true
this.trackingSubscribers[i].scrollStartIndex = 1
this.trackingSubscribers[i].scrollEndIndex = 6
this.trackingSubscribers[i].startOffset = 0
this.trackingSubscribers[i].listHeight = 0
// 高度置为0是为了切换时间后再打开时间线让滚动条置顶
const timer = setTimeout(() => {
this.trackingSubscribers[i].listHeight = this.trackingSubscriberRecordMap[this.trackingSubscribers[i].subscriberId].length * this.scrollInfo.itemSize
clearTimeout(timer)
}, 100)
} else {
const timer = setTimeout(() => {
this.trackingSubscribers[i].showLine = false
clearTimeout(timer)
}, 200)
}
}
},
changeCurrentShowSubscriber (subscriber) {
if (subscriber.subscriberId !== this.currentShowSubscriber.subscriberId) {
this.currentShowSubscriber = subscriber
}
},
hideBaseStation () {
this.baseStationMarkers.forEach(bs => {
bs.addClassName && bs.addClassName('map-marker--hidden')
})
},
showBaseStation () {
this.baseStationMarkers.forEach(bs => {
bs.removeClassName && bs.removeClassName('map-marker--hidden')
})
},
hideFollowed () {
this.humanMarkers.forEach(human => {
human.addClassName && human.addClassName('map-marker--hidden')
})
},
showFollowed () {
this.humanMarkers.forEach(human => {
human.removeClassName && human.removeClassName('map-marker--hidden')
})
},
hideHexagon () {
this.mapChart.getLayer('hexagon') && this.mapChart.setLayoutProperty('hexagon', 'visibility', 'none')
this.mapChart.getLayer('highlightHexagon') && this.mapChart.setLayoutProperty('highlightHexagon', 'visibility', 'none')
},
showHexagon () {
this.mapChart.getLayer('hexagon') && this.mapChart.setLayoutProperty('hexagon', 'visibility', 'visible')
this.mapChart.getLayer('highlightHexagon') && this.mapChart.setLayoutProperty('highlightHexagon', 'visibility', 'visible')
},
// 关注列表的添加、删除追踪
addOrRemoveTrackingSubscriber (subscriber) {
const find = this.trackingSubscribers.find(s => s.subscriberId === subscriber.subscriberId)
if (find) {
const index = this.trackingSubscribers.findIndex(s => s.subscriberId === subscriber.subscriberId)
this.trackingSubscribers.splice(index, 1)
} else {
this.trackingSubscribers.push({ ...subscriber, show: false, showLine: false, scrollStartIndex: 1, scrollEndIndex: 6, startOffset: 0, listHeight: 0 })
}
this.opacity = 0
setInterval(() => {
this.opacity += 0.05
if (this.opacity >= 1) {
this.opacity = 1
}
}, 16)
},
// 追踪页删除追踪
removeTrackingSubscriber (subscriber) {
const find = this.trackingSubscribers.find(s => s.subscriberId === subscriber.subscriberId)
if (find) {
const index = this.trackingSubscribers.findIndex(s => s.subscriberId === subscriber.subscriberId)
this.trackingSubscribers.splice(index, 1)
if (subscriber.subscriberId === this.currentShowSubscriber.subscriberId) {
// 如果删除的是当前正在地图上展示的,那么切换为展示第一个;如果删除后追踪列表清空了,则置为空
if (this.trackingSubscribers.length === 0) {
this.currentShowSubscriber = null
} else {
this.currentShowSubscriber = this.trackingSubscribers[0]
}
}
}
},
// 关注列表追踪图标class
symbolClass (subscriber) {
const find = this.trackingSubscribers.find(s => s.subscriberId === subscriber.subscriberId)
return find ? 'cn-icon-a-' : 'cn-icon-a-1'
},
// 地图上人图标鼠标悬浮框中点击追踪事件
trackSubscriber (subscriber) {
const find = this.trackingSubscribers.find(s => s.subscriberId === subscriber.subscriberId)
if (!find) {
this.trackingSubscribers.push({ ...subscriber, show: false, showLine: false, scrollStartIndex: 1, scrollEndIndex: 6, startOffset: 0, listHeight: 0 })
}
this.currentShowSubscriber = subscriber
this.activeTab = 'traceTracking'
this.tooltip.showMarkerTooltip = false
},
// 地图上点击多边形
hexagonClick (e) {
// 点击时已经高亮的话,取消过滤条件
if (this.tooltip.type === this.tooltipType.hexagon) {
if (this.searchList[2].value === this.currentPolygon.hexId) {
this.searchList[2].value = ''
} else {
this.searchList[2].value = this.currentPolygon.hexId
this.searchList[2].active = 1
this.rightExpanded = true
}
}
},
addHighlightHexagon () {
// 先在地图上找是否存在色块,存在则创建高亮色块
const sourceData = this.mapChart.getSource('hexGrid')._data
const find = sourceData.features.find(d => d.properties.hexId === this.searchList[2].value)
if (find) {
const featureCollection = { type: 'FeatureCollection' }
featureCollection.features = [{
id: 9920447175, // 随便写个数
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [
h3ToGeoBoundary(this.searchList[2].value, true)
]
}
}]
this.mapChart.addSource('highlightHexGrid', {
type: 'geojson',
data: featureCollection
})
this.mapChart.addLayer({
id: 'highlightHexagon',
type: 'line',
source: 'highlightHexGrid',
layout: {
visibility: this.currentZoom >= 9 ? 'visible' : 'none'
},
paint: {
'line-color': 'rgb(255,255,255)',
'line-width': 3
}
})
}
},
removeHighlightHexagon () {
this.mapChart.getLayer('highlightHexagon') && this.mapChart.removeLayer('highlightHexagon')
this.mapChart.getSource('highlightHexGrid') && this.mapChart.removeSource('highlightHexGrid')
},
async initSubscriberList () {
const params = {
startTime: this.timeFilter.startTime,
endTime: this.timeFilter.endTime,
pageNo: this.curPageNum,
pageSize: 20,
params: '',
isFollowed: this.onlyShowFollowed ? 1 : 0
}
const paramArray = []
if (this.getSearchItem('HEX ID').value) {
const levelField = this.mapLevelField.find(item => item.level === this.mapLevel)
paramArray.push(levelField ? levelField.field + "='" + this.getSearchItem('HEX ID').value + "'" : '')
}
this.searchList.forEach((s, i) => {
if (s.value && s.label !== 'HEX ID' && s.label !== this.$t('overall.source') && s.active === 1) {
paramArray.push(`${s.name} like '%${s.value}%'`)
} else if (s.value && s.label === this.$t('overall.source')) {
paramArray.push(`data_source='${s.value}'`)
}
})
if (paramArray.length > 0) {
params.params = paramArray.join(' and ')
}
try {
this.loading.subscriberLoading = true
// 根据顶部的时间条件查列表再根据底部时间轴的时间时间来查列表里的subscriber是否在线
// 加载新数据、时间轴变化时,重新查在线状态
await axios.get(api.location.totalCount, { params }).then(async response => {
if (response.status === 200) {
this.subscribersTotalCount = response.data.data[0].count
}
})
await axios.get(api.location.list, { params }).then(async response => {
if (response.status === 200) {
if (response.data.data.length === 0 && this.curPageNum > 1) {
this.loading.timeBarLoading = false
} else if (response.data.data.length === 0 && this.curPageNum === 1) {
this.loading.timeBarLoading = false
this.subscribersList = []
} else {
await this.setSubscriberActiveStatus(response.data.data)
if (params.pageNo === 1) {
this.subscribersList = response.data.data
} else {
response.data.data.forEach(d => {
this.subscribersList.push(d)
})
}
this.renderMarker(response.data.data, this.tooltipType.human)
}
}
})
// 异步查tag避免阻塞
setTimeout(() => {
this.subscribersList.forEach(s => {
if (!s.tags) {
s.tags = []
axios.get(`${api.entity.tags}/subscriber`, { params: { resource: s.subscriberId } }).then(response => {
if (response.status === 200) {
if (response.data.data && response.data.data.tags) {
s.tags = response.data.data.tags.map(tag => {
return { value: tag.name, color: intentColor[tag.intent] || entityDefaultColor }
})
}
}
})
}
})
}, 200)
} catch (e) {
this.$message.error(this.errorMsgHandler(e))
console.error(e)
} finally {
this.loading.subscriberLoading = false
}
},
async setSubscriberActiveStatus (subscriberList) {
this.loading.timeBarLoading = true
const subscriberIds = subscriberList.map(d => d.subscriberId)
const timelineParams = {
startTime: this.minuteTimeFilter.startTime,
endTime: this.minuteTimeFilter.endTime,
level: this.mapLevel,
subscriberIds: subscriberIds.map(id => `'${id}'`).join(',')
}
if (this.getSearchItem(this.$t('overall.source')).value) {
timelineParams.params = `data_source='${this.getSearchItem(this.$t('overall.source')).value}'`
}
await axios.get(api.location.tracking, { params: timelineParams }).then(async response => {
subscriberList.forEach(item => {
const find = response.data.data.result.find(d => {
if (d.subscriberId === item.subscriberId) {
const hasHexIdArr = d.trackRecords.filter(t => t.hexId)
if (hasHexIdArr.length > 0) {
// 将session record和sd的坐标点区分开
const sessionRecordPoint = hasHexIdArr.find(d => d.dataSource === 'Session Record')
const sdPoint = hasHexIdArr.find(d => d.dataSource === 'SD')
if (sessionRecordPoint) {
item.sessionRecordPoint = {
coordinate: [sessionRecordPoint.subscriberLongitude, sessionRecordPoint.subscriberLatitude],
hexId: sessionRecordPoint.hexId
}
}
if (sdPoint) {
item.sdPoint = {
coordinate: [sdPoint.subscriberLongitude, sdPoint.subscriberLatitude],
hexId: sdPoint.hexId
}
}
return true
} else {
item.sessionRecordPoint = null
item.sdPoint = null
}
}
return false
})
item.active = find ? 1 : 0
})
}).finally(() => {
this.loading.timeBarLoading = false
})
},
// 针对我的关注列表的单条记录的关注和取消关注
async handleFollow (item) {
const subscriber = this.subscribersList.find(subscriber => subscriber.subscriberId === item.subscriberId)
if (subscriber) {
if (item.isFollowed === 1) {
/* 刷新右侧列表 */
subscriber.isFollowed = 1
// 刷新地图上的人
const find = this.humanMarkers.find(m => m.subscriberId === subscriber.subscriberId)
if (find) {
find.removeClassName && find.removeClassName('map-marker--unfollowed')
}
// 变更地图上图标的class
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
this.hideFollowed()
} else {
this.showBaseStation()
this.showFollowed()
}
} else {
subscriber.isFollowed = 0
// 刷新地图上的人
const find = this.humanMarkers.find(m => m.subscriberId === subscriber.subscriberId)
if (find) {
find.addClassName && find.addClassName('map-marker--unfollowed')
}
// 变更地图上图标的class
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
this.hideFollowed()
} else {
this.showBaseStation()
this.showFollowed()
}
}
}
},
// 针对我的关注列表的批量取消关注操作
async handleCancelFollowBatch (items) {
this.closeRightBox()
items.forEach(item => {
this.handleFollow(item)
})
},
cancelFollowSubscribers (item) {
axios.delete(api.location.follow + '?subscriberIds=' + item.subscriberId).then(res => {
if (res.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('location.cancelFollow.success') })
/* 刷新右侧列表 */
item.isFollowed = 0
} else {
this.$message.error(res.data.message)
}
}).catch(e => {
this.$message.error(this.errorMsgHandler(e))
})
},
followSubscribers (item) {
if (item.isFollowed === 1) {
axios.delete(api.location.follow + '?subscriberIds=' + item.subscriberId).then(res => {
if (res.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('location.cancelFollow.success') })
/* 刷新右侧列表 */
item.isFollowed = 0
// 变更地图上图标的class
const find = this.humanMarkers.find(m => m.subscriberId === item.subscriberId)
if (find) {
find.addClassName && find.addClassName('map-marker--unfollowed')
}
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
this.hideFollowed()
} else {
this.showBaseStation()
this.showFollowed()
}
} else {
this.$message.error(res.data.message)
}
}).catch(e => {
this.$message.error(this.errorMsgHandler(e))
})
} else {
axios.post(api.location.follow, { subscriberIds: item.subscriberId }).then(async res => {
if (res.status === 200) {
this.$message({ duration: 2000, type: 'success', message: this.$t('location.follow.success') })
/* 刷新右侧列表 */
item.isFollowed = 1
// 变更地图上图标的class
const find = this.humanMarkers.find(m => m.subscriberId === item.subscriberId)
if (find) {
find.removeClassName && find.removeClassName('map-marker--unfollowed')
}
if (this.currentZoom && this.currentZoom < 6) {
this.hideBaseStation()
this.hideFollowed()
} else {
this.showBaseStation()
this.showFollowed()
}
} else {
this.$message.error(res.data.message)
}
}).catch(e => {
this.$message.error(this.errorMsgHandler(e))
})
}
},
async showFollowedSubscribers () {
this.curPageNum = 1
this.subscribersList = []
this.humanMarkers.forEach(marker => {
marker.remove && marker.remove()
})
this.humanMarkers = []
await this.initSubscriberList()
},
async searchSubscribers () {
this.curPageNum = 1
this.subscribersList = []
// 根据输入字符串,查找用户信息
await this.initSubscriberList()
},
mapTimeLineChange (timeFilter) {
this.minuteTimeFilter = {
startTime: getSecond(timeFilter.startTime),
endTime: getSecond(timeFilter.endTime)
}
},
async minuteTimeFilterChange () {
this.humanMarkers.forEach(marker => {
marker.remove && marker.remove()
})
this.humanMarkers = []
await this.setSubscriberActiveStatus(this.subscribersList)
this.renderMarker(this.subscribersList, this.tooltipType.human)
},
onResize () {
this.$nextTick(() => {
this.mapDomHeight = document.getElementById('analysisMap').offsetHeight + 150
})
},
onScroll (e) {
const find = this.trackingSubscribers.find(d => d.subscriberId === e.target.id)
// 当前滚动位置
const scrollTop = e.target.scrollTop
// 列表开始索引
const startIndex = Math.floor(scrollTop / this.scrollInfo.itemSize) || 1
// 列表结束索引
const endIndex = Math.ceil((scrollTop + this.scrollInfo.containerHeight) / this.scrollInfo.itemSize)
find.scrollStartIndex = startIndex
find.scrollEndIndex = endIndex
// 列表距离顶部距离
find.startOffset = scrollTop - (scrollTop % this.scrollInfo.itemSize)
},
getSearchItem (label) {
return this.searchList.find(l => l.label === label)
},
cleanCellsAndLines () {
this.cellMarkers.forEach(marker => {
marker.remove && marker.remove()
})
this.cellMarkers = []
this.mapChart.getLayer('baseStationToCellLines') && this.mapChart.removeLayer('baseStationToCellLines')
this.mapChart.getSource('baseStationToCellLines') && this.mapChart.removeSource('baseStationToCellLines')
},
drawCellsAndLines (targets) {
// 画cell
this.renderMarker(targets, this.tooltipType.cell)
// 画线
const points = targets.map(target => ([target.sourceCoordinate, [target.longitude, target.latitude]]))
const lines = turf.multiLineString(points)
this.mapChart.addSource('baseStationToCellLines', {
type: 'geojson',
data: lines
})
this.mapChart.addLayer({
id: 'baseStationToCellLines',
type: 'line',
source: 'baseStationToCellLines',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#586A7F',
'line-width': 2,
'line-dasharray': [2, 2]
}
})
}
},
watch: {
searchList: {
deep: true,
handler (n) {
// 强制更新dom解决按钮移动了但是弹框未移动的问题
this.$refs.searchItemList.$forceUpdate()
}
},
'searchList.0.active': {
deep: true,
handler (n, o) {
if (n === 0) {
if (this.searchList[0].value) {
this.searchList[0].value = ''
this.debounceSearch()
}
}
}
},
'searchList.1.active': {
deep: true,
handler (n, o) {
if (n === 0) {
if (this.searchList[1].value) {
this.searchList[1].value = ''
this.debounceSearch()
}
}
}
},
'searchList.2.active': {
deep: true,
handler (n, o) {
if (n === 0 && this.searchList[2].value) {
this.searchList[2].value = ''
}
}
},
'searchList.3.active': {
deep: true,
handler (n, o) {
if (n === 0 && this.searchList[3].value) {
this.searchList[3].value = ''
}
}
},
'searchList.2.value': {
deep: true,
async handler (n, o) {
this.removeHighlightHexagon()
// 如果值不为空,寻找对应色块并高亮
if (n) {
this.addHighlightHexagon()
}
this.debounceSearch()
}
},
'searchList.3.value': {
deep: true,
async handler (n, o) {
if (this.activeTab === 'locationMap') {
// 由于调整了source需要重新调整地图上marker的图标
this.updateHumanMarker()
this.debounceSearch()
}
}
},
async activeTab (n) {
this.initFlag = true
this.$store.state.headerMenuByTab = n
if (n === 'traceTracking') {
// 切换到轨迹追踪tab时先移除地图上已有的图层和事件绑定、人型图标。基站予以保留
this.unbindHexagonEvents()
this.mapChart.getLayer('hexagon') && this.mapChart.removeLayer('hexagon')
this.mapChart.getSource('hexGrid') && this.mapChart.removeSource('hexGrid')
this.removeHighlightHexagon()
this.searchList[2].value = ''
this.humanMarkers.forEach(marker => {
marker.remove && marker.remove()
})
this.humanMarkers = []
const newUrl = urlParamsHandler(`${window.location.protocol}//${window.location.host}/#/location/tracking`, {}, this.$route.query)
overwriteUrl(newUrl)
this.timeRefreshChange()
} else if (n === 'locationMap') {
this.unbindTrackingHexagonEvents()
this.mapChart.getLayer('trackingHexagon') && this.mapChart.removeLayer('trackingHexagon')
this.mapChart.getLayer('trackingLine') && this.mapChart.removeLayer('trackingLine')
this.mapChart.getSource('trackingHexGrid') && this.mapChart.removeSource('trackingHexGrid')
this.mapChart.getSource('trackingLineSource') && this.mapChart.removeSource('trackingLineSource')
this.trackingHumanMarker.remove && this.trackingHumanMarker.remove()
this.trackingHumanMarker = {}
const newUrl = urlParamsHandler(`${window.location.protocol}//${window.location.host}/#/location/map`, {}, this.$route.query)
overwriteUrl(newUrl)
this.timeRefreshChange()
}
},
// 时间轴改变时重新查询人marker
async minuteTimeFilter (n) {
// 避免初始化时请求,造成人的图标会闪一下
if (this.initFlag) {
this.initFlag = false
return
}
this.debounceMinuteChange?.()
},
// 切换追踪的用户
currentShowSubscriber (n) {
this.mapChart.getLayer('trackingHexagon') && this.mapChart.removeLayer('trackingHexagon')
this.mapChart.getLayer('trackingLine') && this.mapChart.removeLayer('trackingLine')
this.mapChart.getSource('trackingHexGrid') && this.mapChart.removeSource('trackingHexGrid')
this.mapChart.getSource('trackingLineSource') && this.mapChart.removeSource('trackingLineSource')
this.trackingHumanMarker.remove && this.trackingHumanMarker.remove()
this.trackingHumanMarker = {}
if (n) {
this.renderTrackingHexagon()
}
},
async timeFilter (n) {
this.boundaryBoxExtreme = null
if (this.activeTab === 'locationMap') {
this.initFlag = true
this.unbindHexagonEvents()
this.mapChart.getLayer('hexagon') && this.mapChart.removeLayer('hexagon')
this.mapChart.getSource('hexGrid') && this.mapChart.removeSource('hexGrid')
this.removeHighlightHexagon()
this.humanMarkers.forEach(marker => {
marker.remove && marker.remove()
})
this.humanMarkers = []
await this.initLocationMapTab()
if (this.searchList[2].value) {
this.addHighlightHexagon()
}
} else if (this.activeTab === 'traceTracking') {
this.unbindTrackingHexagonEvents()
this.mapChart.getLayer('trackingHexagon') && this.mapChart.removeLayer('trackingHexagon')
this.mapChart.getLayer('trackingLine') && this.mapChart.removeLayer('trackingLine')
this.mapChart.getSource('trackingHexGrid') && this.mapChart.removeSource('trackingHexGrid')
this.mapChart.getSource('trackingLineSource') && this.mapChart.removeSource('trackingLineSource')
this.trackingHumanMarker.remove && this.trackingHumanMarker.remove()
this.trackingHumanMarker = {}
await this.initTraceTrackingTab()
}
},
trackingSubscribers: {
deep: true,
handler (n) {
sessionStorage.setItem(storageKey.trackingSubscribers, JSON.stringify(n))
}
},
// 控制map loading
'loading.hexagonLoading': {
handler (n) {
this.loading.mapLoading = n || this.loading.timeBarLoading || this.loading.baseStationLoading
}
},
'loading.timeBarLoading': {
handler (n) {
this.loading.mapLoading = n || this.loading.hexagonLoading || this.loading.baseStationLoading
}
},
'loading.baseStationLoading': {
handler (n) {
this.loading.mapLoading = n || this.loading.timeBarLoading || this.loading.hexagonLoading
}
},
currentZoom (n, o) {
// zoom 小于11隐藏 marker
if (o && n < 6) {
this.hideBaseStation()
this.hideFollowed()
} else {
this.showBaseStation()
this.showFollowed()
}
// zoom 小于9隐藏色块
if (o && n < 9) {
this.hideHexagon()
} else {
this.showHexagon()
}
},
leftExpanded (n, o) {
if (n) {
setTimeout(() => {
this.pieChart.resize && this.pieChart.resize()
this.lineChart.resize && this.lineChart.resize()
}, 300)
}
}
},
computed: {
computeListHeight () {
const step = 34
const baseHeight = 68
const showCount = this.searchList.filter(l => l.active === 1).length
return { height: `calc(100% - ${baseHeight + showCount * step}px)` }
},
tooltipHeaderColor () {
if (this.tooltip.type === this.tooltipType.hexagon) {
const color = this.currentPolygon.color.split(',')
color[0] = color[0].split('[')[1]
color[2] = color[2].split(']')[0]
return `rgba(${color.join(',')},.7)`
} else if (this.tooltip.type === this.tooltipType.human) {
return 'var(--el-color-business)'
} else if (this.tooltip.type === this.tooltipType.baseStation) {
return '#233447'
} else if (this.tooltip.type === this.tooltipType.cell) {
return '#274E85'
}
return ''
},
locationHandler () {
return function (item) {
const result = []
if (item) {
if (item.country) {
result.push(item.country)
}
if (item.superAdministrativeArea) {
result.push(item.superAdministrativeArea)
}
if (item.administrativeArea) {
result.push(item.administrativeArea)
}
}
return result.length > 0 ? result.join(', ') : '-'
}
}
},
async mounted () {
/* const result = []
const startLat = 39.5
const startLng = 115.8
const endLat = 40.3
const endLng = 116.8
const latStep = 0.0028
const lngStep = 0.00435
for (let i = startLat; i < endLat; i += latStep) {
for (let j = startLng; j < endLng; j += lngStep) {
const r = Math.random()
let number = Math.round(r * r * r * 1000)
if (number > 1000) {
number = 1000
}
if (number < 1) {
number = 1
}
result.push({ hexId: geoToH3(i, j, 8), number })
}
}
const uniqueData = removeDuplicateHexIds(result)
console.info(JSON.stringify(uniqueData))
function removeDuplicateHexIds (array) {
// 创建一个Map来存储唯一的hexId和对应的对象
const uniqueObjects = new Map()
// 遍历数组如果Map中没有这个hexId则将其加入Map
array.forEach(item => {
if (!uniqueObjects.has(item.hexId)) {
uniqueObjects.set(item.hexId, item)
}
})
// 将Map中的值即对象转换回数组
return Array.from(uniqueObjects.values())
} */
await this.initMap()
this.debounceMinuteChange = _.debounce(this.minuteTimeFilterChange, 500)
this.debounceOnResize = _.debounce(this.onResize, 500)
this.debounceVisualChange = _.debounce(this.hexagonVisualRangeChange, 500)
this.debounceSearch = _.debounce(this.searchSubscribers, 500)
this.onResize()
},
setup () {
const { currentRoute } = useRouter()
const currentPath = currentRoute.value.path
const activeTab = ref('')
const dropDownValue = ref('')
switch (currentPath) {
case ('/location/map'): {
activeTab.value = 'locationMap'
break
}
case ('/location/tracking'): {
activeTab.value = 'traceTracking'
break
}
}
const { query } = useRoute()
// 获取url携带的range、startTime、endTime、id(subscriberId)
const rangeParam = query.range
const startTimeParam = query.startTime
const endTimeParam = query.endTime
const idParam = query.subscriberId
// 优先级url > config.js > 默认值。
const dateRangeValue = rangeParam ? parseInt(rangeParam) : (DEFAULT_TIME_FILTER_RANGE.dashboard || 60)
const timeFilter = ref({ dateRangeValue })
if (!startTimeParam || !endTimeParam || dateRangeValue > -1) {
const { startTime, endTime } = getNowTime(dateRangeValue)
timeFilter.value.startTime = getSecond(startTime)
timeFilter.value.endTime = getSecond(endTime)
// 将参数写入url
const newUrl = urlParamsHandler(window.location.href, useRoute().query, { startTime: timeFilter.value.startTime, endTime: timeFilter.value.endTime, range: dateRangeValue })
overwriteUrl(newUrl)
} else {
timeFilter.value.startTime = parseInt(startTimeParam)
timeFilter.value.endTime = parseInt(endTimeParam)
}
const minuteTimeFilter = ref({})
const tooltip = ref({
type: ''
})
// 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 = ['196,214,59', '190,230,255', '135,206,250', '63,133,186', '37,55,128']
// const pieColorRamp = ['196,214,59', '135,206,250', '63,133,186', '45,65,135', '34,7,90']
const pieColorRamp = ['135,206,250', '63,133,186', '45,65,135', '34,7,90']
const pieValueRamp = ref([])
const subscribersList = ref([])
const subscribersTotalCount = ref(0)
const searchList = ref([
{
label: 'ID',
name: 'subscriber_id',
value: '',
type: 'input',
active: 1
},
{
label: 'MSISDN',
name: 'phone_number',
value: '',
type: 'input',
active: 0
},
{
label: 'HEX ID',
name: 'second_location',
value: '',
type: 'input',
active: 0
},
{
label: i18n.global.t('overall.source'),
name: 'data_source',
value: '',
type: 'select',
options: [
{
label: i18n.global.t('overall.all'),
value: ''
},
{
label: i18n.global.t('location.baseStation'),
value: 'SD'
},
{
label: i18n.global.t('overall.leaked'),
value: 'Session Record'
}
],
active: 0
}
])
const boundaryBox = ref({}) // minLongitude、maxLongitude、minLatitude、maxLatitude
const boundaryBoxExtreme = ref(null) // minLongitude、maxLongitude、minLatitude、maxLatitude
const mapChart = shallowRef(null)
const currentMarkerDom = shallowRef(null)
const humanMarkers = shallowRef([])
const baseStationMarkers = shallowRef([])
const cellMarkers = shallowRef([])
const trackingHumanMarker = shallowRef({})
const pieChart = shallowRef(null)
const pieOption = ref({})
const lineChart = shallowRef(null)
const lineOption = ref({})
const currentBaseStation = ref({})
const currentSubscriber = ref({})
const currentPolygon = ref({})
const currentCell = ref({})
const highlightTrackingTimeline = ref({})
const highlightSubscriber = ref({})
const highlightBaseStation = ref({})
// 从localStorage中获取数据
const trackingSubscribers = ref([])
sessionStorage.getItem(storageKey.trackingSubscribers) && (trackingSubscribers.value = JSON.parse(sessionStorage.getItem(storageKey.trackingSubscribers)).map(item => ({ ...item, show: false, showLine: false, scrollStartIndex: 1, scrollEndIndex: 6, startOffset: 0, listHeight: 0 })))
/* const test = ['gary6411', 'test6431', 'test6430', 'test6422']
test.forEach(id => {
trackingSubscribers.value.push({ subscriberId: id, show: false, showLine: false })
}) */
const currentShowSubscriber = ref(null)
const loading = ref({
mapLoading: true, // mapLoading控制location地图的loading它状态同时受hexagonLoading、timeBarLoading、baseStationLoading影响
hexagonLoading: true, // 六边形加载状态
timeBarLoading: true, // 时间轴和地图上的人型图标的加载状态
baseStationLoading: true, // 基站加载状态
subscriberLoading: true, // 控制右侧关注用户列表加载状态
pieLoading: true, // 控制饼图加载状态
lineLoading: true, // 控制折线图加载状态
searchLoading: false, // 搜索框加载状态
trackingMapLoading: true // 控制追踪地图加载状态
})
const mapDomHeight = ref(0)
const tooltipDomHeight = {
hexagon: 153,
baseStation: 153,
human: 167
}
const mapConfig = localStorage.getItem(storageKey.mapConfig) ? JSON.parse(localStorage.getItem(storageKey.mapConfig)) : defaultMapConfig
return {
activeTab,
dropDownValue,
timeFilter,
idFromUrl: ref(idParam), // 从url获取的subscriberId
minuteTimeFilter, // 底下时间轴的时间
searchList, // 搜索框下拉列表
leftExpanded: ref(false), // 左右侧栏是否展开
rightExpanded: ref(true),
tooltip, // 控制鼠标悬浮框
pieColorRamp, // 六边形颜色坡度
pieValueRamp, // 饼图数值坡度,动态获取
subscribersList, // Location用户列表
subscribersTotalCount, // Location用户总数
boundaryBox, // 查六边形数据的经纬度范围minLongitude、maxLongitude、minLatitude、maxLatitude
boundaryBoxExtreme, // boundaryBox的历史极值用来判断当前boundaryBox下是否需要查数据
mapChart, // 地图对象
currentMarkerDom, // 记录当前鼠标悬停的marker的dom
humanMarkers, // 储存人marker的引用
baseStationMarkers, // 储存基站marker的引用
cellMarkers, // 储存cell marker的引用
trackingHumanMarker, // 追踪页的人marker
pieChart, // 饼图对象
pieOption,
lineChart, // 折线图对象
lineOption,
mapPolygonSourceData: shallowRef({}), // locationMap 的 maplibre sourceData
trackingPolygonSourceData: shallowRef({}), // traceTracking 的 maplibre sourceData
currentBaseStation, // 鼠标当前悬浮的基站
currentSubscriber, // 鼠标当前悬浮的Subscriber
currentPolygon, // 鼠标当前悬浮的六边形
currentCell, // 鼠标当前悬浮的cell
highlightTrackingTimeline, // tracking页高亮的时间线
highlightSubscriber, // locationMap页保持高亮的subscriber
highlightBaseStation, // 高亮的基站
trackingSubscribers, // 存放当前追踪的Subscriber列表
currentShowSubscriber, // 当前在地图上展示轨迹的Subscriber
trackingSubscriberRecordMap: [], // record数据量大时vue监听性能开销太大所以单独用非监听的数组来维护subscriberId与record的关系
loading, // 控制组件内各处loading图标
maxZoom: mapConfig.maxZoom, // 地图最小缩放比例
minZoom: mapConfig.minZoom, // 地图最大缩放比例
currentZoom: ref(mapConfig.defaultZoom),
mapLevel: mapConfig.mapLevel, // 地图精度 1、2、3
unitTypes,
defaultZoom: mapConfig.defaultZoom, // 地图默认缩放比例
center: mapConfig.center, // 地图默认中心点。北京:[116.38, 39.9] 纽约:[-73.94539, 40.841843]
debounceMinuteChange: shallowRef(null),
debounceOnResize: shallowRef(null),
debounceVisualChange: shallowRef(null),
debounceSearch: shallowRef(null),
mapLevelField,
mapDomHeight, // 地图dom的高度用来计算悬浮框的位置
tooltipDomHeight // 计算悬浮框位置时默认的悬浮框高度
}
},
unmounted () {
if (this.mapChart && this.mapChart.remove) {
this.mapChart && this.mapChart.remove()
}
if (this.pieChart && this.pieChart.dispose) {
this.pieChart && this.pieChart.dispose()
}
if (this.lineChart && this.lineChart.dispose) {
this.lineChart && this.lineChart.dispose()
}
}
}
</script>