package com.dji.sample.map.service.impl; import com.dji.sample.common.error.CommonErrorEnum; import com.dji.sample.component.oss.model.OssConfiguration; import com.dji.sample.component.oss.service.impl.OssServiceContext; import com.dji.sample.component.websocket.model.BizCodeEnum; import com.dji.sample.component.websocket.service.IWebSocketMessageService; import com.dji.sample.component.websocketWmm.WebSocketServerPlayBack; import com.dji.sample.manage.model.dto.DeviceDTO; import com.dji.sample.manage.model.dto.TelemetryDTO; import com.dji.sample.manage.service.IDeviceRedisService; import com.dji.sample.map.model.dto.*; import com.dji.sample.map.model.entity.FlightAreaFileEntity; import com.dji.sample.map.model.enums.FlightAreaGeometryTypeEnum; import com.dji.sample.map.model.enums.FlightAreaOpertaionEnum; import com.dji.sample.map.model.param.PostFlightAreaParam; import com.dji.sample.map.model.param.PutFlightAreaParam; import com.dji.sample.map.service.*; import com.dji.sample.wayline.dao.IWaylineFileMapper; import com.dji.sample.wayline.model.dto.WaylineFileDTO; import com.dji.sample.wayline.model.entity.WaylineFileEntity; import com.dji.sdk.cloudapi.flightarea.*; import com.dji.sdk.cloudapi.flightarea.api.AbstractFlightAreaService; import com.dji.sdk.cloudapi.map.*; import com.dji.sdk.common.HttpResultResponse; import com.dji.sdk.common.SDKManager; import com.dji.sdk.mqtt.MqttReply; import com.dji.sdk.mqtt.events.TopicEventsRequest; import com.dji.sdk.mqtt.events.TopicEventsResponse; import com.dji.sdk.mqtt.requests.TopicRequestsRequest; import com.dji.sdk.mqtt.requests.TopicRequestsResponse; import com.dji.sdk.mqtt.services.ServicesReplyData; import com.dji.sdk.mqtt.services.TopicServicesResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.MessageHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; //import org.springframework.util.DigestUtils; import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import org.apache.commons.codec.digest.DigestUtils; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; import static com.dji.sample.wayline.model.dto.KmzFileProperties.WAYLINE_FILE_SUFFIX; /** * @author sean * @version 1.9 * @date 2023/11/22 */ @Service @Transactional @Slf4j public class FlightAreaServiceImpl extends AbstractFlightAreaService implements IFlightAreaService { @Autowired private IFlightAreaPropertyServices flightAreaPropertyServices; @Autowired private IGroupService groupService; @Autowired private IWorkspaceElementService workspaceElementService; @Autowired private IDeviceRedisService deviceRedisService; @Autowired private SDKFlightAreaService1 abstractFlightAreaService; @Autowired private IFlightAreaFileService flightAreaFileService; @Autowired private IDeviceFlightAreaService deviceFlightAreaService; @Autowired private OssServiceContext ossServiceContext; @Autowired private IWebSocketMessageService webSocketMessageService; @Autowired private ObjectMapper objectMapper; @Autowired private OssServiceContext ossService; @Autowired private IFlightAreaFileService mapper; @Override public Optional getFlightAreaByAreaId(String areaId) { List properties = flightAreaPropertyServices.getPropertyByElementIds(List.of(areaId)); if (CollectionUtils.isEmpty(properties)) { return Optional.empty(); } Optional elementOpt = workspaceElementService.getElementByElementId(areaId); return elementOpt.map(groupElementDTO -> this.element2FlightArea(objectMapper.convertValue(groupElementDTO, MapGroupElement.class), properties.get(0))); } @Override public List getFlightAreaList(String workspaceId) { Optional> elementsOpt = groupService.getCustomGroupByWorkspaceId(workspaceId) .map(group -> workspaceElementService.getAllGroupsByWorkspaceId(workspaceId, group.getId(), null)); if (elementsOpt.isEmpty()) { return Collections.emptyList(); } List elements = elementsOpt.get().get(0).getElements(); Map propertyMap = flightAreaPropertyServices.getPropertyByElementIds( elements.stream().map(MapGroupElement::getId).collect(Collectors.toList())) .stream().collect(Collectors.toMap(FlightAreaPropertyDTO::getElementId, property -> property)); return elements.stream().map(element -> this.element2FlightArea(element, propertyMap.get(element.getId()))).collect(Collectors.toList()); } @Override public void createFlightArea(String workspaceId, String username, PostFlightAreaParam param) { Optional groupOpt = groupService.getCustomGroupByWorkspaceId(workspaceId); if (groupOpt.isEmpty()) { throw new RuntimeException("The custom flight area group does not exist, please create it first."); } ElementGeometryType geometry = param.getContent().getGeometry(); String type = geometry.getType(); ElementResourceTypeEnum typeEnum; FlightAreaPropertyDTO property = new FlightAreaPropertyDTO(); property.setType(param.getType()); property.setStatus(true); property.setElementId(param.getId()); if (GeometrySubTypeEnum.CIRCLE.getSubType().equals(type)) { ElementCircleGeometry circleGeometry = (ElementCircleGeometry) geometry; property.setRadius(circleGeometry.getRadius()); property.setSubType(GeometrySubTypeEnum.find(type)); typeEnum = ElementResourceTypeEnum.find(ElementResourceTypeEnum.POINT.getTypeName()); param.getContent().setGeometry(new ElementPointGeometry().setCoordinates(circleGeometry.getCoordinates())); } else { typeEnum = ElementResourceTypeEnum.find(type); } HttpResultResponse response = workspaceElementService.saveElement(workspaceId, groupOpt.get().getId(), new CreateMapElementRequest().setId(param.getId()).setName(param.getName()) .setResource(new ElementResource() .setUsername(username) .setType(typeEnum) .setContent(new ElementContent() .setGeometry(param.getContent().getGeometry()) .setProperties(param.getContent().getProperties()))), false); if (HttpResultResponse.CODE_FAILED == response.getCode()) { throw new RuntimeException(response.getMessage()); } int id = flightAreaPropertyServices.saveProperty(property); if (id <= 0) { throw new RuntimeException("Failed to save flight area properties."); } flightAreaFileService.setNonLatestByWorkspaceId(workspaceId); webSocketMessageService.sendBatch(workspaceId, BizCodeEnum.FLIGHT_AREAS_UPDATE.getCode(), FlightAreaWs.builder() .operation(FlightAreaOpertaionEnum.ADD) .areaId(param.getId()) .username(username) .type(param.getType()) .name(param.getName()) .updateTime(System.currentTimeMillis()) .createTime(System.currentTimeMillis()) .content(FlightAreaContent.builder().geometry(geometry).properties(param.getContent().getProperties()).build()) .status(true) .build()); } @Override public void syncFlightArea(String workspaceId, List deviceSns) { for (String deviceSn : deviceSns) { Optional deviceOpt = deviceRedisService.getDeviceOnline(deviceSn); if (deviceOpt.isEmpty() || !workspaceId.equals(deviceOpt.get().getWorkspaceId())) { throw new RuntimeException(CommonErrorEnum.ILLEGAL_ARGUMENT.getMessage()); } TopicServicesResponse response = abstractFlightAreaService.flightAreasUpdate(SDKManager.getDeviceSDK(deviceSn)); if (!response.getData().getResult().isSuccess()) { throw new RuntimeException(response.getData().getResult().getMessage()); } } packageFlightArea(workspaceId); } @Override public FlightAreaFileDTO packageFlightArea(String workspaceId) { List flightAreas = getFlightAreaList(workspaceId); return flightAreaFileService.packageFlightAreaFile(workspaceId, flightAreas); } @Override public void deleteFlightArea(String workspaceId, String areaId) { HttpResultResponse response = workspaceElementService.deleteElement(workspaceId, areaId, false); if (HttpResultResponse.CODE_SUCCESS != response.getCode()) { throw new RuntimeException(response.getMessage()); } int id = flightAreaPropertyServices.deleteProperty(areaId); if (id <= 0) { throw new RuntimeException("Failed to delete the flight area property."); } flightAreaFileService.setNonLatestByWorkspaceId(workspaceId); webSocketMessageService.sendBatch(workspaceId, BizCodeEnum.FLIGHT_AREAS_UPDATE.getCode(), FlightAreaWs.builder() .operation(FlightAreaOpertaionEnum.DELETE) .areaId(areaId) .build()); } @Override public void updateFlightArea(String workspaceId, String areaId, PutFlightAreaParam param) { Float radius = null; if (Objects.nonNull(param.getContent())) { ElementGeometryType geometry = param.getContent().getGeometry(); FlightAreaGeometryTypeEnum typeEnum = FlightAreaGeometryTypeEnum.find(geometry.getType()); if (FlightAreaGeometryTypeEnum.CIRCLE == typeEnum) { radius = ((ElementCircleGeometry) geometry).getRadius(); geometry = new ElementPointGeometry().setCoordinates(((ElementCircleGeometry) geometry).getCoordinates()); } Optional elementOpt = workspaceElementService.getElementByElementId(areaId); if (elementOpt.isEmpty() || !elementOpt.get().getResource().getType().getTypeName().equals(geometry.getType())) { throw new RuntimeException(CommonErrorEnum.ILLEGAL_ARGUMENT.getMessage()); } workspaceElementService.updateElement(workspaceId, areaId, new UpdateMapElementRequest() .setName(param.getName()) .setContent(new ElementContent() .setProperties(param.getContent().getProperties()) .setGeometry(geometry)), null, false); } int id = flightAreaPropertyServices.updateProperty(FlightAreaPropertyUpdate.builder() .elementId(areaId).status(param.getStatus()).radius(radius).build()); if (id <= 0) { throw new RuntimeException("Failed to update flight area properties."); } flightAreaFileService.setNonLatestByWorkspaceId(workspaceId); Optional areaOpt = getFlightAreaByAreaId(areaId); areaOpt.ifPresent(area -> webSocketMessageService.sendBatch(workspaceId, BizCodeEnum.FLIGHT_AREAS_UPDATE.getCode(), FlightAreaWs.builder() .operation(FlightAreaOpertaionEnum.UPDATE) .areaId(areaId) .name(area.getName()) .content(area.getContent()) .status(area.getStatus()) .type(area.getType()) .username(area.getUsername()) .createTime(area.getCreateTime()) .updateTime(area.getUpdateTime()) .build())); } @Override public TopicEventsResponse flightAreasSyncProgress(TopicEventsRequest request, MessageHeaders headers) { Optional deviceOpt = deviceRedisService.getDeviceOnline(request.getGateway()); if (deviceOpt.isEmpty()) { log.warn("method: flight_areas_sync_progress. Dock is offline."); return null; } FlightAreasSyncProgress data = request.getData(); String workspaceId = deviceOpt.get().getWorkspaceId(); DeviceFlightAreaDTO deviceFlightArea = DeviceFlightAreaDTO.builder() .deviceSn(request.getGateway()) .workspaceId(workspaceId) .syncStatus(data.getStatus()) .syncCode(data.getReason()) .build(); deviceFlightAreaService.updateOrSaveDeviceFile(deviceFlightArea); webSocketMessageService.sendBatch(workspaceId, BizCodeEnum.FLIGHT_AREAS_SYNC_PROGRESS.getCode(), FlightAreaNotifyDTO.builder() .sn(request.getGateway()) .result(data.getReason().getReason()) .message(data.getReason().getMsg()) .status(data.getStatus().getStatus()) .build()); WebSocketServerPlayBack.sendInfo(request.toString()); return new TopicEventsResponse<>(); } @Override public TopicEventsResponse flightAreasDroneLocation(TopicEventsRequest request, MessageHeaders headers) { Optional deviceOpt = deviceRedisService.getDeviceOnline(request.getGateway()); if (deviceOpt.isEmpty()) { log.warn("method: flight_areas_drone_location. Dock is offline."); return null; } if (request.getData().getDroneLocations().isEmpty()) { return new TopicEventsResponse<>(); } webSocketMessageService.sendBatch(deviceOpt.get().getWorkspaceId(), BizCodeEnum.FLIGHT_AREAS_DRONE_LOCATION.getCode(), TelemetryDTO.builder().sn(deviceOpt.get().getChildDeviceSn()).host(request.getData()).build()); WebSocketServerPlayBack.sendInfo(request.toString()); return new TopicEventsResponse<>(); } @Override public TopicRequestsResponse> flightAreasGet(TopicRequestsRequest request, MessageHeaders headers) { Optional deviceOpt = deviceRedisService.getDeviceOnline(request.getGateway()); if (deviceOpt.isEmpty()) { return null; } DeviceDTO device = deviceOpt.get(); // Optional flightAreaOpt = deviceFlightAreaService.getDeviceFlightAreaByDevice(device.getWorkspaceId(), device.getDeviceSn()); Optional flightAreaOpt = flightAreaFileService.getFirstFlightArea(); // Optional fileOpt = Optional.empty(); // if (flightAreaOpt.isPresent()) { // fileOpt = flightAreaFileService.getFlightAreaFileByFileId(flightAreaOpt.get().getFileId()); // } else { // throw new RuntimeException("there is no flightArea file."); // } // FlightAreaFileDTO file = fileOpt.orElse(null); // if (flightAreaOpt.isEmpty() || fileOpt.isEmpty()) { // file = packageFlightArea(device.getWorkspaceId()); // } return new TopicRequestsResponse>().setData( MqttReply.success(new FlightAreasGetResponse().setFiles( flightAreaOpt == null ? null : List.of(new FlightAreaGetFile() .setName(flightAreaOpt.get().getName()) .setSize(flightAreaOpt.get().getSize()) .setChecksum(flightAreaOpt.get().getSign()) .setUrl(ossServiceContext.getObjectUrl(OssConfiguration.bucket, flightAreaOpt.get().getObjectKey()).toString().replace(OssConfiguration.endpoint,OssConfiguration.endpointSpare)) )))); } private FlightAreaDTO element2FlightArea(MapGroupElement element, FlightAreaPropertyDTO property) { FlightAreaDTO.FlightAreaDTOBuilder builder = FlightAreaDTO.builder() .areaId(element.getId()) .name(element.getName()) .createTime(element.getCreateTime()) .updateTime(element.getUpdateTime()) .username(element.getResource().getUsername()) .content(FlightAreaContent.builder() .properties(element.getResource().getContent().getProperties()) .geometry(element.getResource().getContent().getGeometry()) .build()); if (Objects.isNull(property)) { return builder.build(); } FlightAreaDTO flightArea = builder.type(property.getType()).status(property.getStatus()).build(); if (GeometrySubTypeEnum.CIRCLE == property.getSubType()) { ElementPointGeometry point = (ElementPointGeometry) flightArea.getContent().getGeometry(); flightArea.getContent().setGeometry(new ElementCircleGeometry() .setRadius(property.getRadius()) .setCoordinates(new Double[]{point.getCoordinates()[0], point.getCoordinates()[1]})); } return flightArea; } @Override public void importJsonFile(MultipartFile file, String workspaceId, String creator) { Optional flightAreaFileOpt = validJsonFile(file); if (flightAreaFileOpt.isEmpty()) { throw new RuntimeException("The file format is incorrect."); } try { FlightAreaFileDTO flightAreaFile = flightAreaFileOpt.get(); // flightAreaFile.set.setUsername(creator); ossService.putObject(OssConfiguration.bucket, flightAreaFile.getObjectKey(), file.getInputStream()); this.saveFlightAreaFile(flightAreaFile); } catch (IOException e) { e.printStackTrace(); } } public Integer saveFlightAreaFile(FlightAreaFileDTO metadata) { metadata.setFileId(UUID.randomUUID().toString()); int insertId = mapper.saveFlightAreaFile(metadata); return insertId; } private Optional validJsonFile(MultipartFile file) { String filename = file.getOriginalFilename(); try(ByteArrayOutputStream os = new ByteArrayOutputStream(64);) { InputStream fileInputStream = file.getInputStream(); byte[] bytes = StreamUtils.copyToByteArray(fileInputStream); return Optional.of(FlightAreaFileDTO.builder() .objectKey(OssConfiguration.objectDirPrefix + File.separator + filename) .name(filename.substring(0, filename.lastIndexOf("json"))) .sign(DigestUtils.sha256Hex(fileInputStream)) .size(fileInputStream.read(bytes)) .build()); } catch (IOException e) { e.printStackTrace(); } return Optional.empty(); } /** * Convert the received wayline object into a database entity object. * @param file * @return */ private FlightAreaFileEntity dtoConvertToEntity(FlightAreaFileDTO file) { FlightAreaFileEntity.FlightAreaFileEntityBuilder builder = FlightAreaFileEntity.builder(); if (file != null) { builder.name(file.getName()) .objectKey(file.getObjectKey()) .sign(file.getSign()) .build(); } return builder.build(); } }