rain
2024-06-11 6c321434d8f0bc78ae86640653eccbf4cfc2c1d3
SM3加密,媒体文件存储
10 files modified
341 ■■■■■ changed files
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java 66 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/controller/PatchesController.java 61 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/service/GetPatchesService.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/service/impl/GetPatchesServiceImpl.java 85 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/service/impl/ShpToDataSourceServiceImpl.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/controller/TbDkjbxxController.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/utils/SM2SignUtil.java 48 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/utils/Sm3Util.java 44 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/utils/WaterMarkUtil.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
@@ -90,48 +90,52 @@
    }
    public void saveMarkFile(String workspaceId, FileUploadDTO file) throws IOException, FontFormatException, ImageProcessingException {
        String name = TimerUtil.getDkbh(file.getName());
        List<LotInfo> lotInfos = patchesMapper.selectList(new LambdaQueryWrapper<LotInfo>().eq(LotInfo::getDkbh, name));
        if (!lotInfos.isEmpty()) {
            LambdaUpdateWrapper<LotInfo> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(LotInfo::getDkbh, name)
                    .eq(LotInfo::getInvestigate, 0)
                    .set(LotInfo::getInvestigate, 1);
            patchesMapper.update(null, updateWrapper);
        }
        boolean endsWith = file.getObjectKey().endsWith(".mp4");
        if (endsWith) {
            MediaFileMarkEntity mediaFileMarkEntity = this.fileUploadConvertToMarkEntity(file);
            mediaFileMarkEntity.setWorkspaceId(workspaceId);
            mediaFileMarkEntity.setFileId(UUID.randomUUID().toString());
            markMapper.insert(mediaFileMarkEntity);
        } else {
            boolean contains = file.getName().contains("~");
            if (contains) {
                String name = TimerUtil.getDkbh(file.getName());
                List<LotInfo> lotInfos = patchesMapper.selectList(new LambdaQueryWrapper<LotInfo>().eq(LotInfo::getDkbh, name));
                if (!lotInfos.isEmpty()) {
                    LambdaUpdateWrapper<LotInfo> updateWrapper = new LambdaUpdateWrapper<>();
                    updateWrapper.eq(LotInfo::getDkbh, name)
                            .eq(LotInfo::getInvestigate, 0)
                            .set(LotInfo::getInvestigate, 1);
                    patchesMapper.update(null, updateWrapper);
                }
            }
            MediaFileMarkEntity mediaFileMarkEntity = this.fileUploadConvertToMarkEntity(file);
            String url = "http://dev.jxpskj.com:9000/cloud-bucket" + file.getObjectKey();
            File file1 = TbFjServiceImpl.downloadFile(url);
            long timestamp = convertToTimestamp(file.getMetadata().getCreatedTime());
            File file2 = new File(WaterMarkUtil.addWatermark(file1, timestamp, file.getMetadata().getShootPosition().getLat(),
                    file.getMetadata().getShootPosition().getLng(), file.getMetadata().getGimbalYawDegree()).toURI());
            Object data = ImgUtil.getInfo(file1);
            mediaFileMarkEntity.setDronedata(data);
            mediaFileMarkEntity.setWorkspaceId(workspaceId);
            mediaFileMarkEntity.setFileId(UUID.randomUUID().toString());
            mediaFileMarkEntity.setObjectKey("/mark" + file.getPath() + "/" + file.getName());
            mediaFileMarkEntity.setFileName("mark" + file.getName());
            mediaFileMarkEntity.setFilePath("mark" + file.getPath());
            String endpoint = "http://dev.jxpskj.com:9000";
            String accessKey = "pskj";
            String secretKey = "pskj@2021";
            String bucketName = "cloud-bucket";
            String objectName = mediaFileMarkEntity.getObjectKey(); // 例如 "folder/file.txt"
            uploadFile(endpoint, accessKey, secretKey, bucketName, objectName, file2);
            markMapper.insert(mediaFileMarkEntity);
        }
        MediaFileMarkEntity mediaFileMarkEntity = this.fileUploadConvertToMarkEntity(file);
        String url = "http://dev.jxpskj.com:9000/cloud-bucket" + file.getObjectKey();
        File file1 = TbFjServiceImpl.downloadFile(url);
        long timestamp = convertToTimestamp(file.getMetadata().getCreatedTime());
        File file2 = new File(WaterMarkUtil.addWatermark(file1, timestamp, file.getMetadata().getShootPosition().getLat(),
                file.getMetadata().getShootPosition().getLng(), file.getMetadata().getGimbalYawDegree()).toURI());
        Object data = ImgUtil.getInfo(file1);
        mediaFileMarkEntity.setDronedata(data);
        mediaFileMarkEntity.setWorkspaceId(workspaceId);
        mediaFileMarkEntity.setFileId(UUID.randomUUID().toString());
        mediaFileMarkEntity.setObjectKey("/mark" + file.getPath() + "/" + file.getName());
        mediaFileMarkEntity.setFileName("mark" + file.getName());
        mediaFileMarkEntity.setFilePath("mark" + file.getPath());
        String endpoint = "http://dev.jxpskj.com:9000";
        String accessKey = "pskj";
        String secretKey = "pskj@2021";
        String bucketName = "cloud-bucket";
        String objectName = mediaFileMarkEntity.getObjectKey(); // 例如 "folder/file.txt"
        uploadFile(endpoint, accessKey, secretKey, bucketName, objectName, file2);
        markMapper.insert(mediaFileMarkEntity);
    }
    @Override
    public Object mediaInfo(String filename) {
        String name= "mark"+filename;
        String name = "mark" + filename;
        MediaFileMarkEntity entity = markMapper.selectOne(new LambdaQueryWrapper<MediaFileMarkEntity>()
                .eq(MediaFileMarkEntity::getFileName, name));
src/main/java/com/dji/sample/media/service/impl/MediaServiceImpl.java
@@ -226,7 +226,6 @@
        String objectKey = callback.getFile().getObjectKey();
        callback.getFile().setPath(objectKey.substring(objectKey.indexOf("/") + 1, objectKey.lastIndexOf("/")));
        try {
            System.out.println(callback.getFile().toString());
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.execute(() -> {
                try {
src/main/java/com/dji/sample/patches/controller/PatchesController.java
@@ -21,6 +21,7 @@
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URL;
import java.sql.SQLException;
import java.util.HashMap;
@@ -56,11 +57,10 @@
    public ResponseResult<PaginationData<LotInfo>> page(@RequestParam Integer page,
                                                        @RequestParam(name = "page_size", defaultValue = "10") Integer pageSize,
                                                        @RequestParam String workspaceId,
                                                        @RequestParam(name = "dkbh", defaultValue = "") String dkbh,
                                                        @RequestParam(name = "isPlan", required = false) Integer isPlan,
                                                        @RequestParam(name = "xzqdm", defaultValue = "") String xzqdm,
                                                        @RequestParam(name = "bsm", defaultValue = "") String bsm,
                                                        @RequestParam(name = "xmc", defaultValue = "") String xmc,
                                                        @RequestParam(name = "dkbh", required = false) String dkbh,
                                                        @RequestParam(name = "xzqdm", required = false) String xzqdm,
                                                        @RequestParam(name = "bsm", required = false) String bsm,
                                                        @RequestParam(name = "xmc", required = false) String xmc,
                                                        @RequestParam(name = "investigate", required = false) Integer investigate
    ) {
        //调用service分页查询
@@ -69,7 +69,6 @@
                .workspaceId(workspaceId)
                .pageSize(pageSize)
                .dkbh(dkbh)
                .isPlan(isPlan)
                .xzqdm(xzqdm)
                .bsm(bsm)
                .xmc(xmc)
@@ -86,9 +85,17 @@
     * @return 返回操作结果,如果操作成功,则返回一个成功的响应结果。
     */
    @DeleteMapping("/deletePatches")
    public ResponseResult del() {
        getPatchesService.delPatches();
        return ResponseResult.success();
    public ResponseResult del(String workspaceId) {
        int count=getPatchesService.delPatches(workspaceId);
        return ResponseResult.success("删除的图斑数量是"+count);
    }
    @DeleteMapping("/deleteOne")
    public ResponseResult deleteOne(int id){
        int count= getPatchesService.deleteOne(id);
        if (count!=0){
            return ResponseResult.success("图斑删除成功id为"+id);
        }
        return ResponseResult.error("图斑删除失败");
    }
    @GetMapping("/getXzq")
@@ -101,16 +108,48 @@
        return ResponseResult.success(getPatchesService.getPatchesFromId(patchesId));
    }
    @PostMapping("/patchesToWayline")
    public ResponseResult patchesToWayline(List<LotInfo> list,
                                           String waylineName,
                                           String workspaceId,
                                           HttpServletRequest request) throws IOException {
        CustomClaim customClaim = (CustomClaim)request.getAttribute(TOKEN_CLAIM);
        String creator = customClaim.getUsername();
        MultipartFile file=timerUtil.getFile(waylineName,list);
        waylineFileService.importKmzFileBack(file, workspaceId, creator);
        WaylineFileEntity entity = waylineFileService.selectByName(waylineName);
        Map<String, String> infoMap = new HashMap<>();
        URL url = null;
        try {
            url = waylineFileService.getObjectUrl(workspaceId, entity.getWaylineId());
            infoMap.put("url", String.valueOf(MinioUrlUtils.getUrl(url)));
            infoMap.put("waylineId", entity.getWaylineId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return ResponseResult.success(infoMap);
    }
    @GetMapping("/useMyTask")
    public ResponseResult useMyTask() throws Exception {
        try {
            timerUtil.myTask();
//            timerUtil.myTask2();
//            timerUtil.myTask();
            timerUtil.myTask2();
            return ResponseResult.success();
        } catch (Exception e) {
            throw new RuntimeException("db存储发送出现异常");
        }
    }
    @GetMapping ("/tests")
    public ResponseResult use()  {
        try {
            TimerUtil.sendPostWithFileAndParameter("src/main/resources/tmp/20240607/103145_635148ea-0ddb-4b23-945c-8a67abd813c9.db",
                    "635148ea-0ddb-4b23-945c-8a67abd813c9");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return ResponseResult.success();
    }
    /**
     * 根据图斑的地块编号获取相对应音视频
src/main/java/com/dji/sample/patches/service/GetPatchesService.java
@@ -14,7 +14,9 @@
     * @return 返回一个包含查询结果和分页信息的PaginationData对象。
     */
    PaginationData<LotInfo> limitGet(PatchesParam param);
    void delPatches();
    int delPatches(String workspaceId);
    int deleteOne(int id);
    /**
     * 根据条件获取照片的分页数据
     *
src/main/java/com/dji/sample/patches/service/impl/GetPatchesServiceImpl.java
@@ -1,6 +1,7 @@
package com.dji.sample.patches.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.model.Pagination;
import com.dji.sample.common.model.PaginationData;
@@ -16,6 +17,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
@@ -33,47 +35,60 @@
     */
    @Override
    public PaginationData<LotInfo> limitGet(PatchesParam param) {
        if (param.getInvestigate() != null) {
            Page<LotInfo> page = mapper.selectPage(new Page<LotInfo>(param.getPage(), param.getPageSize()),
                    new LambdaQueryWrapper<LotInfo>()
                            .eq(LotInfo::getWorkspaceId, param.getWorkspaceId())
                            .like(LotInfo::getDkbh, param.getDkbh())
                            .like(LotInfo::getXzqdm, param.getXzqdm())
                            .like(LotInfo::getBsm,param.getBsm())
                            .like(LotInfo::getXmc,param.getXmc())
                            .eq(LotInfo::getInvestigate, param.getInvestigate()));
            List<LotInfo> records = page.getRecords()
                    .stream()
                    .peek(lotInfo -> {
//                        // 修改 xzqdm 字段的值
//                        lotInfo.setXzqdm(DistrictCodeUtils.codeToName(lotInfo.getXzqdm()));
                    })
                    .collect(Collectors.toList());
            return new PaginationData<LotInfo>(records, new Pagination(page));
        } else {
            Page<LotInfo> page = mapper.selectPage(new Page<LotInfo>(param.getPage(), param.getPageSize()),
                    new LambdaQueryWrapper<LotInfo>()
                            .eq(LotInfo::getWorkspaceId, param.getWorkspaceId())
                            .like(LotInfo::getDkbh, param.getDkbh())
                            .like(LotInfo::getBsm,param.getBsm())
                            .like(LotInfo::getXmc,param.getXmc())
                            .like(LotInfo::getXzqdm, param.getXzqdm()));
            List<LotInfo> records = page.getRecords()
                    .stream()
                    .peek(lotInfo -> {
//                        // 修改 xzqdm 字段的值
//                        lotInfo.setXzqdm(DistrictCodeUtils.codeToName(lotInfo.getXzqdm()));
                    })
                    .collect(Collectors.toList());
            return new PaginationData<LotInfo>(records, new Pagination(page));
        // 创建分页对象
        Page<LotInfo> page = new Page<>(param.getPage(), param.getPageSize());
        // 创建查询条件对象
        LambdaQueryWrapper<LotInfo> queryWrapper = new LambdaQueryWrapper<>();
        // 添加查询条件
        queryWrapper.eq(LotInfo::getWorkspaceId, param.getWorkspaceId());
        if (param.getDkbh() != null && !param.getDkbh().isEmpty()) {
            queryWrapper.like(LotInfo::getDkbh, param.getDkbh());
        }
        if (param.getInvestigate() != null) {
            queryWrapper.eq(LotInfo::getInvestigate, param.getInvestigate());
        }
        if (param.getDkbh() != null && !param.getDkbh().isEmpty()) {
            queryWrapper.like(LotInfo::getDkbh, param.getDkbh());
        }
        if (param.getXzqdm() != null && !param.getXzqdm().isEmpty()) {
            queryWrapper.like(LotInfo::getXzqdm, param.getXzqdm());
        }
        if (param.getXmc() != null && !param.getXmc().isEmpty()) {
            queryWrapper.like(LotInfo::getXmc, param.getXmc());
        }
        if (param.getBsm() != null && !param.getBsm().isEmpty()) {
            queryWrapper.like(LotInfo::getXmc, param.getBsm());
        }
        // 执行分页查询
        Page<LotInfo> resultPage = mapper.selectPage(page, queryWrapper);
        // 处理查询结果
        List<LotInfo> records = resultPage.getRecords()
                .stream()
                .peek(lotInfo -> {
                    // 修改 xzqdm 字段的值
                     lotInfo.setXmc(DistrictCodeUtils.codeToName(lotInfo.getXzqdm()));
                })
                .collect(Collectors.toList());
        // 返回分页数据
        return new PaginationData<>(records, new Pagination(resultPage));
    }
    @Override
    public int delPatches(String workspaceId) {
        return mapper.delete(new LambdaQueryWrapper<LotInfo>().eq(LotInfo::getWorkspaceId,workspaceId));
    }
    @Override
    public void delPatches() {
        mapper.delete(null);
    public int deleteOne(int id) {
        return mapper.deleteById(id);
    }
    /**
     * 根据条件获取照片的分页数据
     *
src/main/java/com/dji/sample/patches/service/impl/ShpToDataSourceServiceImpl.java
@@ -105,6 +105,7 @@
                .dkfw(file.getFShape())
                .workspaceId(workspaceId)
                .isPlan(0)
                .xmc(DistrictCodeUtils.nameToCode(file.getFXzqdm()))
                .investigate(0)
                .taskId(id)
                .taskName(name)
src/main/java/com/dji/sample/territory/controller/TbDkjbxxController.java
@@ -93,13 +93,12 @@
    }
    @PostMapping("/uploadUrl")
    public ResponseResult uploadUrl( @RequestBody UploadUrlParam param){
    public ResponseResult uploadUrl(@RequestBody UploadUrlParam param) {
        tbDkjbxxService.uploadUrl(param.getDbUrl(), param.getTaskName());
        String workspaceId="4a574db8-4ad3-48f7-9f16-3edbcd8056e1";
        String workspaceId = "4a574db8-4ad3-48f7-9f16-3edbcd8056e1";
        List<TbDkjbxxEntity> list = tbDkjbxxService.list();
        shpToDataSourceService.savaInMysql(list, workspaceId,param.getTaskId(),param.getTaskName());
        shpToDataSourceService.savaInMysql(list, workspaceId, param.getTaskId(), param.getTaskName());
        return ResponseResult.success(200);
    }
src/main/java/com/dji/sample/territory/utils/SM2SignUtil.java
@@ -109,32 +109,32 @@
        return hexString.toString().toUpperCase();
    }
    public static void main(String[] args) throws Exception {
        // 示例哈希值(这里只是一个占位符,你应该使用实际的哈希值)
        String sm3 = Sm3Util.calculateSM3Hash("FJHXZ,PSSJ,Longitude,Latitude,PSFYJ,PSJD,PSHGJ,PSRY,ZSDM");
        System.out.println("sm3 in hash: " + sm3);
        byte[] hash = sm3.getBytes(); // 注意:使用SM3或其他哈希算法来计算数据的哈希值
        // 加载私钥
        ECPrivateKeyParameters sm2PrivateKey = getSM2PrivateKey();
        // 使用SM2私钥对哈希值进行签名
        String signatureHex = signWithSM2(hash, sm2PrivateKey);
        System.out.println("Signature in hex (uppercase): " + signatureHex);
//        //验证
//        // 示例数据哈希(SM3哈希值)
//        byte[] dataHash = hash; // 注意:这里仅作为示例,你需要用实际的SM3哈希值
//    public static void main(String[] args) throws Exception {
//        // 示例哈希值(这里只是一个占位符,你应该使用实际的哈希值)
////        String sm3 = Sm3Util.calculateSM3Hash("FJHXZ,PSSJ,Longitude,Latitude,PSFYJ,PSJD,PSHGJ,PSRY,ZSDM");
////        System.out.println("sm3 in hash: " + sm3);
//        byte[] hash = sm3.getBytes(); // 注意:使用SM3或其他哈希算法来计算数据的哈希值
//
//        // 示例签名(字节数组)
//        byte[] signature = signatureHex.getBytes(); // 注意:这里仅作为示例,你需要用实际的签名值
//        // 加载私钥
//        ECPrivateKeyParameters sm2PrivateKey = getSM2PrivateKey();
//
//        // 验证签名
//        boolean isValid = verify(dataHash, signature);
//        // 使用SM2私钥对哈希值进行签名
//        String signatureHex = signWithSM2(hash, sm2PrivateKey);
////        System.out.println("Signature in hex (uppercase): " + signatureHex);
//
//        // 输出验证结果
//        System.out.println("Signature is valid: " + isValid);
    }
////        //验证
////        // 示例数据哈希(SM3哈希值)
////        byte[] dataHash = hash; // 注意:这里仅作为示例,你需要用实际的SM3哈希值
////
////        // 示例签名(字节数组)
////        byte[] signature = signatureHex.getBytes(); // 注意:这里仅作为示例,你需要用实际的签名值
////
////        // 验证签名
////        boolean isValid = verify(dataHash, signature);
////
////        // 输出验证结果
////        System.out.println("Signature is valid: " + isValid);
//
//    }
}
src/main/java/com/dji/sample/territory/utils/Sm3Util.java
@@ -19,23 +19,23 @@
     * @param input 输入的字符串
     * @return SM3哈希值的十六进制字符串表示
     */
    public static String calculateSM3Hash(String input) {
        // 转换为字节数组
        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
        // 初始化SM3摘要
        SM3Digest digest = new SM3Digest();
        // 更新摘要
        digest.update(inputBytes, 0, inputBytes.length);
        // 完成摘要计算并获取结果
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        // 将字节数组转换为十六进制大写字符串
        return Hex.toHexString(hash).toUpperCase();
    }
//    public static String calculateSM3Hash(String input) {
//        // 转换为字节数组
//        byte[] inputBytes = input.getBytes(StandardCharsets.UTF_8);
//
//        // 初始化SM3摘要
//        SM3Digest digest = new SM3Digest();
//
//        // 更新摘要
//        digest.update(inputBytes, 0, inputBytes.length);
//
//        // 完成摘要计算并获取结果
//        byte[] hash = new byte[digest.getDigestSize()];
//        digest.doFinal(hash, 0);
//
//        // 将字节数组转换为十六进制大写字符串
//        return Hex.toHexString(hash).toUpperCase();
//    }
    /**
     * 验证给定的哈希值是否与原始数据的SM3哈希值匹配
@@ -44,11 +44,11 @@
     * @param sm3HexString 预期的哈希值(十六进制字符串)
     * @return 如果匹配则返回true,否则返回false
     */
    public static boolean verify(String str, String sm3HexString) {
        String calculatedHash = calculateSM3Hash(str);
        return sm3HexString.equalsIgnoreCase(calculatedHash);
    }
//    public static boolean verify(String str, String sm3HexString) {
//
//        String calculatedHash = calculateSM3Hash(str);
//        return sm3HexString.equalsIgnoreCase(calculatedHash);
//    }
//    public static void main(String[] args) {
//        System.out.println(calculateSM3Hash("123"));
src/main/java/com/dji/sample/territory/utils/WaterMarkUtil.java
@@ -63,8 +63,8 @@
        alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f);
        g2d.setComposite(alphaComposite);
        String extraInfo = String.format("Lon:%.7f Lat:%.7f\n%s %s", lng, lat, angel, sd);
        File fontFile = new File("/usr/share/fonts/MiSans-Normal.ttf"); // 替换为你的字体文件路径
        File fontFile = new File("src/main/resources/MiSans-Normal.ttf"); // 替换为你的字体文件路径
//        File fontFile = new File("/usr/share/fonts/MiSans-Normal.ttf"); // 替换为你的字体文件路径
        Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(36.00F);
        // 注册字体
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
@@ -129,27 +129,39 @@
        if (angle > 0 && angle < 45) {
            return "北偏东";
        }
        if (angle >= 45 && angle < 90) {
        if (angle > 45 && angle < 90) {
            return "东偏北";
        }
        if (angle > 90 && angle < 135) {
            return "东偏南";
        }
        if (angle >= 135 && angle < 180) {
        if (angle > 135 && angle < 180) {
            return "南偏东";
        }
        if (angle >= 315 && angle < 360) {
        if (angle > 315 && angle < 360) {
            return "北偏西";
        }
        if (angle > 270 && angle < 315) {
            return "西偏北";
        }
        if (angle >= 225 && angle < 270) {
        if (angle > 225 && angle < 270) {
            return "西偏南";
        }
        if (angle > 180 && angle < 225) {
            return "南偏西";
        }
        if (angle == 45) {
            return "东北";
        }
        if (angle == 135) {
            return "东南";
        }
        if (angle == 225) {
            return "西南";
        }
        if (angle == 315) {
            return "西北";
        }
        if (angle == 0 || angle == 360) {
            return "正北";
        }