rain
2024-06-03 28f661933ffdeb1f65ecee52f8f4b7a3b373da9b
完善图斑功能,完善水印,增加图片的角度信息
12 files modified
1 files added
289 ■■■■ changed files
pom.xml 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/controller/FileController.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/model/MediaFileMarkEntity.java 3 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/IFileService.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/media/util/ImgUtil.java 120 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/controller/PatchesController.java 7 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/model/Param/PatchesParam.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/service/impl/GetPatchesServiceImpl.java 32 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/patches/xml/mode/share/action/utils/ActionUtils.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/model/entity/TbFjEntity.java 2 ●●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/service/impl/TbFjServiceImpl.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/dji/sample/territory/utils/WaterMarkUtil.java 59 ●●●●● patch | view | raw | blame | history
pom.xml
@@ -289,6 +289,17 @@
            <version>1.70</version>
        </dependency>
        <!--图片信息获取-->
        <dependency>
            <groupId>com.drewnoakes</groupId>
            <artifactId>metadata-extractor</artifactId>
            <version>2.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.adobe.xmp</groupId>
            <artifactId>xmpcore</artifactId>
            <version>5.1.3</version>
        </dependency>
    </dependencies>
src/main/java/com/dji/sample/media/controller/FileController.java
@@ -66,6 +66,11 @@
    }
    @GetMapping("/getMediaInfo")
    public ResponseResult mediaInfo(@RequestParam String fileId) {
        return ResponseResult.success(fileService.mediaInfo(fileId));
    }
    @GetMapping("/{workspace_id}/files/{job_id}")
    public ResponseResult findFilesList(@PathVariable(name = "workspace_id") String workspaceId, @PathVariable(name = "job_id") String jobId) {
        return ResponseResult.success(fileService.listMediaFileEntity(workspaceId, jobId));
src/main/java/com/dji/sample/media/model/MediaFileMarkEntity.java
@@ -78,5 +78,8 @@
    @TableField("isadd")
    private Integer isadd;
    @TableField(value = "drone_data", typeHandler = FastjsonTypeHandler.class)
    private Object dronedata;
}
src/main/java/com/dji/sample/media/service/IFileService.java
@@ -5,6 +5,7 @@
import com.dji.sample.media.model.MediaFileDTO;
import com.dji.sample.media.model.MediaFileEntity;
import com.dji.sample.media.model.MediaFileQueryParam;
import com.drew.imaging.ImageProcessingException;
import java.awt.*;
import java.io.IOException;
@@ -36,8 +37,9 @@
     */
    Integer saveFile(String workspaceId, FileUploadDTO file);
    Integer saveMarkFile(String workspaceId, FileUploadDTO file) throws IOException, FontFormatException;
    Integer saveMarkFile(String workspaceId, FileUploadDTO file) throws IOException, FontFormatException, ImageProcessingException;
    Object mediaInfo(String fileId);
    /**
     * Query information about all files in this workspace based on the workspace id.
     *
src/main/java/com/dji/sample/media/service/impl/FileServiceImpl.java
@@ -15,9 +15,11 @@
import com.dji.sample.media.dao.IMarkMapper;
import com.dji.sample.media.model.*;
import com.dji.sample.media.service.IFileService;
import com.dji.sample.media.util.ImgUtil;
import com.dji.sample.territory.service.impl.TbFjServiceImpl;
import com.dji.sample.territory.utils.VideoZipUtil;
import com.dji.sample.territory.utils.WaterMarkUtil;
import com.drew.imaging.ImageProcessingException;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import org.springframework.beans.factory.annotation.Autowired;
@@ -82,7 +84,7 @@
        return mapper.insert(fileEntity);
    }
    public Integer saveMarkFile(String workspaceId, FileUploadDTO file) throws IOException, FontFormatException {
    public Integer saveMarkFile(String workspaceId, FileUploadDTO file) throws IOException, FontFormatException, ImageProcessingException {
        File file2 = null;
        MediaFileMarkEntity mediaFileMarkEntity = this.fileUploadConvertToMarkEntity(file);
        String url = "http://dev.jxpskj.com:9000/cloud-bucket" + file.getObjectKey();
@@ -92,9 +94,9 @@
        if (!endsWith) {
            file2 = new File(WaterMarkUtil.addWatermark(file1, timestamp, file.getMetadata().getShootPosition().getLat(),
                    file.getMetadata().getShootPosition().getLng(), file.getMetadata().getGimbalYawDegree()).toURI());
        } else {
            file2 = VideoZipUtil.compressVideo(file1, 1600000, 128000, 1280, 720);
        }
        Object data = ImgUtil.getInfo(file1);
        mediaFileMarkEntity.setDronedata(data);
        mediaFileMarkEntity.setWorkspaceId(workspaceId);
        mediaFileMarkEntity.setFileId(UUID.randomUUID().toString());
        mediaFileMarkEntity.setObjectKey("/mark" + file.getPath() + "/" + file.getName());
@@ -110,6 +112,13 @@
    }
    @Override
    public Object mediaInfo(String fileId) {
        MediaFileMarkEntity entity = markMapper.selectOne(new LambdaQueryWrapper<MediaFileMarkEntity>()
                .eq(MediaFileMarkEntity::getFileId, fileId));
        return entity.getDronedata();
    }
    @Override
    public List<MediaFileDTO> getAllFilesByWorkspaceId(String workspaceId) {
        return mapper.selectList(new LambdaQueryWrapper<MediaFileEntity>()
                        .eq(MediaFileEntity::getWorkspaceId, workspaceId))
src/main/java/com/dji/sample/media/util/ImgUtil.java
New file
@@ -0,0 +1,120 @@
package com.dji.sample.media.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dji.sample.territory.service.impl.TbFjServiceImpl;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.xmp.XmpDirectory;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class ImgUtil {
    public static Map<String, String> readPicExifInfo(File file)
            throws ImageProcessingException, IOException {
        Map<String, String> map = new HashMap<>();
        Metadata metadata = ImageMetadataReader.readMetadata(file);
        for (Directory directory : metadata.getDirectories()) {
            for (Tag tag : directory.getTags()) {
                // 将Tag名称和描述添加到Map中
                map.put(tag.getTagName(), tag.getDescription());
            }
            if (directory.hasErrors()) {
                for (String error : directory.getErrors()) {
                    log.error(error);
                }
            }
        }
        return map;
    }
    public static String getXmp(File file) {
        try {
            Metadata metadata = JpegMetadataReader.readMetadata(file);
            for (Directory directory : metadata.getDirectories()) {
                if (directory instanceof XmpDirectory) {
                    XmpDirectory xmpDirectory = (XmpDirectory) directory;
                    Map<String, String> xmpProperties = xmpDirectory.getXmpProperties();
                    return JSON.toJSONString(xmpProperties);
                }
            }
        } catch (Exception e) {
            log.error(String.valueOf(e));
        }
        return null;
    }
    public static String[] getImageParam(File file) {
        try {
            String[] param = new String[5];
            Metadata metadata = JpegMetadataReader.readMetadata(file);
            for (Directory directory : metadata.getDirectories()) {
                //获取图片的Exif拍摄时间
                if (directory instanceof ExifIFD0Directory) {
                    ExifIFD0Directory exifIFD0Directory = (ExifIFD0Directory) directory;
                    exifIFD0Directory.getName();
                    param[4] = JSON.toJSONString(exifIFD0Directory.getDate(306));
                }
                if (directory instanceof XmpDirectory) {
                    XmpDirectory xmpDirectory = (XmpDirectory) directory;
                    Map<String, String> xmpProperties = xmpDirectory.getXmpProperties();
                    System.out.println(xmpProperties);
                    String degree = xmpProperties.get("drone-dji:GimbalYawDegree");
                    String lat = xmpProperties.get("drone-dji:GpsLatitude");
                    String lon = xmpProperties.get("drone-dji:GpsLongtitude");
                    String relativeAltitude = xmpProperties.get("drone-dji:RelativeAltitude");
                    param[0] = degree;
                    param[1] = lat;
                    param[2] = lon;
                    param[3] = relativeAltitude;
                    System.out.println(Arrays.toString(param));
                }
            }
            return param;
        } catch (Exception e) {
            log.error(String.valueOf(e));
            return null;
        }
    }
    public static Object getInfo(File file) throws ImageProcessingException, IOException {
        String str =getXmp(file);
        // 解析 JSON 字符串为 JSON 对象
        JSONObject jsonObject = JSON.parseObject(str);
        // 获取指定的键值对
        String gimbalYawDegree = jsonObject.getString("drone-dji:GimbalYawDegree");
        String flightYawDegree = jsonObject.getString("drone-dji:FlightYawDegree");
        String flightPitchDegree = jsonObject.getString("drone-dji:FlightPitchDegree");
        String gimbalPitchDegree = jsonObject.getString("drone-dji:GimbalPitchDegree");
        String mm=readPicExifInfo(file).get("Focal Length 35");
        // 构造新的 JSON 对象
        JSONObject newJsonObject = new JSONObject();
        newJsonObject.put("GimbalYawDegree", gimbalYawDegree);
        newJsonObject.put("FlightYawDegree", flightYawDegree);
        newJsonObject.put("FlightPitchDegree", flightPitchDegree);
        newJsonObject.put("GimbalPitchDegree", gimbalPitchDegree);
        newJsonObject.put("FocalLength", mm);
        return newJsonObject;
    }
    public static void main(String[] args) throws ImageProcessingException, IOException {
        File file1 = TbFjServiceImpl.downloadFile("http://dev.jxpskj.com:9000/cloud-bucket/05708dc5-4273-4b12-ad54-e05c89d6c3d4/DJI_202406031131_010_05708dc5-4273-4b12-ad54-e05c89d6c3d4/DJI_20240603113305_0001_W_航点1.jpeg");
        System.out.println(getInfo(file1));
    }
}
src/main/java/com/dji/sample/patches/controller/PatchesController.java
@@ -55,7 +55,9 @@
    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 = "dkbh", defaultValue = "") String dkbh,
                                                        @RequestParam(name = "isPlan", required = false) Integer isPlan,
                                                        @RequestParam(name = "xzqdm", defaultValue = "") String xzqdm
    ) {
        //调用service分页查询
        PatchesParam param = PatchesParam.builder()
@@ -63,8 +65,9 @@
                .workspaceId(workspaceId)
                .pageSize(pageSize)
                .dkbh(dkbh)
                .isPlan(isPlan)
                .xzqdm(xzqdm)
                .build();
        PaginationData<LotInfo> data = getPatchesService.limitGet(param);
        return ResponseResult.success(data);
    }
src/main/java/com/dji/sample/patches/model/Param/PatchesParam.java
@@ -19,4 +19,8 @@
    private String workspaceId;
    private String dkbh;
    private Integer isPlan;
    private String xzqdm;
}
src/main/java/com/dji/sample/patches/service/impl/GetPatchesServiceImpl.java
@@ -12,6 +12,8 @@
import com.dji.sample.patches.service.GetPatchesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,14 +32,28 @@
     */
    @Override
    public PaginationData<LotInfo> limitGet(PatchesParam param) {
        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()));
        List<LotInfo> records = page.getRecords()
                .stream()
                .collect(Collectors.toList());
        return new PaginationData<LotInfo>(records, new Pagination(page));
        if (param.getIsPlan() != 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())
                            .eq(LotInfo::getIsPlan, param.getIsPlan()));
            List<LotInfo> records = page.getRecords()
                    .stream()
                    .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::getXzqdm, param.getXzqdm()));
            List<LotInfo> records = page.getRecords()
                    .stream()
                    .collect(Collectors.toList());
            return new PaginationData<LotInfo>(records, new Pagination(page));
        }
    }
    @Override
