Commit f3c6c57a authored by liziwl's avatar liziwl
Browse files

用vue3重写bus地图

parent 43f33df3
Loading
Loading
Loading
Loading
+430 −0
Original line number Diff line number Diff line
<template>
  <v-chart class="chart" :option="echartsOption"/>
</template>

<script>
import {use} from 'echarts/core';
import {CanvasRenderer} from 'echarts/renderers';
import {PieChart} from 'echarts/charts';
import {
  TitleComponent,
  TooltipComponent,
  LegendComponent,
} from 'echarts/components';
import VChart, {THEME_KEY} from 'vue-echarts';
import {ref, defineComponent} from 'vue';
import axios from 'axios';
import {nearestPoint, nearestPointOnLine, length, point, along, rhumbBearing} from '@turf/turf'

use([
  CanvasRenderer,
  PieChart,
  TitleComponent,
  TooltipComponent,
  LegendComponent,
]);

export default defineComponent({
  name: 'BusChartVue',
  components: {
    VChart,
  },
  provide: {
    [THEME_KEY]: 'dark',
  },
  data: () => ({
    bus_plate_hash: {
      "8371": {"plate": "粤BDF371", 'route': 1},//确定
      "8421": {"plate": "粤BDF421", 'route': 1},//确定
      "8471": {"plate": "粤BDF471", 'route': 1},//确定
      "8147": {"plate": "粤BDF147", 'route': 1},//确定
      "8335": {"plate": "粤BDF335", 'route': 1},//确定
      "8345": {"plate": "粤BDF345", 'route': 1},//确定
      "8365": {"plate": "粤BDF365", 'route': 1},//确定
      "8411": {"plate": "粤BDF411", 'route': 1},//确定
      "8447": {"plate": "粤BDE447", 'route': 1},//确定
      "18447": {"plate": "粤BDF447", 'route': 1},//确定
      "8458": {"plate": "粤BDF458", 'route': 1},//确定
      "8267": {"plate": "粤BDF267", 'route': 1},
      "8338": {"plate": "粤BDF338", 'route': 1},
      "8330": {"plate": "粤BDF330", 'route': 1},
      "298": {"plate": "粤BDF298", 'route': 1},
      "363": {"plate": "粤BDF363", 'route': 1},
      "8040": {"plate": "粤BDF040", 'route': 1},
      "8430": {"plate": "粤BDF430", 'route': 1},
      "8470": {"plate": "粤BDF470", 'route': 1}//确定
    },
    lines: [],
    stops1: [],
    stops2: [],
    historyBusData: [],
    echartsOption: {
      tooltip: {
        show: false,
      },
      // title: [{
      //   text: '南科大校巴实时位置',
      //   subtext: 'bilibili@交通数据小旭学长'
      // }],
      grid: [{
        top: '13%',
        bottom: '0%',
        left: '3%',
        right: '6%',
      }],
      yAxis: [{
        inverse: true,
        min: -100,
        max: 5000,
        type: 'value',
        boundaryGap: false,
        show: false,

        splitLine: {
          show: false
        }
      }],
      xAxis: [{
        position: 'top',
        verticalAlign: 'top',
        axisLine: {
          show: false
        },
        axisTick: {
          show: false
        },
        axisLabel: {interval: 0},
        type: 'category',
        data: ['1号线\n工学院方向',
          '1号线\n欣园方向',
          '2号线\n科研楼方向',
          '2号线\n欣园方向'],
        splitLine: {
          show: false
        }

      }],
      series: [{
        type: 'scatter',
        label: {
          fontSize: 10,
          show: true,
          color: '#999',
          position: 'right',
          formatter: '{b}'
        },
        data: []
      }, {
        type: 'lines',
        coordinateSystem: 'cartesian2d',
        data: []
      }, {
        label: {
          fontSize: 11,
          show: true,
          fontWeight: 'bold',
          position: 'right',
          distance: -5,
          formatter: '{b}'
        },
        type: 'scatter',
        name: 'bus',
        data: []
      }
      ],
      stateAnimation: {
        duration: 500
      }
    },
    timer: {},
    second: 0
  }),
  methods: {
    setEchartsOption(appendOption) {
      this.echartsOption = Object.assign({}, this.echartsOption, appendOption)
    },
    //加载线路与站点信息
    load_data() {
      axios.all([
        axios.get('/bus_echart/line1.json'),
        axios.get('/bus_echart/line1_dir2.json'),
        axios.get('/bus_echart/line2.json'),
        axios.get('/bus_echart/stop1.json'),
        axios.get('/bus_echart/stop2.json'),
      ])
          .then(axios.spread((line1, line1_dir2, line2, stop1, stop2) => {
            const line1data = line1.data
            const line1data2 = line1_dir2.data
            const line2data = line2.data
            const stop1data = stop1.data
            const stop2data = stop2.data
            this.lines = [line1data, line2data, line1data2];
            this.stops1 = stop1data;
            this.stops2 = stop2data;
            const line1dir1 = stop1data.features.map(f => {
              return {
                value: [1, nearestPointOnLine(line1data['features'][0], f).properties.location * 1000],
                name: f.properties.name,
                symbolSize: 8,
                itemStyle: {color: '#ff881b', opacity: 1}
              }
            })
            const line1dir2 = stop1data.features.map(f => {
              return {
                value: [0, length(line1data2['features'][0]) * 1000 - nearestPointOnLine(line1data2['features'][0], f).properties.location * 1000],
                name: f.properties.name,
                symbolSize: 8,
                itemStyle: {color: '#ff881b', opacity: 1}
              }
            })
            const line2dir1 = stop2data.features.map(f => {
              return {
                value: [3, nearestPointOnLine(line2data['features'][0], f).properties.location * 1000],
                name: f.properties.name,
                symbolSize: 8,
                itemStyle: {color: '#379ff4', opacity: 1}
              }
            })
            const line2dir2 = stop2data.features.map(f => {
              return {
                value: [2, length(line2data['features'][0]) * 1000 - nearestPointOnLine(line2data['features'][0], f).properties.location * 1000],
                name: f.properties.name,
                symbolSize: 8,
                itemStyle: {color: '#379ff4', opacity: 1}
              }
            })
            const echartsOptionAppend = {
              series: [{
                data: [...line1dir1, ...line1dir2, ...line2dir1, ...line2dir2]
              }, {
                data: [
                  {
                    coords: [[0, 0], [0, length(line1data2['features'][0]) * 1000]],
                    lineStyle: {color: '#ff881b', width: 2, opacity: 1}
                  },
                  {
                    coords: [[1, 0], [1, length(line1data['features'][0]) * 1000]],
                    lineStyle: {color: '#ff881b', width: 2, opacity: 1}
                  },
                  {
                    coords: [[2, 0], [2, length(line2data['features'][0]) * 1000]],
                    lineStyle: {color: '#379ff4', width: 2, opacity: 1}
                  },
                  {
                    coords: [[3, 0], [3, length(line2data['features'][0]) * 1000]],
                    lineStyle: {color: '#379ff4', width: 2, opacity: 1}
                  }
                ]
              }]
            }
            this.setEchartsOption(echartsOptionAppend)
          }));
    },
    updateBusPos() {
      if (this.lines.length > 0) {
        axios.get('https://bus.sustcra.com/api/v2/monitor_osm/').then(response => {
          const res = response.data
          const busData = res.filter(f => f.it == 0).map(f => {
                //哪条线路
                let thisRoute = 0
                if (f.route_code == 202) {
                  thisRoute = 1
                }
                //判断是在哪个方向上
                const mcp = point([f.lng, f.lat])
                let thisLine = this.lines[thisRoute]['features'][0]
                //线上最近点
                let p_nearest = nearestPointOnLine(thisLine, mcp)
                let p_nearest_loc = p_nearest.properties.location
                //线上最近点+1米处的点
                const p_next = along(thisLine, p_nearest_loc + 0.0001);
                //计算切线角度
                let bearing = rhumbBearing(p_nearest, p_next);
                if (bearing < 0) {
                  bearing += 360
                }
                //通过车辆方向角判断车辆行进方向
                let route_dir = 2
                const angle = parseInt(f.course) - parseInt(bearing)
                if ((angle < 20) && (angle > -20)) {
                  route_dir = 1
                  return {
                    value: [route_dir + thisRoute * 2, p_nearest_loc * 1000],
                    name: this.bus_plate_hash[f.id].plate, itemStyle: {color: '#222'},
                    symbol: 'image://https://bus.sustcra.com/bus-top-view.png',
                    symbolSize: 30,
                    symbolRotate: 180,
                    speed: f.speed
                  }
                } else if ((angle < -160) || (angle > 160)) {
                  route_dir = 0
                  let dist = length(thisLine) * 1000 - p_nearest_loc * 1000
                  //如果是线路1的另一个方向,则重新计算距离
                  if (thisRoute == 0) {
                    thisLine = this.lines[2]['features'][0]
                    p_nearest = nearestPointOnLine(thisLine, mcp)
                    p_nearest_loc = p_nearest.properties.location
                    dist = length(thisLine) * 1000 - p_nearest_loc * 1000
                  }
                  return {
                    value: [route_dir + thisRoute * 2, dist],
                    name: this.bus_plate_hash[f.id].plate, itemStyle: {color: '#222'},
                    symbol: 'image://https://bus.sustcra.com/bus-top-view.png',
                    symbolSize: 30,
                    symbolRotate: 180,
                    speed: f.speed
                  }
                } else {
                  //从历史信息里找到这辆车的信息
                  if (this.historyBusData.length > 0) {
                    const historyBusData_this = this.historyBusData.filter(p => {
                          if (typeof (p) != 'undefined') {
                            return p.name == this.bus_plate_hash[f.id].plate
                          }
                        }
                    )
                    if (historyBusData_this.length > 0) {
                      return historyBusData_this[0]
                    }
                  }
                }
              }
          )
          this.historyBusData = busData.filter(p => typeof (p) != 'undefined')
          const echartsOptionAppend = {series: [{}, {}, {data: busData}]}
          this.setEchartsOption(echartsOptionAppend)
        })
      }
    },
    refresh() {
      this.second += 1
      //5秒校准一次正确位置
      if (this.second % 10 === 0) {
        this.updateBusPos()
      } else {
        //0.5秒通过速度推测一次车辆的位置
        const newdata = this.historyBusData.map(f => {
          if (typeof (f) != 'undefined') {
            let routedir = 2
            if (f.value[0] == 1) {
              routedir = 0
            }
            if (f.value[0] == 2) {
              routedir = 1
            }
            if (f.value[0] == 3) {
              routedir = 1
            }
            if (f.value[0] == 0) {
              routedir = 2
            }
            let next_dist = f.value[1] + f.speed * 0.5 * 1000 / 7200
            if (next_dist > length(this.lines[routedir]['features'][0]) * 1000
            ) {
              next_dist = length(this.lines[routedir]['features'][0]) * 1000
            }
            return {...f, value: [f.value[0], next_dist]}
          }
        })
        this.historyBusData = newdata.filter(p => typeof (p) != 'undefined');
        this.setEchartsOption({series: [{}, {}, {data: newdata}]})
        //获取定位
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(pos => {
            let lat = pos.coords.latitude,
                lng = pos.coords.longitude;
            //标记出最近的站点
            const p = point([lng, lat])
            const nearest_line1 = nearestPoint(p, this.stops1);
            const line1_pos_dir1 = nearestPointOnLine(this.lines[0]['features'][0], nearest_line1).properties.location * 1000
            const line1_pos_dir2 = length(this.lines[2]['features'][0]) * 1000 - nearestPointOnLine(this.lines[2]['features'][0], nearest_line1).properties.location * 1000
            const nearest_line2 = nearestPoint(p, this.stops2);

            const line2_pos_dir1 = nearestPointOnLine(this.lines[1]['features'][0], nearest_line2).properties.location * 1000
            const line2_pos_dir2 = length(this.lines[1]['features'][0]) * 1000 - nearestPointOnLine(this.lines[1]['features'][0], nearest_line2).properties.location * 1000
            //计算下一趟车大概多久到
            //0
            const buspos_0 = this.historyBusData.filter(f => (f.value[0] === 0) && (f.value[1] <= line1_pos_dir2)).map(f => parseInt((line1_pos_dir2 - f.value[1]) / 250))
            //1
            const buspos_1 = this.historyBusData.filter(f => (f.value[0] === 1) && (f.value[1] <= line1_pos_dir1)).map(f => parseInt((line1_pos_dir1 - f.value[1]) / 250))
            //2
            const buspos_2 = this.historyBusData.filter(f => (f.value[0] === 2) && (f.value[1] <= line2_pos_dir2)).map(f => parseInt((line2_pos_dir2 - f.value[1]) / 250))
            //3
            const buspos_3 = this.historyBusData.filter(f => (f.value[0] === 3) && (f.value[1] <= line2_pos_dir1)).map(f => parseInt((line2_pos_dir1 - f.value[1]) / 250))
            const bustext_0 = buspos_0.length > 0 ? '\n' + Math.min(...buspos_0) + '分钟' : ''
            const bustext_1 = buspos_1.length > 0 ? '\n' + Math.min(...buspos_1) + '分钟' : ''
            const bustext_2 = buspos_2.length > 0 ? '\n' + Math.min(...buspos_2) + '分钟' : ''
            const bustext_3 = buspos_3.length > 0 ? '\n' + Math.min(...buspos_3) + '分钟' : ''
            let data1 = [];
            let data2 = [];

            data1 = [
              {
                name: '最近' + bustext_1,
                itemStyle: {color: '#ff881b'},
                coord: [1, line1_pos_dir1]
              },
              {
                name: '最近' + bustext_0,
                itemStyle: {color: '#ff881b'},
                coord: [0, line1_pos_dir2]
              }]

            data2 = [{
              name: '最近' + bustext_3,
              itemStyle: {color: '#379ff4'},
              coord: [3, line2_pos_dir1]
            },
              {
                name: '最近' + bustext_2,
                itemStyle: {color: '#379ff4'},
                coord: [2, line2_pos_dir2]
              }
            ]

            this.setEchartsOption({
              series: [{
                markPoint: {
                  symbol: 'arrow',
                  symbolRotate: -90,
                  symbolOffset: ['-50%', 0],
                  symbolSize: 10,
                  label: {
                    fontSize: 10,
                    show: true,
                    color: '#666',
                    position: 'left', formatter: '{b}'
                  },
                  data: [
                    ...data1,
                    ...data2
                  ]
                }
              }, {}, {}]
            })
          })
        }
      }
    }
  },
  watch: {
    lines: 'updateBusPos'
  },
  mounted() {
    this.load_data();
    this.timer = setInterval(() => {
      this.refresh();
    }, 5000);
  },
  unmounted() {
    clearInterval(this.timer);
  }
});
</script>

