ArcGIS Maritime Server 开发教程(四)Maritime Service 开发实践

本章导读:Maritime Service 属于 MapService 的一个扩展,大部分功能继承 MapService ,但海图的特性也是非常明显。上一章已对比过期功能的差异,本章直接进入到代码模式,通过代码方式进行调用和操作。本教程只针对 Maritime Service 的操作进行讲解,涉及到 ArcGIS JavaScript API 基础部分不在此处进行讲述,建议读者先学习 ArcGIS JavaScript API ,熟悉之后才开始阅读下面的内容。By 李远祥

预览 Maritime Service

Maritime Service 在 ArcGIS 的 rest 页面中是不提供 JavaScript 的预览方式,因此,如果发布了 Maritime Service ,则需要编写一个简单的代码进行调用。

既然无法通过 rest 页面进行预览,那么就可以编写一个页面进行调用。这个页面功能非常简单,就是引用 ArcGIS JavaScript API ,利用 ArcGISDynamicMapServiceLayer 接口进行加载。其核代码如下

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" /><title>加载海图服务</title><link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/esri/css/esri.css"><style>html,body,#map {height: 100%;margin: 0;padding: 0;}</style><script src="http://localhost/arcgis_js_api/3.19/init.js"></script><script>var map;require(["esri/map","esri/layers/ArcGISDynamicMapServiceLayer","esri/geometry/Extent","dojo/domReady!"], function (Map, DynamicLayer, Extent) {var initexten = new Extent({ "xmin": 113.42, "ymin": 22.15, "xmax": 113.58, "ymax": 22.26, "spatialReference": { "wkid": 4326 } });map = new Map("map", { extent: initexten });//加载 WGS 84 服务var basemap = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");map.addLayer(basemap);//加载海图服务var enc84 = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");map.addLayer(enc84);});</script>
</head>
<body><div id="map"></div>
</body>
</html>

从上述的代码例子可以餐刀,其加载方式完全使用了 ArcGIS JavaScript API 中的 ArcGISDynamicMapServiceLayer 接口,用法保持一致。由于海图数据基本上是在海上,一般情况下先加载一个底图,再叠加海图服务,不然在服务加载的时候很难看到对应的位置。最好给 map 对象加入初始化的 extent 值,直接显示海图所在的范围。

下图是使用了全球范围的地图,加载了一个非常小的海图数据,可以看出在初始显示的时候肉眼几乎没能找到其数据所在的位置。如下图,绿色箭头部分就是数据所在位置。因此,初始化地图的时候给 extent 值是非常重要的。

加入了 extent 值之后,其效果基本上就是在底图上叠加显示了海图服务。

ArcGIS 在 B/S 模式下没有专门的 Maritime Service 的 API ,所以,如果从事 Maritime 的开发,则是使用 ArcGIS JavaScript API 传统的开发接口。笔者这里的代码例子全部采用 3.19 版本的 API ,如果后续有三维的需求,建议开发人员及早从 4.x API 开始构建应用程序。

Maritime Service 的切片服务加载

严格来说,Maritime 产品是没有切片地图服务的,切片服务是属于传统的 ArcGIS Server 的切片服务,是通过 ArcGISTiledMapServiceLayer 接口进行调用。
Maritime Server 提供了 tpk 生成工具来生成地图切片。该工具是在 Martime Server 的安装目录下的 Bin 文件夹中。原则上是使用桌面小工具的方式进行切片包生成,生成的 tpk 切片包则可以被所有的 ArcGIS 软件加载。

Maritime Service 切片服务的制作分为 Server 方式和 Portal 方式。ArcGIS Server 方式比较麻烦,必须是非常了解 ArcGIS Server 服务机制的人员才知道怎么发布。笔者在之前的文章 《ArcGIS Maritime 发布海图切片服务详解》有着非常详细的论述。
Portal 方式则是非常简单直接。先用 tpk 工具生成切片包,在 Portal 的资源上传页面中选择该切片包,勾选发布为切片服务即可。

下面是海图切片服务通过传统的 JavaScript 脚本预览。

