作者: MR
之前两篇(扩展一和扩展二)完成了服务端的设计和实现,本篇介绍扩展iClient for JavaScript对新扩展的资源进行对接。先放张应用效果预览图:
###一、入门
扩展一个iClient for JavaScript类的基本方式如下:
变量名=SuperMap.Class(继承的父类,{本类的实现});
其中,本类的实现要有一个初始化的initialize
方法,初始化时可以继承父类(父类.prototype.apply(this, arguments)
),当然也可以不继承。
###二、输入输出类的准备以及JSON与本地对象互转
输入类即向服务端请求的对象(会转成JSON发给服务端);输出类则是服务端返回的结果(服务端返回的JSON转成本地类型)。显而易见的,输入类对应服务端创建参数映射的Map对象,即:{params:LCPInputFormat[] }
;输出类对应LCPOutputFormat
类。这里输入类命名SuperMap.REST.LineCapturePointParameter
;输出类命名SuperMap.REST.LineCapturePointResult
,注意 SuperMap是iClient for JavaScript定义的全局变量,SuperMap.REST
也是,所以我们的命名实际是给SuperMap.REST
指向的对象添加一个属性,所以,不能任意命名,变量名必须有效。当然,这里只对接了JSON格式的表述,xml等格式(服务端是支持的)的输入输出这里不考虑。
定义字段就不介绍了,跟服务端的对应类一致就行了,主要介绍iClient for JavaScript的对象转成服务端接受的对象的方法。
iClient for JavaScript已经定义好了与服务端一致的Geometry类及Feature类,分别叫做:SuperMap.REST.ServerGeometry和SuperMap.REST.ServerFeature,并且提供了,与iClient for JavaScript自己的Geometry类及Feature类互转的方法,我们只需要直接使用即可。在输入类转为服务端接受类型的时候需要做些兼容,以兼容多种参数类型。另外输入类还初步检查了参数是否合法,规则和服务端检查的一致。还有就是,若设置了返回指定字段,脚本里过滤了下上传点和上传线的字段(服务端不处理上传点和上传线的字段,原样返回);设置了上传线的ID,因为服务端打断线时是通过判断线的ID来实现同一条线上多个捕获点(或叫垂足)进行打断的,所以每条线都需要不同的ID,唯一的问题是,若是同时还查询数据集线,而数据集线的ID(就是属性表SmID值)和上传的线ID一致时,打断线存放对应线的结果不对,若有需要上传线和查询数据集线在同一个输入项同时设置,可以自己修改,上传线的ID给加上一个数值(比如加1w,10000 + “1234” = “100001234”),使不可能和数据集线ID一致。这里不再贴代码,将iClient for JavaScript的Geometry对象转为ServerGeometry对象的方法如下,其中lineFeature
为线要素对象:
SuperMap.REST.ServerGeometry.fromGeometry(lineFeature.geometry)
服务端返回JSON转为对象后,再转成iClient for JavaScript的Feature对象的方法如下,其中jsonObj是JSON转成的JavaScript对象:
SuperMap.REST.ServerFeature.fromJson(jsonObj.line).toFeature()
因为服务端返回的点、线等可能为null,所以处理输出需要先判断一下。
三、构造服务类,处理发送请求及执行回调
iClient for JavaScript是开源的,可以去GitHub下载到源码,在libs/SuperMap/REST目录下找一个服务类照着写行了,默认会根据是否跨域发POST请求(JSON表述)或GET请求(使用iServer JSONP表述)。因为设计了一个轮询功能,所以这里扩展的服务类只会发POST请求。简单解释下这里的轮询功能:
服务设计的请求体是
LCPInputFormat[]
,轮询的作用和别的iClient for JavaScript的轮询功能一致,就是将请求分散发送给不同的URL进行处理,这样每个URL处理的数据量就会减少,也就是意味着需要多个服务端发布同一个工作空间(或数据相同的服务);这里拆分的单位是一个LCPInputFormat
输入项,并且加入失败重试功能,直到所有URL都请求失败才会返回失败。
服务类命名为:SuperMap.REST.LineCapturePointService
,其请求方法如下:
/**
* APIMethod: processAsync
* 发送请求
* Parameters:
* params - {<SuperMap.REST.LineCapturePointParameter>} 点靠近线参数类.
* Returns:
* {<SuperMap.REST.LineCapturePointResult>} 返回点靠近线结果。
*/
processAsync: function (params) {
var me = this, Parameters = null;
if (!params || !me.eventListeners || !me.url.length) {
console.log("请检查服务类及请求参数!");
return;
}
Parameters = params.getParameter();
if (!Parameters || !Parameters.params.length) {
console.log("点靠近线参数无效!");
return;
}
//暂存SuperMap.Credential.CREDENTIAL
me.CD = SuperMap.Credential.CREDENTIAL;
me.setUrl(me.url);
//检查url,虽然可以不这么严
if (!(typeof (me.urls) == "object" && me.urls.length && typeof (me.urls[0]) == "string"))
{ return; }
var PLEN = Parameters.params.length;
var count = Math.floor(PLEN / me.urls.length);
var extra = PLEN % me.urls.length;
var LEN = me.urls.length;
var Result = new SuperMap.REST.LineCapturePointResult({
totalSucessCount: 0,
totalFailedCount: 0,
msg: "",
results: []
});
var failRt = [];//失败一次性返回每个结果最后一个失败url的commit对象
//成功加失败=拆数据的份数 时返回
var Num = 0;
var part = extra == 0 ? LEN : extra;
var hasSucess = false;
//最终失败的也调用下它,使二者都能正常返回
var handleSucess = function (rt) {
if (rt) {
Result.totalSucessCount += rt.totalSucessCount;
Result.totalFailedCount += rt.totalFailedCount;
Result.msg += Result.msg.length ? ("|" + rt.msg) : rt.msg;
Result.results = Result.results.concat(rt.results);
if (Num == part) {
handleFailure(false, false, LEN, 0);
}
}
if (Num == part && hasSucess) {
me.getFeatureComplete(Result);
}
};
//递归(不太算,只是顺序执行请求,失败换个url再来)直到成功或所有url都失败,可以加个重试次数
//fe: ajax请求对象,pr: 请求参数对象,deep: 轮询url,ep: 已经失败的url
var handleFailure = function (fe, pr, deep, ep) {
//过滤掉已经失败的url
if (deep == ep) {
++deep;
}
//失败重试
if (deep < LEN) {
SuperMap.Credential.CREDENTIAL = null;
// me.request({
// method: "POST",
// url: me.urls[deep],
// data: SuperMap.Util.toJSON(pr),
// scope: me,
// success: function (e) {
// hasSucess = true;
// ++Num;
// handleSucess(me.getResult(e));
// },
// failure: function (e) {//pr指向未变
// handleFailure(e, pr, ++deep, ep);
// }
// });
SuperMap.Util.committer({
method: "POST",
url: me.urls[deep],
data: SuperMap.Util.toJSON(pr),
scope: me,
isInTheSameDomain: me.isInTheSameDomain,
success: function (e) {
if (me.isReturnPartial) {
me.getFeatureComplete(me.getResult(e));
} else {
hasSucess = true;
++Num;
handleSucess(me.getResult(e));
}
},
failure: function (e) {//pr指向未变
handleFailure(e, pr, ++deep, ep);
}
});
SuperMap.Credential.CREDENTIAL = me.CD;
} else {//所有url都失败
if (fe) {
++Num;
//可修改为每次失败结果都返回之类的比如可按url返回每个url失败的结果,从me.url[deep]取
if (!me.isReturnPartial) {
failRt.push(fe);
}
if (Num == part) {
handleSucess(false);
}
}
if (fe && me.isReturnPartial) {
me.getFeatureError([fe]);
} else if (Num == part && failRt.length) {//或全部执行完毕
me.getFeatureError(failRt);
}
}
};
//拆数据&发请求
for (var i = 0; i < LEN; ++i) {
//拆数据
var start, end, pm = { params: [] };
if (i < extra) {
start = i * (count + 1);
end = start + count + 1;
} else {
start = i * count + extra;
end = start + count;
}
if (start >= PLEN) { break; }
while (start < end) {
pm.params.push(Parameters.params[start]);
++start;
}
SuperMap.Credential.CREDENTIAL = null;
// 发请求
// me.request({
// method: "POST",
// url: me.urls[i],
// data: SuperMap.Util.toJSON(pm),
// scope: me,
// success: function (e) {
// if (me.isReturnPartial) {
// me.getFeatureComplete(me.getResult(e));
// } else {
// hasSucess = true;
// ++Num;
// handleSucess(me.getResult(e));
// }
// },
// failure: (function (mp, j) {
// return function (e) {
// //修正mp指向,和i值
// //从me.urls[0]开始尝试,跳过me.urls[i]
// handleFailure(e, mp, 0, j);
// }
// })(pm, i)
// });
SuperMap.Util.committer({
method: "POST",
url: me.urls[i],
data: SuperMap.Util.toJSON(pm),
scope: me,
isInTheSameDomain: me.isInTheSameDomain,
success: function (e) {
if (me.isReturnPartial) {
me.getFeatureComplete(me.getResult(e));
} else {
hasSucess = true;
++Num;
handleSucess(me.getResult(e));
}
},
failure: (function (mp, j) {
return function (e) {
//修正mp指向,和i值
//从me.urls[0]开始尝试,跳过me.urls[i]
handleFailure(e, mp, 0, j);
}
})(pm, i)
});
SuperMap.Credential.CREDENTIAL = me.CD;
}
}
失败重试使用的是顺序执行的方式,即,失败之后才重试,而不是每个URL都发请求,第一个成功则中断其余请求,各有优缺点,可以自己尝试这种方式。
getFeatureComplete
、getFeatureError
分别是请求成功和失败时的回调,其它请参考源码。
SuperMap.Util.committer()
方法用于发送请求、绑定回调,父类(SuperMap.ServiceBase
)的request
方法也是使用它,这里我们和父类的逻辑不同,所以直接使用SuperMap.Util.committer()
;可以看到还做了些别的操作,比如若填了多个URL并且该服务设置了授权,那么这里让iClient for JavaScript的认证类失效,自己处理认证(tooken字符串需要和URL一一对应)。其它请参见下方源码,使用时可以先压缩一下,因为注释很多。
###四、测试
边写边测,上述扩展完成后,需要在iClient for JavaScript的类库(SuperMap.Include.js
)之后引入或者修改SuperMap.Include.js
文件。使用方式和别的iClient for JavaScript对接iServer服务的类一致,如下:
// 请求参数
var param = new SuperMap.REST.LineCapturePointParameter.requestItem({具体输入设置项});
var param1 = new SuperMap.REST.LineCapturePointParameter.requestItem({具体输入设置项});
var params = new SuperMap.REST.LineCapturePointParameter({
parameters: [param,param1] //也可以不是数组,直接填param
});
// 服务类
var service = new SuperMap.REST.LineCapturePointService(
[url1,url2,url3...], //也可以不是数组,直接填一个url字符串
{
eventListeners: { // 注册事件监听
"processCompleted": function (e) {
console.log("成功结果", e);
},
"processFailed": function (e) {
console.log("失败结果", e);
}
}
//以下可选
, isReturnPartial: false //分批返回,url为数组时生效,true时回调可能将被执行多次,默认false
, CREDENTIALS: ["","",""...] //tooken字符串数组,若服务需要tooken验证,与url数组一一对应,SuperMap.Credential.CREDENTIAL对本类无效
//将参数发送给服务端
service.processAsync(params);
验证情况:
验证设置了两个输入项,有上传点及线,也有查询点,故意在URL数组写了两个一定会请求失败的URL,之后才是能成功执行的URL,验证了脚本无误,当然,也有别的测试,但是难免也会出现问题,包括服务端,欢迎大家一起交流讨论。
###五、实例验证
这就到了最开头那张图片了,验证情况如下:
使用的是iServer自带示范数据,长春市区图(坐标系为平面无投影,也就是没有坐标系,但是数据应该是投影坐标系下采集的,因为根据坐标计算的距离大致准确),也有试过经纬度坐标系,经纬度下,一般实际容限比设置的容限略大,返回成功结果的距离可能略大于设置的容限,这里不在服务端处理(本来就是为了快),也不在js脚本再次过滤结果。
###六、完结&致谢
到这里,这个扩展就全部完结了,感谢SuperMap,感谢CSDN。下面提供对接脚本和应用示例的下载链接,欢迎传播和围观。
http://download.csdn.net/detail/supermapsupport/9809547
最后,特别感谢 柳慧珠 小仙女帮我找bug和提供应用示例(动图那个)。