xjj před 2 roky
rodič
revize
2abfc7bad1

+ 829 - 0
public/Lushu.js

@@ -0,0 +1,829 @@
+/**
+ * @fileoverview 百度地图的轨迹跟随类,对外开放。
+ * 用户可以在地图上自定义轨迹运动
+ * 可以自定义路过某个点的图片,文字介绍等。
+ * 主入口类是<a href="examples/index.html">LuShu</a>,
+ * 基于Baidu Map API GL 1.0
+ *
+ * @author Baidu Map Api Group
+ * @version 1.0
+ */
+
+/**
+ * @namespace BMapGL的所有library类均放在BMapGLLib命名空间下
+ */
+var BMapGLLib = (window.BMapGLLib = BMapGLLib || {});
+
+(function () {
+  var T;
+  var baidu = (T = baidu || { version: "gl 1.0" });
+  baidu.guid = "$BAIDU$";
+  (function () {
+    window[baidu.guid] = window[baidu.guid] || {};
+    baidu.dom = baidu.dom || {};
+    baidu.dom.g = function (id) {
+      if ("string" == typeof id || id instanceof String) {
+        return document.getElementById(id);
+      } else if (id && id.nodeName && (id.nodeType == 1 || id.nodeType == 9)) {
+        return id;
+      }
+      return null;
+    };
+    baidu.g = baidu.G = baidu.dom.g;
+    baidu.lang = baidu.lang || {};
+    baidu.lang.isString = function (source) {
+      return "[object String]" == Object.prototype.toString.call(source);
+    };
+    baidu.isString = baidu.lang.isString;
+    baidu.dom._g = function (id) {
+      if (baidu.lang.isString(id)) {
+        return document.getElementById(id);
+      }
+      return id;
+    };
+    baidu._g = baidu.dom._g;
+    baidu.dom.getDocument = function (element) {
+      element = baidu.dom.g(element);
+      return element.nodeType == 9
+        ? element
+        : element.ownerDocument || element.document;
+    };
+    baidu.browser = baidu.browser || {};
+    baidu.browser.ie = baidu.ie = /msie (\d+\.\d+)/i.test(navigator.userAgent)
+      ? document.documentMode || +RegExp["\x241"]
+      : undefined;
+    baidu.dom.getComputedStyle = function (element, key) {
+      element = baidu.dom._g(element);
+      var doc = baidu.dom.getDocument(element),
+        styles;
+      if (doc.defaultView && doc.defaultView.getComputedStyle) {
+        styles = doc.defaultView.getComputedStyle(element, null);
+        if (styles) {
+          return styles[key] || styles.getPropertyValue(key);
+        }
+      }
+      return "";
+    };
+    baidu.dom._styleFixer = baidu.dom._styleFixer || {};
+    baidu.dom._styleFilter = baidu.dom._styleFilter || [];
+    baidu.dom._styleFilter.filter = function (key, value, method) {
+      var filters = baidu.dom._styleFilter;
+      var filter;
+      for (var i = 0; (filter = filters[i]); i++) {
+        if ((filter = filter[method])) {
+          value = filter(key, value);
+        }
+      }
+      return value;
+    };
+    baidu.string = baidu.string || {};
+
+    baidu.string.toCamelCase = function (source) {
+      if (source.indexOf("-") < 0 && source.indexOf("_") < 0) {
+        return source;
+      }
+      return source.replace(/[-_][^-_]/g, function (match) {
+        return match.charAt(1).toUpperCase();
+      });
+    };
+    baidu.dom.getStyle = function (element, key) {
+      var dom = baidu.dom;
+      element = dom.g(element);
+      key = baidu.string.toCamelCase(key);
+
+      var value =
+        element.style[key] ||
+        (element.currentStyle ? element.currentStyle[key] : "") ||
+        dom.getComputedStyle(element, key);
+
+      if (!value) {
+        var fixer = dom._styleFixer[key];
+        if (fixer) {
+          value = fixer.get
+            ? fixer.get(element)
+            : baidu.dom.getStyle(element, fixer);
+        }
+      }
+
+      if ((fixer = dom._styleFilter)) {
+        value = fixer.filter(key, value, "get");
+      }
+      return value;
+    };
+    baidu.getStyle = baidu.dom.getStyle;
+    baidu.dom._NAME_ATTRS = (function () {
+      var result = {
+        cellpadding: "cellPadding",
+        cellspacing: "cellSpacing",
+        colspan: "colSpan",
+        rowspan: "rowSpan",
+        valign: "vAlign",
+        usemap: "useMap",
+        frameborder: "frameBorder",
+      };
+
+      if (baidu.browser.ie < 8) {
+        result["for"] = "htmlFor";
+        result["class"] = "className";
+      } else {
+        result["htmlFor"] = "for";
+        result["className"] = "class";
+      }
+      return result;
+    })();
+
+    baidu.dom.setAttr = function (element, key, value) {
+      element = baidu.dom.g(element);
+      if ("style" == key) {
+        element.style.cssText = value;
+      } else {
+        key = baidu.dom._NAME_ATTRS[key] || key;
+        element.setAttribute(key, value);
+      }
+      return element;
+    };
+    baidu.setAttr = baidu.dom.setAttr;
+    baidu.dom.setAttrs = function (element, attributes) {
+      element = baidu.dom.g(element);
+      for (var key in attributes) {
+        baidu.dom.setAttr(element, key, attributes[key]);
+      }
+      return element;
+    };
+    baidu.setAttrs = baidu.dom.setAttrs;
+    baidu.dom.create = function (tagName, opt_attributes) {
+      var el = document.createElement(tagName),
+        attributes = opt_attributes || {};
+      return baidu.dom.setAttrs(el, attributes);
+    };
+    baidu.object = baidu.object || {};
+    baidu.extend = baidu.object.extend = function (target, source) {
+      for (var p in source) {
+        if (source.hasOwnProperty(p)) {
+          target[p] = source[p];
+        }
+      }
+      return target;
+    };
+  })();
+
+  WORLD_SIZE_MC_HALF = 20037726.372307256;
+  WORLD_SIZE_MC = WORLD_SIZE_MC_HALF * 2;
+  /**
+   * @exports LuShu as BMapGLLib.LuShu
+   */
+  var LuShu =
+    /**
+         * LuShu类的构造函数
+         * @class LuShu <b>入口</b>。
+         * 实例化该类后,可调用,start,end,pause等方法控制覆盖物的运动。
+    
+         * @constructor
+             * @param {Map} map Baidu map的实例对象.
+             * @param {Array} path 构成路线的point的数组.
+             * @param {Json Object} opts 可选的输入参数,非必填项。可输入选项包括:<br />
+             * {<br />"<b>landmarkPois</b>" : {Array} 要在覆盖物移动过程中,显示的特殊点。格式如下:landmarkPois:[<br />
+             *      {lng:116.314782,lat:39.913508,html:'加油站',pauseTime:2},<br />
+             *      {lng:116.315391,lat:39.964429,html:'高速公路收费站,pauseTime:3}]<br />
+             * <br />"<b>icon</b>" : {Icon} 覆盖物的icon,
+             * <br />"<b>speed</b>" : {Number} 覆盖物移动速度,单位米/秒    <br />
+             * <br />"<b>defaultContent</b>" : {String} 覆盖物中的内容    <br />
+             * }<br />.
+             * @example <b>参考示例:</b><br />
+             * var lushu = new BMapGLLib.LuShu(map,arrPois,{defaultContent:"从北京到天津",landmarkPois:[]});
+         */
+    (BMapGLLib.LuShu = function (map, path, opts) {
+      if (!path || path.length < 1) {
+        return;
+      }
+      this._map = map;
+      //存储一条路线
+      if (opts["geodesic"]) {
+        this.path = getGeodesicPath(path);
+      } else {
+        this.path = path;
+      }
+      //移动到当前点的索引
+      this.i = 0;
+      //控制暂停后开始移动的队列的数组
+      this._setTimeoutQuene = [];
+      //进行坐标转换的类
+      // this._projection = this._map.getMapType().getProjection();
+      this._opts = {
+        icon: null,
+        //默认速度 米/秒
+        speed: 400,
+        defaultContent: "",
+      };
+      if (!opts["landmarkPois"]) {
+        opts["landmarkPois"] = [];
+      }
+      this._setOptions(opts);
+      this._rotation = 0; //小车转动的角度
+
+      //如果不是默认实例,则使用默认的icon
+      if (!(this._opts.icon instanceof BMapGL.Icon)) {
+        this._opts.icon = defaultIcon;
+      }
+    });
+  /**
+   * 根据用户输入的opts,修改默认参数_opts
+   * @param {Json Object} opts 用户输入的修改参数.
+   * @return 无返回值.
+   */
+  LuShu.prototype._setOptions = function (opts) {
+    if (!opts) {
+      return;
+    }
+    for (var p in opts) {
+      if (opts.hasOwnProperty(p)) {
+        this._opts[p] = opts[p];
+      }
+    }
+  };
+
+  /**
+   * @description 开始运动
+   * @param none
+   * @return 无返回值.
+   *
+   * @example <b>参考示例:</b><br />
+   * lushu.start();
+   */
+  LuShu.prototype.start = function () {
+    var me = this,
+      len = me.path.length;
+    //不是第一次点击开始,并且小车还没到达终点
+    if (me.i && me.i < len - 1) {
+      //没按pause再按start不做处理
+      if (!me._fromPause) {
+        return;
+      } else if (!me._fromStop) {
+        //按了pause按钮,并且再按start,直接移动到下一点
+        //并且此过程中,没有按stop按钮
+        //防止先stop,再pause,然后连续不停的start的异常
+        me._moveNext(++me.i);
+      }
+    } else {
+      //第一次点击开始,或者点了stop之后点开始
+      me._addMarker();
+      //等待marker动画完毕再加载infowindow
+      me._timeoutFlag = setTimeout(function () {
+        me._addInfoWin();
+        if (me._opts.defaultContent == "") {
+          me.hideInfoWindow();
+        }
+        me._moveNext(me.i);
+      }, 400);
+    }
+    //重置状态
+    this._fromPause = false;
+    this._fromStop = false;
+  };
+
+  /**
+   * 结束运动
+   * @return 无返回值.
+   *
+   * @example <b>参考示例:</b><br />
+   * lushu.stop();
+   */
+  LuShu.prototype.stop = function () {
+    this.i = 0;
+    this._fromStop = true;
+    clearInterval(this._intervalFlag);
+    this._clearTimeout();
+    //重置landmark里边的poi为未显示状态
+    for (var i = 0, t = this._opts.landmarkPois, len = t.length; i < len; i++) {
+      t[i].bShow = false;
+    }
+  };
+
+  /**
+   * 暂停运动
+   * @return 无返回值.
+   */
+  LuShu.prototype.pause = function () {
+    clearInterval(this._intervalFlag);
+
+    //标识是否是按过pause按钮
+    this._fromPause = true;
+    this._clearTimeout();
+  };
+
+  LuShu.prototype.clear = function () {
+    this._clearMark();
+  };
+
+  /**
+   * 隐藏上方overlay
+   * @return 无返回值.
+   *
+   * @example <b>参考示例:</b><br />
+   * lushu.hideInfoWindow();
+   */
+  LuShu.prototype.hideInfoWindow = function () {
+    this._overlay._div.style.visibility = "hidden";
+  };
+
+  /**
+   * 显示上方overlay
+   * @return 无返回值.
+   *
+   * @example <b>参考示例:</b><br />
+   * lushu.showInfoWindow();
+   */
+  LuShu.prototype.showInfoWindow = function () {
+    this._overlay._div.style.visibility = "visible";
+  };
+
+  //Lushu私有方法
+  baidu.object.extend(LuShu.prototype, {
+    /**
+     * 添加marker到地图上
+     * @param {Function} 回调函数.
+     * @return 无返回值.
+     */
+    _addMarker: function (callback) {
+      if (this._marker) {
+        this.stop();
+        // 变更
+        this._map.removeOverlay(this._marker);
+        this._map.removeOverlay(this._markerL);
+        this._map.removeOverlay(this._markerR);
+        clearTimeout(this._timeoutFlag);
+      }
+      //移除之前的overlay
+      this._overlay && this._map.removeOverlay(this._overlay);
+      var marker = new BMapGL.Marker(this.path[0]);
+      this._opts.icon && marker.setIcon(this._opts.icon);
+      this._map.addOverlay(marker);
+      marker.setAnimation(BMAP_ANIMATION_DROP);
+      this._marker = marker;
+      // 变更
+      var markerL = new BMapGL.Marker(this.path[0], { left: true });
+      this._opts.icon && markerL.setIcon(this._opts.icon);
+      this._map.addOverlay(markerL);
+      markerL.setAnimation(BMAP_ANIMATION_DROP);
+      this._markerL = markerL;
+
+      var markerR = new BMapGL.Marker(this.path[0], { right: true });
+      this._opts.icon && markerR.setIcon(this._opts.icon);
+      this._map.addOverlay(markerR);
+      markerR.setAnimation(BMAP_ANIMATION_DROP);
+      this._markerR = markerR;
+    },
+    /**
+     * 销毁--(用户自定义)
+     * @param f
+     * @private
+     */
+    _clearMark: function (f) {
+      this.stop();
+      this._map.removeOverlay(this._marker);
+      this._map.removeOverlay(this._markerL);
+      this._map.removeOverlay(this._markerR);
+      clearTimeout(this._timeoutFlag);
+      this._overlay && this._map.removeOverlay(this._overlay);
+    },
+    /**
+     * 添加上方overlay
+     * @return 无返回值.
+     */
+    _addInfoWin: function () {
+      var me = this;
+      var overlay = new CustomOverlay(
+        me._marker.getPosition(),
+        me._opts.defaultContent
+      );
+      //将当前类的引用传给overlay。
+      overlay.setRelatedClass(this);
+      this._overlay = overlay;
+      this._map.addOverlay(overlay);
+    },
+
+    /**
+     * 获取墨卡托坐标
+     * @param {Point} poi 经纬度坐标.
+     * @return 无返回值.
+     */
+    _getMercator: function (poi) {
+      return this._map.getMapType().getProjection().lngLatToPoint(poi);
+    },
+
+    /**
+     * 计算两点间的距离
+     * @param {Point} poi 经纬度坐标A点.
+     * @param {Point} poi 经纬度坐标B点.
+     * @return 无返回值.
+     */
+    _getDistance: function (pxA, pxB) {
+      return Math.sqrt(Math.pow(pxA.x - pxB.x, 2) + Math.pow(pxA.y - pxB.y, 2));
+    },
+
+    //目标点的  当前的步长,position,总的步长,动画效果,回调
+    /**
+     * 移动小车
+     * @param {Number} poi 当前的步长.
+     * @param {Point} initPos 经纬度坐标初始点.
+     * @param {Point} targetPos 经纬度坐标目标点.
+     * @param {Function} effect 缓动效果.
+     * @return 无返回值.
+     */
+    _move: function (initPos, targetPos, effect) {
+      var me = this,
+        //当前的帧数
+        currentCount = 0,
+        //步长,米/秒
+        timer = 10,
+        step = this._opts.speed / (1000 / timer),
+        //初始坐标
+        init_pos = BMapGL.Projection.convertLL2MC(initPos),
+        //获取结束点的(x,y)坐标
+        target_pos = BMapGL.Projection.convertLL2MC(targetPos);
+      init_pos = new BMapGL.Pixel(init_pos.lng, init_pos.lat);
+      target_pos = new BMapGL.Pixel(target_pos.lng, target_pos.lat);
+      // 变更
+      var mcDis = me._getDistance(init_pos, target_pos);
+      var direction = null;
+      if (mcDis > 30037726) {
+        if (target_pos.x < init_pos.x) {
+          target_pos.x += WORLD_SIZE_MC;
+          direction = "right";
+        } else {
+          target_pos.x -= WORLD_SIZE_MC;
+          direction = "left";
+        }
+      }
+      //总的步长
+      var count = Math.round(me._getDistance(init_pos, target_pos) / step);
+
+      //如果小于1直接移动到下一点
+      if (count < 1) {
+        me._moveNext(++me.i);
+        return;
+      }
+      //两点之间匀速移动
+      me._intervalFlag = setInterval(function () {
+        //两点之间当前帧数大于总帧数的时候,则说明已经完成移动
+        if (currentCount >= count) {
+          clearInterval(me._intervalFlag);
+          //移动的点已经超过总的长度
+          if (me.i > me.path.length) {
+            return;
+          }
+          //运行下一个点
+          me._moveNext(++me.i);
+        } else {
+          currentCount++;
+          var x = effect(init_pos.x, target_pos.x, currentCount, count),
+            y = effect(init_pos.y, target_pos.y, currentCount, count),
+            pos = BMapGL.Projection.convertMC2LL(new BMapGL.Point(x, y));
+          if (pos.lng > 180) {
+            pos.lng = pos.lng - 360;
+          }
+          if (pos.lng < -180) {
+            pos.lng = pos.lng + 360;
+          }
+          //设置marker
+          if (currentCount == 1) {
+            var proPos = null;
+            if (me.i - 1 >= 0) {
+              proPos = me.path[me.i - 1];
+            }
+            if (me._opts.enableRotation == true) {
+              me.setRotation(proPos, initPos, targetPos, direction);
+            }
+            if (me._opts.autoView) {
+              if (!me._map.getBounds().containsPoint(pos)) {
+                me._map.setCenter(pos);
+              }
+            }
+          }
+          // 变更
+          //正在移动
+          me._marker.setPosition(pos);
+          me._markerL.setPosition(pos);
+          me._markerR.setPosition(pos);
+          //设置自定义overlay的位置
+          me._setInfoWin(pos);
+        }
+      }, timer);
+    },
+
+    /**
+     * 在每个点的真实步骤中设置小车转动的角度
+     */
+    setRotation: function (prePos, curPos, targetPos, direction) {
+      var me = this;
+      var deg = 0;
+      //start!
+      curPos = me._map.pointToPixel(curPos);
+      targetPos = me._map.pointToPixel(targetPos);
+
+      if (targetPos.x != curPos.x) {
+        var tan = (targetPos.y - curPos.y) / (targetPos.x - curPos.x),
+          atan = Math.atan(tan);
+        deg = (atan * 360) / (2 * Math.PI);
+        //degree  correction;
+        if ((!direction && targetPos.x < curPos.x) || direction === "left") {
+          deg = -deg + 90 + 90;
+        } else {
+          deg = -deg;
+        }
+        // 变更
+        me._marker.setRotation(-deg);
+        me._markerL.setRotation(-deg);
+        me._markerR.setRotation(-deg);
+      } else {
+        var disy = targetPos.y - curPos.y;
+        var bias = 0;
+        if (disy > 0) {
+          bias = -1;
+        } else {
+          bias = 1;
+        }
+        // 变更
+        me._marker.setRotation(-bias * 90);
+        me._markerL.setRotation(-bias * 90);
+        me._markerR.setRotation(-bias * 90);
+      }
+      return;
+    },
+
+    linePixellength: function (from, to) {
+      return Math.sqrt(
+        Math.abs(from.x - to.x) * Math.abs(from.x - to.x) +
+          Math.abs(from.y - to.y) * Math.abs(from.y - to.y)
+      );
+    },
+    pointToPoint: function (from, to) {
+      return (
+        Math.abs(from.x - to.x) * Math.abs(from.x - to.x) +
+        Math.abs(from.y - to.y) * Math.abs(from.y - to.y)
+      );
+    },
+
+    /**
+     * 移动到下一个点
+     * @param {Number} index 当前点的索引.
+     * @return 无返回值.
+     */
+    _moveNext: function (index) {
+      var me = this;
+      // debugger;
+      if (index < this.path.length - 1) {
+        me._move(me.path[index], me.path[index + 1], me._tween.linear);
+      } else {
+        me._opts.onComplete && me._opts.onComplete()
+      }
+    },
+
+    /**
+     * 设置小车上方infowindow的内容,位置等
+     * @param {Point} pos 经纬度坐标点.
+     * @return 无返回值.
+     */
+    _setInfoWin: function (pos) {
+      //设置上方overlay的position
+      var me = this;
+      if (!me._overlay) {
+        return;
+      }
+      me._overlay.setPosition(pos, me._marker.getIcon().size);
+      var index = me._troughPointIndex(pos);
+      if (index != -1) {
+        clearInterval(me._intervalFlag);
+        me._overlay.setHtml(me._opts.landmarkPois[index].html);
+        me._overlay.setPosition(pos, me._marker.getIcon().size);
+        me._pauseForView(index);
+      } else {
+        me._overlay.setHtml(me._opts.defaultContent);
+      }
+    },
+
+    /**
+     * 在某个点暂停的时间
+     * @param {Number} index 点的索引.
+     * @return 无返回值.
+     */
+    _pauseForView: function (index) {
+      var me = this;
+      var t = setTimeout(function () {
+        //运行下一个点
+        me._moveNext(++me.i);
+      }, me._opts.landmarkPois[index].pauseTime * 1000);
+      me._setTimeoutQuene.push(t);
+    },
+    //清除暂停后再开始运行的timeout
+    _clearTimeout: function () {
+      for (var i in this._setTimeoutQuene) {
+        clearTimeout(this._setTimeoutQuene[i]);
+      }
+      this._setTimeoutQuene.length = 0;
+    },
+    //缓动效果
+    _tween: {
+      //初始坐标,目标坐标,当前的步长,总的步长
+      linear: function (initPos, targetPos, currentCount, count) {
+        var b = initPos;
+        var c = targetPos - initPos;
+        var t = currentCount;
+        var d = count;
+        return (c * t) / d + b;
+      },
+    },
+
+    /**
+     * 否经过某个点的index
+     * @param {Point} markerPoi 当前小车的坐标点.
+     * @return 无返回值.
+     */
+    _troughPointIndex: function (markerPoi) {
+      var t = this._opts.landmarkPois;
+      var distance;
+      for (var i = 0, len = t.length; i < len; i++) {
+        //landmarkPois中的点没有出现过的话
+        if (!t[i].bShow) {
+          distance = this._map.getDistance(
+            new BMapGL.Point(t[i].lng, t[i].lat),
+            markerPoi
+          );
+          //两点距离小于10米,认为是同一个点
+          if (distance < 10) {
+            t[i].bShow = true;
+            return i;
+          }
+        }
+      }
+      return -1;
+    },
+  });
+
+  /**
+   * 获取大圆点
+   * @return {Array} 大圆点
+   */
+  function getGeodesicPath(points) {
+    var gPath = [];
+    for (var i = 0; i < points.length - 1; i++) {
+      var great = calcGreatCirclePath(points[i], points[i + 1]);
+      gPath = gPath.concat(great);
+    }
+    gPath = gPath.concat(points[points.length - 1]);
+    return gPath;
+  }
+
+  /**
+   * 计算大圆上的点
+   * @param {Object} latLng1 点1
+   * @param {Object} latLng2 点2
+   * @return {Array} 扩充后的点
+   */
+  function calcGreatCirclePath(latLng1, latLng2) {
+    // 计算需要多少个插值点,根据显示效果,每250公里需要一个。
+    if (latLng1.equals(latLng2)) {
+      // 两个相等的坐标通过下面距离计算会得到地球周长,因此提前判断
+      return [latLng1];
+    }
+    var distance = BMapGL.Projection.getDistance(
+      toRadian(latLng1.lng),
+      toRadian(latLng1.lat),
+      toRadian(latLng2.lng),
+      toRadian(latLng2.lat)
+    );
+    var distance = BMapGL.Projection.getDistanceByLL(latLng1, latLng2);
+    if (distance < 250000) {
+      return [latLng1];
+    }
+    // 清空现有数据
+    // this.greatCirclePoints.length = 0;
+    var result = [];
+    // 间隔设置小于250公里是因为在靠近南北两极同样的公里数所代表的平面跨度增加
+    var count = Math.round(distance / 150000);
+    var angularDistance = calcAngularDistance(latLng1, latLng2);
+    result.push(latLng1);
+
+    for (var i = 0; i < count; i++) {
+      var eachLatLng = calcMiddlePoint(
+        latLng1,
+        latLng2,
+        i / count,
+        angularDistance
+      );
+      result.push(eachLatLng);
+    }
+
+    result.push(latLng2);
+    return result;
+  }
+
+  /**
+   * 给两个点,计算地球大圆上的中间点
+   * https://www.movable-type.co.uk/scripts/latlong.html
+   *
+   * @param {Point} latLng1 起点
+   * @param {Point} latLng2 终点
+   * @param {number} f fraction along great circle route
+   * @param {number} delta angular distance d/R between the two points.
+   * @return {Point} 大圆上的中间点
+   */
+  function calcMiddlePoint(latLng1, latLng2, f, delta) {
+    var lat1 = latLng1.lat;
+    var lat2 = latLng2.lat;
+    var lon1 = latLng1.lng;
+    var lon2 = latLng2.lng;
+    var phi1 = toRadian(lat1);
+    var phi2 = toRadian(lat2);
+    var lambda1 = toRadian(lon1);
+    var lambda2 = toRadian(lon2);
+    var a = Math.sin((1 - f) * delta) / Math.sin(delta);
+    var b = Math.sin(f * delta) / Math.sin(delta);
+    var x =
+      a * Math.cos(phi1) * Math.cos(lambda1) +
+      b * Math.cos(phi2) * Math.cos(lambda2);
+    var y =
+      a * Math.cos(phi1) * Math.sin(lambda1) +
+      b * Math.cos(phi2) * Math.sin(lambda2);
+    var z = a * Math.sin(phi1) + b * Math.sin(phi2);
+    var phi = Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
+    var lambda = Math.atan2(y, x);
+    return new BMapGL.Point(toAngle(lambda), toAngle(phi));
+  }
+
+  function toRadian(angle) {
+    return (angle * Math.PI) / 180;
+  }
+
+  function toAngle(radian) {
+    return (radian / Math.PI) * 180;
+  }
+
+  /**
+   * 计算角距离
+   *
+   * @param {Point} latLng1 起点
+   * @param {Point} latLng2 终点
+   * @return {number} 角距离
+   */
+  function calcAngularDistance(latLng1, latLng2) {
+    // console.log(latLng1.lat, latLng1.lng, latLng2.lat, latLng2.lng);
+    var lat1 = toRadian(latLng1.lat);
+    var lat2 = toRadian(latLng2.lat);
+    var lng1 = toRadian(latLng1.lng);
+    var lng2 = toRadian(latLng2.lng);
+    return Math.acos(
+      Math.sin(lat1) * Math.sin(lat2) +
+        Math.cos(lat1) * Math.cos(lat2) * Math.cos(Math.abs(lng2 - lng1))
+    );
+  }
+
+  /**
+   * 自定义的overlay,显示在小车的上方
+   * @param {Point} Point 要定位的点.
+   * @param {String} html overlay中要显示的东西.
+   * @return 无返回值.
+   */
+  function CustomOverlay(point, html) {
+    this._point = point;
+    this._html = html;
+  }
+  CustomOverlay.prototype = new BMapGL.Overlay();
+  CustomOverlay.prototype.initialize = function (map) {
+    var div = (this._div = baidu.dom.create("div", {
+      style:
+        "border:solid 1px #ccc;width:auto;min-width:50px;text-align:center;position:absolute;background:#fff;color:#000;font-size:12px;border-radius: 10px;padding:5px;white-space: nowrap;",
+    }));
+    div.innerHTML = this._html;
+    map.getPanes().floatPane.appendChild(div);
+    this._map = map;
+    return div;
+  };
+  CustomOverlay.prototype.draw = function () {
+    this.setPosition(
+      this.lushuMain._marker.getPosition(),
+      this.lushuMain._marker.getIcon().size
+    );
+  };
+  baidu.object.extend(CustomOverlay.prototype, {
+    //设置overlay的position
+    setPosition: function (poi, markerSize) {
+      // 此处的bug已修复,感谢 苗冬(diligentcat@gmail.com) 的细心查看和认真指出
+      var px = this._map.pointToOverlayPixel(poi);
+      var styleW = baidu.dom.getStyle(this._div, "width");
+      var styleH = baidu.dom.getStyle(this._div, "height");
+      var overlayW = parseInt(this._div.clientWidth || styleW, 10);
+      var overlayH = parseInt(this._div.clientHeight || styleH, 10);
+      this._div.style.left = px.x - overlayW / 2 + "px";
+      this._div.style.bottom = -(px.y - markerSize.height) + "px";
+    },
+    //设置overlay的内容
+    setHtml: function (html) {
+      this._div.innerHTML = html;
+    },
+    //跟customoverlay相关的实例的引用
+    setRelatedClass: function (lushuMain) {
+      this.lushuMain = lushuMain;
+    },
+  });
+})();