以下的代码例子是一个 WGS84 的地图,叠加一个海图切片服务(姑且这么称呼它,实际上切片之后跟海图的数据没有任何关系,也不是针对海图的服务)

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" /><title>加载wgs84坐标系海图切片</title><link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/esri/css/esri.css"><style>html,body,#map {height: 100%;margin: 0;padding: 0;}</style><script src="http://localhost/arcgis_js_api/3.19/init.js"></script><script>var map;require(["esri/map","esri/layers/ArcGISDynamicMapServiceLayer","esri/layers/ArcGISTiledMapServiceLayer","esri/SpatialReference","esri/geometry/Extent","dojo/domReady!"], function (Map, DynamicLayer,ArcGISTiledMapServiceLayer,SpatialReference, Extent) {var initexten = new Extent({ "xmin": 113.42, "ymin": 22.15, "xmax": 113.58, "ymax": 22.26, "spatialReference": { "wkid": 4326 } });map = new Map("map", { extent: initexten });//测试动态投影,目前 海图切片只支持 web 墨卡托投影//ArcGIS 中坐标系是以第一个加进的服务为主var haituTile = new ArcGISTiledMapServiceLayer("http://localhost:6080/arcgis/rest/services/haitu/MapServer");map.addLayer(haituTile);map.removeLayer(haituTile);//加载基础服务WGS 84var basemap = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");map.addLayer(basemap);//重新加载海图服务map.addLayer(haituTile);});</script>
<body><div id="map"></div>
</body></html>

目前 Maritime Server 自带的 mcstpk 工具只能生成基于 Web 墨卡托坐标系,同时只支持谷歌微软等标准的地图切片方案。尽管在 ServerConfiguration.xml 文件中可以定义 wkid ,但从生成的切片的结构来看,还是 web 墨卡托投影。所以,在生成切片包的操作之前,必须将 ServerConfiguration.xml 的 wkid 修改为 102100 。

以下是从 tpk 包解压后提取的 config 文件,在修改了 ServerConfiguration.xml 的 wkid 为 4326 之后,其结构依然为 Web 墨卡托投影坐标。

所以,上面的代码笔者是先加载一个完海图切片服务,然后删除掉,确保第一个加载进去的服务的坐标为 web 墨卡托(ArcGIS 默认第一个加载进去的服务的坐标系为地图的坐标系,如果其他服务为动态服务,则会自动投影到第一个坐标系中)。

Maritime Service 属性查找

在上一章中介绍过 Maritime Service 的查询接口只有 find ,没有任何的 图层 Query 操作。为什么会这样?如果从 S-57 数据本身来看,可以从中发现一些问题。
S-57 数据是以对象方式来存储的,没有所谓的图层概念,例如 水深点和锚地,都放在同一个区域中,有点像一个杂乱的仓库,东西随便对方,只能辨别出它是属于什么类型。
而 GIS 数据是以层的形式进行组织的,同样是一个仓库作为比喻,GIS 会将仓库划分为好多个储物间,水深放一个储物间,锚地放另一个储物间,有严格的划分,不能放错。所以 GIS 的查找可以直接到对应的储物间(图层)中精确获取所需的各种要素。
所以,在检索方面,Maritime Service 由于数据的原因,包括使用多个分幅来组织,导致不可能使用 Query 来操作。

那么,同样是 find 的方式进行检索,二者有什么本质的区别。下面来看看 Maritime Service 的检索的核心代码。

//查找地图function findNow() {var findtask1 = new FindTask("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");var fParam = new FindParameters();fParam.returnGeometry = true;fParam.searchFields = ["*"];var findTxt = dojo.byId("ftxt").value;fParam.searchText = findTxt;findtask1.execute(fParam, getResult);}function getResult(results) {map.graphics.clear();dojo.forEach(results, function (result) {setGrapic(result.feature);});}

从代码中看到,调用的方式同样是通过 FindTask 以及 FindParameters 组合来检索,也支持检索结果返回的字段和图形。但这个结果的结果是没法在服务器端进行结果过滤的。
从 getResult 方法可以看到其返回的结果其实一堆的 graphic 对象。通过graphic 接口可以获取到对应的属性值以及使用 symbol 对 graphic 进行渲染。这都跟传统的 find 方法是一样的。以下是完整的代码,包括从结果查询到结果返回到结果渲染和地图跳转等。

<!DOCTYPE html>
<html>
<head lang="en"><meta charset="UTF-8"><title>全局查找_findTask</title><link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/dijit/themes/claro/claro.css"><link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/esri/css/esri.css"><script src="http://localhost/arcgis_js_api/3.19/init.js"></script><style>html,body,#mapDiv {width: 100%;height: 100%;margin: 0;padding: 0;}</style><script>var map;require(["esri/map","esri/layers/ArcGISDynamicMapServiceLayer","esri/Color", "esri/tasks/FindTask","esri/tasks/FindParameters","esri/symbols/SimpleMarkerSymbol","esri/symbols/SimpleLineSymbol","esri/symbols/SimpleFillSymbol","esri/geometry/Extent","dojo/domReady!"], function (Map, ArcGISDynamicMapServiceLayer, eColor,FindTask, FindParameters, SimpleMarkerSymbol,SimpleLineSymbol, SimpleFillSymbol, Extent) {var initexten = new Extent({ "xmin": 113.42, "ymin": 22.15, "xmax": 113.58, "ymax": 22.26, "spatialReference": { "wkid": 4326 } });map = new Map("mapDiv", { extent: initexten });//加载 WGS 84var basemap = new ArcGISDynamicMapServiceLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");map.addLayer(basemap);var tLayer2 = new ArcGISDynamicMapServiceLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");map.addLayer(tLayer2);//注册查询事件dojo.connect(dojo.byId("x1"), "onclick", findNow);//查找地图function findNow() {var findtask1 = new FindTask("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");var fParam = new FindParameters();fParam.returnGeometry = true;fParam.searchFields = ["*"];var findTxt = dojo.byId("ftxt").value;fParam.searchText = findTxt;findtask1.execute(fParam, getResult);}function getResult(results) {map.graphics.clear();dojo.forEach(results, function (result) {setGrapic(result.feature);});}function setGrapic(gra) {//如果需要查看所有属性,可以参考下面的脚本//var featureAttributes = gra.attributes;//for (var attr in featureAttributes) //{                           //alert("<b>" + attr + ":</b>  " + featureAttributes[attr] + "<br>");//}var pointSym = new SimpleMarkerSymbol();var lineSym = new SimpleLineSymbol();var polySym = new SimpleFillSymbol();var color = new eColor([255, 0, 0, 0.5]);var outColor = new eColor([0, 255, 0, 0.5]);pointSym.setColor(color);pointSym.setSize(12);pointSym.outline.setColor(outColor);lineSym.setColor(color);lineSym.setWidth(3);polySym.setColor(color);polySym.outline.setColor(outColor);if (gra.geometry.type == "point") {gra.symbol = pointSym;} else if (gra.geometry.type == "polyline") {gra.symbol = lineSym;} else if (gra.geometry.type == "polygon") {gra.symbol = polySym;}map.graphics.add(gra);jumptoMap(gra);}function jumptoMap(gra) {if (gra.geometry.type == "point") {map.centerAndZoom(gra.geometry);} else {//map.setLevel(6);map.setExtent(gra.geometry.getExtent());}}});</script>
</head><body><div><input type="text" value="亚太" id="ftxt"><button id="x1">搜索</button></div><div id="mapDiv"></div>
</body></html>

Maritime Service identify 操作

上一章提及的 Maritime Service 的 Identify 接口只提供一个容差值得设置,但实际上不是这样的,Identify 接口还是跟传统的点查询接口是一样的,只是屏蔽了大部分的参数。下面来看看 Maritime Service 的 Identify 操作的核心代码:

