支持服务端抽稀和服务发布
This commit is contained in:
@@ -50,6 +50,7 @@ public class VideoManagerConstants {
|
||||
public static final String WAITE_SEND_PUSH_STREAM = "VMP_WAITE_SEND_PUSH_STREAM:";
|
||||
public static final String START_SEND_PUSH_STREAM = "VMP_START_SEND_PUSH_STREAM:";
|
||||
public static final String SSE_TASK_KEY = "SSE_TASK_";
|
||||
public static final String DRAW_THIN_PROCESS_PREFIX = "VMP_DRAW_THIN_PROCESS_";
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.genersoft.iot.vmp.gb28181.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DrawThinProcess {
|
||||
|
||||
private double process;
|
||||
private String msg;
|
||||
|
||||
public DrawThinProcess(double process, String msg) {
|
||||
this.process = process;
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
@@ -502,7 +502,37 @@ public class ChannelController {
|
||||
channelService.resetLevel();
|
||||
}
|
||||
|
||||
@Operation(summary = "为地图提供标准的mvt图层", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Operation(summary = "执行抽稀", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@PostMapping("/map/thin/draw")
|
||||
public String drawThin(@RequestBody DrawThinParam param){
|
||||
if(param == null || param.getZoomParam() == null || param.getZoomParam().isEmpty()) {
|
||||
throw new ControllerException(ErrorCode.ERROR400);
|
||||
}
|
||||
return channelService.drawThin(param.getZoomParam(), param.getExtent(), param.getGeoCoordSys());
|
||||
}
|
||||
|
||||
@Operation(summary = "清除未保存的抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "id", description = "抽稀ID", required = true)
|
||||
@GetMapping("/map/thin/clear")
|
||||
public void clearThin(String id){
|
||||
|
||||
}
|
||||
|
||||
@Operation(summary = "保存的抽稀结果", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "id", description = "抽稀ID", required = true)
|
||||
@GetMapping("/map/thin/save")
|
||||
public void saveThin(String id){
|
||||
|
||||
}
|
||||
|
||||
@Operation(summary = "获取抽稀执行的进度", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@Parameter(name = "id", description = "抽稀ID", required = true)
|
||||
@GetMapping("/map/thin/progress")
|
||||
public DrawThinProcess thinProgress(String id){
|
||||
return channelService.thinProgress(id);
|
||||
}
|
||||
|
||||
@Operation(summary = "为地图提供标准mvt图层", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@GetMapping(value = "/map/tile/{z}/{x}/{y}", produces = "application/x-protobuf")
|
||||
@Parameter(name = "geoCoordSys", description = "地理坐标系, WGS84/GCJ02")
|
||||
public ResponseEntity<byte[]> getTile(@PathVariable int z, @PathVariable int x, @PathVariable int y, String geoCoordSys){
|
||||
@@ -519,5 +549,24 @@ public class ChannelController {
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "为地图提供经过抽稀的标准mvt图层", security = @SecurityRequirement(name = JwtUtils.HEADER))
|
||||
@GetMapping(value = "/map/thin/tile/{z}/{x}/{y}", produces = "application/x-protobuf")
|
||||
@Parameter(name = "geoCoordSys", description = "地理坐标系, WGS84/GCJ02")
|
||||
@Parameter(name = "thinId", description = "抽稀结果ID")
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.genersoft.iot.vmp.gb28181.controller.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class DrawThinParam {
|
||||
private Map<Integer, Double> zoomParam;
|
||||
private Extent extent;
|
||||
|
||||
/**
|
||||
* 地理坐标系, WGS84/GCJ02, 用来标识 extent 参数的坐标系
|
||||
*/
|
||||
private String geoCoordSys;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.genersoft.iot.vmp.gb28181.controller.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class Extent {
|
||||
private Double minLng;
|
||||
private Double maxLng;
|
||||
private Double minLat;
|
||||
private Double maxLat;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -676,4 +677,20 @@ public interface CommonGBChannelMapper {
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryCameraChannelInBox")
|
||||
List<CommonGBChannel> queryCameraChannelInBox(@Param("minLon") double minLon, @Param("maxLon") double maxLon,
|
||||
@Param("minLat") double minLat, @Param("maxLat") double maxLat);
|
||||
|
||||
@Select("select " +
|
||||
"MAX(coalesce(gb_longitude, longitude)) as maxLng, " +
|
||||
"MIN(coalesce(gb_longitude, longitude)) as minLng, " +
|
||||
"MAX(coalesce(gb_latitude, latitude)) as maxLat, " +
|
||||
"MIN(coalesce(gb_latitude, latitude)) as minLat " +
|
||||
" from wvp_device_channel " +
|
||||
" where channel_type = 0")
|
||||
Extent queryExtent();
|
||||
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryAllWithPosition")
|
||||
List<CommonGBChannel> queryAllWithPosition();
|
||||
|
||||
@SelectProvider(type = ChannelProvider.class, method = "queryListInExtent")
|
||||
List<CommonGBChannel> queryListInExtent(Extent extent);
|
||||
|
||||
}
|
||||
|
||||
@@ -307,14 +307,14 @@ public interface GroupMapper {
|
||||
Group queryGroupByAliasAndBusinessGroup(@Param("alias") String alias, @Param("deviceId") String businessGroup);
|
||||
|
||||
|
||||
@Select(" <script>" +
|
||||
@Select("<script>" +
|
||||
" SELECT " +
|
||||
" ANY_VALUE(coalesce( wdc.gb_parent_id, wdc.parent_id)) as deviceId," +
|
||||
" COUNT(*) AS allCount," +
|
||||
" SUM(CASE WHEN coalesce( wdc.gb_status, wdc.status) = 'ON' THEN 1 ELSE 0 END) AS onlineCount" +
|
||||
" FROM " +
|
||||
" wvp_device_channel wdc " +
|
||||
" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce( wdc.gb_parent_id, wdc.parent_id) in " +
|
||||
" <foreach collection='groupList' item='item' open='(' separator=',' close=')' > #{item.deviceId}</foreach>" +
|
||||
" GROUP BY coalesce(wdc.gb_parent_id, wdc.parent_id)" +
|
||||
|
||||
@@ -583,7 +583,7 @@ public class ChannelProvider {
|
||||
public String queryListForSy(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) AND coalesce(wdc.gb_parent_id, wdc.parent_id) = #{groupDeviceId}");
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) AND coalesce(wdc.gb_parent_id, wdc.parent_id) = #{groupDeviceId}");
|
||||
if (params.get("online") != null && (Boolean)params.get("online")) {
|
||||
sqlBuild.append(" AND coalesce(wdc.gb_status, wdc.status) = 'ON'");
|
||||
}
|
||||
@@ -598,7 +598,7 @@ public class ChannelProvider {
|
||||
public String queryListWithChildForSy(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) ");
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) ");
|
||||
|
||||
|
||||
List<CameraGroup> groupList = (List<CameraGroup>)params.get("groupList");
|
||||
@@ -672,7 +672,7 @@ public class ChannelProvider {
|
||||
public String queryListInBox(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in (");
|
||||
|
||||
sqlBuild.append(" ");
|
||||
@@ -700,7 +700,7 @@ public class ChannelProvider {
|
||||
public String queryListInCircleForMysql(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in (");
|
||||
|
||||
sqlBuild.append(" ");
|
||||
@@ -729,7 +729,7 @@ public class ChannelProvider {
|
||||
public String queryListInCircleForKingBase(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in (");
|
||||
|
||||
sqlBuild.append(" ");
|
||||
@@ -758,7 +758,7 @@ public class ChannelProvider {
|
||||
public String queryListInPolygonForMysql(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in (");
|
||||
|
||||
sqlBuild.append(" ");
|
||||
@@ -796,7 +796,7 @@ public class ChannelProvider {
|
||||
public String queryListInPolygonForKingBase(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL_FOR_CAMERA_DEVICE);
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 && wdc.gb_ptz_type != 99)) " +
|
||||
sqlBuild.append(" where wdc.channel_type = 0 AND wdc.data_type != 2 AND (wdc.gb_ptz_type is null || ( wdc.gb_ptz_type != 98 AND wdc.gb_ptz_type != 99)) " +
|
||||
" AND coalesce(wdc.gb_parent_id, wdc.parent_id) in (");
|
||||
|
||||
sqlBuild.append(" ");
|
||||
@@ -901,4 +901,22 @@ public class ChannelProvider {
|
||||
public String queryCameraChannelById(Map<String, Object> params ){
|
||||
return BASE_SQL_FOR_CAMERA_DEVICE + " where wdc.id = #{gbId}";
|
||||
}
|
||||
|
||||
public String queryAllWithPosition(Map<String, Object> params ){
|
||||
return BASE_SQL + " where channel_type = 0 " +
|
||||
" AND coalesce(gb_longitude, longitude) > 0" +
|
||||
" AND coalesce(gb_latitude, latitude) > 0";
|
||||
}
|
||||
|
||||
public String queryListInExtent(Map<String, Object> params ){
|
||||
StringBuilder sqlBuild = new StringBuilder();
|
||||
sqlBuild.append(BASE_SQL);
|
||||
sqlBuild.append(" where channel_type = 0 " +
|
||||
"AND coalesce(gb_longitude, longitude) > #{minLng} " +
|
||||
"AND coalesce(gb_longitude, longitude) <= #{maxLng} " +
|
||||
"AND coalesce(gb_latitude, latitude) > #{minLat} " +
|
||||
"AND coalesce(gb_latitude, latitude) <= #{maxLat}");
|
||||
return sqlBuild.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -8,6 +9,7 @@ import com.github.pagehelper.PageInfo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface IGbChannelService {
|
||||
|
||||
@@ -109,4 +111,7 @@ public interface IGbChannelService {
|
||||
|
||||
byte[] getTile(int z, int x, int y, String geoCoordSys);
|
||||
|
||||
String drawThin(Map<Integer, Double> zoomParam, Extent extent, String geoCoordSys);
|
||||
|
||||
DrawThinProcess thinProgress(String id);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package com.genersoft.iot.vmp.gb28181.service.impl;
|
||||
|
||||
import com.alibaba.excel.support.cglib.beans.BeanMap;
|
||||
import com.alibaba.excel.util.BeanMapUtils;
|
||||
import com.genersoft.iot.vmp.common.VideoManagerConstants;
|
||||
import com.genersoft.iot.vmp.common.enums.ChannelDataType;
|
||||
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;
|
||||
@@ -14,10 +17,12 @@ import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
|
||||
import com.genersoft.iot.vmp.gb28181.event.channel.ChannelEvent;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IGbChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.service.IPlatformChannelService;
|
||||
import com.genersoft.iot.vmp.gb28181.utils.VectorTileUtils;
|
||||
import com.genersoft.iot.vmp.service.bean.GPSMsgInfo;
|
||||
import com.genersoft.iot.vmp.streamPush.bean.StreamPush;
|
||||
import com.genersoft.iot.vmp.utils.Coordtransform;
|
||||
import com.genersoft.iot.vmp.utils.DateUtil;
|
||||
import com.genersoft.iot.vmp.utils.TileUtils;
|
||||
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
@@ -26,14 +31,16 @@ import no.ecc.vectortile.VectorTileEncoder;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.Point;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -57,6 +64,12 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
@Autowired
|
||||
private GroupMapper groupMapper;
|
||||
|
||||
@Autowired
|
||||
private DynamicTask dynamicTask;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<Object, Object> redisTemplate;
|
||||
|
||||
private final GeometryFactory geometryFactory = new GeometryFactory();
|
||||
|
||||
@Override
|
||||
@@ -157,6 +170,20 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
try {
|
||||
// 发送通知
|
||||
eventPublisher.channelEventPublishForUpdate(commonGBChannel, oldChannel);
|
||||
|
||||
if (commonGBChannel.getGbLongitude() != null && !Objects.equals(oldChannel.getGbLongitude(), commonGBChannel.getGbLongitude())
|
||||
&& commonGBChannel.getGbLatitude() != null && !Objects.equals(oldChannel.getGbLatitude(), commonGBChannel.getGbLatitude())) {
|
||||
MobilePosition mobilePosition = new MobilePosition();
|
||||
mobilePosition.setDeviceId(commonGBChannel.getGbDeviceId());
|
||||
mobilePosition.setChannelId(commonGBChannel.getGbId());
|
||||
mobilePosition.setDeviceName(commonGBChannel.getGbName());
|
||||
mobilePosition.setCreateTime(DateUtil.getNow());
|
||||
mobilePosition.setTime(DateUtil.getNow());
|
||||
mobilePosition.setLongitude(commonGBChannel.getGbLongitude());
|
||||
mobilePosition.setLatitude(commonGBChannel.getGbLatitude());
|
||||
eventPublisher.mobilePositionEventPublish(mobilePosition);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("[更新通道通知] 发送失败,{}", commonGBChannel.getGbDeviceId(), e);
|
||||
}
|
||||
@@ -906,13 +933,6 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
Point pointGeom = geometryFactory.createPoint(new Coordinate(px[0], px[1]));
|
||||
|
||||
BeanMap beanMap = BeanMapUtils.create(commonGBChannel);
|
||||
|
||||
// Map<String, Object> props = new HashMap<>();
|
||||
// props.put("id", commonGBChannel.getGbId());
|
||||
// props.put("name", commonGBChannel.getGbName());
|
||||
// props.put("deviceId", commonGBChannel.getGbDeviceId());
|
||||
// props.put("status", commonGBChannel.getGbStatus());
|
||||
|
||||
encoder.addFeature("points", beanMap, pointGeom);
|
||||
});
|
||||
}
|
||||
@@ -951,4 +971,148 @@ public class GbChannelServiceImpl implements IGbChannelService {
|
||||
|
||||
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;
|
||||
if (extent == null) {
|
||||
log.info("[抽稀] ID: {}, 未设置范围,从数据库读取摄像头的范围", id);
|
||||
extent = commonGBChannelMapper.queryExtent();
|
||||
channelList = commonGBChannelMapper.queryAllWithPosition();
|
||||
|
||||
}else {
|
||||
if (geoCoordSys != null && geoCoordSys.equalsIgnoreCase("GCJ02")) {
|
||||
Double[] maxPosition = Coordtransform.GCJ02ToWGS84(extent.getMaxLng(), extent.getMaxLng());
|
||||
Double[] minPosition = Coordtransform.GCJ02ToWGS84(extent.getMinLng(), extent.getMinLat());
|
||||
|
||||
extent.setMaxLng(maxPosition[0]);
|
||||
extent.setMaxLat(maxPosition[1]);
|
||||
|
||||
extent.setMinLng(minPosition[0]);
|
||||
extent.setMinLat(minPosition[1]);
|
||||
}
|
||||
// 获取数据源
|
||||
channelList = commonGBChannelMapper.queryListInExtent(extent);
|
||||
}
|
||||
Assert.isTrue(!channelList.isEmpty(), "通道数据为空");
|
||||
|
||||
log.info("[开始抽稀] ID: {}, 范围,[{}, {}, {}, {}]", id, extent.getMinLng(), extent.getMinLat(), extent.getMaxLng(), extent.getMaxLat());
|
||||
|
||||
Extent finalExtent = extent;
|
||||
// 记录进度
|
||||
saveProcess(id, 0, "开始抽稀");
|
||||
dynamicTask.startDelay(id, () -> {
|
||||
try {
|
||||
// 存储每层的抽稀结果, key为层级(zoom),value为摄像头数组
|
||||
Map<Integer, Collection<CommonGBChannel>> zoomCameraMap = new HashMap<>();
|
||||
|
||||
// 冗余一份已经处理过的摄像头的数据, 避免多次循环获取
|
||||
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;
|
||||
}
|
||||
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) {
|
||||
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 -> new Double((double) (v + 0.5 / zoomParam.size())));
|
||||
saveProcess(id, process.get(), "抽稀图层: " + zoom);
|
||||
}
|
||||
// 抽稀完成, 对数据生成mvt矢量瓦片
|
||||
zoomCameraMap.forEach((key, value) -> {
|
||||
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);
|
||||
}
|
||||
});
|
||||
log.info("[抽稀完成] ID:{}, 耗时: {}ms", id, (System.currentTimeMillis() - time));
|
||||
saveProcess(id, 1, "抽稀完成");
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
|
||||
}, 1);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private void saveTile(String id, int z, int x, int y, String geoCoordSys, Collection<CommonGBChannel> commonGBChannelList ) {
|
||||
VectorTileEncoder encoder = new VectorTileEncoder();
|
||||
commonGBChannelList.forEach(commonGBChannel -> {
|
||||
double lon = commonGBChannel.getGbLongitude();
|
||||
double lat = commonGBChannel.getGbLatitude();
|
||||
if (geoCoordSys != null && geoCoordSys.equalsIgnoreCase("GCJ02")) {
|
||||
Double[] minPosition = Coordtransform.WGS84ToGCJ02(lon, lat);
|
||||
lon = minPosition[0];
|
||||
lat = minPosition[1];
|
||||
}
|
||||
|
||||
// 将 lon/lat 转为瓦片内像素坐标(0..256)
|
||||
double[] px = 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);
|
||||
}
|
||||
|
||||
private void saveProcess(String id, double process, String msg) {
|
||||
String key = VideoManagerConstants.DRAW_THIN_PROCESS_PREFIX + id;
|
||||
Duration duration = Duration.ofMinutes(30);
|
||||
redisTemplate.opsForValue().set(key, new DrawThinProcess(process, msg), duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrawThinProcess thinProgress(String id) {
|
||||
String key = VideoManagerConstants.DRAW_THIN_PROCESS_PREFIX + id;
|
||||
return (DrawThinProcess) redisTemplate.opsForValue().get(key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.genersoft.iot.vmp.gb28181.utils;
|
||||
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public enum VectorTileUtils {
|
||||
INSTANCE;
|
||||
|
||||
private Map<String, byte[]> vectorTileMap = new ConcurrentReferenceHashMap<>();
|
||||
|
||||
public void addVectorTile(String key, byte[] content) {
|
||||
vectorTileMap.put(key, content);
|
||||
}
|
||||
|
||||
public byte[] getVectorTile(String key) {
|
||||
return vectorTileMap.get(key);
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,6 @@ public class GpsUtil {
|
||||
|
||||
|
||||
public static BaiduPoint Wgs84ToBd09(String xx, String yy) {
|
||||
|
||||
|
||||
double lng = Double.parseDouble(xx);
|
||||
double lat = Double.parseDouble(yy);
|
||||
Double[] gcj02 = Coordtransform.WGS84ToGCJ02(lng, lat);
|
||||
|
||||
122
src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java
Normal file
122
src/main/java/com/genersoft/iot/vmp/utils/TileUtils.java
Normal file
@@ -0,0 +1,122 @@
|
||||
package com.genersoft.iot.vmp.utils;
|
||||
|
||||
import com.genersoft.iot.vmp.gb28181.controller.bean.Extent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TileUtils {
|
||||
private static final double MAX_LATITUDE = 85.05112878;
|
||||
|
||||
/**
|
||||
* 根据坐标获取指定层级的x y 值
|
||||
*
|
||||
* @param lon 经度
|
||||
* @param lat 纬度
|
||||
* @param z 层级
|
||||
* @return double[2] = {xTileFloat, yTileFloat}
|
||||
*/
|
||||
public static double[] lonLatToTileXY(double lon, double lat, int z) {
|
||||
double n = Math.pow(2.0, z);
|
||||
double x = (lon + 180.0) / 360.0 * n;
|
||||
|
||||
// clamp latitude to WebMercator bounds
|
||||
double latClamped = Math.max(Math.min(lat, MAX_LATITUDE), -MAX_LATITUDE);
|
||||
double latRad = Math.toRadians(latClamped);
|
||||
double y = (1.0 - (Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI)) / 2.0 * n;
|
||||
|
||||
return new double[]{x, y};
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据坐标范围获取指定层级的x y 范围
|
||||
*
|
||||
* @param bbox array length 4: {minLon, minLat, maxLon, maxLat}
|
||||
* @param z zoom level
|
||||
* @return TileRange object with xMin,xMax,yMin,yMax,z
|
||||
*/
|
||||
public static TileRange bboxToTileRange(double[] bbox, int z) {
|
||||
double minLon = bbox[0], minLat = bbox[1], maxLon = bbox[2], maxLat = bbox[3];
|
||||
|
||||
// If bbox crosses antimeridian (minLon > maxLon), caller should split into two bboxes.
|
||||
if (minLon > maxLon) {
|
||||
throw new IllegalArgumentException("bbox crosses antimeridian; split it before calling bboxToTileRange.");
|
||||
}
|
||||
|
||||
double[] tMin = lonLatToTileXY(minLon, maxLat, z); // top-left (use maxLat)
|
||||
double[] tMax = lonLatToTileXY(maxLon, minLat, z); // bottom-right (use minLat)
|
||||
|
||||
int xMin = (int) Math.floor(Math.min(tMin[0], tMax[0]));
|
||||
int xMax = (int) Math.floor(Math.max(tMin[0], tMax[0]));
|
||||
int yMin = (int) Math.floor(Math.min(tMin[1], tMax[1]));
|
||||
int yMax = (int) Math.floor(Math.max(tMin[1], tMax[1]));
|
||||
|
||||
int maxIndex = ((int) Math.pow(2, z)) - 1;
|
||||
xMin = clamp(xMin, 0, maxIndex);
|
||||
xMax = clamp(xMax, 0, maxIndex);
|
||||
yMin = clamp(yMin, 0, maxIndex);
|
||||
yMax = clamp(yMax, 0, maxIndex);
|
||||
|
||||
return new TileRange(xMin, xMax, yMin, yMax, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* If bbox crosses antimeridian (minLon > maxLon), split into two bboxes:
|
||||
* [minLon, minLat, 180, maxLat] and [-180, minLat, maxLon, maxLat]
|
||||
*
|
||||
* @param bbox input bbox array length 4
|
||||
* @return list of 1 or 2 bboxes (each double[4])
|
||||
*/
|
||||
public static List<double[]> splitAntimeridian(double[] bbox) {
|
||||
double minLon = bbox[0], minLat = bbox[1], maxLon = bbox[2], maxLat = bbox[3];
|
||||
List<double[]> parts = new ArrayList<>();
|
||||
if (minLon <= maxLon) {
|
||||
parts.add(new double[]{minLon, minLat, maxLon, maxLat});
|
||||
} else {
|
||||
parts.add(new double[]{minLon, minLat, 180.0, maxLat});
|
||||
parts.add(new double[]{-180.0, minLat, maxLon, maxLat});
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
private static int clamp(int v, int a, int b) {
|
||||
return Math.max(a, Math.min(b, v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of tile coordinates (x,y,z) covering bbox at zoom z.
|
||||
* Be careful: the number of tiles can be large for high zooms & big bbox.
|
||||
*
|
||||
*/
|
||||
public static List<TileCoord> tilesForBoxAtZoom(Extent extent, int z) {
|
||||
List<TileCoord> tiles = new ArrayList<>();
|
||||
List<double[]> parts = splitAntimeridian(new double[]{extent.getMinLng(), extent.getMinLat(),
|
||||
extent.getMaxLng(), extent.getMaxLat()});
|
||||
for (double[] part : parts) {
|
||||
TileRange range = bboxToTileRange(part, z);
|
||||
for (int x = range.xMin; x <= range.xMax; x++) {
|
||||
for (int y = range.yMin; y <= range.yMax; y++) {
|
||||
tiles.add(new TileCoord(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
// Simple helper classes
|
||||
public static class TileRange {
|
||||
public final int xMin, xMax, yMin, yMax, z;
|
||||
public TileRange(int xMin, int xMax, int yMin, int yMax, int z) {
|
||||
this.xMin = xMin; this.xMax = xMax; this.yMin = yMin; this.yMax = yMax; this.z = z;
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" + "z=" + z + ", x=" + x + ", y=" + y + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user