<style scoped>
.chart {
  height: 800px;
  width: 100%;
}
</style>
+111 −106
Original line number Diff line number Diff line
@@ -340,7 +340,8 @@ export default {
      ]
    },
    bus_marker_arr: [],
    map: []
    map: [],
    timer: {}
  }),

  async created() {
@@ -608,11 +609,15 @@ export default {

    //refresh timer

    const timer = setInterval(() => {
    this.timer = setInterval(() => {
      this.refresh();
    }, 5000);


  },

  async unmounted() {
    clearInterval(this.timer);
  }
};
</script>
+4 −4
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ function post_to_wx() {
setInterval(post_to_wx, 1000);

function handleOutURL(url, whitelist_flag, file_flag, file_ext) {
    console.log("劫持链接 " + url);
    // console.log("劫持链接 " + url);
    wx.miniProgram.navigateTo({
        url: '/pages/index/redirect?outURL=' + encodeURIComponent(url) +
            '&inwhitelist=' + whitelist_flag +
@@ -51,13 +51,13 @@ function override_onclick(event) {
        let file_flag = supportFiles.has(path_ext);
        if (whitelist_flag && !file_flag) {
            // 当 url 在白名单里面,且不为可微信显示的文件。
            console.log("放行白名单页面 " + url);
            // console.log("放行白名单页面 " + url);
            window.location.href = url;
            return;
        }

        event.preventDefault();
        console.log("小程序环境,拦截外部链接或者可显示文件。");
        // console.log("小程序环境,拦截外部链接或者可显示文件。");
        handleOutURL(url, whitelist_flag, file_flag, path_ext);
        // return false;
    }
@@ -72,7 +72,7 @@ function reset_all_anchor() {
    for (var i = 0; i < anchors.length; i++) {
        var anchor = anchors[i];
        if (anchor.hasAttribute("data-fancybox")) {
            console.log("skip fancybox a tag: ", anchor.getAttribute('href'));
            // console.log("skip fancybox a tag: ", anchor.getAttribute('href'));
        } else {
            anchor.onclick = function () {
                override_onclick(event);
+1 −0
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@
    "@vuepress/plugin-toc": "2.0.0-beta.49",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "vue-echarts": "^6.2.3",
    "vuepress": "2.0.0-beta.49",
    "vuepress-plugin-sitemap2": "^2.0.0-beta.86"
  },
+18 −0
Original line number Diff line number Diff line
@@ -5844,6 +5844,11 @@ require-from-string@^2.0.2:
  resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==

resize-detector@^0.3.0:
  version "0.3.0"
  resolved "https://registry.npmmirror.com/resize-detector/-/resize-detector-0.3.0.tgz#fe495112e184695500a8f51e0389f15774cb1cfc"
  integrity sha512-R/tCuvuOHQ8o2boRP6vgx8hXCCy87H1eY9V5imBYeVNyNVpuL9ciReSccLj2gDcax9+2weXy3bc8Vv+NRXeEvQ==

resize-observer-polyfill@^1.5.1:
  version "1.5.1"
  resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
@@ -6497,6 +6502,19 @@ vue-demi@*:
  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.12.5.tgz#8eeed566a7d86eb090209a11723f887d28aeb2d1"
  integrity sha512-BREuTgTYlUr0zw0EZn3hnhC3I6gPWv+Kwh4MCih6QcAeaTlaIX0DwOVN0wHej7hSvDPecz4jygy/idsgKfW58Q==

vue-demi@^0.13.2:
  version "0.13.11"
  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
  integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==

vue-echarts@^6.2.3:
  version "6.2.3"
  resolved "https://registry.npmmirror.com/vue-echarts/-/vue-echarts-6.2.3.tgz#77973c417a56bca76847576ab903ab92979d75bb"
  integrity sha512-xHzUvgsgk/asJTcNa8iVVwoovZU3iEUHvmBa3bzbiP3Y6OMxM1YXsoWOKVmVVaUusGs4ob4pSwjwNy2FemAz9w==
  dependencies:
    resize-detector "^0.3.0"
    vue-demi "^0.13.2"

vue-loader@^17.0.0:
  version "17.0.0"
  resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb"