diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/bean/VectorTileSource.java b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/VectorTileSource.java new file mode 100644 index 000000000..730cf5655 --- /dev/null +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/bean/VectorTileSource.java @@ -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 vectorTileMap = new ConcurrentHashMap<>(); + + /** + * 抽稀的原始数据 + */ + private List channelList = new ArrayList<>(); + + /** + * 创建时间, 大于6小时后删除 + */ + private long time; + + public VectorTileSource() { + this.time = System.currentTimeMillis(); + } +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java index 77acf0cef..90ecd3c24 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/ChannelController.java @@ -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 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 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 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); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java index 92e171f35..6954101a1 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/controller/DeviceQuery.java @@ -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()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java index 681a594b5..e6e7b1ac7 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/CommonGBChannelMapper.java @@ -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}" + " " + "") - void saveLevel(List channels); + void saveLevel(List channels); @SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelByIds") List queryCameraChannelByIds(List channelList); @@ -691,6 +690,11 @@ public interface CommonGBChannelMapper { List queryAllWithPosition(); @SelectProvider(type = ChannelProvider.class, method = "queryListInExtent") - List queryListInExtent(Extent extent); + List queryListInExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng, + @Param("minLat") double minLat, @Param("maxLat") double maxLat); + + @SelectProvider(type = ChannelProvider.class, method = "queryListOutExtent") + List queryListOutExtent(@Param("minLng") double minLng, @Param("maxLng") double maxLng, + @Param("minLat") double minLat, @Param("maxLat") double maxLat); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java index 6d61c6494..5e4841361 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/dao/provider/ChannelProvider.java @@ -919,4 +919,16 @@ public class ChannelProvider { return sqlBuild.toString(); } + public String queryListOutExtent(Map 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(); + } + } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java index b6b95d4d7..2a564095e 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/IGbChannelService.java @@ -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 queryListForMap(String query, Boolean online, Boolean hasRecordPlan, Integer channelType); - void saveLevel(List channels); - CommonGBChannel queryCommonChannelByDeviceChannel(DeviceChannel channel); void resetLevel(); @@ -114,4 +111,6 @@ public interface IGbChannelService { String drawThin(Map zoomParam, Extent extent, String geoCoordSys); DrawThinProcess thinProgress(String id); + + void saveThin(String id); } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java index 1647e5665..0bad5c089 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/service/impl/GbChannelServiceImpl.java @@ -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 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 zoomParam, Extent extent, String geoCoordSys) { long time = System.currentTimeMillis(); String id = UUID.randomUUID().toString(); - List channelList; + List 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 useCameraMap = new HashMap<>(); AtomicReference process = new AtomicReference<>((double) 0); for (Integer zoom : zoomParam.keySet()) { - Double diff = zoomParam.get(zoom); - // 对这个层级展开抽稀 - log.info("[抽稀] ID:{},当前层级: {}, 坐标间隔: {}", id, zoom, diff); + Map useCameraMapForZoom = new HashMap<>(); Map 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 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 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 commonGBChannelList ) { - VectorTileEncoder encoder = new VectorTileEncoder(); + private void saveTile(String id, int z, String geoCoordSys, Collection commonGBChannelList ) { + Map 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 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); + } + } + } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java index 037d9332e..d7d374400 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommanderForPlatform.java @@ -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 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 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()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java index ee389cf0c..dbe2b5e61 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/query/cmd/CatalogQueryMessageHandler.java @@ -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()); diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java index 4ae5844e8..738430a80 100755 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/transmit/event/request/impl/message/response/cmd/CatalogResponseMessageHandler.java @@ -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 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); } } } diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java index 59c9ed722..90a7e2aac 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/SipUtils.java @@ -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); } -} \ No newline at end of file +} diff --git a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/VectorTileUtils.java b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/VectorTileUtils.java index 47b9e234e..8e114fab3 100644 --- a/src/main/java/com/genersoft/iot/vmp/gb28181/utils/VectorTileUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/gb28181/utils/VectorTileUtils.java @@ -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 vectorTileMap = new ConcurrentReferenceHashMap<>(); + private Map 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 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 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); + } + + } diff --git a/src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java b/src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java index 04a67520d..a989ab51f 100644 --- a/src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java @@ -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 }; + } } diff --git a/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java b/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java index 85d818761..75b92994e 100644 --- a/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java +++ b/src/main/java/com/genersoft/iot/vmp/web/custom/CameraChannelController.java @@ -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 { diff --git a/web/src/views/channel/index.vue b/web/src/views/channel/index.vue index 7ac73c67f..9f7f8849c 100755 --- a/web/src/views/channel/index.vue +++ b/web/src/views/channel/index.vue @@ -96,7 +96,7 @@ - + @@ -222,7 +222,7 @@ export default { }, 经度: 'gbLongitude', 纬度: 'gbLatitude', - 云台类型: 'ptzTypeText', + 摄像头类型: 'ptzTypeText', 状态: { field: 'gbStatus', callback: (value) => { diff --git a/web/src/views/common/MapComponent.vue b/web/src/views/common/MapComponent.vue index 4235736bf..16d3c70eb 100755 --- a/web/src/views/common/MapComponent.vue +++ b/web/src/views/common/MapComponent.vue @@ -16,6 +16,7 @@ import VectorTileLayer from 'ol/layer/VectorTile.js' import VectorTileSource from 'ol/source/VectorTile.js' import WebGLVectorLayer from 'ol/layer/WebGLVector' import Style from 'ol/style/Style' +import Circle from 'ol/style/Circle' import Stroke from 'ol/style/Stroke' import Icon from 'ol/style/Icon' import View from 'ol/View' @@ -159,7 +160,7 @@ export default { style: function(feature) { let status = feature.properties_.gbStatus if (layer.get('hideFeatures').includes(feature.properties_.gbId)) { - return new Style() + return } if (status === 'ON') { return new Style({ diff --git a/web/src/views/device/channel/index.vue b/web/src/views/device/channel/index.vue index 167d2dd61..0fd52293a 100755 --- a/web/src/views/device/channel/index.vue +++ b/web/src/views/device/channel/index.vue @@ -97,7 +97,7 @@ - + diff --git a/web/src/views/dialog/channelMapInfobox.vue b/web/src/views/dialog/channelMapInfobox.vue index fa7ae5c90..7061b0333 100755 --- a/web/src/views/dialog/channelMapInfobox.vue +++ b/web/src/views/dialog/channelMapInfobox.vue @@ -7,7 +7,7 @@ {{channel.owner}} {{channel.civilCode}} {{channel.address}} - {{channel.ptzTypeText}} + {{channel.ptzTypeText}} {{channel.longitude}},{{channel.latitude}} 在线 diff --git a/web/src/views/dialog/hasStreamChannel.vue b/web/src/views/dialog/hasStreamChannel.vue index 7775ef44f..a492ea449 100644 --- a/web/src/views/dialog/hasStreamChannel.vue +++ b/web/src/views/dialog/hasStreamChannel.vue @@ -27,7 +27,7 @@ - + diff --git a/web/src/views/map/dialog/drawThinProgress.vue b/web/src/views/map/dialog/drawThinProgress.vue index 47ae756b5..8590dbff3 100755 --- a/web/src/views/map/dialog/drawThinProgress.vue +++ b/web/src/views/map/dialog/drawThinProgress.vue @@ -12,7 +12,7 @@ style="text-align: center" @close="close()" > - +
{{ msg }}
@@ -30,7 +30,7 @@ export default { data() { return { endCallBack: null, - syncStatus: null, + syncStatus: '', percentage: 0, showDialog: false, isLoging: false, @@ -51,17 +51,23 @@ export default { this.msg = '' this.percentage = 0 this.syncFlag = false - this.syncStatus = null + this.syncStatus = '' this.endCallBack = endCallBack this.getProgress() }, getProgress() { this.$store.dispatch('commonChanel/thinProgress', this.drawThinId) - .then(({ data }) => { + .then((data) => { + console.log(data) this.syncFlag = true this.percentage = data.process * 100 this.msg = data.msg console.log('drawThinId: ' + data.drawThinId) + if (data.process >= 1) { + this.syncStatus = 'success' + this.close() + return + } this.timer = setTimeout(this.getProgress, 300) }).catch((error) => { @@ -74,10 +80,11 @@ export default { }) }, close: function() { + window.clearTimeout(this.timer) + this.showDialog = false if (this.endCallBack) { this.endCallBack() } - window.clearTimeout(this.timer) } } } diff --git a/web/src/views/map/index.vue b/web/src/views/map/index.vue index 438893425..62432986f 100755 --- a/web/src/views/map/index.vue +++ b/web/src/views/map/index.vue @@ -5,11 +5,6 @@
-
- -