@tmsfe/tms-core 0.0.26 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cloudReport.ts +199 -0
- package/src/fastreport.js +17 -56
- package/src/index-proxy.js +10 -0
- package/src/index.js +11 -4
- package/src/distanceBetweenPoints.js +0 -73
package/package.json
CHANGED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 调用云函数上报埋点
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import syncApi from './syncfnmanager';
|
|
6
|
+
|
|
7
|
+
type DataType = string | null;
|
|
8
|
+
|
|
9
|
+
interface IInitOptions {
|
|
10
|
+
client: string, // sinan、mycar等
|
|
11
|
+
appVersion: string,
|
|
12
|
+
cloudEnvId: string,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface IPage {
|
|
16
|
+
route: string,
|
|
17
|
+
options: object,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const openidIndex = 7; // openId的下标,由云函数填充
|
|
21
|
+
|
|
22
|
+
let initOptions: IInitOptions;
|
|
23
|
+
|
|
24
|
+
function init(options: IInitOptions): void {
|
|
25
|
+
if (!initOptions) {
|
|
26
|
+
initOptions = options;
|
|
27
|
+
wx.cloud.init({ env: options.cloudEnvId });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getSystemInfoString(): string {
|
|
32
|
+
const system = syncApi.getSystemInfoSync();
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
// eslint-disable-next-line
|
|
35
|
+
const { model = '', version: wxVersion = '', platform = '', SDKVersion = '', host = '' } = system;
|
|
36
|
+
const info = { model, wxVersion, platform, SDKVersion, host };
|
|
37
|
+
return encodeURIComponent(JSON.stringify(info));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function convert2String(value: any): string {
|
|
41
|
+
const type = typeof value;
|
|
42
|
+
if (type === 'string') {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
if (type === 'object') {
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getSeqInfo(): { timestamp: number, seqId: string } {
|
|
52
|
+
const timestamp = Date.now();
|
|
53
|
+
const random = Math.random().toString();
|
|
54
|
+
const randomStr = random.slice(2, 7);
|
|
55
|
+
const sourceId = 7; // 6 未知 7 云函数 8 出行 9 我的车
|
|
56
|
+
const seqId = `${timestamp}${sourceId}${randomStr}`;
|
|
57
|
+
return { timestamp, seqId };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getPageInfo(): IPage | null {
|
|
61
|
+
const pages = getCurrentPages();
|
|
62
|
+
// 首页未渲染
|
|
63
|
+
if (pages.length === 0) {
|
|
64
|
+
const launch = syncApi.getLaunchOptionsSync();
|
|
65
|
+
return { route: launch.path, options: launch.query };
|
|
66
|
+
}
|
|
67
|
+
const page = pages.pop();
|
|
68
|
+
// 插件页
|
|
69
|
+
if (!page) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return page;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 获取埋点的基础字段
|
|
77
|
+
* @param page
|
|
78
|
+
* @param seqId 请求ID
|
|
79
|
+
*/
|
|
80
|
+
function getBaseData(page: IPage, seqId: string): { arr: DataType[], nextIndex: number } {
|
|
81
|
+
const pageQuery = encodeURIComponent(JSON.stringify(page.options));
|
|
82
|
+
const pageUrl = page.route;
|
|
83
|
+
const { appVersion, client } = initOptions;
|
|
84
|
+
const system = getSystemInfoString();
|
|
85
|
+
// @ts-ignore
|
|
86
|
+
const { scene = -1 } = syncApi.getLaunchOptionsSync();
|
|
87
|
+
|
|
88
|
+
const arr = new Array(41);
|
|
89
|
+
// 0: log_time
|
|
90
|
+
// 1: access_time,用户点击时间,服务端统一处理
|
|
91
|
+
// 2: user_ip,前端无需赋值
|
|
92
|
+
// 3: qimei,灯塔中的用户ID
|
|
93
|
+
// 4: imei,Android手机的imei IOS系统中的idfv 车联网中的wecarid
|
|
94
|
+
// 5: user_id,用户ID
|
|
95
|
+
// 6: qq_no,登录QQ的openid,
|
|
96
|
+
// 7: wechat_id,登录微信的openid,由云函数填充
|
|
97
|
+
// 8: phone_no,手机号
|
|
98
|
+
// 9: platform,客户端请求来源,小程序是2
|
|
99
|
+
arr[9] = '2';
|
|
100
|
+
// 10: app_version,客户端|小程序版本号
|
|
101
|
+
arr[10] = appVersion;
|
|
102
|
+
// 11: channel,外部推广渠道或车辆所属渠道
|
|
103
|
+
// 12: net_type,wifi、3G、4G等
|
|
104
|
+
// 13: product_id,手图APP、车联网等
|
|
105
|
+
// 14: busi_type,1: 定位、2: 检索、3: 导航、4: 车主服务、5: 小程序
|
|
106
|
+
arr[14] = '5';
|
|
107
|
+
// 15: request_id,请求ID
|
|
108
|
+
arr[15] = seqId;
|
|
109
|
+
// 16: session_id,标识用户一次访问过程的ID
|
|
110
|
+
// 17: f17 - province,省份
|
|
111
|
+
// 18: f18 - city,城市
|
|
112
|
+
// 19: f19 - 系统信息
|
|
113
|
+
arr[19] = system;
|
|
114
|
+
// 20: f20 - sinan、mycar等
|
|
115
|
+
arr[20] = client;
|
|
116
|
+
// 21: f21 - 小程序场景值
|
|
117
|
+
arr[21] = scene.toString();
|
|
118
|
+
// 22: f22 - 标记是云函数上报
|
|
119
|
+
arr[22] = 'cloud';
|
|
120
|
+
// 23: f23 - 当前页面的url
|
|
121
|
+
arr[23] = pageUrl;
|
|
122
|
+
// 24: f24 - 当前页面的query
|
|
123
|
+
arr[24] = pageQuery;
|
|
124
|
+
// 25 ~ 30: 预留字典给后续扩展使用
|
|
125
|
+
// 31 ~ 40: 提供给开发自定义
|
|
126
|
+
return { arr, nextIndex: 31 };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function formatData(page: IPage, seqId: string, data: any[]): DataType[] {
|
|
130
|
+
const { arr, nextIndex } = getBaseData(page, seqId);
|
|
131
|
+
let index = nextIndex;
|
|
132
|
+
for (const item of data) {
|
|
133
|
+
if (index >= arr.length) {
|
|
134
|
+
console.error(data);
|
|
135
|
+
throw new Error('埋点参数个数超出上限');
|
|
136
|
+
}
|
|
137
|
+
arr[index] = convert2String(item);
|
|
138
|
+
index += 1;
|
|
139
|
+
}
|
|
140
|
+
return arr;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function report(...data: any[]): void {
|
|
144
|
+
console.log('云埋点', data);
|
|
145
|
+
const page = getPageInfo();
|
|
146
|
+
if (page === null) {
|
|
147
|
+
console.log('插件页不上报埋点');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// @ts-ignore
|
|
151
|
+
const { scene = -1 } = syncApi.getLaunchOptionsSync();
|
|
152
|
+
// 小程序爬虫产生的日志
|
|
153
|
+
if (scene === 1129 || scene === 1030) {
|
|
154
|
+
console.warn('小程序爬虫,不上报');
|
|
155
|
+
} else {
|
|
156
|
+
const { timestamp, seqId } = getSeqInfo();
|
|
157
|
+
const arr = formatData(page, seqId, data);
|
|
158
|
+
doCallCloudFunc(arr, timestamp, seqId, openidIndex);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function doCallCloudFunc(arr: DataType[], timestamp: number, seqId: string, openidIndex: number): void {
|
|
163
|
+
const { cloudEnvId, appVersion } = initOptions;
|
|
164
|
+
const onFail = (err: any) => {
|
|
165
|
+
console.error('云埋点失败:', arr, err);
|
|
166
|
+
};
|
|
167
|
+
wx.cloud.callFunction({
|
|
168
|
+
name: 'report',
|
|
169
|
+
data: {
|
|
170
|
+
openidIndex,
|
|
171
|
+
params: { seqId, timestamp, appVersion, batch: [arr] },
|
|
172
|
+
},
|
|
173
|
+
config: { env: cloudEnvId },
|
|
174
|
+
success: (res: any) => {
|
|
175
|
+
const body = JSON.parse(res.result.body);
|
|
176
|
+
if (body.errCode !== 0) {
|
|
177
|
+
onFail(res);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
fail: onFail,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 调用云函数将已经组装完整的埋点数据arr发出去
|
|
186
|
+
* @param arr
|
|
187
|
+
*/
|
|
188
|
+
function callCloudFunc(arr: string[]): void {
|
|
189
|
+
const { timestamp, seqId } = getSeqInfo();
|
|
190
|
+
doCallCloudFunc(arr, timestamp, seqId, openidIndex);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default {
|
|
194
|
+
init,
|
|
195
|
+
report,
|
|
196
|
+
callCloudFunc,
|
|
197
|
+
convert2String,
|
|
198
|
+
getSystemInfoString,
|
|
199
|
+
};
|
package/src/fastreport.js
CHANGED
|
@@ -3,28 +3,14 @@
|
|
|
3
3
|
* @author Fenggang.Sun <fenggangsun@tencent.com>
|
|
4
4
|
* @file 快速上报数据,不处理过多逻辑,保证快速上报
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import Sync from './syncfnmanager';
|
|
9
|
-
|
|
10
|
-
let simulatedUserIdCache; // 模拟用户id在内存里的缓存
|
|
11
|
-
|
|
12
|
-
const getSystemInfo = () => {
|
|
13
|
-
const system = Sync.getSystemInfoSync();
|
|
14
|
-
const { model = '', version: wxVersion = '', platform = '', SDKVersion = '', host = '' } = system;
|
|
15
|
-
|
|
16
|
-
return { model, wxVersion, platform, SDKVersion, host };
|
|
17
|
-
};
|
|
6
|
+
import { getEnvInfo, getAuthInfo } from './env';
|
|
7
|
+
import CloudReport from './cloudReport';
|
|
18
8
|
|
|
19
9
|
const handleParamOfDifferentType = (param) => {
|
|
20
10
|
const data = new Array(41);
|
|
21
11
|
Object.keys(param).forEach((key) => {
|
|
22
|
-
|
|
23
|
-
if (valType === 'string') data[key] = param[key];
|
|
24
|
-
else if (valType === 'object') data[key] = JSON.stringify(param[key]);
|
|
25
|
-
else data[key] = String(param[key]);
|
|
12
|
+
data[key] = CloudReport.convert2String(param[key]);
|
|
26
13
|
});
|
|
27
|
-
|
|
28
14
|
return data;
|
|
29
15
|
};
|
|
30
16
|
|
|
@@ -39,52 +25,27 @@ export default class FastReport {
|
|
|
39
25
|
* @memberof FastReport
|
|
40
26
|
* @description 快速上报
|
|
41
27
|
* @param {Object} param 埋点数据
|
|
42
|
-
* @param {Boolean} simulatedUserId 是否上报模拟用户Id
|
|
43
|
-
* @param {Number} simulatedUserIdIndex 上报模拟用户ID时放在哪个字段
|
|
44
|
-
* @param {Boolean} reportShowScene 是否上报小程序onShow场景值
|
|
45
|
-
* @param {Boolean} appVer 是否上报小程序版本
|
|
46
|
-
* @returns {Promsie} 返回上报结果
|
|
47
28
|
*/
|
|
48
|
-
static report(param = {}
|
|
49
|
-
if (!param[27])
|
|
29
|
+
static report(param = {}) {
|
|
30
|
+
if (!param[27]) {
|
|
31
|
+
return Promise.reject('invalid report param');
|
|
32
|
+
}
|
|
50
33
|
const env = getEnvInfo();
|
|
51
34
|
const data = handleParamOfDifferentType(param);
|
|
52
35
|
data[9] = '2';
|
|
53
|
-
data[
|
|
54
|
-
if (appVer) {
|
|
55
|
-
data[10] = defAssign(data[10], env.appVersion);
|
|
56
|
-
}
|
|
36
|
+
data[10] = defAssign(data[10], env.appVersion);
|
|
57
37
|
data[26] = defAssign(data[26], data[27]?.[0]);
|
|
58
38
|
data[28] = env.client;
|
|
59
|
-
if (
|
|
39
|
+
if (!data[29]) {
|
|
60
40
|
const appShowScene = wx.getStorageSync('appShowScene');
|
|
61
|
-
if (appShowScene)
|
|
41
|
+
if (appShowScene) {
|
|
42
|
+
data[29] = String(appShowScene);
|
|
43
|
+
}
|
|
62
44
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
.catch(() => null);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* @memberof FastReport
|
|
73
|
-
* @description 获取模拟的用户身份标识
|
|
74
|
-
* @returns {String} 用户的临时标识
|
|
75
|
-
*/
|
|
76
|
-
static getSimulatedUserId() {
|
|
77
|
-
// 优先使用内存级缓存
|
|
78
|
-
if (simulatedUserIdCache) return simulatedUserIdCache;
|
|
79
|
-
const key = 'SimulatedUserKey';
|
|
80
|
-
// 读取本地缓存记录的值
|
|
81
|
-
simulatedUserIdCache = wx.getStorageSync(key);
|
|
82
|
-
if (simulatedUserIdCache) return simulatedUserIdCache;
|
|
83
|
-
// 生成新的值
|
|
84
|
-
const nonce = Math.random().toString(36)
|
|
85
|
-
.substr(2, 10);
|
|
86
|
-
simulatedUserIdCache = `${Date.now()}_${nonce}`;
|
|
87
|
-
wx.setStorage({ key, data: simulatedUserIdCache });
|
|
88
|
-
return simulatedUserIdCache;
|
|
45
|
+
data[33] = CloudReport.getSystemInfoString();
|
|
46
|
+
getAuthInfo().then((user) => {
|
|
47
|
+
data[5] = user.uid;
|
|
48
|
+
CloudReport.callCloudFunc(data);
|
|
49
|
+
});
|
|
89
50
|
}
|
|
90
51
|
};
|
package/src/index-proxy.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import syncApi from './syncfnmanager';
|
|
9
9
|
import md5 from './md5';
|
|
10
|
+
import CloudReport from './cloudReport';
|
|
10
11
|
import { rpxToPx } from './rpx';
|
|
11
12
|
import { serialize } from './objUtils';
|
|
12
13
|
import * as stringUtils from './stringUtils';
|
|
@@ -39,6 +40,7 @@ function invoke(obj, funcName, args) {
|
|
|
39
40
|
function initProxy(appObj, options) {
|
|
40
41
|
app = appObj;
|
|
41
42
|
initOptions = options;
|
|
43
|
+
CloudReport.init(options);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function awaitTMS() {
|
|
@@ -201,10 +203,18 @@ function getHomePage() {
|
|
|
201
203
|
return initOptions.homePage;
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
function getCloudReport() {
|
|
207
|
+
if (tms) {
|
|
208
|
+
return tms.getCloudReport();
|
|
209
|
+
}
|
|
210
|
+
return CloudReport;
|
|
211
|
+
}
|
|
212
|
+
|
|
204
213
|
const api = {
|
|
205
214
|
isProxy: true, // 方便定位问题时判断是否proxy
|
|
206
215
|
initProxy,
|
|
207
216
|
md5,
|
|
217
|
+
getCloudReport,
|
|
208
218
|
getCarManager,
|
|
209
219
|
getLocationManager,
|
|
210
220
|
rpxToPx,
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Request from './request';
|
|
2
2
|
import FastReport from './fastreport';
|
|
3
|
+
import CloudReport from './cloudReport';
|
|
3
4
|
import { getConfig } from './config';
|
|
4
5
|
import syncApi from './syncfnmanager';
|
|
5
6
|
import nav from './navigator';
|
|
@@ -11,7 +12,6 @@ import { cache as report, startListenApp } from './report';
|
|
|
11
12
|
import EventDispatcher from './eventDispatcher';
|
|
12
13
|
import { serialize } from './objUtils';
|
|
13
14
|
import { rpxToPx } from './rpx';
|
|
14
|
-
import { distanceBetweenPoints } from './distanceBetweenPoints';
|
|
15
15
|
import {
|
|
16
16
|
formatPlate,
|
|
17
17
|
subStr,
|
|
@@ -74,6 +74,11 @@ const getReporter = () => ({ report });
|
|
|
74
74
|
*/
|
|
75
75
|
const getFastReporter = () => FastReport;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* @description 云函数埋点上报
|
|
79
|
+
*/
|
|
80
|
+
const getCloudReport = () => CloudReport;
|
|
81
|
+
|
|
77
82
|
/**
|
|
78
83
|
* @description 自定义事件机制
|
|
79
84
|
* @returns {Object} [EventDispatcher实例](#class-eventdispatcher)
|
|
@@ -100,14 +105,16 @@ const getLocationBaseClass = () => LocationBase;
|
|
|
100
105
|
const init = (options = {}) => {
|
|
101
106
|
startListenApp();
|
|
102
107
|
const { appVersion, wxAppId, client, defaultHost, cloudEnvId, appEnv, appPagePaths, homePage } = options;
|
|
103
|
-
|
|
108
|
+
const envInfo = {
|
|
104
109
|
wxAppId,
|
|
105
110
|
appVersion,
|
|
106
111
|
appEnv,
|
|
107
112
|
client,
|
|
108
113
|
cloudEnvId,
|
|
109
|
-
}
|
|
114
|
+
};
|
|
115
|
+
setEnvInfo(envInfo);
|
|
110
116
|
setAppPagePaths(appPagePaths, homePage);
|
|
117
|
+
CloudReport.init(envInfo);
|
|
111
118
|
Request.defaultHost = defaultHost;
|
|
112
119
|
// 初始化云环境
|
|
113
120
|
wx.cloud.init({ env: cloudEnvId });
|
|
@@ -133,6 +140,7 @@ const api = {
|
|
|
133
140
|
getRealtimeLogManager,
|
|
134
141
|
md5,
|
|
135
142
|
getReporter,
|
|
143
|
+
getCloudReport,
|
|
136
144
|
getFastReporter,
|
|
137
145
|
getLocationManager,
|
|
138
146
|
getLocationBaseClass,
|
|
@@ -181,7 +189,6 @@ const api = {
|
|
|
181
189
|
|
|
182
190
|
/** rpx转px */
|
|
183
191
|
rpxToPx,
|
|
184
|
-
distanceBetweenPoints,
|
|
185
192
|
|
|
186
193
|
storage,
|
|
187
194
|
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
// 墨卡托坐标转经纬度
|
|
3
|
-
function mercator2LonLat(p) {
|
|
4
|
-
const r = p.y / 111319.49077777778;
|
|
5
|
-
const n = Math.atan(Math.exp(0.017453292519943295 * r)) / 0.008726646259971648 - 90;
|
|
6
|
-
return {
|
|
7
|
-
x: p.x / 111319.49077777778,
|
|
8
|
-
y: n,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// 角度转弧度
|
|
14
|
-
function angle2Rad(t) {
|
|
15
|
-
return (t * Math.PI) / 180;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function client2ServerX(t) {
|
|
20
|
-
return t - 20037508;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function client2ServerY(t) {
|
|
24
|
-
return t - 30240971;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function longitude2ClientX(t) {
|
|
28
|
-
return Number(111319.49077777778 * Number(t) + 20037508).toFixed(0);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function latitude2ClientY(t) {
|
|
32
|
-
const n = Math.log(Math.tan(0.008726646259971648 * (90 + Number(t)))) / 0.017453292519943295;
|
|
33
|
-
return Number(111319.49077777778 * n + 30240971).toFixed(0);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function geoPointToServerPoint(t) {
|
|
37
|
-
let n = {};
|
|
38
|
-
if (t) {
|
|
39
|
-
n = {
|
|
40
|
-
x: client2ServerX(longitude2ClientX(t.longitude)),
|
|
41
|
-
y: client2ServerY(latitude2ClientY(t.latitude)),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return n;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 两个经纬度间地理距离(单位:米)
|
|
50
|
-
* @param {*} start {longitude, latitude}
|
|
51
|
-
* @param {*} end {longitude, latitude}
|
|
52
|
-
* @returns
|
|
53
|
-
*/
|
|
54
|
-
function distanceBetweenPoints(start, end) {
|
|
55
|
-
let s = geoPointToServerPoint(start);
|
|
56
|
-
let t = geoPointToServerPoint(end);
|
|
57
|
-
s = mercator2LonLat(s); // eslint-disable-line
|
|
58
|
-
t = mercator2LonLat(t); // eslint-disable-line
|
|
59
|
-
const deltaY = angle2Rad(s.y) - angle2Rad(t.y);
|
|
60
|
-
const deltaX = angle2Rad(s.x) - angle2Rad(t.x);
|
|
61
|
-
let res = 2
|
|
62
|
-
* Math.asin(Math.sqrt(Math.pow(Math.sin(deltaY / 2), 2) // eslint-disable-line
|
|
63
|
-
+ Math.cos(angle2Rad(s.y))
|
|
64
|
-
* Math.cos(angle2Rad(t.y))
|
|
65
|
-
* Math.pow(Math.sin(deltaX / 2), 2))); // eslint-disable-line
|
|
66
|
-
res *= 6378137;
|
|
67
|
-
res = Math.floor(res * 10000 + 0.5) / 10000;
|
|
68
|
-
return res;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export {
|
|
72
|
-
distanceBetweenPoints,
|
|
73
|
-
};
|