# 场景整合

# 适用场景

多场景下,地图不重新加载

# 示例

场景整合-水-固定源 (opens new window)

# 功能基础

  • 框架 - 图层列表 - 支持动态增、删、改图层
  • 框架 - 图例 - 支持动态修改
  • 框架 - 时间轴 - 支持动态修改

# 实现逻辑

# 基础

  • 多场景有 同一个坐标系和初始范围
  • 每个场景都有 唯一 的标识,场景唯一标识由大屏维护

# 整合

# 场景

  • 保证底图和地图初始配置和主体应用相同
  • 在开发者代码中通过特定代码监听场景销毁的消息,一但发现要销毁的是自己,断开和大屏的联系,销毁所有监听事件和代码中加载的图层(保证自己的场景不再影响地图

# 主体

# 场景加载

通过大屏发送过来的要加载场景的预览地址,动态读取场景的图层配置、图例配置、时间轴配置,在现有的基础上以的方式添加场景,并把场景的开发者代码执行一遍

# 场景销毁
  • 通过已读取的图层配置、图例配置、时间轴配置删除要销毁场景的图层、图例、时间轴
  • 通过在主体应用中向全局发送销毁场景的唯一标识,让所有的场景能接收到销毁的场景是谁

# 大屏

  • 首次加载场景:
    • 大屏等待地图发出地图加载完成的消息后,首次把要加载的场景信息发给地图并保存当前场景标识
  • 点击场景按钮:
    • 向地图发送销毁当前场景,加载点击场景的消息,并把当前场景变量置为按钮场景

# 相关说明

# 底图相关

因为不同坐标系不同切片方案的影像服务是不好相互叠加的。因此,需要一开始就明确统一场景的底图服务和坐标系。

这些可以在 主体应用 中配置,然后告知其他场景的开发人员,防止因为底图相关问题导致返工。

地图初始范围同理。

# 公共要素

如果要添加公共要素的话,有两种实现方式:

  • 可以在 主体应用 中的直接配置和开发

  • 把公共要素作为一个场景,在 主体应用 准备好接收大屏消息后,第一个加载,不移除

# 图层命名

在 MapGO 平台中 配置图层时,要保证图层名称和图层分类名称的唯一性,统一使用 专题-场景-图层含义 对图层进行中文命名,如:大气-预报-监测站点

# 消息命名

在 MapGO 平台中 的开发者中编写代码时,如需自定义消息/事件名,要保证消息/事件名称的唯一性。统一使用 专题-场景-消息/事件含义 对 消息/事件进行命名:如 水环境质量现状图层过滤 消息/事件的可命名为:water_qualitystatus_layerfilter。

# 代码

# 主体

/**
 * 场景整合
 * @module
 * @author suyp
 * @date 2021-3-20
 * @version 1.0
 */

var webSocketURL = "http://192.168.0.140:7070/view"; // 大屏地址 IP 和 端口 改为要连接大屏的 IP 和 端口
// getQueryVariable 是根据window.location 去获取的参数
var clientCode = getQueryVariable("wsClientCode")
  ? getQueryVariable("wsClientCode")
  : "thsgis_"; // 获取大屏传过来的跨屏交互ID
var mapGoConstants = {
  // MapGo 平台相关常量
  userName: "userName", // 应用文件路径中代表用户名的key
  appType: "appType", // 应用文件路径中代表应用类型的key
  appID: "appID", // 应用文件路径中代表应用在该用户该应用类型下唯一标识的key(旧版为appName)
  middleUrl: "/mapgo/apps/", // 应用配置文件和IP端口中间路径部分
  configAddress: {
    // 平台-应用配置文件的固定地址片段
    layerManager: "/LayerManager/config.json", // 图层配置
    timeLine: "/TimeLineManager/config.json", // 时间轴配置
    legendManager: "/LegendManager/config.json", // 图例配置
    interactions: "/interactions.js", // 开发者代码
  },
  interactions: {
    // 开发者代码相关
    // 新旧两个能提取用户开发者代码的关键字符串
    oldDefault:
      "subscribeEvent('allLayersLoaded',function(){unsubscribe(alllayerLoadedHandle);",
    newestDefault: "subscribeEvent('allLayersLoaded',function(){",
  },
};
var sceneIntegration = {
  // 场景整合相关常量
  init: "scene_init", // 场景初始化的key
  destroy: "scene_destroy", // 销毁场景的key
  gisMessageKey: "thsGis", // 标识是发给GIS的消息(如果不用可修改相关位置)
  mapLoadMessage: {
    // websocket连接后,通知大屏GIS已经准备好接收消息了
    type: "runInteractive",
    data: [
      {
        shareCode: "mapLoaded",
        runtimeValue: "true",
      },
    ],
  },
  error: {
    // 相关错误提示
    initScene: "要加载的场景已存在或者场景地址不存在!",
    interactions: "开发者代码错误:",
    replaceJSONStr: "JSON替换失败:",
  },
};
var topicEvents = {
  // 二维发布订阅的消息key集合
  layersUpdated: "layers-updated", // 图层列表-复数图层添加/更新/移除完成
  customLayersUpdated: "layers-updated-custom", // 自定义复数图层操作完成
};
var customWidgetType = {
  // 自定义微件的类型
  legend: "legend", // 图例
  timeLine: "time-line", // 时间轴
};

var allLayers = {}; // 初始图层列表的图层
var curScensePolygonLayers = []; // 要调整顺序的面图层数据,记得调整完清空
var mapPolygonLayerLength = 0; // 当前地图面图层的数量
var curSceneLayers = []; // 当前场景全部图层转为一级后的数组
var isAdd = true; // 第一次加载场景的时候,需要合并初始图层列表图层和场景图层,用来标识是否是第一次
var initLayers = []; // 初始地图所有图层id的数组,由开始时从 map._layers 获取
// 用于替换场景配置文件的字符,适用于服务迁移时IP等改变、引用文件路径变更等
// 因为在vue中也有替换字符的代码,所以这里的地址需要加上特定字符来保证不会被vue替换为新的地址(如果确定没有vue参与,可以不加特殊字符)
var replaceResources = [
  // {
  //   replaceStr: '"..\/icons', // 要替换的字符串
  //   replacedStr: '"http://' + environmentalParam.gisFrameWorkSocket + '/gis_framework/icons', // 替换后的字符串
  // },
];
var sceneList = []; // 场景对象集合
var layers = []; // 所有的图层
var sceneOb = null; // 最新场景的配置
var handlingScene = []; // 处理场景的命令缓冲区

// 获取初始图层列表配置,用来和第一次加载的场景的图层合并配置
if (
  layerManager.layerMangerInstance &&
  layerManager.layerMangerInstance.getLayersConfig
) {
  var layerConfig = layerManager.layerMangerInstance.getLayersConfig();
  if (layerConfig && typeof layerConfig.layers === "object") {
    allLayers = layerConfig.layers;
  }
}

//!------------ 大屏消息处理 ------------!//

/**
 * 初始化webSocket连接
 * @param {string} clientCode 跨屏交互ID
 * @param {Function} receiveCallback 接受消息的回调
 * @param {Function} closeCallback 关闭连接的回调
 * @param {string} webSocketURL webSocket服务器地址
 * @description sceneCoordinate 为场景整合模块在webSocket中的ket
 */
var thsMapSocket = ths.initSocket(
  clientCode,
  {
    sceneCoordinate: function(content) {
      content = JSON.parse(content.data).data;
      // 先通过 { 初步判断是json格式
      if (content.indexOf("{") > -1) {
        // 和大屏人员确定过,给GIS发的消息中都会带有thsGis关键字,不是给gis发的消息,不处理(可以根据实际情况修改)
        var sceneProArray = JSON.parse(content).data;
        if (content.indexOf(sceneIntegration.gisMessageKey) > -1) {
          // 单场景-场景初始化和销毁消息合一
          var operations = JSON.parse(sceneProArray[0].runtimeValue);
          // 场景操作信息,包含销毁和加载
          var sceneOperation = {};
          // 传过来的应该是个数组
          if (isArray(operations)) {
            //!-----场景加载和销毁-------!//
            operations.forEach(function(pro) {
              if (pro.messageType === sceneIntegration.init) {
                sceneOperation.init = pro;
              }
              if (pro.messageType === sceneIntegration.destroy) {
                sceneOperation.destroy = pro;
              }
            });
            // 将待处理操作放入命令缓冲区
            handlingScene.push(sceneOperation);
            // 因为处理场景的命令是执行完一个才会执行下一个,所以,在接收到消息并把消息放到命令缓冲区中后,
            // 只有一个待执行命令的时候才会在这里开始处理命令,其他的时候会在  "layers-updated-custom" 订阅的回调中继续执行
            if (handlingScene.length === 1) {
              // 开始处理场景
              handleScene(sceneOperation);
            }
          }
        }
      }
    },
  },
  null,
  webSocketURL,
  function() {
    // 向大屏发送地图加载完成的消息,大屏收到后向地图发送要加和销毁什么场景
    sendMessage(sceneIntegration.mapLoadMessage, webSocketURL, clientCode);
  }
);

//!------------ 场景整合处理 ------------!//

/**
 * 开始处理场景事件
 * @param {Object} sceneOperation 场景
 * @example
 * {
 *  destroy: {
 *    messageType: "scene_destroy", // 标识命名为销毁场景
 *    name: "water_yy", // 要销毁场景的唯一key
 *  },
 *  init: {
 *    messageType: "scene_init", // 标识命名为加载场景
 *    name: "air_quality", // 要加载场景的唯一key
 *    url: "http://10.100.245.102:8089/visualmap/#/MapViews?userName=xuckj4&appType=2D&appName=大气一张图", // 场景应用的预览地址
 *  }
 * }
 */
function handleScene(sceneOperation) {
  if (sceneOperation) {
    // 销毁场景
    if (sceneOperation.destroy) {
      destroyScene(sceneOperation.destroy);
    }
    // 加载场景
    if (sceneOperation.init) {
      var searchSceneIndex = searchScene(sceneOperation.init);
      // 判断url是否存在,不存在,不加载
      // 如果场景已经存在,不重复加载
      if (sceneOperation.init.url && searchSceneIndex < 0) {
        loadScene(sceneOperation.init, searchSceneIndex);
      } else {
        // 要加载的场景已存在或者场景地址不存在
        console.log(sceneIntegration.error.initScene);
      }
    }
    // 待处理任务减1,下个任务将在场景处理完开始
    handlingScene.splice(0, 1);
  }
}

/**
 * 加载场景
 * @param sceneObj
 * @param searchSceneIndex
 */
function loadScene(sceneObj, searchSceneIndex) {
  // 首先获取场景对应所有的配置文件
  getConfigOptions(sceneObj, function() {
    // 把多级图层转为一级,放到变量curSceneLayers中,为调整面图层顺序做准备
    processLayers(sceneOb.layerManager.layers);
    // 找出面图层
    handlePolygonLayer();
    // 合并场景图层和现有图层数据
    layers = layers.concat(sceneOb.layerManager.layers);
    // 第一次加载场景的时候,把主体的图层信息也放到layers中
    if (isAdd) {
      layers = layers.concat(allLayers);
      isAdd = false;
    }
    // 把场景信息放到场景集合中
    sceneList.push(sceneOb);
    // 加载图层
    if (sceneOb.layerManager && sceneOb.layerManager.layers) {
      // 如果要加载的场景图层为空,直接发消息进入图层加载完的事件
      if (sceneOb.layerManager.layers.length === 0) {
        publishEvent(topicEvents.layersUpdated);
      } else {
        // 发消息,让图层列表加载场景图层
        // publishEvent('updateLayer', {
        //   controlLayer: sceneOb.layerManager.layers, // 要加的图层
        //   type: 'add', // 操作类型为加载
        //   layers: layers // 加载完后,全部的图层
        // });
        // TODO 目前需要这种方式才能同时更新图层和图层列表面板,后期可能会有变化
        window.postMessage({
          controlLayer: sceneOb.layerManager.layers, // 要加的图层
          type: "add", // 操作类型为加载
          layers: layers, // 加载完后,全部的图层
          messageType: "layerChanged",
        });
      }
    }
  });
}

/**
 * 获取该场景的所有配置文件
 * @param sceneObj
 * @param callBack
 */
function getConfigOptions(sceneObj, callBack) {
  // 要加载场景的预览地址
  var appUrl = sceneObj.url;
  // 地图类型(2D/3D)
  var appType = getUrlParam(mapGoConstants.appType, appUrl);
  // 场景的用户名
  var userName = getUrlParam(mapGoConstants.userName, appUrl);
  // 场景应用标识
  var appID = getUrlParam(mapGoConstants.appID, appUrl);
  // 获取地址中端口的正则
  var hostNamePortReg = /.+:(\d{1,5})/;
  // 端口
  var hostNamePort = appUrl.match(hostNamePortReg);
  // 场景配置文件根目录地址
  var appAddressUrl =
    hostNamePort[0] +
    mapGoConstants.middleUrl +
    userName +
    "/" +
    appType +
    "/" +
    appID;
  // 场景图层列表配置文件完整地址
  var layerManager = appAddressUrl + mapGoConstants.configAddress.layerManager;
  // 场景时间轴配置文件完整地址
  var timeline = appAddressUrl + mapGoConstants.configAddress.timeLine;
  // 场景图例配置文件完整地址
  var legend = appAddressUrl + mapGoConstants.configAddress.legendManager;
  // 场景开发者代码配置文件完整地址
  var interactions = appAddressUrl + mapGoConstants.configAddress.interactions;
  // 请求配置文件(注意低版本浏览器不支持Promise,请自行引入兼容性代码)
  Promise.all([
    getData(layerManager),
    getData(timeline),
    getData(legend),
    getData(interactions),
  ]).then(function(values) {
    // 等所有文件请求完执行
    // 如果有替换的文本的需求
    if (replaceResources && replaceResources.length > 0) {
      for (var i = 0; i < replaceResources.length; i++) {
        sceneObj.layerManager = replaceStrInJSON(
          sceneObj.layerManager || values[0],
          new RegExp(replaceResources[i].replaceStr, "g"),
          replaceResources[i].replacedStr
        );
        sceneObj.timeLine = replaceStrInJSON(
          sceneObj.timeLine || values[1],
          new RegExp(replaceResources[i].replaceStr, "g"),
          replaceResources[i].replacedStr
        );
        sceneObj.legend = replaceStrInJSON(
          sceneObj.legend || values[2],
          new RegExp(replaceResources[i].replaceStr, "g"),
          replaceResources[i].replacedStr
        );
        if (values[3].replace) {
          sceneObj.interactions = (sceneObj.interactions || values[3]).replace(
            new RegExp(replaceResources[i].replaceStr, "g"),
            replaceResources[i].replacedStr
          );
        } else {
          sceneObj.interactions = sceneObj.interactions || values[3];
        }
        sceneOb = sceneObj;
      }
    } else {
      sceneOb = sceneObj;
      // 使用replaceStrInJSON的目的只要把配置的文本转为对象,保证是否有替换,变量都是一致的
      sceneOb.layerManager = replaceStrInJSON(values[0]);
      sceneObj.timeLine = replaceStrInJSON(values[1]);
      sceneObj.legend = replaceStrInJSON(values[2]);
      sceneObj.interactions = values[3]; // 开发者代码就应该是字符串
    }
    callBack();
  });
}

/**
 * 订阅图层列表-复数图层加载/更新/销毁完成
 * @desc 在场景整合中的作用主要是新场景图层列表加载完成后执行什么
 */
subscribeEvent(topicEvents.layersUpdated, function() {
  // 为了和图层列表的事件进行区分
  publishEvent(topicEvents.customLayersUpdated);
});

/**
 * 图层加载完成或者销毁完成
 */
subscribeEvent(topicEvents.customLayersUpdated, function() {
  // 只要走到这里,不管是加载场景还是销毁场景,当前任务就算是处理完了,所以需要去掉当前的任务
  handlingScene.splice(0, 1);
  // 更新自定义微件(时间轴和图例)
  widgetsUpdated();
  // 因为销毁场景的时候也会进这里,所有在执行的时候,要先判断sceneList中这个场景存在
  if (searchScene(sceneOb) > -1) {
    // 如果还有待处理任务,将继续执行下一个任务
    if (handlingScene.length > 0) {
      // 取出下一个任务(已处理任务的下一个,不是最新的)
      var sceneObj = handlingScene[0];
      // TODO 可能会出现连续几个任务有问题的情况
      // 获取要处理的场景在已有场景的索引(兼可判断场景是否存在)
      var searchSceneIndex = searchScene(sceneObj);
      // 处理场景任务
      handleScene(sceneObj, searchSceneIndex);
    }
    // 如果当前任务是加载场景,要在图层加载完后执行对应的开发者代码
    if (sceneOb.messageType === sceneIntegration.init) {
      // 执行开发者代码
      playScript(sceneOb);
      // 把面图层跳到点图层下面
      movePolygonLayer();
    }
  }
});

/**
 * 单独销毁一个场景
 * @param {Object} destroySceneInfo 要销毁的场景信息
 * @example
 * {
 *  name: 要销毁场景的唯一标识
 * }
 */
function destroyScene(destroySceneInfo) {
  if (destroySceneInfo.name && destroySceneInfo.name !== "undefined") {
    // 获取该场景在场景集合中的索引
    var searchSceneIndex = searchScene(destroySceneInfo);
    // 场景存在即删除场景
    if (searchSceneIndex > -1) {
      // 向整个地图发出消息,告诉所有场景要销毁哪个场景了
      publishEvent(sceneIntegration.destroy, destroySceneInfo);
      // 根据场景集合中的场景图层配置,删除图层集合中的该场景的图层
      sceneList[searchSceneIndex].layerManager.layers.forEach(function(layer) {
        layers.forEach(function(allLayer, index) {
          if (allLayer.name === layer.name) {
            layers.splice(index, 1);
          }
        });
      });
      // 根据场景信息,减小面图层相关变量
      reducePolygonLengthByScene(
        sceneList[searchSceneIndex].layerManager.layers
      );
      // TODO 多场景时,考虑让图层去移除图层
      // 删除除初始图层以外的所有图层
      onlyRetainInitLayers(initLayers);
      // 在所有场景列表中删除该场景
      sceneList.splice(searchSceneIndex, 1);
      // 都先隐藏时间轴微件、置空图例微件
      setCustomWidgetVisible(customWidgetType.timeLine, false);
      addLegendToMap(null, [
        {
          options: [],
          defaultVisible: true,
          isDefaultOpen: false,
        },
      ]);
    }
  }
}

/**
 * 处理当前场景的面图层
 * @description
 * 把当前场景的面图层筛选出来(主要为了多场景叠加的时候,新场景的面图层不能把老图层的点线图层盖住)
 */
function handlePolygonLayer() {
  if (curSceneLayers.length) {
    // geojson面、feature 面 (未考虑image、wmts、wfs)
    for (var i = 0; i < curSceneLayers.length; i++) {
      if (
        (curSceneLayers[i].type === "geojson" ||
          curSceneLayers[i].type === "feature") &&
        curSceneLayers[i].geometryType === "polygon"
      ) {
        curScensePolygonLayers.push({
          layerName: curSceneLayers[i].name,
          index: mapPolygonLayerLength + curScensePolygonLayers.length,
        });
      }
    }
    mapPolygonLayerLength += curScensePolygonLayers.length;
  }
}

/**
 * 执行新场景的开发者代码
 * @param {Object} sceneObj 场景对象
 */
function playScript(sceneObj) {
  // 把开发者代码中默认的代码去掉,只保留用户编写的
  var scriptSegment = handleScriptStr(sceneObj);
  try {
    eval(scriptSegment);
  } catch (e) {
    console.log(sceneIntegration.error.interactions + e);
  }
}

/**
 * 处理开发者代码,去掉在这里不需要的代码
 * @param {Object} sceneObj 图层对象
 * @returns {string} 要执行的开发者代码
 */
function handleScriptStr(sceneObj) {
  if (sceneObj.interactions && typeof sceneObj.interactions === "string") {
    var code = sceneObj.interactions.split(
      mapGoConstants.interactions.oldDefault
    )[1];
    if (code) {
      code = code.slice(0, -50);
    } else {
      code = sceneObj.interactions
        .split(mapGoConstants.interactions.newestDefault)[1]
        .slice(0, -4);
      code = code.slice(0, code.length - 1);
    }
    return code;
  }
}

/**
 * 根据场景信息,降低面图层的相关变量(移除场景时使用)
 * @param {object} layerConfig 图层配置
 */
function reducePolygonLengthByScene(layerConfig) {
  // 三级变一级,统计当前图层
  processLayers(layerConfig);
  if (curSceneLayers.length) {
    // geojson面、feature 面 (未考虑image、wmts、wfs)
    for (var i = 0; i < curSceneLayers.length; i++) {
      if (
        (curSceneLayers[i].type === "geojson" ||
          curSceneLayers[i].type === "feature") &&
        curSceneLayers[i].geometryType === "polygon"
      ) {
        mapPolygonLayerLength--;
      }
    }
  }
  // 重置
  curScensePolygonLayers = [];
  curSceneLayers = [];
}

/**
 * 把当前场景的面图层跳到所有点位图层的下面
 * @description 新场景的面会在旧场景的面的上面
 */
function movePolygonLayer() {
  if (curScensePolygonLayers.length) {
    for (var i = 0; i < curScensePolygonLayers.length; i++) {
      reorderLayer(
        curScensePolygonLayers[i].layerName,
        curScensePolygonLayers[i].index
      );
    }
  }
  // 调整完重置
  curScensePolygonLayers = [];
  curSceneLayers = [];
}

/**
 * 根据场景对象找出在场景列表中的索引
 * @param sceneObj 场景对象
 * @returns {number}
 */
function searchScene(sceneObj) {
  return sceneList.findIndex(function(scene) {
    if (scene.name === sceneObj.name) {
      return true;
    }
  });
}

/**
 * 自定义微件的更新(图例和时间轴)
 */
function widgetsUpdated() {
  // 获取当前最新的场景数据
  var sceneNearest = sceneList[sceneList.length - 1];
  // 存在,根据数据更新图例和时间轴
  if (sceneNearest) {
    // 如果厂商在开发者中加的时间轴,需要自行设置时间轴显示
    if (sceneNearest.timeLine.length === 0) {
      setCustomWidgetVisible(customWidgetType.timeLine, false);
    } else {
      setCustomWidgetVisible(customWidgetType.timeLine, true);
      // 记载时间轴(只加载最新的时间轴)
      addTimeLineToMap(null, sceneNearest.timeLine);
    }
    // 初始图例配置
    var legendOptions = {
      options: [],
      defaultVisible: true,
      isDefaultOpen: false,
    };
    // 遍历所有场景,综合所有场景的要显示的图例配置
    sceneList.forEach(function(scene, index) {
      // 获取具体的图例配置
      var legendConfig = scene.legend;
      // 找到默认显示的图层场景
      var defaultVisibleTrueConfig = legendConfig.find(function(legend) {
        return legend.defaultVisible;
      });
      // 如果有默认显示的
      if (defaultVisibleTrueConfig) {
        // 合并图例数据
        legendOptions.options = legendOptions.options.concat(
          defaultVisibleTrueConfig.options
        );
        // 如果遍历到最后一个场景
        if (index === sceneList.length - 1) {
          var mergeLegend = Object.assign(
            {},
            legendOptions,
            defaultVisibleTrueConfig
          );
          if (mergeLegend.options) {
            delete mergeLegend.options;
          }
          legendOptions = Object.assign({}, legendOptions, mergeLegend);
        }
      }
    });
    // 加载图例
    addLegendToMap(null, [legendOptions]);
  } else {
    // 不存在,隐藏时间轴,清空图例
    setCustomWidgetVisible(customWidgetType.timeLine, false);
    addLegendToMap(null, [
      {
        options: [],
        defaultVisible: true,
        isDefaultOpen: false,
      },
    ]);
  }
}

/**
 * 移除场景的时候只保留初始图层
 * @param initLayers 要保留的图层id
 */
// TODO 当支持多场景叠加的时候,需要只去掉销毁场景的图层
function onlyRetainInitLayers(initLayers) {
  if (initLayers) {
    // 获取当前地图中的所有图层
    var curAllLayers = Object.keys(map._layers);
    for (var i = 0; i < curAllLayers.length; i++) {
      // 如果图层在当前地图中存在
      if (initLayers.indexOf(curAllLayers[i]) < 0) {
        // 因为不好确定图层类型去使用适应的方法,所有都调一遍
        // 删除Echarts图层
        removeEchartsLayer(curAllLayers[i], true);
        if (getLayer(curAllLayers[i])) {
          // 删除HTML图层
          removeCustomLayer(curAllLayers[i]);
          if (getLayer(curAllLayers[i])) {
            // 删除图层
            removeLayer(curAllLayers[i]);
          }
        }
      }
    }
  }
}

/**
 * 对JSON字符串或者对应执行字符替换
 * @param {(object|string)} config 要替换的字符串或者对象
 * @param {(Object|string)} replaceRegExp 正则对象(要替换的规则)
 * @param {string} replacedStr 要替换为的字符串
 * @return {Object}
 */
function replaceStrInJSON(config, replaceStr, replacedStr) {
  var json = config;
  if (json) {
    try {
      if (typeof json !== "string") {
        json = JSON.stringify(json);
      }
      if (json.replace) {
        json = JSON.parse(json.replace(replaceStr, replacedStr));
      }
    } catch (error) {
      console.log(sceneIntegration.error.replaceJSONStr + error);
    }
  }
  return json;
}

// 获取初始图层id数组
initLayers = initLayers.concat(Object.keys(map._layers));

//!------------ 相关工具方法 ------------!//

/**
 * 获取url中的特定参数的值
 * @param {string} name 特定参数(key)
 * @param {string} url http地址
 * @returns {string|null}
 */
function getUrlParam(name, url) {
  var reg = new RegExp(name + "=([^&]*)"); // 构造一个含有目标参数的正则表达式对象
  var r = url.substr(1).match(reg); // 匹配目标参数
  if (r != null) return unescape(r[1]);
  return null; // 返回参数值
}

/**
 * 转三级图层列表为一级
 * @param {Object} configs 图层配置
 * @description 把图层转为以及放到变量curSceneLayers中,为了后面统计面图层和调整面图层顺序做准备
 */
function processLayers(configs) {
  if (configs) {
    for (var i = 0; i < configs.length; i++) {
      if (configs[i].children) {
        // 存在子节点,则当前节点为分类,而不是具体图层
        processLayers(configs[i].children);
      } else {
        curSceneLayers.push(configs[i]);
      }
    }
  }
}

/**
 * 加载数据
 * @param {string} url 数据地址
 * @returns {Promise}
 */
function getData(url) {
  // 如果需要对地址进行操作,请处理后在传入该函数
  return new Promise(function(resolve, reject) {
    fetch(encodeURI(url))
      .then(function(response) {
        return response.text();
      })
      .then(function(data) {
        resolve(data);
      })
      .catch(function(e) {
        resolve({});
      });
  });
}

//!------------ 模拟大屏消息 ------------!//

// 向地图中添加两个按钮并模拟大屏消息加载场景
createButton(20, 100, "加载水,销毁固定源", function() {
  sendMessage({
    type: "runInteractive",
    data: [
      {
        code: "thsGis",
        shareCode: "thsGis",
        defaultValue: "",
        runtimeValue: JSON.stringify([
          {
            messageType: "scene_init",
            name: "water",
            url:
              "http://121.46.19.2:20724/mapgo/#/MapViews?userName=template&appType=2D&appID=z1zb1vbokmhajncu",
          },
          {
            messageType: "scene_destroy",
            name: "stationary-source",
          },
        ]),
      },
    ],
  });
});
createButton(20, 300, "加载固定源,销毁水", function() {
  sendMessage({
    type: "runInteractive",
    data: [
      {
        code: "thsGis",
        shareCode: "thsGis",
        defaultValue: "",
        runtimeValue: JSON.stringify([
          {
            messageType: "scene_init",
            name: "stationary-source",
            url:
              "http://121.46.19.2:20724/mapgo/#/MapViews?userName=template&appType=2D&appID=z1zbremkmhak2t9",
          },
          {
            messageType: "scene_destroy",
            name: "water",
          },
        ]),
      },
    ],
  });
});
/**
 * 向页面中添加个按钮(绝对定位)
 * @param {number} top 距上面的像素
 * @param {number} left 距左侧的像素
 * @param {string} text 显示文本
 * @param {Function} clickCallback 点击的回调
 */
function createButton(top, left, text, clickCallback) {
  // 向body中添加按钮
  var btn = document.createElement("button");
  btn.style.width = "150px";
  btn.style.height = "40px";
  btn.style.position = "absolute";
  btn.style.top = top + "px";
  btn.style.left = left + "px";
  btn.innerText = text;
  document.querySelector("body").appendChild(btn);
  if (clickCallback) {
    btn.onclick = clickCallback;
  }
}

# 场景

//订阅销毁场景的事件
var subscribeHandler = null;
subscribeEvent(
  "scene_destroy",
  function(sceneObj) {
    //注意一定要根据场景名称判断
    if (sceneObj.name === "water") {
      console.log("水场景接收到了场景销毁事件");
      // 需要处理的操作逻辑,销毁地图监听事件
      // if (handler && handler.remove instanceof Function)         {
      // handler.remove();
      // }
      //移除通过代码添加的图层
      // 取消订阅的销毁场景事件
      if (subscribeHandler) {
        unsubscribe(subscribeHandler);
      }
      // 取消连接
      if (thsMapSocket) {
        // water要和建立WebSocket方法的第二个参数的key一致
        thsMapSocket.removeListenMessage("water");
      }
    }
  },
  function(event) {
    subscribeHandler = event;
  }
);

# 部分变量说明

# sceneList

已加载/正在加载的的场景的配置信息集合

# 示例
[
  {
    name: "air_quality", // 场景应用的唯一标识(要求场景之间,该名称唯一)
    messageType: "scene_init", // 大屏消息中标识操作的类型(scene_init:加载场景)
    url:
      "http://10.100.245.102:8089/visualmap/#/MapViews?userName=xuckj4&appType=2D&appName=大气一张图", // 场景的预览地址(如果没有平台支持或者已经把场景应用配置单独拿出来,请自行改为能指向对应文件的路径并修改相关加载逻辑)
    layerManager: {}, // 场景应用的图层配置
    legend: [], // 场景应用的图例配置
    timeLine: [], // 场景应用的时间轴配置
    interactions: "if(window.addEventListener){window.addEventListene...", // 场景应用的完整开发者代码
  },
];

# replaceResources

针对场景配置文件进行整体文字替换的配置

# 描述

主要是在服务迁移、文件路径变更时使用,例如:整体替换 IP、端口、图标路径等。

# 针对范围

针对场景配置的所有文本信息。(有需要请自行变更代码逻辑)

# 示例
[
  {
    replaceStr: "10.100.245.102:8089", // 要替换的字符
    replacedStr: "10.100.245.220:8089", // 修改后的字符
  },
];

# handlingScene

待处理的加载/销毁场景命令的集合

# 描述
  • 在场景整合中都是处理完一个命令再处理另一个命令的,为了保证所有操作都能处理并且防止相关逻辑冲突
  • 现在加载场景和销毁的命令是合一的
# 示例
{
  destroy: {
    messageType: "scene_destroy", // 标识命名为销毁场景
    name: "water_yy", // 要销毁场景的唯一key
  },
  init: {
    messageType: "scene_init", // 标识命名为加载场景
    name: "air_quality", // 要加载场景的唯一key
    url: "http://10.100.245.102:8089/visualmap/#/MapViews?userName=xuckj4&appType=2D&appName=大气一张图", // 场景应用的预览地址
  }
}

# sceneOb

保存当前获取到的并 JSON 化的场景应用配置数据

# 示例

内容和 sceneList 的子级对象结构相同

# 参考资料

环境一张图开发规范 (opens new window)