//点击地图,通过屏幕上的点进行查询function mapIdentify(evt) {var point, identifyParams, identifyTask;point = evt.mapPoint;identifyTask = new IdentifyTask("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");identifyParams = new IdentifyParameters();identifyParams.tolerance = 30;identifyParams.returnGeometry = true;identifyParams.geometry = point;//mapExtent 必须设置,不然查询不响应identifyParams.mapExtent = map.extent;//layerOption 实际上是不生效的identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_VISIBLE;identifyParams.width = map.width;identifyParams.height = map.height;identifyTask.execute(identifyParams, getResult);}function getResult(results) {//先清空地图上的要素,便于展示结果数据map.graphics.clear();//遍历所有的查找结果dojo.forEach(results, function (result) {//对每个查找结果进行渲染setGrapic(result.feature);});}

上述代码同样是使用了 IdentifyTask 和 IdentifyParameters 组合进行查询,跟传统方法一样,但这里有两个地方必须要注意的。identifyParams.layerOption 实际上是不生效的,它不能处理查询的图层是否可见图层或全部图层查询,因为 S-57 数据本身就没有层的概念。
另一个是必须设置 identifyParams.mapExtent,不然无法响应查询。因为 S-57 数据是没有空间索引的,可以将其看做是一个大文本,没有章节等划分。如果不设定该参数,默认整个地图范围所有的图幅都进行点查询计算,那效率就非常低下了。

下面是点击查询的完整代码

<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" /><title>点查询海图要素</title><link rel="stylesheet" href="http://localhost/arcgis_js_api/3.19/esri/css/esri.css"><style>html,body,#mapDiv {width: 100%;height: 100%;margin: 0;padding: 0;}</style><script src="http://localhost/arcgis_js_api/3.19/init.js"></script><script>var map;require(["esri/map","esri/layers/ArcGISDynamicMapServiceLayer","esri/geometry/Extent","esri/symbols/SimpleMarkerSymbol","esri/symbols/SimpleLineSymbol","esri/symbols/SimpleFillSymbol","esri/tasks/IdentifyTask","esri/tasks/IdentifyParameters","esri/tasks/IdentifyResult","dojo/Deferred","esri/Color", "dojo/domReady!"], function (Map, DynamicLayer, Extent,SimpleMarkerSymbol, SimpleLineSymbol, SimpleFillSymbol,IdentifyTask, IdentifyParameters, IdentifyResult, Deferred,eColor) {//初始化显示范围var initexten = new Extent({ "xmin": 113.42, "ymin": 22.15, "xmax": 113.58, "ymax": 22.26, "spatialReference": { "wkid": 4326 } });map = new Map("mapDiv", { extent: initexten });//加载基础服务WGS 84var basemap = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer");map.addLayer(basemap);//ENC WGS 84var enc84 = new DynamicLayer("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");map.addLayer(enc84);//注册点击查询事件dojo.connect(dojo.byId("identity_Btn"), "onclick", identityFun);function identityFun() {//注册map的click 事件,进入点击查询map.on("click", mapIdentify);}//点击地图,通过屏幕上的点进行查询function mapIdentify(evt) {var point, identifyParams, identifyTask;point = evt.mapPoint;identifyTask = new IdentifyTask("http://localhost:6080/arcgis/rest/services/SampleWorldCities/MapServer/exts/MaritimeChartService/MapServer");identifyParams = new IdentifyParameters();identifyParams.tolerance = 30;identifyParams.returnGeometry = true;identifyParams.geometry = point;//mapExtent 必须设置,不然查询不响应identifyParams.mapExtent = map.extent;//layerOption 实际上是不生效的identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_VISIBLE;identifyParams.width = map.width;identifyParams.height = map.height;identifyTask.execute(identifyParams, getResult);}function getResult(results) {//先清空地图上的要素,便于展示结果数据map.graphics.clear();//遍历所有的查找结果dojo.forEach(results, function (result) {//对每个查找结果进行渲染setGrapic(result.feature);});}//渲染查找结果function setGrapic(gra) {//定义点线面符号var pointSym = new SimpleMarkerSymbol();var lineSym = new SimpleLineSymbol();var polySym = new SimpleFillSymbol();var color = new eColor([255, 0, 0, 0.5]);var outColor = new eColor([0, 255, 0, 0.5]);//点符号设置pointSym.setColor(color);pointSym.setSize(12);pointSym.outline.setColor(outColor);//线符号设置lineSym.setColor(color);lineSym.setWidth(3);//面符号设置polySym.setColor(color);polySym.outline.setColor(outColor);//对返回的物标要素进行渲染//获取物标所有的属性,其中objectType(固定标识) 为物标的唯一标识var featureAttributes = gra.attributes;for (var attr in featureAttributes) {if (attr == "objectType") {var oType = featureAttributes[attr];if (oType == "TSSBND") {//对点线面图形设置符号if (gra.geometry.type == "point") {gra.symbol = pointSym;} else if (gra.geometry.type == "polyline") {gra.symbol = lineSym;} else if (gra.geometry.type == "polygon") {gra.symbol = polySym;}map.graphics.add(gra);//跳转到地图位置jumptoMap(gra);}}}}//跳转到要素所在的位置function jumptoMap(gra) {if (gra.geometry.type == "point") {map.centerAndZoom(gra.geometry);} else {map.setLevel(6);map.setExtent(gra.geometry.getExtent());}}//跳转到要素所在的位置function jumptoMap(gra) {if (gra.geometry.type == "point") {map.centerAndZoom(gra.geometry);} else {map.setLevel(6);map.setExtent(gra.geometry.getExtent());}}});</script>
</head>
<body><div><button id="identity_Btn">点击此按钮然后点选地图查询(此处代码限定为TSSBND)</button></div><div id="mapDiv"></div>
</body>
</html>

