完善地图的抽稀展示、执行和保存

This commit is contained in:
lin
2025-11-03 15:52:20 +08:00
parent 984d2d56ee
commit 5d471ded1a
21 changed files with 359 additions and 301 deletions

View File

@@ -0,0 +1,33 @@
package com.genersoft.iot.vmp.gb28181.bean;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@Getter
@Setter
public class VectorTileSource {
/**
* 抽稀的图层数据
*/
private Map<String, byte[]> vectorTileMap = new ConcurrentHashMap<>();
/**
* 抽稀的原始数据
*/
private List<CommonGBChannel> channelList = new ArrayList<>();
/**
* 创建时间, 大于6小时后删除
*/
private long time;
public VectorTileSource() {
this.time = System.currentTimeMillis();
}
}

View File

@@ -8,9 +8,9 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.*;
import com.genersoft.iot.vmp.gb28181.service.IGbChannelPlayService;
import com.genersoft.iot.vmp.gb28181.service.IGbChannelService;
import com.genersoft.iot.vmp.gb28181.utils.VectorTileUtils;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.InviteErrorCode;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
@@ -23,6 +23,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -45,7 +46,7 @@ import java.util.concurrent.TimeUnit;
public class ChannelController {
@Autowired
private IRedisCatchStorage redisCatchStorage;
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private IGbChannelService channelService;
@@ -490,12 +491,6 @@ public class ChannelController {
return channelService.queryListForMap(query, online, hasRecordPlan, channelType);
}
@Operation(summary = "为地图保存抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER))
@PostMapping("/map/save-level")
public void saveLevel(@RequestBody List<ChannelForThin> channels){
channelService.saveLevel(channels);
}
@Operation(summary = "为地图去除抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER))
@PostMapping("/map/reset-level")
public void resetLevel(){
@@ -515,14 +510,14 @@ public class ChannelController {
@Parameter(name = "id", description = "抽稀ID", required = true)
@GetMapping("/map/thin/clear")
public void clearThin(String id){
VectorTileUtils.INSTANCE.remove(id);
}
@Operation(summary = "保存的抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER))
@Parameter(name = "id", description = "抽稀ID", required = true)
@GetMapping("/map/thin/save")
public void saveThin(String id){
channelService.saveThin(id);
}
@Operation(summary = "获取抽稀执行的进度", security = @SecurityRequirement(name = JwtUtils.HEADER))
@@ -541,6 +536,10 @@ public class ChannelController {
byte[] mvt = channelService.getTile(z, x, y, geoCoordSys);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/x-protobuf"));
if (mvt == null) {
headers.setContentLength(0);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
headers.setContentLength(mvt.length);
return new ResponseEntity<>(mvt, headers, HttpStatus.OK);
} catch (Exception e) {
@@ -556,16 +555,20 @@ public class ChannelController {
public ResponseEntity<byte[]> getThinTile(@PathVariable int z, @PathVariable int x, @PathVariable int y,
String geoCoordSys, @RequestParam(required = false) String thinId){
try {
byte[] mvt = channelService.getTile(z, x, y, geoCoordSys);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/x-protobuf"));
headers.setContentLength(mvt.length);
return new ResponseEntity<>(mvt, headers, HttpStatus.OK);
} catch (Exception e) {
log.error("构建矢量瓦片失败: z: {}, x: {}, y:{}", z, x, y, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
if (ObjectUtils.isEmpty(thinId)) {
thinId = "DEFAULT";
}
String catchKey = z + "_" + x + "_" + y + "_" + geoCoordSys.toUpperCase();
byte[] mvt = VectorTileUtils.INSTANCE.getVectorTile(thinId, catchKey);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/x-protobuf"));
if (mvt == null) {
headers.setContentLength(0);
return ResponseEntity.status(HttpStatus.OK).body(null);
}
headers.setContentLength(mvt.length);
return new ResponseEntity<>(mvt, headers, HttpStatus.OK);
}

View File

@@ -341,9 +341,13 @@ public class DeviceQuery {
}else if (channelSyncStatus.getErrorMsg() != null) {
wvpResult.setCode(ErrorCode.ERROR100.getCode());
wvpResult.setMsg(channelSyncStatus.getErrorMsg());
}else if (channelSyncStatus.getTotal() == null || channelSyncStatus.getTotal() == 0){
}else if (channelSyncStatus.getTotal() == null){
wvpResult.setCode(ErrorCode.SUCCESS.getCode());
wvpResult.setMsg("等待通道信息...");
}else if (channelSyncStatus.getTotal() == 0){
wvpResult.setCode(ErrorCode.SUCCESS.getCode());
wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
wvpResult.setData(channelSyncStatus);
}else {
wvpResult.setCode(ErrorCode.SUCCESS.getCode());
wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());

View File

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.gb28181.dao;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.Extent;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelForThin;
import com.genersoft.iot.vmp.gb28181.dao.provider.ChannelProvider;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.streamPush.bean.StreamPush;
@@ -660,7 +659,7 @@ public interface CommonGBChannelMapper {
"WHERE id = #{item.gbId}" +
"</foreach> " +
"</script>")
void saveLevel(List<ChannelForThin> channels);
void saveLevel(List<CommonGBChannel> channels);
@SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelByIds")
List<CameraChannel> queryCameraChannelByIds(List<CommonGBChannel> channelList);
@@ -691,6 +690,11 @@ public interface CommonGBChannelMapper {
List<CommonGBChannel> queryAllWithPosition();
@SelectProvider(type = ChannelProvider.class, method = "queryListInExtent")
List<CommonGBChannel> queryListInExtent(Extent extent);
List<CommonGBChannel> queryListInExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng,
@Param("minLat") double minLat, @Param("maxLat") double maxLat);
@SelectProvider(type = ChannelProvider.class, method = "queryListOutExtent")
List<CommonGBChannel> queryListOutExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng,
@Param("minLat") double minLat, @Param("maxLat") double maxLat);
}

View File

@@ -919,4 +919,16 @@ public class ChannelProvider {
return sqlBuild.toString();
}
public String queryListOutExtent(Map<String, Object> params ){
StringBuilder sqlBuild = new StringBuilder();
sqlBuild.append(BASE_SQL);
sqlBuild.append(" where channel_type = 0 AND ( " +
"coalesce(gb_longitude, longitude) <= #{minLng} " +
"or coalesce(gb_longitude, longitude) > #{maxLng} " +
"or coalesce(gb_latitude, latitude) <= #{minLat} " +
"or coalesce(gb_latitude, latitude) > #{maxLat}" +
")");
return sqlBuild.toString();
}
}

View File

@@ -2,7 +2,6 @@ package com.genersoft.iot.vmp.gb28181.service;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.Extent;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelForThin;
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
import com.genersoft.iot.vmp.streamPush.bean.StreamPush;
import com.github.pagehelper.PageInfo;
@@ -103,8 +102,6 @@ public interface IGbChannelService {
List<CommonGBChannel> queryListForMap(String query, Boolean online, Boolean hasRecordPlan, Integer channelType);
void saveLevel(List<ChannelForThin> channels);
CommonGBChannel queryCommonChannelByDeviceChannel(DeviceChannel channel);
void resetLevel();
@@ -114,4 +111,6 @@ public interface IGbChannelService {
String drawThin(Map<Integer, Double> zoomParam, Extent extent, String geoCoordSys);
DrawThinProcess thinProgress(String id);
void saveThin(String id);
}

View File

@@ -8,7 +8,6 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.controller.bean.Extent;
import com.genersoft.iot.vmp.gb28181.controller.bean.ChannelForThin;
import com.genersoft.iot.vmp.gb28181.dao.CommonGBChannelMapper;
import com.genersoft.iot.vmp.gb28181.dao.GroupMapper;
import com.genersoft.iot.vmp.gb28181.dao.PlatformChannelMapper;
@@ -867,23 +866,6 @@ public class GbChannelServiceImpl implements IGbChannelService {
return commonGBChannelMapper.queryList(query, online, hasRecordPlan, channelType, null, null);
}
@Override
@Transactional
public void saveLevel(List<ChannelForThin> channels) {
int limitCount = 1000;
if (channels.size() > limitCount) {
for (int i = 0; i < channels.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > channels.size()) {
toIndex = channels.size();
}
commonGBChannelMapper.saveLevel(channels.subList(i, toIndex));
}
} else {
commonGBChannelMapper.saveLevel(channels);
}
}
@Override
public CommonGBChannel queryCommonChannelByDeviceChannel(DeviceChannel channel) {
return commonGBChannelMapper.queryCommonChannelByDeviceChannel(channel);
@@ -896,10 +878,10 @@ public class GbChannelServiceImpl implements IGbChannelService {
@Override
public byte[] getTile(int z, int x, int y, String geoCoordSys) {
double minLon = tile2lon(x, z);
double maxLon = tile2lon(x + 1, z);
double maxLat = tile2lat(y, z);
double minLat = tile2lat(y + 1, z);
double minLon = TileUtils.tile2lon(x, z);
double maxLon = TileUtils.tile2lon(x + 1, z);
double maxLat = TileUtils.tile2lat(y, z);
double minLat = TileUtils.tile2lat(y + 1, z);
if (geoCoordSys != null) {
if (geoCoordSys.equalsIgnoreCase("GCJ02")) {
@@ -929,7 +911,7 @@ public class GbChannelServiceImpl implements IGbChannelService {
}
// 将 lon/lat 转为瓦片内像素坐标0..256
double[] px = lonLatToTilePixel(lon, lat, z, x, y);
double[] px = TileUtils.lonLatToTilePixel(lon, lat, z, x, y);
Point pointGeom = geometryFactory.createPoint(new Coordinate(px[0], px[1]));
BeanMap beanMap = BeanMapUtils.create(commonGBChannel);
@@ -939,53 +921,20 @@ public class GbChannelServiceImpl implements IGbChannelService {
return encoder.encode();
}
/**
* tile X/Z -> longitude (deg)
*/
private double tile2lon(int x, int z) {
double n = Math.pow(2.0, z);
return x / n * 360.0 - 180.0;
}
/**
* tile Y/Z -> latitude (deg)
*/
private double tile2lat(int y, int z) {
double n = Math.pow(2.0, z);
double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2.0 * y / n)));
return Math.toDegrees(latRad);
}
/**
* lon/lat -> pixel in tile (0..256)
*/
private double[] lonLatToTilePixel(double lon, double lat, int z, int tileX, int tileY) {
double n = Math.pow(2.0, z);
double xtile = (lon + 180.0) / 360.0 * n;
double latRad = Math.toRadians(lat);
double ytile = (1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n;
double pixelX = (xtile - tileX) * 256.0;
double pixelY = (ytile - tileY) * 256.0;
return new double[] { pixelX, pixelY };
}
@Override
public String drawThin(Map<Integer, Double> zoomParam, Extent extent, String geoCoordSys) {
long time = System.currentTimeMillis();
String id = UUID.randomUUID().toString();
List<CommonGBChannel> channelList;
List<CommonGBChannel> channelListInExtent;
if (extent == null) {
log.info("[抽稀] ID: {}, 未设置范围,从数据库读取摄像头的范围", id);
extent = commonGBChannelMapper.queryExtent();
channelList = commonGBChannelMapper.queryAllWithPosition();
channelListInExtent = commonGBChannelMapper.queryAllWithPosition();
}else {
if (geoCoordSys != null && geoCoordSys.equalsIgnoreCase("GCJ02")) {
Double[] maxPosition = Coordtransform.GCJ02ToWGS84(extent.getMaxLng(), extent.getMaxLng());
Double[] maxPosition = Coordtransform.GCJ02ToWGS84(extent.getMaxLng(), extent.getMaxLat());
Double[] minPosition = Coordtransform.GCJ02ToWGS84(extent.getMinLng(), extent.getMinLat());
extent.setMaxLng(maxPosition[0]);
@@ -995,9 +944,9 @@ public class GbChannelServiceImpl implements IGbChannelService {
extent.setMinLat(minPosition[1]);
}
// 获取数据源
channelList = commonGBChannelMapper.queryListInExtent(extent);
channelListInExtent = commonGBChannelMapper.queryListInExtent(extent.getMinLng(), extent.getMaxLng(), extent.getMinLat(), extent.getMaxLat());
}
Assert.isTrue(!channelList.isEmpty(), "通道数据为空");
Assert.isTrue(!channelListInExtent.isEmpty(), "通道数据为空");
log.info("[开始抽稀] ID {} 范围,[{}, {}, {}, {}]", id, extent.getMinLng(), extent.getMinLat(), extent.getMaxLng(), extent.getMaxLat());
@@ -1013,62 +962,82 @@ public class GbChannelServiceImpl implements IGbChannelService {
Map<Integer, CommonGBChannel> useCameraMap = new HashMap<>();
AtomicReference<Double> process = new AtomicReference<>((double) 0);
for (Integer zoom : zoomParam.keySet()) {
Double diff = zoomParam.get(zoom);
// 对这个层级展开抽稀
log.info("[抽稀] ID{},当前层级: {}, 坐标间隔: {}", id, zoom, diff);
Map<String, CommonGBChannel> useCameraMapForZoom = new HashMap<>();
Map<String, CommonGBChannel> cameraMapForZoom = new HashMap<>();
// 更新上级图层的数据到当前层级,确保当前层级展示时考虑到之前层级的数据
for (CommonGBChannel channel : useCameraMap.values()) {
int lngGrid = (int)(channel.getGbLongitude() / diff);
int latGrid = (int)(channel.getGbLatitude() / diff);
String gridKey = latGrid + ":" + lngGrid;
useCameraMapForZoom.put(gridKey, channel);
}
// 对数据开始执行抽稀
for (CommonGBChannel channel : channelList) {
if (useCameraMap.containsKey(channel.getGbId())) {
continue;
if (Objects.equals(zoom, Collections.max(zoomParam.keySet()))) {
// 最大层级不进行抽稀, 将未进行抽稀的数据直接存储到这个层级
for (CommonGBChannel channel : channelListInExtent) {
if (!useCameraMap.containsKey(channel.getGbId())) {
// 这个的key跟后面的不一致是因为无需抽稀 直接存储原始数据
cameraMapForZoom.put(channel.getGbId() + "", channel);
}
}
int lngGrid = (int)(channel.getGbLongitude() / diff);
int latGrid = (int)(channel.getGbLatitude() / diff);
// 数据网格Id
String gridKey = latGrid + ":" + lngGrid;
if (useCameraMapForZoom.containsKey(gridKey)) {
continue;
}else {
Double diff = zoomParam.get(zoom);
// 对这个层级展开抽稀
log.info("[抽稀] ID{},当前层级: {}, 坐标间隔: {}", id, zoom, diff);
// 更新上级图层的数据到当前层级,确保当前层级展示时考虑到之前层级的数据
for (CommonGBChannel channel : useCameraMap.values()) {
int lngGrid = (int)(channel.getGbLongitude() / diff);
int latGrid = (int)(channel.getGbLatitude() / diff);
String gridKey = latGrid + ":" + lngGrid;
useCameraMapForZoom.put(gridKey, channel);
}
if (cameraMapForZoom.containsKey(gridKey)) {
CommonGBChannel oldChannel = cameraMapForZoom.get(gridKey);
// 如果一个网格存在多个数据,则选择最接近中心点的, 目前只选择了经度方向作为参考
if (channel.getGbLongitude() % diff < oldChannel.getGbLongitude() % diff) {
// 对数据开始执行抽稀
for (CommonGBChannel channel : channelListInExtent) {
// 已经分配再其他层级的,本层级不再使用
if (useCameraMap.containsKey(channel.getGbId())) {
continue;
}
int lngGrid = (int)(channel.getGbLongitude() / diff);
int latGrid = (int)(channel.getGbLatitude() / diff);
// 数据网格Id
String gridKey = latGrid + ":" + lngGrid;
if (useCameraMapForZoom.containsKey(gridKey)) {
continue;
}
if (cameraMapForZoom.containsKey(gridKey)) {
CommonGBChannel oldChannel = cameraMapForZoom.get(gridKey);
// 如果一个网格存在多个数据,则选择最接近中心点的, 目前只选择了经度方向作为参考
if (channel.getGbLongitude() % diff < oldChannel.getGbLongitude() % diff) {
channel.setMapLevel(zoom);
cameraMapForZoom.put(gridKey, channel);
useCameraMap.put(channel.getGbId(), channel);
useCameraMap.remove(oldChannel.getGbId());
oldChannel.setMapLevel(null);
}
}else {
channel.setMapLevel(zoom);
cameraMapForZoom.put(gridKey, channel);
useCameraMap.put(channel.getGbId(), channel);
useCameraMap.remove(oldChannel.getGbId());
}
}else {
cameraMapForZoom.put(gridKey, channel);
useCameraMap.put(channel.getGbId(), channel);
}
}
// 存储
zoomCameraMap.put(zoom, cameraMapForZoom.values());
process.updateAndGet(v -> (v + 0.5 / zoomParam.size()));
saveProcess(id, process.get(), "抽稀图层: " + zoom);
}
// 抽稀完成, 对数据生成mvt矢量瓦片
zoomCameraMap.forEach((key, value) -> {
List<CommonGBChannel> beforeData = new ArrayList<>();
for (Integer key : zoomCameraMap.keySet()) {
beforeData.addAll(zoomCameraMap.get(key));
log.info("[抽稀-生成mvt矢量瓦片] ID{},当前层级: {}", id, key);
// 按照 z/x/y 数据组织数据, 矢量数据暂时保存在内存中
// 按照范围生成 x y范围
List<TileUtils.TileCoord> tileCoords = TileUtils.tilesForBoxAtZoom(finalExtent, key);
for (TileUtils.TileCoord tileCoord : tileCoords) {
saveTile(id, tileCoord.z, tileCoord.x, tileCoord.y, "WGS84", value);
saveTile(id, tileCoord.z, tileCoord.x, tileCoord.y, "GCJ02", value);
process.updateAndGet(v -> (v + 0.5 / zoomParam.size() / tileCoords.size()));
saveProcess(id, process.get(), "发布矢量瓦片: " + key);
}
});
saveTile(id, key, "WGS84", beforeData);
saveTile(id, key, "GCJ02", beforeData);
process.updateAndGet(v -> (v + 0.5 / zoomParam.size()));
saveProcess(id, process.get(), "生成mvt矢量瓦片 " + key);
}
// 记录原始数据,未保存做准备
VectorTileUtils.INSTANCE.addSource(id, new ArrayList<>(useCameraMap.values()));
log.info("[抽稀完成] ID{}, 耗时: {}ms", id, (System.currentTimeMillis() - time));
saveProcess(id, 1, "抽稀完成");
} catch (Exception e) {
@@ -1080,8 +1049,8 @@ public class GbChannelServiceImpl implements IGbChannelService {
return id;
}
private void saveTile(String id, int z, int x, int y, String geoCoordSys, Collection<CommonGBChannel> commonGBChannelList ) {
VectorTileEncoder encoder = new VectorTileEncoder();
private void saveTile(String id, int z, String geoCoordSys, Collection<CommonGBChannel> commonGBChannelList ) {
Map<String, VectorTileEncoder> encoderMap = new HashMap<>();
commonGBChannelList.forEach(commonGBChannel -> {
double lon = commonGBChannel.getGbLongitude();
double lat = commonGBChannel.getGbLatitude();
@@ -1090,18 +1059,24 @@ public class GbChannelServiceImpl implements IGbChannelService {
lon = minPosition[0];
lat = minPosition[1];
}
double[] doubles = TileUtils.lonLatToTileXY(lon, lat, z);
int x = (int) doubles[0];
int y = (int) doubles[1];
String key = z + "_" + x + "_" + y + "_" + geoCoordSys;
VectorTileEncoder encoder =encoderMap.get(key);
if (encoder == null) {
encoder = new VectorTileEncoder();
encoderMap.put(key, encoder);
}
// 将 lon/lat 转为瓦片内像素坐标0..256
double[] px = lonLatToTilePixel(lon, lat, z, x, y);
double[] px = TileUtils.lonLatToTilePixel(lon, lat, z, x, y);
Point pointGeom = geometryFactory.createPoint(new Coordinate(px[0], px[1]));
BeanMap beanMap = BeanMapUtils.create(commonGBChannel);
encoder.addFeature("points", beanMap, pointGeom);
});
byte[] encode = encoder.encode();
String catchKey = id + "_" + z + "_" + x + "_" + y + "_" + geoCoordSys;
VectorTileUtils.INSTANCE.addVectorTile(catchKey, encode);
encoderMap.forEach((key, encoder) -> {
VectorTileUtils.INSTANCE.addVectorTile(id, key, encoder.encode());
});
}
private void saveProcess(String id, double process, String msg) {
@@ -1115,4 +1090,26 @@ public class GbChannelServiceImpl implements IGbChannelService {
String key = VideoManagerConstants.DRAW_THIN_PROCESS_PREFIX + id;
return (DrawThinProcess) redisTemplate.opsForValue().get(key);
}
@Override
@Transactional
public void saveThin(String id) {
commonGBChannelMapper.resetLevel();
VectorTileUtils.INSTANCE.save(id);
List<CommonGBChannel> channelList = VectorTileUtils.INSTANCE.getChannelList(id);
if (channelList != null && !channelList.isEmpty()) {
int limitCount = 1000;
if (channelList.size() > limitCount) {
for (int i = 0; i < channelList.size(); i += limitCount) {
int toIndex = i + limitCount;
if (i + limitCount > channelList.size()) {
toIndex = channelList.size();
}
commonGBChannelMapper.saveLevel(channelList.subList(i, toIndex));
}
} else {
commonGBChannelMapper.saveLevel(channelList);
}
}
}
}

View File

@@ -42,6 +42,7 @@ import javax.sip.header.WWWAuthenticateHeader;
import javax.sip.message.Request;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Slf4j
@@ -218,16 +219,18 @@ public class SIPCommanderForPlatform implements ISIPCommanderForPlatform {
if (index > channels.size()) {
return;
}
List<CommonGBChannel> deviceChannels;
if (index + parentPlatform.getCatalogGroup() < channels.size()) {
deviceChannels = channels.subList(index, index + parentPlatform.getCatalogGroup());
String catalogXml;
if (channels.isEmpty()) {
catalogXml = getCatalogXml(Collections.emptyList(), sn, parentPlatform, 0);
}else {
deviceChannels = channels.subList(index, channels.size());
List<CommonGBChannel> subChannelList;
if (index + parentPlatform.getCatalogGroup() < channels.size()) {
subChannelList = channels.subList(index, index + parentPlatform.getCatalogGroup());
}else {
subChannelList = channels.subList(index, channels.size());
}
catalogXml = getCatalogXml(subChannelList, sn, parentPlatform, channels.size());
}
if(deviceChannels.isEmpty()) {
return;
}
String catalogXml = getCatalogXml(deviceChannels, sn, parentPlatform, channels.size());
// callid
CallIdHeader callIdHeader = sipSender.getNewCallIdHeader(parentPlatform.getDeviceIp(),parentPlatform.getTransport());

View File

@@ -22,6 +22,7 @@ import javax.sip.SipException;
import javax.sip.header.FromHeader;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;
@Slf4j
@@ -75,7 +76,7 @@ public class CatalogQueryMessageHandler extends SIPRequestProcessorParent implem
cmderFroPlatform.catalogQuery(channelList, platform, sn, fromHeader.getTag());
}else {
// 回复无通道
cmderFroPlatform.catalogQuery(null, platform, sn, fromHeader.getTag(), 0);
cmderFroPlatform.catalogQuery(Collections.emptyList(), platform, sn, fromHeader.getTag());
}
} catch (SipException | InvalidArgumentException | ParseException e) {
log.error("[命令发送失败] 国标级联 目录查询回复: {}", e.getMessage());

View File

@@ -26,6 +26,7 @@ import javax.sip.SipException;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -114,13 +115,19 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
Element deviceListElement = rootElement.element("DeviceList");
Element sumNumElement = rootElement.element("SumNum");
Element snElement = rootElement.element("SN");
sn = Integer.parseInt(snElement.getText());
int sumNum = Integer.parseInt(sumNumElement.getText());
if (sumNum == 0) {
log.info("[收到通道]设备:{}的: 0个", take.getDevice().getDeviceId());
// 数据已经完整接收
deviceChannelService.cleanChannelsForDevice(take.getDevice().getId());
// 推送空数据,不然无法及时结束
catalogDataCatch.put(take.getDevice().getDeviceId(), sn, 0, take.getDevice(),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), sn, null);
return;
} else {
Iterator<Element> deviceListIterator = deviceListElement.elementIterator();
if (deviceListIterator != null) {
@@ -167,7 +174,7 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
}
channelList.add(channel);
}
sn = Integer.parseInt(snElement.getText());
catalogDataCatch.put(take.getDevice().getDeviceId(), sn, sumNum, take.getDevice(),
channelList, regionList, groupList);
log.info("[收到通道]设备: {} -> {}个,{}/{}", take.getDevice().getDeviceId(), channelList.size(), catalogDataCatch.size(take.getDevice().getDeviceId(), sn), sumNum);
@@ -177,15 +184,17 @@ public class CatalogResponseMessageHandler extends SIPRequestProcessorParent imp
log.warn("[收到通道] 发现未处理的异常, \r\n{}", evt.getRequest());
log.error("[收到通道] 异常内容: ", e);
} finally {
if (catalogDataCatch.size(take.getDevice().getDeviceId(), sn) == catalogDataCatch.sumNum(take.getDevice().getDeviceId(), sn)) {
String deviceId = take.getDevice().getDeviceId();
if (catalogDataCatch.size(deviceId, sn) > 0
&& catalogDataCatch.size(deviceId, sn) == catalogDataCatch.sumNum(deviceId, sn)) {
// 数据已经完整接收, 此时可能存在某个设备离线变上线的情况,但是考虑到性能,此处不做处理,
// 目前支持设备通道上线通知时和设备上线时向上级通知
boolean resetChannelsResult = saveData(take.getDevice(), sn);
if (!resetChannelsResult) {
String errorMsg = "接收成功,写入失败,共" + catalogDataCatch.sumNum(take.getDevice().getDeviceId(), sn) + "条,已接收" + catalogDataCatch.getDeviceChannelList(take.getDevice().getDeviceId(), sn).size() + "";
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), sn, errorMsg);
String errorMsg = "接收成功,写入失败,共" + catalogDataCatch.sumNum(deviceId, sn) + "条,已接收" + catalogDataCatch.getDeviceChannelList(take.getDevice().getDeviceId(), sn).size() + "";
catalogDataCatch.setChannelSyncEnd(deviceId, sn, errorMsg);
} else {
catalogDataCatch.setChannelSyncEnd(take.getDevice().getDeviceId(), sn, null);
catalogDataCatch.setChannelSyncEnd(deviceId, sn, null);
}
}
}

View File

@@ -187,8 +187,16 @@ public class SipUtils {
remotePort = request.getTopmostViaHeader().getRPort();
// 解析本地地址替代
if (ObjectUtils.isEmpty(remoteAddress) || remotePort == -1) {
remoteAddress = request.getPeerPacketSourceAddress().getHostAddress();
remotePort = request.getPeerPacketSourcePort();
if (request.getPeerPacketSourceAddress() != null) {
remoteAddress = request.getPeerPacketSourceAddress().getHostAddress();
}else {
remoteAddress = request.getRemoteAddress().getHostAddress();
}
if( request.getPeerPacketSourcePort() > 0) {
remotePort = request.getPeerPacketSourcePort();
}else {
remotePort = request.getRemotePort();
}
}
}
@@ -256,4 +264,4 @@ public class SipUtils {
}
return localDateTime.format(DateUtil.formatter);
}
}
}

View File

@@ -1,19 +1,66 @@
package com.genersoft.iot.vmp.gb28181.utils;
import com.genersoft.iot.vmp.gb28181.bean.CommonGBChannel;
import com.genersoft.iot.vmp.gb28181.bean.VectorTileSource;
import org.springframework.util.ConcurrentReferenceHashMap;
import java.util.List;
import java.util.Map;
public enum VectorTileUtils {
INSTANCE;
private Map<String, byte[]> vectorTileMap = new ConcurrentReferenceHashMap<>();
private Map<String, VectorTileSource> vectorTileMap = new ConcurrentReferenceHashMap<>();
public void addVectorTile(String key, byte[] content) {
vectorTileMap.put(key, content);
public void addVectorTile(String id, String key, byte[] content) {
VectorTileSource vectorTileSource = vectorTileMap.get(id);
if (vectorTileSource == null) {
vectorTileSource = new VectorTileSource();
vectorTileMap.put(id, vectorTileSource);
}
vectorTileSource.getVectorTileMap().put(key, content);
}
public byte[] getVectorTile(String key) {
return vectorTileMap.get(key);
public byte[] getVectorTile(String id, String key) {
if (!vectorTileMap.containsKey(id)) {
return null;
}
return vectorTileMap.get(id).getVectorTileMap().get(key);
}
public void addSource(String id, List<CommonGBChannel> channelList) {
VectorTileSource vectorTileSource = vectorTileMap.get(id);
if (vectorTileSource == null) {
vectorTileSource = new VectorTileSource();
vectorTileMap.put(id, vectorTileSource);
}
vectorTileMap.get(id).getChannelList().addAll(channelList);
}
public void remove(String id) {
vectorTileMap.remove(id);
}
public List<CommonGBChannel> getChannelList(String id) {
if (!vectorTileMap.containsKey(id)) {
return null;
}
return vectorTileMap.get(id).getChannelList();
}
public void save(String id) {
if (!vectorTileMap.containsKey(id)) {
return;
}
VectorTileSource vectorTileSource = vectorTileMap.get(id);
if (vectorTileSource == null) {
return;
}
vectorTileMap.remove(id);
vectorTileMap.put("DEFAULT", vectorTileSource);
}
}

View File

@@ -113,10 +113,48 @@ public class TileUtils {
public static class TileCoord {
public final int x, y, z;
public TileCoord(int x, int y, int z) { this.x = x; this.y = y; this.z = z; }
public TileCoord(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public String toString() {
return "{" + "z=" + z + ", x=" + x + ", y=" + y + '}';
}
}
/**
* tile X/Z -> longitude (deg)
*/
public static double tile2lon(int x, int z) {
double n = Math.pow(2.0, z);
return x / n * 360.0 - 180.0;
}
/**
* tile Y/Z -> latitude (deg)
*/
public static double tile2lat(int y, int z) {
double n = Math.pow(2.0, z);
double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2.0 * y / n)));
return Math.toDegrees(latRad);
}
/**
* lon/lat -> pixel in tile (0..256)
*/
public static double[] lonLatToTilePixel(double lon, double lat, int z, int tileX, int tileY) {
double n = Math.pow(2.0, z);
double xtile = (lon + 180.0) / 360.0 * n;
double latRad = Math.toRadians(lat);
double ytile = (1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n;
double pixelX = (xtile - tileX) * 256.0;
double pixelY = (ytile - tileY) * 256.0;
return new double[] { pixelX, pixelY };
}
}

View File

@@ -265,8 +265,12 @@ public class CameraChannelController {
}
CameraStreamContent cameraStreamContent = new CameraStreamContent(streamInfo);
cameraStreamContent.setName(channel.getGbName());
cameraStreamContent.setControlType(
(channel.getGbPtzType() == 1 || channel.getGbPtzType() == 4 || channel.getGbPtzType() == 5) ? 1 : 0);
if (channel.getGbPtzType() != null) {
cameraStreamContent.setControlType(
(channel.getGbPtzType() == 1 || channel.getGbPtzType() == 4 || channel.getGbPtzType() == 5) ? 1 : 0);
}else {
cameraStreamContent.setControlType(0);
}
wvpResult.setData(cameraStreamContent);
}else {