Revert "feat: 新增雷达图"
This reverts commit df7358530f428751cdbce3f4220f1925e7b616c2.
This commit is contained in:
parent
0da2c838c2
commit
cfb0bca723
@ -339,13 +339,12 @@ func intFromMeta(m map[string]any, key string) int {
|
|||||||
// radarLatestWindHandler queries Caiyun realtime wind for the latest query candidates
|
// radarLatestWindHandler queries Caiyun realtime wind for the latest query candidates
|
||||||
// and provides per-cluster aggregated wind and basic coming/ETA analysis toward station.
|
// and provides per-cluster aggregated wind and basic coming/ETA analysis toward station.
|
||||||
func radarLatestWindHandler(c *gin.Context) {
|
func radarLatestWindHandler(c *gin.Context) {
|
||||||
// 使用极坐标法:对每个云团仅在质心取一次风,直接判定靠近与ETA
|
// Constants per user request
|
||||||
// 常量:目标点(站点/雷达点)坐标
|
|
||||||
const (
|
const (
|
||||||
stationLat = 23.097234
|
stationLat = 23.097234
|
||||||
stationLon = 108.715433
|
stationLon = 108.715433
|
||||||
)
|
)
|
||||||
// 读取最新元数据
|
// Read latest metadata into struct
|
||||||
latestRoot := "./radar_data/latest"
|
latestRoot := "./radar_data/latest"
|
||||||
metaPath := latestRoot + "/metadata.json"
|
metaPath := latestRoot + "/metadata.json"
|
||||||
b, err := os.ReadFile(metaPath)
|
b, err := os.ReadFile(metaPath)
|
||||||
@ -358,8 +357,7 @@ func radarLatestWindHandler(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "解析元数据失败"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "解析元数据失败"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// For each query candidate, call Caiyun
|
||||||
// 输出结构保持兼容:仍提供 candidates,但每个cluster仅一个(质心)
|
|
||||||
type Wind struct {
|
type Wind struct {
|
||||||
Speed float64 `json:"speed_ms"`
|
Speed float64 `json:"speed_ms"`
|
||||||
DirFrom float64 `json:"dir_from_deg"`
|
DirFrom float64 `json:"dir_from_deg"`
|
||||||
@ -375,38 +373,29 @@ func radarLatestWindHandler(c *gin.Context) {
|
|||||||
Wind *Wind `json:"wind,omitempty"`
|
Wind *Wind `json:"wind,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
}
|
}
|
||||||
|
outs := make([]CandOut, 0, len(meta.QueryCandidates))
|
||||||
outs := make([]CandOut, 0, len(meta.Clusters))
|
for _, q := range meta.QueryCandidates {
|
||||||
|
speed, dirFrom, tempC, rh, pPa, err := rf.FetchCaiyunRealtime(q.Lon, q.Lat)
|
||||||
// 工具函数
|
co := CandOut{QueryCandidate: q}
|
||||||
mPerDegLat := 111320.0
|
if err != nil {
|
||||||
mPerDegLon := func(lat float64) float64 { return 111320.0 * math.Cos(lat*math.Pi/180.0) }
|
co.Error = err.Error()
|
||||||
// 计算极坐标ETA(到站点本身,不再使用侧向与半径作为命中条件)
|
} else {
|
||||||
approachETA := func(lonC, latC, speedMS, dirToDeg, lonS, latS float64) (coming bool, etaMin float64, distanceKm float64, vrMS float64) {
|
dirTo := mathMod(dirFrom+180.0, 360.0)
|
||||||
wx := mPerDegLon(latC)
|
u, v := windVectorUV(speed, dirTo)
|
||||||
wy := mPerDegLat
|
// pressure in hPa for display
|
||||||
dx := (lonS - lonC) * wx // 东向米
|
pHpa := pPa / 100.0
|
||||||
dy := (latS - latC) * wy // 北向米
|
co.Wind = &Wind{Speed: speed, DirFrom: dirFrom, DirTo: dirTo, U: u, V: v, TempC: tempC, RH: rh, PressureHpa: pHpa}
|
||||||
D := math.Hypot(dx, dy)
|
|
||||||
if D == 0 {
|
|
||||||
return true, 0, 0, speedMS
|
|
||||||
}
|
}
|
||||||
// 云→站方位角(北=0,顺时针)
|
outs = append(outs, co)
|
||||||
theta := math.Atan2(dx, dy) * 180 / math.Pi
|
|
||||||
if theta < 0 {
|
|
||||||
theta += 360
|
|
||||||
}
|
}
|
||||||
beta := mathMod(dirToDeg, 360.0)
|
// Aggregate by cluster id
|
||||||
delta := (beta - theta) * math.Pi / 180.0
|
agg := map[int][]Wind{}
|
||||||
vr := speedMS * math.Cos(delta) // 指向站点的径向速度
|
for _, co := range outs {
|
||||||
if vr <= 0 {
|
if co.Wind == nil {
|
||||||
return false, -1, D / 1000.0, vr
|
continue
|
||||||
}
|
}
|
||||||
etaSec := D / vr
|
agg[co.ClusterID] = append(agg[co.ClusterID], *co.Wind)
|
||||||
return true, etaSec / 60.0, D / 1000.0, vr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 为每个云团质心取一次风,构造 candidates 与 per-cluster 分析
|
|
||||||
type ClusterAnal struct {
|
type ClusterAnal struct {
|
||||||
ClusterID int `json:"cluster_id"`
|
ClusterID int `json:"cluster_id"`
|
||||||
Lon float64 `json:"lon"`
|
Lon float64 `json:"lon"`
|
||||||
@ -422,64 +411,77 @@ func radarLatestWindHandler(c *gin.Context) {
|
|||||||
DistanceKm float64 `json:"distance_km"`
|
DistanceKm float64 `json:"distance_km"`
|
||||||
LateralKm float64 `json:"lateral_km"`
|
LateralKm float64 `json:"lateral_km"`
|
||||||
RCloudKm float64 `json:"r_cloud_km"`
|
RCloudKm float64 `json:"r_cloud_km"`
|
||||||
VrMS float64 `json:"vr_ms"`
|
|
||||||
}
|
}
|
||||||
analyses := []ClusterAnal{}
|
analyses := []ClusterAnal{}
|
||||||
|
// helpers
|
||||||
// 等效云半径与侧向距离仅用于展示(不再作为判定条件)
|
mPerDegLat := 111320.0
|
||||||
cellDims := func(lat float64) (float64, float64) { // 每像素米宽/米高
|
mPerDegLon := func(lat float64) float64 { return 111320.0 * math.Cos(lat*math.Pi/180.0) }
|
||||||
|
cellDims := func(lat float64) (float64, float64) { // width (lon), height (lat) in meters per pixel
|
||||||
return meta.ResDeg * mPerDegLon(lat), meta.ResDeg * mPerDegLat
|
return meta.ResDeg * mPerDegLon(lat), meta.ResDeg * mPerDegLat
|
||||||
}
|
}
|
||||||
|
const hitRadiusM = 5000.0
|
||||||
for _, cl := range meta.Clusters {
|
for _, cl := range meta.Clusters {
|
||||||
// 取质心风
|
winds := agg[cl.ID]
|
||||||
speed, dirFrom, tempC, rh, pPa, err := rf.FetchCaiyunRealtime(cl.Lon, cl.Lat)
|
if len(winds) == 0 {
|
||||||
q := rf.QueryCandidate{ClusterID: cl.ID, Role: "center", Lon: cl.Lon, Lat: cl.Lat}
|
|
||||||
co := CandOut{QueryCandidate: q}
|
|
||||||
if err != nil {
|
|
||||||
co.Error = err.Error()
|
|
||||||
outs = append(outs, co)
|
|
||||||
// 即便取风失败,也继续下一个云团
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dirTo := mathMod(dirFrom+180.0, 360.0)
|
// vector average in u,v (to-direction)
|
||||||
u, v := windVectorUV(speed, dirTo)
|
sumU, sumV := 0.0, 0.0
|
||||||
pHpa := pPa / 100.0
|
for _, wv := range winds {
|
||||||
co.Wind = &Wind{Speed: speed, DirFrom: dirFrom, DirTo: dirTo, U: u, V: v, TempC: tempC, RH: rh, PressureHpa: pHpa}
|
sumU += wv.U
|
||||||
outs = append(outs, co)
|
sumV += wv.V
|
||||||
|
|
||||||
// 极坐标法靠近/ETA(到站点)
|
|
||||||
coming, etaMin, distKm, vr := approachETA(cl.Lon, cl.Lat, speed, dirTo, stationLon, stationLat)
|
|
||||||
|
|
||||||
// 展示参数:侧向距与等效半径
|
|
||||||
// 侧向距 = 距离向量对速度方向单位向量的叉积绝对值
|
|
||||||
wx, wy := mPerDegLon(cl.Lat), mPerDegLat
|
|
||||||
px := (stationLon - cl.Lon) * wx
|
|
||||||
py := (stationLat - cl.Lat) * wy
|
|
||||||
vnorm := math.Hypot(u, v)
|
|
||||||
lateral := 0.0
|
|
||||||
if vnorm > 0 {
|
|
||||||
vx, vy := u/vnorm, v/vnorm
|
|
||||||
lateral = math.Abs(px*vy - py*vx)
|
|
||||||
}
|
}
|
||||||
|
u := sumU / float64(len(winds))
|
||||||
|
v := sumV / float64(len(winds))
|
||||||
|
speed := math.Hypot(u, v)
|
||||||
|
dirTo := uvToDirTo(u, v)
|
||||||
|
// project geometry
|
||||||
|
wx, wy := mPerDegLon(cl.Lat), mPerDegLat
|
||||||
|
// position of cluster and station in meters (local tangent plane)
|
||||||
|
px := (cl.Lon - stationLon) * wx
|
||||||
|
py := (cl.Lat - stationLat) * wy
|
||||||
|
// vector from cluster to station
|
||||||
|
dx := -px
|
||||||
|
dy := -py
|
||||||
|
d := math.Hypot(dx, dy)
|
||||||
|
// radial component of velocity towards station
|
||||||
|
if d == 0 {
|
||||||
|
d = 1e-6
|
||||||
|
}
|
||||||
|
vr := (dx*u + dy*v) / d
|
||||||
|
// cluster equivalent radius
|
||||||
cw, ch := cellDims(cl.Lat)
|
cw, ch := cellDims(cl.Lat)
|
||||||
areaM2 := float64(cl.AreaPx) * cw * ch
|
areaM2 := float64(cl.AreaPx) * cw * ch
|
||||||
rCloud := math.Sqrt(areaM2 / math.Pi)
|
rCloud := math.Sqrt(areaM2 / math.Pi)
|
||||||
|
// lateral offset (perpendicular distance from station line)
|
||||||
|
vnorm := math.Hypot(u, v)
|
||||||
|
lateral := 0.0
|
||||||
|
if vnorm > 0 {
|
||||||
|
// |d x vhat|
|
||||||
|
vx, vy := u/vnorm, v/vnorm
|
||||||
|
lateral = math.Abs(dx*vy - dy*vx)
|
||||||
|
}
|
||||||
|
coming := vr > 0 && lateral <= (rCloud+hitRadiusM)
|
||||||
|
etaMin := 0.0
|
||||||
|
if coming && vr > 0 {
|
||||||
|
distToEdge := d - (rCloud + hitRadiusM)
|
||||||
|
if distToEdge < 0 {
|
||||||
|
distToEdge = 0
|
||||||
|
}
|
||||||
|
etaMin = distToEdge / vr / 60.0
|
||||||
|
}
|
||||||
analyses = append(analyses, ClusterAnal{
|
analyses = append(analyses, ClusterAnal{
|
||||||
ClusterID: cl.ID,
|
ClusterID: cl.ID,
|
||||||
Lon: cl.Lon, Lat: cl.Lat,
|
Lon: cl.Lon, Lat: cl.Lat,
|
||||||
AreaPx: cl.AreaPx, MaxDBZ: cl.MaxDBZ,
|
AreaPx: cl.AreaPx, MaxDBZ: cl.MaxDBZ,
|
||||||
SpeedMS: speed, DirToDeg: dirTo, U: u, V: v,
|
SpeedMS: speed, DirToDeg: dirTo, U: u, V: v,
|
||||||
Coming: coming, ETAMin: round2(etaMin),
|
Coming: coming, ETAMin: round2(etaMin),
|
||||||
DistanceKm: round2(distKm), LateralKm: round2(lateral / 1000.0), RCloudKm: round2(rCloud / 1000.0),
|
DistanceKm: round2(d / 1000.0), LateralKm: round2(lateral / 1000.0), RCloudKm: round2(rCloud / 1000.0),
|
||||||
VrMS: round2(vr),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"station": gin.H{"lon": stationLon, "lat": stationLat},
|
"station": gin.H{"lon": stationLon, "lat": stationLat},
|
||||||
"params": meta.QueryParams, // 兼容保留
|
"params": meta.QueryParams,
|
||||||
"candidates": outs,
|
"candidates": outs,
|
||||||
"clusters": analyses,
|
"clusters": analyses,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -720,7 +720,7 @@
|
|||||||
window.RadarLatestGrid = data;
|
window.RadarLatestGrid = data;
|
||||||
renderPlotlyHeat(data);
|
renderPlotlyHeat(data);
|
||||||
renderClustersPanel();
|
renderClustersPanel();
|
||||||
renderMethodNote();
|
renderWindQueryList();
|
||||||
renderWindResults();
|
renderWindResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,7 +838,11 @@
|
|||||||
+ '质心: '+cl.lon.toFixed(4)+', '+cl.lat.toFixed(4)+'<br/>'
|
+ '质心: '+cl.lon.toFixed(4)+', '+cl.lat.toFixed(4)+'<br/>'
|
||||||
+ 'dBZ: max '+cl.max_dbz.toFixed(1)+' / avg '+cl.avg_dbz.toFixed(1)
|
+ 'dBZ: max '+cl.max_dbz.toFixed(1)+' / avg '+cl.avg_dbz.toFixed(1)
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
// 极坐标法:不再展示采样点列表(仅使用质心)
|
if (cl.samples && cl.samples.length) {
|
||||||
|
html += '<div class="mt-1 text-xs text-gray-600">采样点: ' + cl.samples.map(function(s){
|
||||||
|
return s.role+':('+s.lon.toFixed(3)+','+s.lat.toFixed(3)+')';
|
||||||
|
}).join(' | ') + '</div>';
|
||||||
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
@ -846,7 +850,11 @@
|
|||||||
}).catch(function(){ /* ignore */ });
|
}).catch(function(){ /* ignore */ });
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMethodNote(){
|
function renderWindQueryList(){
|
||||||
|
fetch('/api/radar/latest').then(r=>r.json()).then(function(resp){
|
||||||
|
var meta = resp.meta || {};
|
||||||
|
var params = meta.query_params || {};
|
||||||
|
var cands = meta.query_candidates || [];
|
||||||
var containerId = 'radar-wind-query';
|
var containerId = 'radar-wind-query';
|
||||||
var parent = document.getElementById(containerId);
|
var parent = document.getElementById(containerId);
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
@ -857,9 +865,25 @@ function renderMethodNote(){
|
|||||||
root.appendChild(sec);
|
root.appendChild(sec);
|
||||||
parent = sec;
|
parent = sec;
|
||||||
}
|
}
|
||||||
var html = '<div class="text-sm text-gray-700 mb-1">方法</div>';
|
var html = '<div class="text-sm text-gray-700 mb-2">风场查询参数</div>';
|
||||||
html += '<div class="text-xs text-gray-600">极坐标(质心单点):使用云团质心处彩云风,计算与站点的径向分量与 ETA。</div>';
|
html += '<div class="text-xs text-gray-600 mb-2">'
|
||||||
|
+ 'min_area_px='+ (params.min_area_px||9)
|
||||||
|
+ ',strong_dbz_override=' + (params.strong_dbz_override||50)
|
||||||
|
+ ',max_samples_per_cluster=' + (params.max_samples_per_cluster||5)
|
||||||
|
+ ',max_candidates_total=' + (params.max_candidates_total||25)
|
||||||
|
+ '</div>';
|
||||||
|
if (!cands.length) {
|
||||||
|
html += '<div class="text-xs text-gray-500">暂无需要查询的采样点</div>';
|
||||||
|
} else {
|
||||||
|
html += '<div class="text-sm text-gray-700 mb-1">需要查询的采样点(共 '+cands.length+' 个)</div>';
|
||||||
|
html += '<ul class="list-disc pl-5 text-xs text-gray-700">';
|
||||||
|
cands.forEach(function(p){
|
||||||
|
html += '<li>cluster='+p.cluster_id+' | '+p.role+' | lon='+p.lon.toFixed(4)+', lat='+p.lat.toFixed(4)+'</li>';
|
||||||
|
});
|
||||||
|
html += '</ul>';
|
||||||
|
}
|
||||||
parent.innerHTML = html;
|
parent.innerHTML = html;
|
||||||
|
}).catch(function(){});
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderWindResults(){
|
function renderWindResults(){
|
||||||
@ -891,51 +915,38 @@ function renderMethodNote(){
|
|||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
// 朝向云团:极坐标计算明细列表
|
// candidate details
|
||||||
var candByCluster = {};
|
if (cands.length) {
|
||||||
(cands||[]).forEach(function(co){ candByCluster[co.cluster_id] = co; });
|
html += '<div class="text-xs text-gray-700 mb-2">采样点明细:</div>';
|
||||||
var comings = (clusters||[]).filter(function(cl){ return cl.coming; });
|
html += '<div class="overflow-x-auto"><table class="min-w-full text-xs text-gray-700"><thead><tr>'
|
||||||
if (comings.length) {
|
+ '<th class="px-2 py-1 border">cluster</th>'
|
||||||
html += '<div class="text-xs text-gray-700 mb-2">朝向云团(极坐标计算明细)</div>';
|
+ '<th class="px-2 py-1 border">role</th>'
|
||||||
comings.forEach(function(cl){
|
+ '<th class="px-2 py-1 border">lon</th>'
|
||||||
var co = candByCluster[cl.cluster_id] || {};
|
+ '<th class="px-2 py-1 border">lat</th>'
|
||||||
var w = co.wind || {};
|
+ '<th class="px-2 py-1 border">spd(m/s)</th>'
|
||||||
var speed = (w.speed_ms!=null)? w.speed_ms : (cl.speed_ms||0);
|
+ '<th class="px-2 py-1 border">dir_from(°)</th>'
|
||||||
var dirFrom = (w.dir_from_deg!=null)? w.dir_from_deg : null;
|
+ '<th class="px-2 py-1 border">T(°C)</th>'
|
||||||
var dirTo = (w.dir_to_deg!=null)? w.dir_to_deg : (cl.dir_to_deg||0);
|
+ '<th class="px-2 py-1 border">RH</th>'
|
||||||
// 计算几何与极坐标过程(前端复算,便于展示公式与数值)
|
+ '<th class="px-2 py-1 border">P(hPa)</th>'
|
||||||
var mPerDegLat = 111320.0;
|
+ '<th class="px-2 py-1 border">err</th>'
|
||||||
var mPerDegLon = 111320.0 * Math.cos((cl.lat||0) * Math.PI/180.0);
|
+ '</tr></thead><tbody>';
|
||||||
var dx = ((station.lon||0) - (cl.lon||0)) * mPerDegLon; // 东向
|
cands.forEach(function(p){
|
||||||
var dy = ((station.lat||0) - (cl.lat||0)) * mPerDegLat; // 北向
|
var w = p.wind || {};
|
||||||
var D = Math.hypot(dx, dy); // m
|
html += '<tr>'
|
||||||
var theta = Math.atan2(dx, dy) * 180/Math.PI; // 北=0, 顺时针
|
+ '<td class="px-2 py-1 border">'+p.cluster_id+'</td>'
|
||||||
if (theta < 0) theta += 360;
|
+ '<td class="px-2 py-1 border">'+p.role+'</td>'
|
||||||
var delta = dirTo - theta; // deg
|
+ '<td class="px-2 py-1 border">'+p.lon.toFixed(4)+'</td>'
|
||||||
// wrap 到 [-180,180]
|
+ '<td class="px-2 py-1 border">'+p.lat.toFixed(4)+'</td>'
|
||||||
delta = ((delta + 540) % 360) - 180;
|
+ '<td class="px-2 py-1 border">'+(w.speed_ms!=null?w.speed_ms.toFixed(1):'')+'</td>'
|
||||||
var vr = speed * Math.cos(delta * Math.PI/180.0); // m/s(指向站点为正)
|
+ '<td class="px-2 py-1 border">'+(w.dir_from_deg!=null?w.dir_from_deg.toFixed(0):'')+'</td>'
|
||||||
var etaMin = (vr>0) ? (D/vr/60.0) : null;
|
+ '<td class="px-2 py-1 border">'+(w.temp_c!=null?w.temp_c.toFixed(1):'')+'</td>'
|
||||||
var code = ''
|
+ '<td class="px-2 py-1 border">'+(w.rh!=null?(w.rh*100).toFixed(0)+'%':'')+'</td>'
|
||||||
+ 'station = ('+(station.lon||0).toFixed(6)+', '+(station.lat||0).toFixed(6)+')\n'
|
+ '<td class="px-2 py-1 border">'+(w.pressure_hpa!=null?w.pressure_hpa.toFixed(1):'')+'</td>'
|
||||||
+ 'centroid = ('+(cl.lon||0).toFixed(6)+', '+(cl.lat||0).toFixed(6)+')\n'
|
+ '<td class="px-2 py-1 border">'+(p.error||'')+'</td>'
|
||||||
+ 'speed = '+speed.toFixed(2)+' m/s\n'
|
+ '</tr>';
|
||||||
+ (dirFrom!=null?('dir_from = '+dirFrom.toFixed(0)+'°\n'):'')
|
|
||||||
+ 'dir_to = '+dirTo.toFixed(0)+'°\n'
|
|
||||||
+ 'dx = (lonS-lonC) * 111320*cos(latC) = '+dx.toFixed(1)+' m\n'
|
|
||||||
+ 'dy = (latS-latC) * 111320 = '+dy.toFixed(1)+' m\n'
|
|
||||||
+ 'D = hypot(dx,dy) = '+(D/1000.0).toFixed(2)+' km\n'
|
|
||||||
+ 'theta = atan2(dx,dy) = '+theta.toFixed(1)+'°\n'
|
|
||||||
+ 'delta = dir_to - theta = '+delta.toFixed(1)+'°\n'
|
|
||||||
+ 'vr = speed * cos(delta) = '+vr.toFixed(2)+' m/s\n'
|
|
||||||
+ (etaMin!=null?('ETA = D/vr = '+etaMin.toFixed(1)+' min\n'):'ETA = N/A (vr<=0)\n');
|
|
||||||
html += '<div class="mb-3 border border-green-200 rounded p-2 bg-green-50">'
|
|
||||||
+ '<div class="text-xs text-green-800">ID '+cl.cluster_id+' | 质心: '+cl.lon.toFixed(4)+', '+cl.lat.toFixed(4)+'</div>'
|
|
||||||
+ '<pre class="mt-1 text-[11px] leading-4 text-gray-800 overflow-x-auto">'+code.replace(/</g,'<')+'</pre>'
|
|
||||||
+ '</div>';
|
|
||||||
});
|
});
|
||||||
|
html += '</tbody></table></div>';
|
||||||
}
|
}
|
||||||
// 极坐标法:省略采样点明细表,仅保留汇总与朝向明细
|
|
||||||
parent.innerHTML = html;
|
parent.innerHTML = html;
|
||||||
}).catch(function(){});
|
}).catch(function(){});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user