+ 65 - 54
public/index.html

@@ -1,64 +1,75 @@
 <!DOCTYPE html>
 <html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <title>React 大数据可视化</title>
+    <link rel="stylesheet" href="index.css" />
+    <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
+    <link
+      href="//mapopen.bj.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css"
+      rel="stylesheet"
+    />
+    <!-- <script
+      type="text/javascript"
+      src="http://api.map.baidu.com/api?v=2.0&ak=ILQH1WYMHbSlqFNViqca6j62Ga7bSOzW"
+    ></script> -->
+    <script
+      type="text/javascript"
+      src="//api.map.baidu.com/api?type=webgl&v=2.0&ak=ILQH1WYMHbSlqFNViqca6j62Ga7bSOzW"
+    ></script>
+    <script type="text/javascript" src="Lushu.js"></script>
+    <!-- <script
+      type="text/javascript"
+      src="//api.map.baidu.com/library/LuShu/gl/src/LuShu_min.js"
+    ></script> -->
+    <script src="//mapopen.bj.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js"></script>
+  </head>
 
-<head>
-  <meta charset="UTF-8" />
-  <meta name="viewport" content="width=device-width, initial-scale=1" />
-  <title>React 大数据可视化</title>
-  <link rel="stylesheet" href="index.css" />
-  <link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
-  <link href="//mapopen.bj.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.css" rel="stylesheet">
-  <script type="text/javascript"
-    src="//api.map.baidu.com/api?type=webgl&v=4.0&ak=ILQH1WYMHbSlqFNViqca6j62Ga7bSOzW"></script>
-  <script type="text/javascript" src="//api.map.baidu.com/library/LuShu/gl/src/LuShu_min.js"></script>
-  <script src="//mapopen.bj.bcebos.com/github/BMapGLLib/DrawingManager/src/DrawingManager.min.js"></script>
-</head>
+  <body>
+    <div id="root"></div>
+    <script src="index.js"></script>
+  </body>
 