src/main/java/com/dji/sample/patches/xml/mode/share/action/utils/ActionUtils.java
@@ -20,7 +20,8 @@
    public static TakePhotoParam setTakePhoto(String dkbh) {
        TakePhotoParam takePhotoParam = new TakePhotoParam();
        takePhotoParam.setFileSuffix("航点" + dkbh);
        takePhotoParam.setPayloadLensIndex("wide,ir");
//        takePhotoParam.setPayloadLensIndex("wide,ir");
        takePhotoParam.setPayloadLensIndex("wide");
        takePhotoParam.setUseGlobalPayloadLensIndex(1);
        takePhotoParam.setPayloadPositionIndex(0);
        return takePhotoParam;
src/main/java/com/dji/sample/territory/model/entity/TbFjEntity.java
@@ -62,4 +62,6 @@
    private String pstz;
    private Integer psjj;
}
src/main/java/com/dji/sample/territory/service/impl/TbFjServiceImpl.java
@@ -1,8 +1,10 @@
package com.dji.sample.territory.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.dji.sample.media.model.MediaFileEntity;
import com.dji.sample.media.util.ImgUtil;
import com.dji.sample.patches.config.pojo.PatchesConfigPojo;
import com.dji.sample.patches.model.entity.LotInfo;
import com.dji.sample.territory.dao.ITbFjMapper;
@@ -72,7 +74,7 @@
    public void insertOneData(MediaFileEntity mediaFile, LotInfo lotInfo) throws Exception {
        TbFjEntity tbFj = dbConvertToEntity(mediaFile, lotInfo);
        if (tbFj.getFjysgd()==3000){
        if (tbFj.getFjysgd() == 3000) {
            tbFjMapper.insert(tbFj);
        }
    }
@@ -106,7 +108,7 @@
//        double x = Double.parseDouble(((formatCoordinate(coordinates[1]))));
//        double y = Double.parseDouble(((formatCoordinate(coordinates[0]))));
        //本地数据库没有的数据在FJ表非空用0
        int psjd = 0;
        double psjd = WaterMarkUtil.convertAngle(gimbalYawDegree);
        int pshgj = 0;
        String psry = "中图智绘无人机";
        String zsdm = "23C8CCC61E3042FBA6A658F319337B1A";
@@ -120,6 +122,16 @@
        String head = "http://dev.jxpskj.com:9000/cloud-bucket";
        String url = head + key;
        File file = downloadFile(url);
//        JSONObject jsonObjects = JSONObject.parseObject(ImgUtil.getInfo(file));
        JSONObject jsons = (JSONObject) (ImgUtil.getInfo(file));
        String str = jsons.toJSONString();
        String newstr = str.replace("+", "");
        JSONObject json = JSON.parseObject(newstr);
        Double pitch = json.getDouble("GimbalPitchDegree");
        String focal = json.getString("FocalLength");
        String numberPart = focal.replaceAll("[^\\d]", "");
        int psjj = Integer.parseInt(numberPart);
        json.getString("GimbalYawDegree");
        String fjhxz = HashUtil.SM3Hash(file);
        BufferedImage image = ImageIO.read(file);
        int width = image.getWidth();
@@ -127,7 +139,7 @@
        //对应图片和视频文件进行不同处理
        boolean endsWith = key.endsWith(".mp4");
        if (!endsWith) {
            file1 = WaterMarkUtil.addWatermark(file,  sj, lat, lng, gimbalYawDegree);
            file1 = WaterMarkUtil.addWatermark(file, sj, lat, lng, gimbalYawDegree);
            FJ = fileToByteArray(file1);
        } else {
            file1 = VideoZipUtil.compressVideo(file, 800000, 128000, 1280, 720);
@@ -151,11 +163,12 @@
                    .Latitude(truncateToSevenDecimalPlaces(lat))
                    .longitude(truncateToSevenDecimalPlaces(lng))
                    .fj(FJ)
                    .psjj(psjj)
                    .fjmc(fjmc)
                    .fjlx(fjlx)
                    .psfyj(gimbalYawDegree)
                    .psfyj(pitch)
                    .pssj(pssj)
                    .psjd(psjd)
                    .psjd((int) psjd)
                    .fjhxz(fjhxz)
                    .pshgj(pshgj)
                    .zsdm(zsdm)
@@ -235,6 +248,7 @@
        BigDecimal bd = new BigDecimal(value).setScale(2, RoundingMode.DOWN);
        return bd.doubleValue();
    }
    public static Double truncateToSevenDecimalPlaces(Double value) {
        if (value == null) {
            return null;  // 处理null值情况
src/main/java/com/dji/sample/territory/utils/WaterMarkUtil.java
@@ -12,6 +12,7 @@
import java.util.Date;
import static com.dji.sample.territory.utils.ImgZipUtil.compressImageAndGetFile;
@Component
public class WaterMarkUtil {
    /**
@@ -24,9 +25,9 @@
     * @return 添加水印并压缩后的图片文件。
     * @throws IOException 如果读取或保存图片失败。
     */
    public static File addWatermark(File file,  Long pssj, Double lat, Double lng, Double angles) throws IOException, FontFormatException {
        String angel=angle(angles);
    public static File addWatermark(File file, Long pssj, Double lat, Double lng, Double angles) throws IOException, FontFormatException {
        double anglses = convertAngle(angles);
        String angel = angle(anglses);
        Long timestamp = pssj; // 例如:Unix 时间戳(以秒为单位)
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String sd = sdf.format(new Date(Long.parseLong(String.valueOf(timestamp))));
@@ -52,7 +53,7 @@
        // 设置水印位置(左居中)
        int x1 = 600; // 左边距离图片左边缘的距离
        int y1 = (originalImage.getHeight() - textHeights) -800; // 垂直居中
        int y1 = (originalImage.getHeight() - textHeights) - 800; // 垂直居中
        // 旋转35度角
        double angle = Math.toRadians(50);
        AffineTransform at = AffineTransform.getRotateInstance(angle, x1, y1);
@@ -61,9 +62,9 @@
        g2d.drawString(watermarkText, x1, y1);
        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);
        String extraInfo = String.format("Lon:%.7f Lat:%.7f\n%s %s", lng, lat, angel, sd);
        File fontFile = new File("/tmp/jave/MiSans-Normal.ttf"); // 替换为你的字体文件路径
        File fontFile = new File("src/main/java/com/dji/sample/territory/utils/MiSans-Normal.ttf"); // 替换为你的字体文件路径
        Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(36.00F);
        // 注册字体
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
@@ -81,18 +82,18 @@
        int y2 = originalImage.getHeight() - textHeight - 30; // 最底部位置 y 坐标
        int rectX = originalImage.getWidth() - textWidth  +490;
        int rectY = originalImage.getHeight() - textHeight -15;
        int rectX = originalImage.getWidth() - textWidth + 490;
        int rectY = originalImage.getHeight() - textHeight - 15;
        // 绘制半透明黑色背景矩形
        Color semiTransparentColor = new Color(0, 0, 0, 156); // 0-255 的 alpha 值,0 表示完全透明,255 表示完全不透明
        g2d.setColor(semiTransparentColor);
        g2d.fillRect(rectX, rectY, textWidth, textHeight-6);
        g2d.fillRect(rectX, rectY, textWidth, textHeight - 6);
        int rectXs = originalImage.getWidth() - textWidth +420 ;
        int rectYs = originalImage.getHeight() - textHeight  -62;
        int rectXs = originalImage.getWidth() - textWidth + 420;
        int rectYs = originalImage.getHeight() - textHeight - 62;
        // 绘制半透明黑色背景矩形
        g2d.setColor(semiTransparentColor);
        g2d.fillRect(rectXs, rectYs, textWidth, textHeight-10);
        g2d.fillRect(rectXs, rectYs, textWidth, textHeight - 10);
//            int rectXw = originalImage.getWidth() - textWidth  +1510 ;
//            int rectYw = originalImage.getHeight() - textHeight -124;
@@ -128,39 +129,55 @@
        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 > -45 && angle < 0) {
        if (angle >= 315 && angle < 360) {
            return "北偏西";
        }
        if (angle > -90 && angle < -45) {
        if (angle > 270 && angle < 315) {
            return "西偏北";
        }
        if (angle > -135 && angle < -90) {
        if (angle >= 225 && angle < 270) {
            return "西偏南";
        }
        if (angle > -180 && angle < -135) {
        if (angle > 180 && angle < 225) {
            return "南偏西";
        }
        if (angle == 0) {
        if (angle == 0 || angle == 360) {
            return "正北";
        }
        if (angle == 90) {
            return "正东";
        }
        if (angle == 180|| angle == -180) {
        if (angle == 180) {
            return "正南";
        }
        if (angle == -90) {
        if (angle == 270) {
            return "正西";
        }
        return "未知";
    }
    public static double convertAngle(double angle) {
        // 处理负数角度,将其转换为正数
        if (angle < 0) {
            angle += 360;
        }
        return angle;
    }
    public static void main(String[] args) {
        // 测试不同的输入角度
        double[] testAngles = {25};
        for (double angle : testAngles) {
            System.out.printf("输入角度: %.1f, 转换后的方位角: %.1f\n", angle, convertAngle(angle));
        }
    }
}