留意关键部分,由于 S-57 没有图层,只有分类,那么结果返回的是所有的对象,必须通过 S-57 的标准规范对结果进行判定。例如点击一个地方要返回该区域的某一类对象,如 TSSBND ,其做法是通过结果判定 graphic 的 objectType 属性值,如果是 TSSBND ,才获取渲染。完整代码的最终结果如下图

identity 方式都是效率比较低的检索,而且只能从返回结果去做判定过滤,而不能通过服务端请求去过滤。原因还是在 S-57 数据本身。这种情况估计会在 S-100 标准推广之后会得到改善。

本章总结

Maritime Service 的开发绝大部分都跟传统 MapService 是一致的,所以,要在开始功能开发之前,必须先掌握好 ArcGIS JavaScript API ,并主要海图数据引起的服务能力的异同。如果熟悉 ArcGIS JavaScript API ,再懂一点海图数据的规范,那么基于 Maritime Service 开发应用就事半功倍了。

更多的GIS主流和非主流技术,可以持续关注CSDN的GIS制图乐园,以及微信公众号【GIS制图乐园】。BY 李远祥

ArcGIS Maritime Server 开发教程(四)Maritime Service 开发实践相关推荐

  1. 开发教程(四) MIP组件平台使用说明

    组件审核平台用于上传 MIP 组件.经过自动校验之后,提交审核,通过审核的组件会定时推送到线上,供网站使用. 平台地址:https://www.mipengine.org/platform/ 1. 使 ...

  2. Android项目驱动式开发教程 第2版,《Android项目驱动式开发教程》第一章开发入门.ppt...

    <Android项目驱动式开发教程>第一章开发入门 1.4 项目框架分析 4 android:versionName="1.0" > 5 8 第9行代码andro ...

  3. 电商生鲜网站开发(四)——后台开发:商品模块-图片上传/多条件拼接sql

    电商生鲜网站开发(四)--后台开发:商品模块-图片上传/多条件拼接sql 增加商品 上传图片 更新商品 删除商品 批量上下架 图片上传功能 文件名UUID 通用唯一识别码(Universally Un ...

  4. pythoncad二次开发视频_revit二次开发|bim软件二次开发|revit二次开发教程|Revit二次开发技术文档...

    二次开发 revit二次开发|bim软件二次开发|revit二次开发教程|Revit二次开发技术文档2019-07-08赞( 0 ) 记录一下CAD二次开发的一些简单实例. 1.helloworld ...

  5. AutoCAD 开发文档,AutoLISP 教程,.Net AutoCAD开发教程,VB AutoCAD开发教程,ObjectARX 开发指南,VBA AutoCAD开发教程,ActiveX 开发指南

    AutoCAD 开发文档, CAD开发者社区 - AutoCAD二次开发文档,CAD二次开发,CAD插件开发,中文CAD文档 - 中文CAD开发文档,CAD二次开发问题交流,优秀插件分享 AutoLI ...

  6. 微博java客户端开发教程_Java新浪微博客户端开发第四步

    这一步是对之前进行较大的改动.增加的类也比较多.包结构如下: 0.Main:主函数入口 1.MainDialog:主界面 2.WeiboPanel:StatusPanel及CommentPanel的父 ...

  7. ROS1结合自动驾驶数据集Kitti开发教程(四)画出自己车子模型以及照相机视野

    注意: 再学习本系列教程时,应该已经安装过ROS了并且需要有一些ROS的基本知识 ubuntu版本:20.04 ros版本:noetic 课程回顾 ROS1结合自动驾驶数据集Kitti开发教程(一)K ...

  8. php7扩展开发教程,Laravel 7 扩展开发教程

    下面由Laravel入门教程栏目给大家介绍Laravel 7 扩展开发教程,希望对需要的朋友有所帮助! 步骤 1. 创建一个新项目 我更喜欢使用 Laravel 安装程序.laravel new la ...

  9. python做exe开发教程_python做exe开发教程

    python做exe开发教程内容摘要 python做exe开发教程其他方法无效时可用免疫抑制剂,交通部监理员教程证,6.客户端的异常测试.6.机费用械性能(1)抗拉试验:酒店入职教程内容,6.9l-8 ...

  10. python web 开发教程下载_Python Web开发从入门到精通

    Python Web开发从入门到精通循序渐进地讲解了Python Web开发的核心知识,并通过具体实例的实现过程演示了Web开发程序的流程.Python Web开发从入门到精通共15章,内容包括Pyt ...

最新文章

  1. 使用openpyxl去操作Excel表格
  2. gitlab的搭建与汉化
  3. PHP json_decode 对 JSON 格式的字符串进行编码并获取对应的值
  4. 【转】Android 带checkbox的listView 实现多选,全选,反选 -- 不错
  5. 【kali】kali环境下安装dvwa
  6. 删除指定路径下指定天数之前的文件
  7. libeio-异步I/O库初窥
  8. 使用PDF-XChange Editor为PDF文件添加图章(仅图片)
  9. linux tomcat配置https
  10. 【收藏】Windows 8 Consumer Preview的108个运行命令及简要说明
  11. C# 静态变量及静态函数
  12. matlab7.0的序列号
  13. h5难做吗_H5如何制作?制作H5是否很难?-
  14. Rayman的绝顶之路——Leetcode每日一题打卡16
  15. Linux下搭建Oracle11g RAC
  16. 2021最新Java后端面经合集 | 阿里腾讯百度字节
  17. 架构师之路(七)之五视图方法论
  18. 字节跳动校招题目——找零问题
  19. w7系统怎么开启打印机服务器,Win7如何开启打印机服务?
  20. FTP主动模式和被动模式讲解

热门文章

  1. Java:MySQL5.5安装教程
  2. 三种传统电量检测方法对比分析
  3. 英语听说计算机考试演练专用,新中高考英语听说机考时间确定,月底中考模考演练...
  4. linux向上翻页_Linux下vim编辑器命令大全
  5. Error:java: Invalid additional meta-data in ‘META-INF/spring-configuration-metadata.json‘: End of in
  6. ❤️《AOP》(Spring必备技能)
  7. php trait编译实现,为什么PHP Trait不能实现接口?
  8. Linux常用命令集
  9. 改造二叉树 (长乐一中模拟赛day2T1)
  10. C#笔记14 LINQ