-<body>
-  <div id="root"></div>
-  <script src="index.js"></script>
-</body>
+  <style>
+    .drawing-panel {
+      z-index: 999;
+      position: absolute;
+      top: 0.5rem;
+      margin-left: 0.5rem;
+      padding-left: 0;
+      border-radius: 0.25rem;
+      height: 47px;
+      box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
+    }
 
-<style>
-  .drawing-panel {
-    z-index: 999;
-    position: absolute;
-    top: 0.5rem;
-    margin-left: 0.5rem;
-    padding-left: 0;
-    border-radius: .25rem;
-    height: 47px;
-    box-shadow: 0 2px 6px 0 rgba(27, 142, 236, 0.5);
-  }
+    .bmap-btn {
+      border-right: 1px solid #d2d2d2;
+      float: left;
+      width: 64px;
+      height: 100%;
+      background-image: url(//api.map.baidu.com/library/DrawingManager/1.4/src/bg_drawing_tool.png);
+      cursor: pointer;
+    }
 
-  .bmap-btn {
-    border-right: 1px solid #d2d2d2;
-    float: left;
-    width: 64px;
-    height: 100%;
-    background-image: url(//api.map.baidu.com/library/DrawingManager/1.4/src/bg_drawing_tool.png);
-    cursor: pointer;
-  }
+    .drawing-panel .bmap-marker {
+      background-position: -65px 0;
+    }
 
-  .drawing-panel .bmap-marker {
-    background-position: -65px 0;
-  }
+    .drawing-panel .bmap-polyline {
+      background-position: -195px 0;
+    }
 
-  .drawing-panel .bmap-polyline {
-    background-position: -195px 0;
-  }
+    .drawing-panel .bmap-rectangle {
+      background-position: -325px 0;
+    }
 
-  .drawing-panel .bmap-rectangle {
-    background-position: -325px 0;
-  }
+    .drawing-panel .bmap-polygon {
+      background-position: -260px 0;
+    }
 
-  .drawing-panel .bmap-polygon {
-    background-position: -260px 0;
-  }
-
-  .drawing-panel .bmap-circle {
-    background-position: -130px 0;
-  }
-</style>
-
-</html>
+    .drawing-panel .bmap-circle {
+      background-position: -130px 0;
+    }
+  </style>
+</html>

binární
src/assets/icon-arr.png


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
src/assets/icon4.svg


binární
src/assets/icon5.png


binární
src/assets/icon6.png


+ 1 - 0
src/assets/icon8.svg

@@ -0,0 +1 @@
+<svg t="1678511258320" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11730" width="200" height="200"><path d="M325.290667 299.008L235.178667 121.173333c-11.605333-22.869333-49.152-22.869333-61.098667 0L84.309333 299.008c-29.013333 57.685333-18.090667 129.365333 27.306667 174.08C137.898667 499.029333 171.349333 512 204.8 512s66.901333-12.970667 93.184-38.912c45.056-44.714667 56.32-116.394667 27.306667-174.08z m-75.434667 125.610667c-25.6 25.6-64.512 25.6-90.453333 0-23.893333-23.893333-30.037333-63.488-14.336-94.549334L204.8 212.309333l59.392 117.76c15.701333 31.061333 9.898667 70.656-14.336 94.549334zM955.733333 238.933333V170.666667h-68.266666V68.266667h-68.266667v887.466666h136.533333v-68.266666h-68.266666v-170.666667h68.266666v-68.266667h-68.266666v-170.666666h68.266666v-68.266667h-68.266666V238.933333h68.266666z m-413.696 18.090667c-11.946667-22.186667-48.469333-22.186667-60.416 0l-179.882666 337.92c-52.565333 98.645333-32.768 220.501333 48.128 296.277333 45.397333 42.666667 103.424 63.829333 161.792 63.829334s116.394667-21.504 161.792-63.829334c80.896-75.776 100.693333-197.632 48.128-296.277333l-179.541334-337.92z m84.992 584.362667a166.6048 166.6048 0 0 1-230.058666 0c-58.368-54.954667-72.704-143.018667-34.816-214.357334L512 345.770667l149.504 281.258666c38.229333 71.338667 23.893333 159.744-34.474667 214.357334z" p-id="11731" fill="#ffffff"></path></svg>

+ 14 - 0
src/assets/icon9.svg

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="60px" height="60px" xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <radialGradient cx="1156.04255319149" cy="357.758865248227" r="29.9574468085107" gradientTransform="matrix(1 1.89747207845027E-15 -1.89747207845025E-15 0.999999999999992 6.82121026329691E-13 5.11590769747272E-13 )" gradientUnits="userSpaceOnUse" id="RadialGradient351">
+      <stop id="Stop352" stop-color="#f59a23" offset="0" />
+      <stop id="Stop353" stop-color="#c99c47" offset="0.18" />
+      <stop id="Stop354" stop-color="#ad9d5e" offset="0.29" />
+      <stop id="Stop355" stop-color="#02a7f0" offset="1" />
+    </radialGradient>
+  </defs>
+  <g transform="matrix(1 0 0 1 -1126 -328 )">
+    <path d="M 1156 328  C 1172.8 328  1186 341.2  1186 358  C 1186 374.8  1172.8 388  1156 388  C 1139.2 388  1126 374.8  1126 358  C 1126 341.2  1139.2 328  1156 328  Z " fill-rule="nonzero" fill="url(#RadialGradient351)" stroke="none" />
+  </g>
+</svg>

+ 13 - 0
src/assets/legend-active.svg

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="115px" height="42px" xmlns="http://www.w3.org/2000/svg">
+  <defs>
+    <radialGradient cx="535.5" cy="820" r="57.5" gradientTransform="matrix(-1 0 0 -0.365217391304348 1071 1119.47826086957 )" gradientUnits="userSpaceOnUse" id="RadialGradient333">
+      <stop id="Stop334" stop-color="#161617" offset="0" />
+      <stop id="Stop335" stop-color="#027db4" offset="1" />
+    </radialGradient>
+  </defs>
+  <g transform="matrix(1 0 0 1 -478 -799 )">
+    <path d="M 479 800  L 592 800  L 592 840  L 479 840  L 479 800  Z " fill-rule="nonzero" fill="url(#RadialGradient333)" stroke="none" />
+    <path d="M 478.5 799.5  L 592.5 799.5  L 592.5 840.5  L 478.5 840.5  L 478.5 799.5  Z " stroke-width="1" stroke="#027db4" fill="none" stroke-opacity="0.996078431372549" />
+  </g>
+</svg>

+ 7 - 0
src/assets/legend.svg

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="115px" height="42px" xmlns="http://www.w3.org/2000/svg">
+  <g transform="matrix(1 0 0 1 -478 -337 )">
+    <path d="M 479 338  L 592 338  L 592 378  L 479 378  L 479 338  Z " fill-rule="nonzero" fill="#000000" stroke="none" fill-opacity="0.0980392156862745" />
+    <path d="M 478.5 337.5  L 592.5 337.5  L 592.5 378.5  L 478.5 378.5  L 478.5 337.5  Z " stroke-width="1" stroke="#027db4" fill="none" stroke-opacity="0.996078431372549" />
+  </g>
+</svg>

+ 77 - 0
src/components/Map/ArrowPolyline.js

@@ -0,0 +1,77 @@
+// 带箭头的折现
+import React, { useEffect, useRef } from "react";
+import { Polyline } from "react-bmapgl";
+import IconUrl from "../../assets/icon-arr.png";
+const { BMapGL } = window;
+
+const ArrowPolyline = (props) => {
+  const { points, strokeColor, strokeWeight, strokeOpacity, strokeStyle, map } =
+    props;
+  const markerRef = useRef([]);
+
+  const renderArrow = (points) => {
+    var arrowIcon = new BMapGL.Icon(IconUrl, new BMapGL.Size(16, 16), {
+      imageSize: new BMapGL.Size(16, 16),
+    });
+
+    // 绘制箭头
+    for (var i = 1; i < points.length; i++) {
+      var startPoint = points[i - 1];
+      var endPoint = points[i];
+      var angle = getAngle(startPoint, endPoint); // 计算箭头的方向角度
+      var midPoint = new BMapGL.Point(
+        (startPoint.lng + endPoint.lng) / 2,
+        (startPoint.lat + endPoint.lat) / 2
+      );
+      // 计算箭头的中心点坐标
+      var arrowMarker = new BMapGL.Marker(midPoint, {
+        icon: arrowIcon,
+        enableClicking: false,
+      });
+      arrowMarker.setRotation(-angle); // 设置箭头的方向角度
+      map.addOverlay(arrowMarker);
+      markerRef.current.push(arrowMarker);
+    }
+  };
+  // 计算两个点之间的角度
+  const getAngle = (pointA, pointB) => {
+    var d = 0;
+    if (pointB.lng == pointA.lng) {
+      if (pointB.lat > pointA.lat) {
+        d = 90;
+      } else {
+        d = -90;
+      }
+    } else {
+      var k = (pointB.lat - pointA.lat) / (pointB.lng - pointA.lng);
+      var angle = (Math.atan(k) * 180) / Math.PI; // 计算弧度,并将弧度转换为角度
+      if (pointB.lng < pointA.lng) {
+        d = angle + 180; // 如果终点在起点左边,需要加上 180 度
+      } else {
+        d = angle; // 如果终点在起点右边,角度不变
+      }
+    }
+    return d;
+  };
+
+  useEffect(() => {
+    renderArrow(points);
+    return () => {
+      markerRef.current.map((item) => map.removeOverlay(item));
+    };
+  }, []);
+
+  return (
+    <>
+      <Polyline
+        path={points}
+        strokeColor={strokeColor}
+        strokeWeight={strokeWeight}
+        strokeOpacity={strokeOpacity}
+        strokeStyle={strokeStyle}
+      />
+    </>
+  );
+};
+
+export default ArrowPolyline;

+ 41 - 0
src/components/Map/Legend.js

@@ -0,0 +1,41 @@
+// 图例列表
+import styled from "styled-components";
+
+function Legend(props) {
+  const { list, style } = props;
+
+  return (
+    <Box style={style}>
+      {list.map((item) => (
+        <Item>{item}</Item>
+      ))}
+    </Box>
+  );
+}
+
+const Box = styled.ul`
+  margin: 0;
+  padding: 0;
+  width: 140px;
+  position: absolute;
+  z-index: 999;
+  color: #fff;
+  font-size: 16px;
+  top: 100px;
+  text-align: center;
+`;
+const Item = styled.li`
+  margin: 0;
+  padding: 8px 20px;
+  width: 100%;
+  margin-bottom: 14px;
+  cursor: pointer;
+  background: url(${require("../../assets/legend.svg")}) no-repeat center;
+  background-size: 100% 100%;
+  &.active,
+  &:hover {
+    background-image: url(${require("../../assets/legend-active.svg")});
+  }
+`;
+
+export default Legend;

+ 76 - 0
src/components/Map/Lushu.js

@@ -0,0 +1,76 @@
+// 带箭头的折现
+import React, { PureComponent } from "react";
+import { Marker } from "react-bmapgl";
+import imgPerson from "../../assets/icon5.png";
+import imgCar from "../../assets/icon6.png";
+// import "../../utils/Lushu";
+
+const { BMapGL, BMapGLLib } = window;
+
+class Lushu extends PureComponent {
+  lushu = null;
+  constructor(props) {
+    super(props);
+    this.state = { start: false };
+    this.icon = new BMapGL.Icon(
+      props.type == 1 ? imgCar : imgPerson,
+      new BMapGL.Size(30, 30),
+      {
+        // anchor: new BMapGL.Size(24, 24),
+      }
+    );
+  }
+
+  startLushu() {
+    const { points, map } = this.props;
+    // 删除历史的路书动画
+    if (this.lushu) {
+      this.lushu.clear();
+    }
+
+    let pointArr = points.map(
+      (point) => new BMapGL.Point(point.lng, point.lat)
+    );
+    var lushu = new BMapGLLib.LuShu(map, pointArr, {
+      geodesic: true,
+      autoCenter: true,
+      icon: this.icon,
+      speed: 10000,
+      enableRotation: false,
+      onComplete: () => {
+        this.clearLushu();
+      },
+    });
+    this.lushu = lushu;
+    lushu.start();
+    this.setState({
+      start: true,
+    });
+  }
+  componentWillUnmount() {
+    this.clearLushu();
+  }
+  clearLushu() {
+    if (this.lushu) {
+      this.lushu.clear();
+    }
+    this.setState({
+      start: false,
+    });
+  }
+  render() {
+    const { points, position } = this.props;
+    const { start } = this.state;
+    if (start) return null;
+    return (
+      <Marker
+        position={position || points[points.length - 1]}
+        icon={this.icon}
+        enableDragging={false}
+        enableMassClear={true}
+        onClick={() => this.startLushu()}
+      />
+    );
+  }
+}
+export default Lushu;

+ 312 - 0
src/components/Map/MockData.js

@@ -0,0 +1,312 @@
+const ItemType = {
+  text: 0,
+  progressBar: 1,
+  pump: 2,
+};
+
+export default [
+  {
+    type: 0,
+    name: "水厂",
+    data: [
+      {
+        name: "第一水厂",
+        position: { lng: 116.442544, lat: 39.918216 },
+        data: [
+          {
+            type: ItemType.progressBar,
+            name: "运行负荷",
+            value: "90%",
+          },
+          {
+            name: "进水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "进场流量",
+            value: "330 mg/L",
+          },
+          {
+            name: "出场流量",
+            value: "330 mg/L",
+          },
+        ],
+      },
+      {
+        name: "第二水厂",
+        position: { lng: 116.422544, lat: 39.958216 },
+        data: [
+          {
+            type: ItemType.progressBar,
+            name: "运行负荷",
+            value: "90%",
+          },
+          {
+            name: "进水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "进场流量",
+            value: "330 mg/L",
+          },
+          {
+            name: "出场流量",
+            value: "330 mg/L",
+          },
+        ],
+      },
+      {
+        name: "第三水厂",
+        position: { lng: 116.452544, lat: 39.978216 },
+        data: [
+          {
+            type: ItemType.progressBar,
+            name: "运行负荷",
+            value: "90%",
+          },
+          {
+            name: "进水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "进场流量",
+            value: "330 mg/L",
+          },
+          {
+            name: "出场流量",
+            value: "330 mg/L",
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: 1,
+    name: "泵站",
+    data: [
+      {
+        name: "第一泵站",
+        position: { lng: 116.432544, lat: 39.898216 },
+        data: [
+          {
+            type: ItemType.pump,
+            name: "泵运行状态",
+            value: [0, 0, 1, 0],
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: 2,
+    name: "管网",
+    data: [
+      {
+        strokeColor: "red",
+        strokeWeight: 2,
+        fillColor: "#0ab8d9",
+        fillOpacity: 0.3,
+        position: [
+          { lng: 116.39829290421633, lat: 40.18892017402776 },
+          { lng: 116.25916334683096, lat: 40.169957230404016 },
+          { lng: 116.12003378944557, lat: 40.150988945208915 },
+          { lng: 116.13440667760521, lat: 40.04060244030742 },
+          { lng: 116.14877956576487, lat: 39.930035509314486 },
+          { lng: 116.19799852535938, lat: 39.8634192849549 },
+          { lng: 116.24721748495391, lat: 39.796737802722014 },
+        ],
+      },
+      {
+        strokeColor: "#66ccff",
+        strokeWeight: 2,
+        fillColor: "#0ab8d9",
+        fillOpacity: 0.3,
+        position: [
+          { lng: 116.24721748495391, lat: 39.796737802722014 },
+          { lng: 116.322837465736, lat: 39.78383959531996 },
+          { lng: 116.39845744651808, lat: 39.77093895027502 },
+          { lng: 116.53865456380538, lat: 39.74764618546205 },
+          { lng: 116.67885168109265, lat: 39.724345479834405 },
+          { lng: 116.68690049846205, lat: 39.924723760492924 },
+          { lng: 116.69494931583147, lat: 40.12451264487721 },
+          { lng: 116.5466211100239, lat: 40.15672410636046 },
+        ],
+      },
+    ],
+  },
+  {
+    type: 3,
+    name: "大用户企业",
+    data: [
+      {
+        name: "第一泵站",
+        position: { lng: 116.464544, lat: 39.938216 },
+        data: [
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: 4,
+    name: "大用户水表",
+    data: [
+      {
+        name: "XX物业公司",
+        position: { lng: 116.414544, lat: 39.908216 },
+        data: [
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+          {
+            name: "出水COD",
+            value: "330 mg/L",
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: 5,
+    name: "内涝点",
+    data: [
+      {
+        name: "XX区域",
+        position: { lng: 116.404544, lat: 39.918216 },
+        data: [
+          {
+            name: "液位",
+            value: "0.23m",
+          },
+          {
+            name: "视频",
+            value: "查看",
+          },
+        ],
+      },
+    ],
+  },
+  {
+    type: 6,
+    name: "流量",
+    data: [],
+  },
+  {
+    type: 7,
+    name: "降雨量",
+    data: [
+      {
+        name: "XX区域",
+        position: { lng: 116.464544, lat: 39.958216 },
+      },
+    ],
+  },
+  {
+    type: 8,
+    name: "视频",
+    data: [
+      {
+        name: "XX区域",
+        position: { lng: 116.434544, lat: 39.918216 },
+      },
+    ],
+  },
+  {
+    type: 9,
+    name: "人员",
+    data: [
+      {
+        name: "张三",
+        position: { lng: 116.434544, lat: 39.918216 },
+        data: [
+          {
+            name: "部门",
+            value: "管网维修",
+          },
+          {
+            name: "所在区域",
+            value: "XXX",
+          },
+          {
+            name: "电话",
+            value: "13811111111",
+          },
+        ],
+        points: [
+          { lng: 116.24721748495391, lat: 39.796737802722014 },
+          { lng: 116.322837465736, lat: 39.78383959531996 },
+          { lng: 116.102544, lat: 39.748216 },
+        ],
+      },
+    ],
+  },
+  {
+    type: 10,
+    name: "车辆",
+    data: [
+      {
+        name: "抢修车01",
+        position: { lng: 116.434544, lat: 39.948216 },
+        data: [
+          {
+            name: "所在区域",
+            value: "XXX",
+          },
+          {
+            name: "负责人",
+            value: "李四",
+          },
+          {
+            name: "电话",
+            value: "13811111111",
+          },
+        ],
+        points: [
+          { lng: 116.44721748495391, lat: 39.816737802722014 },
+          { lng: 116.452837465736, lat: 39.90383959531996 },
+          { lng: 116.434544, lat: 39.948216 },
+        ],
+      },
+    ],
+  },
+];

+ 133 - 34
src/components/Map/Project.js

@@ -1,21 +1,32 @@
-import React, { useState } from "react";
-import styled from "styled-components";
+import React, { PureComponent } from "react";
+import styled, { keyframes } from "styled-components";
 import { CustomOverlay } from "react-bmapgl";
 import img1 from "../../assets/icon1.jpg";
 import img2 from "../../assets/icon2.jpg";
 import img3 from "../../assets/icon3.png";
+import imgWaterPump from "../../assets/icon4.svg";
+import imgPerson from "../../assets/icon5.png";
+import imgCar from "../../assets/icon6.png";
+// import img7 from "../../assets/icon7.png";
+import imgRainfall from "../../assets/icon8.svg";
+import imgWater from "../../assets/icon9.svg";
 import InfoBox, { ItemType } from "./components/Modal";
+const { BMapGL, BMapGLLib } = window;
+
 export const IconType = {
   WaterWorks: "iconWater",
   Pump: "iconPump",
   Company: "iconCompany",
+
+  Person: "iconPerson",
+  Car: "iconCar",
+  Rainfall: "iconRainfall",
+  Water: "iconWater2",
+  WaterPump: "iconWaterPump",
 };
 
 const dataPump = {
   name: "***泵站",
-  value: "1.5m",
-  position: { lng: 116.202544, lat: 39.908216 },
-  icon: IconType.WaterWorks,
   data: [
     {
       type: ItemType.progressBar,
@@ -50,21 +61,90 @@ const dataPump = {
   ],
 };
 
-function Project(props) {
-  const [visible, setVisible] = useState(false);
-  const { position, icon } = props;
-  return (
-    <CustomOverlay position={position}>
-      <Box>
-        <div className="text">
-          {icon == IconType.Pump ? dataPump.value : dataPump.name}
-        </div>
-        <div onClick={() => setVisible(!visible)} className={icon}></div>
-      </Box>
-      {visible && <InfoBox dataPump={dataPump} />}
-    </CustomOverlay>
-  );
+class Project extends PureComponent {
+  lushu = null;
+  constructor(props) {
+    super(props);
+    this.state = { start: false, visible: false, flash: false };
+    this.icon = new BMapGL.Icon(
+      props.type == 1 ? imgCar : imgPerson,
+      new BMapGL.Size(30, 30)
+    );
+  }
+
+  startLushu() {
+    const { points, map } = this.props;
+    // 删除历史的路书动画
+    if (this.lushu) {
+      this.lushu.clear();
+    }
+
+    let pointArr = points.map(
+      (point) => new BMapGL.Point(point.lng, point.lat)
+    );
+    var lushu = new BMapGLLib.LuShu(map, pointArr, {
+      geodesic: true,
+      autoCenter: true,
+      icon: this.icon,
+      speed: 20000,
+      enableRotation: false,
+      onComplete: () => {
+        this.clearLushu();
+      },
+    });
+    this.lushu = lushu;
+    lushu.start();
+    this.setState({
+      start: true,
+      visible: false,
+    });
+  }
+  componentWillUnmount() {
+    this.clearLushu();
+  }
+  clearLushu() {
+    if (this.lushu) {
+      this.lushu.clear();
+    }
+    this.setState({
+      start: false,
+    });
+  }
+  toggleInfo() {
+    this.setState({
+      visible: !this.state.visible,
+    });
+  }
+  render() {
+    const { position, icon, title, showLushu = false } = this.props;
+    const { start, visible, flash } = this.state;
+    if (start) return null;
+    return (
+      <CustomOverlay position={position}>
+        <Box>
+          <div className="text">{title}</div>
+          <div
+            onClick={() => this.toggleInfo()}
+            className={`icon ${flash ? "flash" : ""}  ${icon}`}
+          ></div>
+        </Box>
+        {visible && (
+          <InfoBox
+            showLushu={showLushu}
+            onClick={() => this.startLushu()}
+            dataPump={dataPump}
+          />
+        )}
+      </CustomOverlay>
+    );
+  }
 }
+
+const blink = keyframes`
+  50% {
+    opacity: 0;
+  }
+`;
 const Box = styled.div`
   position: relative;
   text-align: center;
@@ -73,30 +153,49 @@ const Box = styled.div`
     white-space: nowrap;
     background-color: rgba(8, 41, 75, 0.7);
     color: #fff;
+    position: absolute;
+    bottom: 120%;
+    left: 50%;
+    transform: translateX(-50%);
+    max-width: 100px;
   }
-  .iconWater {
+  .icon {
     width: 30px;
     height: 30px;
     margin: auto;
-    margin-top: 10px;
-    background: url(${img1}) no-repeat center;
     background-size: 100% 100%;
+    background-position: center;
+    background-repeat: no-repeat;
+  }
+  .flash {
+    animation: ${blink} 1s infinite;
+  }
+  .iconWater {
+    background-image: url(${img1});
   }
   .iconPump {
-    width: 30px;
-    height: 30px;
-    margin: auto;
-    margin-top: 10px;
-    background: url(${img2}) no-repeat center;
-    background-size: 100% 100%;
+    background-image: url(${img2});
   }
   .iconCompany {
-    width: 30px;
-    height: 30px;
-    margin: auto;
-    margin-top: 10px;
-    background: url(${img3}) no-repeat center;
-    background-size: 100% 100%;
+    background-image: url(${img3});
+  }
+  .iconRainfall {
+    border-radius: 50%;
+    background-image: url(${imgRainfall});
+    background-size: 60% 60%;
+    background-color: #02a7f0;
+  }
+  .iconPerson {
+    background-image: url(${imgPerson});
+  }
+  .iconCar {
+    background-image: url(${imgCar});
+  }
+  .iconWater2 {
+    background-image: url(${imgWater});
+  }
+  .iconWaterPump {
+    background-image: url(${imgWaterPump});
   }
 `;
 

+ 11 - 2
src/components/Map/components/Modal.js

@@ -7,7 +7,7 @@ export const ItemType = {
 };
 
 const InfoBox = (props) => {
-  const { dataPump } = props;
+  const { dataPump, showLushu, onClick } = props;
   return (
     <Main>
       <div className="top">
@@ -50,6 +50,7 @@ const InfoBox = (props) => {
           );
         }
       })}
+      {showLushu && <LushuBtn onClick={() => onClick()}>显示移动轨迹</LushuBtn>}
     </Main>
   );
 };
@@ -57,7 +58,7 @@ const InfoBox = (props) => {
 const Main = styled.div`
   position: absolute;
   top: -30%;
-  left: 40px;
+  left: 100%;
   width: 200px;
   padding-bottom: 10px;
   border-bottom-width: 6px;
@@ -117,4 +118,12 @@ const Bar = styled.div`
   background-color: yellow;
 `;
 
+const LushuBtn = styled.a`
+  float: right;
+  padding: 10px;
+  padding-bottom: 0;
+  font-size: 16px;
+  text-decoration: underline;
+`;
+
 export default InfoBox;

+ 101 - 0
src/components/Nav/new.js

@@ -0,0 +1,101 @@
+import React, { useState, useEffect, useMemo } from "react";
+import { Card, Table, Empty, Button } from "antd";
+import { connect } from "dva";
+import {
+  GetTokenFromUrl,
+  getToken,
+  PageAction,
+  UnityAction,
+} from "@/utils/utils";
+import Swiper from "react-id-swiper";
+import router from "umi/router";
+// import 'swiper/css/swiper.css';
+import styles from "./indexNew.less";
+
+const menuItemUrl = require("@/assets/menu/item-bg.png");
+const menuSelectItemUrl = require("@/assets/menu/btnBg.png");
+
+function AppMenu(props) {
+  const [swiper, updateSwiper] = useState(null);
+  const [current, setCurrent] = useState();
+
+  const btnDisabled = useMemo(() => {
+    if (waterMenuData.length <= 6) return true;
+    return false;
+  }, [waterMenuData.length]);
+  const itemCount = 6;
+
+  const swiperParams = useMemo(() => {
+    let params = {
+      slidesPerView: itemCount,
+      spaceBetween: 0,
+      observer: true,
+      observeParents: true,
+      // navigation: {
+      //   nextEl: '.swiper-button-next',
+      //   prevEl: '.swiper-button-prev',
+      // },
+      containerClass: `${styles.content} ${styles["content-" + itemCount]}`,
+    };
+    if (!btnDisabled) {
+      params.navigation = {
+        nextEl: ".swiper-button-next",
+        prevEl: ".swiper-button-prev",
+      };
+    }
+    return params;
+  }, [itemCount]);
+
+  return (
+    <div className={styles.menuBg}>
+      <div className={styles.unityMenu}>
+        <div className={styles.leftContent}>
+          <img
+            className={styles.left}
+            src={require("@/assets/menu/left-1.png")}
+          />
+        </div>
+        <div className={styles.center} style={{ width: itemCount * 240 }}></div>
+        <div className={styles.rightContent}>
+          <img
+            className={styles.right}
+            src={require("@/assets/menu/right-1.png")}
+          />
+        </div>
+        <div className={styles.swiper}>
+          {waterMenuData.length > 0 && (
+            <Swiper {...swiperParams} getSwiper={updateSwiper}>
+              {waterMenuData.map((item) => (
+                <div
+                  onClick={() => onClickMenu(item)}
+                  key={item.ID}
+                  className={`${styles.item}`}
+                >
+                  <div
+                    className={`${styles.menu} ${
+                      item.active ? "" : styles.disabled
+                    }`}
+                  >
+                    <div
+                      style={
+                        current == item.ID
+                          ? { textShadow: " 2px 2px 4px #000" }
+                          : null
+                      }
+                      className={styles.font}
+                    >
+                      {item.Name}
+                    </div>
+                    {current == item.ID && <img src={menuSelectItemUrl} />}
+                    {current != item.ID && <img src={menuItemUrl} />}
+                  </div>
+                </div>
+              ))}
+            </Swiper>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+}
+export default AppMenu;

+ 28 - 22
src/components/Sewage/EnergyConsumption.js

@@ -34,25 +34,24 @@ class EnergyConsumption extends PureComponent {
           "10日",
           "11日",
           "12日",
-          // "13日",
-          // "14日",
-          // "15日",
-          // "16日",
-          // "17日",
-          // "18日",
-          // "19日",
-          // "20日",
-          // "21日",
-          // "22日",
-          // "23日",
-          // "24日",
-          // "25日",
-          // "26日",
-          // "27日",
-          // "28日",
-          // "29日",
-          // "30日",
-          // "31日",
+          "13日",
+          "14日",
+          "15日",
+          "16日",
+          "17日",
+          "18日",
+          "19日",
+          "20日",
+          "21日",
+          "22日",
+          "23日",
+          "24日",
+          "25日",
+          "26日",
+          "27日",
+          "28日",
+          "29日",
+          "30日",
         ],
         axisLabel: {
           textStyle: {
@@ -60,15 +59,19 @@ class EnergyConsumption extends PureComponent {
             fontSize: 14,
           },
         },
-        grid: {
-          top: 60,
-        },
         axisLine: {
           lineStyle: {
             color: "#fff",
           },
         },
       },
+      grid: {
+        top: 60,
+        left: 10,
+        right: 10,
+        bottom: 10,
+        containLabel: true,
+      },
       yAxis: {
         type: "value",
         name: "能耗(kWh)",
@@ -83,6 +86,9 @@ class EnergyConsumption extends PureComponent {
             color: "#fff",
           },
         },
+        splitLine: {
+          show: false,
+        },
       },
       series: [
         {

+ 0 - 146
src/components/WaterSupply/EnergyConsumption.js

@@ -1,146 +0,0 @@
-// 当月各个水厂耗能
-import React, { PureComponent } from "react";
-import Chart from "../../utils/chart";
-
-class EnergyConsumption extends PureComponent {
-  constructor(props) {
-    super(props);
-    this.state = {
-      renderer: "canvas",
-    };
-  }
-  getOptions() {
-    function generateNumbers() {
-      var numbers = [];
-      for (var i = 0; i < 30; i++) {
-        numbers.push(Math.floor(Math.random() * 100));
-      }
-      return numbers;
-    }
-    return {
-      // color: ["#3C4FD6", "#F6AC4B"],
-      xAxis: {
-        type: "category",
-        data: [
-          "1日",
-          "2日",
-          "3日",
-          "4日",
-          "5日",
-          "6日",
-          "7日",
-          "8日",
-          "9日",
-          "10日",
-          "11日",
-          "12日",
-          // "13日",
-          // "14日",
-          // "15日",
-          // "16日",
-          // "17日",
-          // "18日",
-          // "19日",
-          // "20日",
-          // "21日",
-          // "22日",
-          // "23日",
-          // "24日",
-          // "25日",
-          // "26日",
-          // "27日",
-          // "28日",
-          // "29日",
-          // "30日",
-          // "31日",
-        ],
-        axisLabel: {
-          textStyle: {
-            color: "#fff",
-            fontSize: 14,
-          },
-        },
-        grid: {
-          top: 60,
-        },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-      },
-      yAxis: {
-        type: "value",
-        name: "能耗(kWh)",
-        axisLabel: {
-          textStyle: {
-            color: "#fff",
-            fontSize: 14,
-          },
-        },
-        axisLine: {
-          lineStyle: {
-            color: "#fff",
-          },
-        },
-      },
-      series: [
-        {
-          data: generateNumbers(),
-          name: "第一水厂",
-          type: "line",
-          smooth: true,
-          symbol: "none",
-        },
-        {
-          data: generateNumbers(),
-          name: "第二水厂",
-          type: "line",
-          smooth: true,
-          symbol: "none",
-        },
-        {
-          data: generateNumbers(),
-          name: "第三水厂",
-          type: "line",
-          smooth: true,
-          symbol: "none",
-        },
-        {
-          data: generateNumbers(),
-          name: "第四水厂",
-          type: "line",
-          smooth: true,
-          symbol: "none",
-        },
-        {
-          data: generateNumbers(),
-          name: "第五水厂",
-          type: "line",
-          smooth: true,
-          symbol: "none",
-        },
-      ],
-      legend: {
-        show: true,
-        textStyle: {
-          color: "#fff",
-        },
-      },
-    };
-  }
-  render() {
-    return (
-      <div
-        style={{
-          width: "100%",
-          height: "100%",
-        }}
-      >
-        <Chart option={this.getOptions()} />
-      </div>
-    );
-  } //endrender
-}
-
-export default EnergyConsumption;

+ 7 - 0
src/components/WaterSupply/WaterVolume.js

@@ -12,6 +12,13 @@ class WaterVolume extends PureComponent {
   getOptions() {
     return {
       color: ["#3C4FD6", "#F6AC4B"],
+      grid: {
+        top: 35,
+        right: 36,
+        left: "3%",
+        bottom: "3%",
+        containLabel: true,
+      },
       xAxis: {
         max: "dataMax",
         axisLine: {

+ 1 - 1
src/components/WaterSupply/index.js

@@ -3,7 +3,7 @@ import React, { PureComponent } from "react";
 import { LeftPage } from "./style";
 import ChartBox from "../ChartBox";
 import UserWaterUsage from "./UserWaterUsage";
-import EnergyConsumption from "./EnergyConsumption";
+import EnergyConsumption from "../Sewage/EnergyConsumption";
 import WaterVolume from "./WaterVolume";
 import BMap from "../centerPage/charts/BMap";
 

+ 125 - 212
src/components/centerPage/charts/BMap.js

@@ -1,30 +1,12 @@
 import React, { PureComponent } from "react";
-import {
-  Map,
-  Marker,
-  Polyline,
-  Label,
-  CustomOverlay,
-  NavigationControl,
-  InfoWindow,
-  Polygon,
-  Arc,
-  LineLayer,
-} from "react-bmapgl";
-import { DrawScene, PolylineEdit } from "bmap-draw";
+import { Map, Marker, Polygon } from "react-bmapgl";
 
 import { styleJson } from "./BMapConfig";
-import { lineData } from "./LineConfig";
 import styled from "styled-components";
 import Project, { IconType } from "../../Map/Project";
-import DrawDom from "./DrawDom";
-const { BMapGL, BMapGLLib, BMap_Symbol_SHAPE_BACKWARD_OPEN_ARROW } = window;
-
-const arrows = [
-  [116.385443, 39.848287],
-  [116.415439, 39.905998],
-  [116.449646, 39.915149],
-];
+import ArrowPolyline from "../../Map/ArrowPolyline";
+import Lushu from "../../Map/Lushu";
+const { BMapGL, BMapGLLib } = window;
 
 class BMap extends PureComponent {
   constructor(props) {
@@ -38,8 +20,7 @@ class BMap extends PureComponent {
     this.draw = this.draw.bind(this);
   }
   map = null;
-  overlay = null;
-  sceneSelectStatus = null;
+  lushu = null;
   drawingManager = null;
   lineLayer = null;
 
@@ -89,120 +70,6 @@ class BMap extends PureComponent {
     }
   }
 
-  // 添加图层
-  addLineLayer() {
-    const upTwo =
-      "https://mapopen-pub-jsapigl.bj.bcebos.com/svgmodel/Icon_road_red_arrow.png";
-    const icon1 =
-      "https://mapopen-pub-jsapigl.bj.bcebos.com/svgmodel/up-two.png";
-    const icon2 =
-      "https://mapopen-pub-jsapigl.bj.bcebos.com/svgmodel/up-two.png";
-    const icon3 =
-      "https://mapopen-pub-jsapigl.bj.bcebos.com/svgmodel/up-two.png";
-    // fetch(
-    //   "https://mapopen-pub-jsapigl.bj.bcebos.com/svgmodel/lineLayerData.json"
-    // )
-    //   .then((res) => {
-    //     return res.json();
-    //   })
-    //   .then((testLineData) => {
-    if (!this.lineLayer) {
-      this.lineLayer = new BMapGL.LineLayer({
-        enablePicked: true,
-        autoSelect: true,
-        pickWidth: 30,
-        pickHeight: 30,
-        opacity: 1,
-        selectedColor: "blue", // 选中项颜色
-        style: {
-          sequence: true, // 是否采用间隔填充纹理,默认false
-          marginLength: 16, // 间隔距离,默认16,单位像素
-          borderColor: "rgba(0,125,125,1)",
-          borderMask: true, // 是否受内部填充区域掩膜,默认true,如果存在borderWeight小于0,则自动切换false
-          borderWeight: 2, // 描边宽度,可以设置负值
-          strokeWeight: 6, // 描边线宽度,默认0
-          strokeLineJoin: "miter", //描边线连接处类型, 可选'miter', 'round', 'bevel'
-          strokeLineCap: "square", // 描边线端头类型,可选'round', 'butt', 'square',默认round
-          // 填充纹理图片地址,默认是空。图片需要是竖向表达,在填充时会自动横向处理。
-          strokeTextureUrl: [
-            "match",
-            ["get", "name"],
-            "demo1",
-            upTwo,
-            "demo2",
-            icon1,
-            "demo3",
-            icon2,
-            icon3,
-          ],
-          strokeTextureWidth: ["match", ["get", "name"], "demo1", 32, 16],
-          strokeTextureHeight: ["match", ["get", "name"], "demo1", 64, 64],
-          strokeColor: [
-            "case",
-            ["boolean", ["feature-state", "picked"], false],
-            "#6704ff",
-            [
-              "match",
-              ["get", "name"],
-              "demo1",
-              "#ce4848",
-              "demo2",
-              "red",
-              "demo3",
-              "#666",
-              "#6704ff",
-            ],
-          ],
-          strokeOpacity: 0.5,
-        },
-      });
-    }
-    this.lineLayer.addEventListener("click", function (e) {
-      if (e.value.dataIndex !== -1 && e.value.dataItem) {
-        this.updateState(e.value.dataIndex, { picked: true });
-      }
-    });
-    this.map.addNormalLayer(this.lineLayer);
-    this.lineLayer.setData(lineData);
-    // });
-  }
-
-  //
-  addLine() {
-    var sy = new BMapGL.Symbol(BMap_Symbol_SHAPE_BACKWARD_OPEN_ARROW, {
-      scale: 0.6, //图标缩放大小
-      strokeColor: "#fff", //设置矢量图标的线填充颜色
-      strokeWeight: "2", //设置线宽
-    });
-    var icons = [
-      {
-        icon: new BMapGL.Icon(sy, new BMapGL.Size(30, 30), {
-          anchor: new BMapGL.Size(15, 15),
-        }),
-        offset: "10px",
-        repeat: true,
-      },
-    ];
-    // var icons = new BMapGL.IconSequence(sy, "100%", "10%", false);
-    // // 创建polyline对象
-    var pois = [
-      new BMapGL.Point(116.350658, 39.938285),
-      new BMapGL.Point(116.386446, 39.939281),
-      new BMapGL.Point(116.389034, 39.913828),
-      new BMapGL.Point(116.442501, 39.914603),
-    ];
-
-    var polyline = new BMapGL.Polyline(pois, {
-      enableEditing: false, //是否启用线编辑,默认为false
-      enableClicking: true, //是否响应点击事件,默认为true
-      icons: [icons],
-      strokeWeight: "8", //折线的宽度,以像素为单位
-      strokeOpacity: 0.8, //折线的透明度,取值范围0 - 1
-      strokeColor: "#18a45b", //折线颜色
-    });
-    this.map.addOverlay(polyline);
-  }
-
   // 移除图层
   removeLineLayer() {
     this.map.removeNormalLayer(this.lineLayer);
@@ -215,7 +82,7 @@ class BMap extends PureComponent {
 
   handleClick(e) {
     // this.boundary();
-    this.addLine();
+    // this.renderLine();
     // console.log("点击的经纬度:" + e.latlng.lng + ", " + e.latlng.lat);
   }
 
@@ -263,10 +130,72 @@ class BMap extends PureComponent {
     });
   }
 
+  renderLine(points) {
+    points = points || [
+      new BMapGL.Point(116.403723, 39.915035),
+      new BMapGL.Point(116.404803, 39.914878),
+      new BMapGL.Point(116.405267, 39.91644),
+      new BMapGL.Point(116.407194, 39.916142),
+      new BMapGL.Point(116.408205, 39.91507),
+    ];
+    var map = this.map;
+    var arrowIcon = new BMapGL.Icon(
+      require("../../../assets/icon-arr.png"),
+      new BMapGL.Size(16, 16),
+      {
+        // imageOffset: new BMapGL.Size(0, -20),
+        imageSize: new BMapGL.Size(16, 16),
+      }
+    );
+    // 绘制折线
+    var polyline = new BMapGL.Polyline(points, {
+      strokeColor: "red",
+      strokeWeight: 3,
+      strokeOpacity: 0.5,
+    });
+    map.addOverlay(polyline);
+    // 绘制箭头
+    for (var i = 1; i < points.length; i++) {
+      var startPoint = points[i - 1];
+      var endPoint = points[i];
+      var angle = this.getAngle(startPoint, endPoint); // 计算箭头的方向角度
+      var midPoint = new BMapGL.Point(
+        (startPoint.lng + endPoint.lng) / 2,
+        (startPoint.lat + endPoint.lat) / 2
+      );
+      // 计算箭头的中心点坐标
+      var arrowMarker = new BMapGL.Marker(midPoint, {
+        icon: arrowIcon,
+        enableClicking: false,
+      });
+      arrowMarker.setRotation(-angle); // 设置箭头的方向角度
+      map.addOverlay(arrowMarker);
+    }
+  }
+  // 计算两个点之间的角度
+  getAngle(pointA, pointB) {
+    var d = 0;
+    if (pointB.lng == pointA.lng) {
+      if (pointB.lat > pointA.lat) {
+        d = 90;
+      } else {
+        d = -90;
+      }
+    } else {
+      var k = (pointB.lat - pointA.lat) / (pointB.lng - pointA.lng);
+      var angle = (Math.atan(k) * 180) / Math.PI; // 计算弧度,并将弧度转换为角度
+      if (pointB.lng < pointA.lng) {
+        d = angle + 180; // 如果终点在起点左边,需要加上 180 度
+      } else {
+        d = angle; // 如果终点在起点右边,角度不变
+      }
+    }
+    return d;
+  }
+
   render() {
     return (
       <BMapBox>
-        {/* <DrawDom draw={this.draw} /> */}
         <Map
           ref={(ref) => {
             this.map = ref.map;
@@ -284,8 +213,18 @@ class BMap extends PureComponent {
           enableScrollWheelZoom
           onClick={(e) => this.handleClick(e)}
         >
-          <Polygon
-            path={[
+          {/* <Lushu
+            ref={(ref) => {
+              this.lushu = ref;
+            }}
+            points={[
+              { lng: 116.24721748495391, lat: 39.796737802722014 },
+              { lng: 116.322837465736, lat: 39.78383959531996 },
+              { lng: 116.39845744651808, lat: 39.77093895027502 },
+            ]}
+          /> */}
+          <ArrowPolyline
+            points={[
               { lng: 116.24721748495391, lat: 39.796737802722014 },
               { lng: 116.322837465736, lat: 39.78383959531996 },
               { lng: 116.39845744651808, lat: 39.77093895027502 },
@@ -294,6 +233,14 @@ class BMap extends PureComponent {
               { lng: 116.68690049846205, lat: 39.924723760492924 },
               { lng: 116.69494931583147, lat: 40.12451264487721 },
               { lng: 116.5466211100239, lat: 40.15672410636046 },
+            ]}
+            strokeColor="#66ccff"
+            strokeWeight={2}
+            fillColor="#0ab8d9"
+            fillOpacity={0.3}
+          />
+          <ArrowPolyline
+            points={[
               { lng: 116.39829290421633, lat: 40.18892017402776 },
               { lng: 116.25916334683096, lat: 40.169957230404016 },
               { lng: 116.12003378944557, lat: 40.150988945208915 },
@@ -302,14 +249,10 @@ class BMap extends PureComponent {
               { lng: 116.19799852535938, lat: 39.8634192849549 },
               { lng: 116.24721748495391, lat: 39.796737802722014 },
             ]}
-            strokeColor="#55636A"
+            strokeColor="red"
             strokeWeight={2}
             fillColor="#0ab8d9"
             fillOpacity={0.3}
-            onMouseover={(e) => {
-              console.log(e);
-            }}
-            // enableEditing
           />
           <Marker
             position={this.state.position}
@@ -321,74 +264,54 @@ class BMap extends PureComponent {
             }}
           />
           <Project
+            title="**公司"
             position={{ lng: 116.402544, lat: 39.928216 }}
             icon={IconType.Company}
           />
-          {/* <ArrowLineLayer points={arrows} /> */}
-          {/* <Label
-            position={new BMapGL.Point(116.322544, 39.910216)}
-            offset={new BMapGL.Size(-40, 10)}
-            text="供水水量: 11415m³"
-            style={{ color: "#fff", border: "none", background: "transparent" }}
-          /> */}
-          {/* <Polyline
-            path={[
-              new BMapGL.Point(116.402544, 39.928216),
-              new BMapGL.Point(116.322544, 39.910216),
-              new BMapGL.Point(116.202544, 39.908216),
-            ]}
-            strokeColor="#f00"
-            // strokeOpacity={0.8}
-            strokeWeight={2}
-            onClick={(e) => this.clickPolyline(e)}
-          /> */}
           <Project
+            title="**水厂"
             position={{ lng: 116.202544, lat: 39.908216 }}
             icon={IconType.WaterWorks}
           />
           <Project
+            title="**泵站"
             position={{ lng: 116.602544, lat: 39.948216 }}
             icon={IconType.Pump}
           />
 
-          {/* <NavigationControl /> */}
-          {/* <Arc
-            autoViewport
-            showStartPoint
-            showEndPoint
-            enableAnimation
-            data={[
-              {
-                from: {
-                  point: { lng: 116.35, lat: 40.01 },
-                },
-                to: {
-                  point: { lng: 116.24721748495391, lat: 39.796737802722014 },
-                },
-              },
-              {
-                color: "#392",
-                from: {
-                  city: "北京",
-                  point: { lng: 116.35, lat: 40.01 },
-                },
-                to: {
-                  name: "哈哈",
-                  point: { lng: 116.53865456380538, lat: 39.74764618546205 },
-                },
-              },
-              {
-                from: {
-                  city: "北京",
-                  point: { lng: 116.35, lat: 40.01 },
-                },
-                to: {
-                  city: "成都",
-                  point: { lng: 116.39829290421633, lat: 40.18892017402776 },
-                },
-              },
+          <Project
+            title="分钟级:0.2mm"
+            position={{ lng: 116.502544, lat: 39.748216 }}
+            icon={IconType.Rainfall}
+          />
+          <Project
+            title="0.2m"
+            position={{ lng: 116.302544, lat: 39.918216 }}
+            icon={IconType.Water}
+          />
+          <Project
+            title="水泵"
+            position={{ lng: 116.802544, lat: 39.768216 }}
+            icon={IconType.WaterPump}
+          />
+
+          <Project
+            title="张三"
+            position={{ lng: 116.702544, lat: 39.848216 }}
+            icon={IconType.Person}
+          />
+          <Project
+            title="抢修车01"
+            position={{ lng: 116.102544, lat: 39.748216 }}
+            type={1}
+            showLushu={true}
+            icon={IconType.Car}
+            points={[
+              { lng: 116.24721748495391, lat: 39.796737802722014 },
+              { lng: 116.322837465736, lat: 39.78383959531996 },
+              { lng: 116.102544, lat: 39.748216 },
             ]}
-          /> */}
+          />
         </Map>
       </BMapBox>
     );
@@ -404,14 +327,4 @@ const BMapBox = styled.div`
   z-index: 1;
 `;
 
-const Icon = styled.div`
-  width: 40px;
-  height: 40px;
-  border-radius: 20px;
-  background-color: rgba(222, 0, 0, 0.8);
-  text-align: ceter;
-  line-height: 40px;
-  color: #fff;
-`;
-
 export default BMap;

+ 19 - 2
src/components/centerPage/charts/Map.js

@@ -1,7 +1,8 @@
 import React, { PureComponent } from "react";
 import Chart from "../../../utils/chart";
+import Legend from "../../Map/Legend";
 import BMap from "./BMap";
-import { mapOptions, BMapOptions } from "./options";
+import { mapOptions } from "./options";
 
 class Map extends PureComponent {
   constructor(props) {
@@ -135,7 +136,23 @@ class Map extends PureComponent {
         //   renderer={renderer}
         //   option={BMapOptions()}
         // />
-        <BMap />
+        <>
+          <BMap />
+          <Legend
+            list={[
+              "水厂",
+              "泵站",
+              "管网",
+              "大用户企业",
+              "大用户水表",
+              "内涝点",
+              "流量",
+              "雨量",
+              "视频",
+              "全部",
+            ]}
+          />
+        </>
       );
     }
   }

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů