1 准备工作
1.1 导入依赖
<!-- 地址定位 -->
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.16.0</version>
</dependency>
1.2 照片图片
图片名和地址如上所述
2 具体实现
2.1 图片工具类ImageUtils.java
package com.wkf.workrecord.utils;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
/**
* 图片工具类
* @author wuKeFan
* @date 2022-08-30 15:16:07
*/
@Slf4j
public class ImageUtils {
public static void main(String[] args) throws Exception {
ArrayList<String> list = new ArrayList<>();
list.add("C:\\Users\\1\\Desktop\\rentou.jpg");
list.add("C:\\Users\\1\\Desktop\\rentou.jpg");
String path="D://1";
String resultPath = doVImageMerging2(list, path);
System.out.println(resultPath);
}
//2张图片水平合并
public static String doVImageMerging2(ArrayList<String> list, String pathFile) {
// 读取待合并的文件
BufferedImage bi1 = null;
BufferedImage bi2 = null;
// 调用mergeImage方法获得合并后的图像
BufferedImage destImg = null;
try {
bi2 = getBufferedImage(list.get(0));
bi1 = getBufferedImage(list.get(1));
} catch (IOException e) {
e.printStackTrace();
}
// 调用mergeImage方法获得合并后的图像
try {
if (bi1 != null && bi2 != null) {
destImg = mergeImage(bi1, bi2, false);//true为水平,false为垂直
}
} catch (IOException e) {
e.printStackTrace();
}
// 保存图像
String savePath = pathFile + "/";//文件存放路径
String fileName = createImageName() + ".jpg";//文件名称
String fileType = "jpg";//文件格式
boolean result = saveImage(destImg, savePath, fileName, fileType);
log.info("图片保存结果:{}", result ? "成功" : "失败");
return savePath + fileName;
}
public static BufferedImage getBufferedImage(String fileUrl) throws IOException {
File f = new File(fileUrl);
return ImageIO.read(f);
}
/**
* 待合并的两张图必须满足这样的前提,如果水平方向合并,则高度必须相等;如果是垂直方向合并,宽度必须相等。
* mergeImage方法不做判断,自己判断。
*
* @param img1
* 待合并的第一张图
* @param img2
* 带合并的第二张图
* @param isHorizontal
* 为true时表示水平方向合并,为false时表示垂直方向合并
* @return 返回合并后的BufferedImage对象
*/
public static BufferedImage mergeImage(BufferedImage img1, BufferedImage img2, boolean isHorizontal) throws IOException {
int w1 = img1.getWidth();
int h1 = img1.getHeight();
int w2 = img2.getWidth();
int h2 = img2.getHeight();
// 从图片中读取RGB
int[] ImageArrayOne = new int[w1 * h1];
ImageArrayOne = img1.getRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 逐行扫描图像中各个像素的RGB到数组中
int[] ImageArrayTwo = new int[w2 * h2];
ImageArrayTwo = img2.getRGB(0, 0, w2, h2, ImageArrayTwo, 0, w2);
// 生成新图片
BufferedImage DestImage;
// 水平方向合并
if (isHorizontal) {
// DestImage = new BufferedImage(w1+w2, h1, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d;
if (h1 >= h2) {
DestImage = new BufferedImage(w1 + w2, h1, BufferedImage.TYPE_INT_RGB);
} else {
DestImage = new BufferedImage(w2, h1, BufferedImage.TYPE_INT_RGB);//TYPE_INT_RGB
}
g2d = DestImage.createGraphics();
g2d.setPaint(Color.WHITE);
g2d.fillRect(0, 0, w1 + w2, h1);
g2d.dispose();
DestImage.setRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 设置上半部分或左半部分的RGB
DestImage.setRGB(w1, 0, w2, h2, ImageArrayTwo, 0, w2);
} else { // 垂直方向合并
Graphics2D g2d;
if (w1 >= w2) {
DestImage = new BufferedImage(w1, h1 + h2, BufferedImage.TYPE_INT_RGB);//TYPE_INT_RGB
} else {
DestImage = new BufferedImage(w2, h1 + h2, BufferedImage.TYPE_INT_RGB);//TYPE_INT_RGB
}
g2d = DestImage.createGraphics();
g2d.setPaint(Color.WHITE);
g2d.fillRect(0, 0, w1 + w2, h1 + h2);
g2d.dispose();
DestImage.setRGB(0, 0, w1, h1, ImageArrayOne, 0, w1); // 设置上半部分或左半部分的RGB
DestImage.setRGB(0, h1, w2, h2, ImageArrayTwo, 0, w2); // 设置下半部分的RGB
}
return DestImage;
}
//随机生成图片名称
public static String createImageName() {
// 创建 GUID 对象
UUID uuid = UUID.randomUUID();
// 得到对象产生的ID
String a = uuid.toString();
// 转换为大写
a = a.toUpperCase();
// 替换 -
a = a.replaceAll("-", "");
return a;
}
public static boolean saveImage(BufferedImage savedImg, String saveDir, String fileName, String format) {
boolean flag = false;
// 先检查保存的图片格式是否正确
String[] legalFormats = { "jpg", "JPG", "png", "PNG", "bmp", "BMP" };
int i;
for (i = 0; i < legalFormats.length; i++) {
if (format.equals(legalFormats[i])) {
break;
}
}
if (i == legalFormats.length) { // 图片格式不支持
System.out.println("不是保存所支持的图片格式!");
return false;
}
// 再检查文件后缀和保存的格式是否一致
String postfix = fileName.substring(fileName.lastIndexOf('.') + 1);
if (!postfix.equalsIgnoreCase(format)) {
System.out.println("待保存文件后缀和保存的格式不一致!");
return false;
}
String fileUrl = saveDir + fileName;
File file = new File(fileUrl);
try {
flag = ImageIO.write(savedImg, format, file);
} catch (IOException e) {
e.printStackTrace();
}
return flag;
}
/**
* 经纬度格式 转换为 度分秒格式 ,如果需要的话可以调用该方法进行转换
*
* @param point 坐标点
* @return 返回时分秒格式
*/
public static String pointToLatLong(String point) {
double du = Double.parseDouble(point.substring(0, point.indexOf("°")).trim());
double fen = Double.parseDouble(point.substring(point.indexOf("°") + 1, point.indexOf("'")).trim());
double miao = Double.parseDouble(point.substring(point.indexOf("'") + 1, point.indexOf("\"")).trim());
double duStr = du + fen / 60 + miao / 60 / 60;
return Double.toString(duStr);
}
/***
* 经纬度坐标格式转换(* °转十进制格式)
* @param gps gps坐标
*/
public static double latLng2Decimal(String gps) {
String a = gps.split("°")[0].replace(" ", "");
String b = gps.split("°")[1].split("'")[0].replace(" ", "");
String c = gps.split("°")[1].split("'")[1].replace(" ", "").replace("\"", "");
return Double.parseDouble(a) + Double.parseDouble(b) / 60 + Double.parseDouble(c) / 60 / 60;
}
}
2.2 地址处理工具类(主要逻辑代码都在这个类)ImageAddressUtils.java
package com.wkf.workrecord.tools.photo.address.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.Tag;
import com.wkf.workrecord.tools.baidu.map.BaiduMapConstants;
import com.wkf.workrecord.tools.photo.address.entity.AddressComponentResponse;
import com.wkf.workrecord.tools.photo.address.entity.ReverseGeocodingRequest;
import com.wkf.workrecord.utils.ImageUtils;
import com.wkf.workrecord.utils.ObjectUtils;
import com.wkf.workrecord.utils.fuying.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
/**
* 图片地址工具类
* @author wuKeFan
* @date 2023-02-24 15:44:31
*/
@Slf4j
@Component
public class ImageAddressUtils {
@Resource
private RestTemplate restTemplate;
public String getPhotoAddress(String fileName) throws ImageProcessingException, IOException {
File file = new File(fileName);
Metadata metadata = ImageMetadataReader.readMetadata(file);
//图片的所有目录信息
Iterable<Directory> directories = metadata.getDirectories();
for (Directory directory : directories) {
for (Tag tag : directory.getTags()) {
log.info("[{}] - {} = {}",
directory.getName(), tag.getTagName(), tag.getDescription());
}
if (directory.hasErrors()) {
for (String error : directory.getErrors()) {
log.info("ERROR: {}", error);
}
}
}
Double lat = null;
Double lng = null;
for (Directory directory : directories) {
for (Tag tag : directory.getTags()) {
String tagName = tag.getTagName(); //标签名
String desc = tag.getDescription(); //标签信息
switch (tagName) {
case "Image Height":
log.info("图片高度: " + desc);
break;
case "Image Width":
log.info("图片宽度: " + desc);
break;
case "Date/Time Original":
log.info("拍摄时间: " + desc);
break;
case "GPS Latitude":
log.info("纬度 : " + desc);
log.info("纬度(度分秒格式) : " + ImageUtils.pointToLatLong(desc));
lat = ImageUtils.latLng2Decimal(desc);
break;
case "GPS Longitude":
log.info("经度: " + desc);
log.info("经度(度分秒格式): " + ImageUtils.pointToLatLong(desc));
lng = ImageUtils.latLng2Decimal(desc);
break;
}
}
}
//经纬度转地主使用百度api
if (lat != null && lng != null) {
ReverseGeocodingRequest request = ReverseGeocodingRequest.builder()
.token(BaiduMapConstants.BAIDU_MAP_TOKEN).output(BaiduMapConstants.OUTPUT_TYPE_JSON)
.coordinateType(BaiduMapConstants.COORDINATE_TYPE_GPS).lat(lat).lng(lng)
.build();
Map<String, Object> map = ObjectUtils.objectToMap(request);
String result = restTemplate.getForObject(BaiduMapConstants.REVERSE_GEOCODING_URL, String.class, map);
JSONObject resultObj = JSONObject.parseObject(RegexUtils.underlineToHump(result));
if (resultObj != null && Objects.equals(resultObj.getInteger("status"), 0)) {
AddressComponentResponse response = resultObj.getJSONObject("result").getObject("addressComponent", AddressComponentResponse.class);
return "拍摄地点:" + response.getCountry() + " " + response.getProvince() + " " + response.getCity() + " " + response.getDirection() + " "
+ response.getStreet() + " " + resultObj.getJSONObject("result").get("formattedAddress") + " " + resultObj.getJSONObject("result").get("business");
}
}
return null;
}
}
2.3 实体类AddressComponentResponse.java和ReverseGeocodingRequest.java
package com.wkf.workrecord.tools.photo.address.entity;
import lombok.Data;
/**
* 地址构成实体类
* @author wuKeFan
* @date 2022-08-18 14:48:28
*/
@Data
public class AddressComponentResponse {
/**
* 国家
*/
private String country;
/**
* 国家编码
*/
private int countryCode;
/**
* 国家英文缩写(三位)
*/
private String countryCodeIso;
/**
* 国家英文缩写(两位)
*/
private String countryCodeIso2;
/**
* 省名
*/
private String province;
/**
* 城市名
*/
private String city;
/**
* 城市所在级别(仅国外有参考意义。
* 国外行政区划与中国有差异,城市对应的层级不一定为『city』。
* country、province、city、district、town分别对应0-4级,
* 若city_level=3,则district层级为该国家的city层级)
*/
private int cityLevel;
/**
* 区县名
*/
private String district;
/**
* 乡镇名,需设置extensions_town=true时才会返回
*/
private String town;
/**
* 乡镇id
*/
private String townCode;
/**
* 相对当前坐标点的距离,
* 当有门牌号的时候返回数据
*/
private String distance;
/**
* 和当前坐标点的方向
*/
private String direction;
/**
* 行政区划代码
*/
private String adcode;
/**
* 街道名(行政区划中的街道层级)
*/
private String street;
/**
* 街道门牌号
*/
private String streetNumber;
}
package com.wkf.workrecord.tools.photo.address.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 逆地理编码参数请求实体类
* @author wuKeFan
* @date 2022-08-18 14:29:15
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ReverseGeocodingRequest {
/**
* 用户申请注册的key,即ak
*/
private String token;
/**
* 输出格式为json或者xml
*/
private String output;
/**
* 坐标的类型,
* 目前支持的坐标类型包括:bd09ll(百度经纬度坐标)、
* bd09mc(百度米制坐标)、gcj02ll(国测局经纬度坐标,仅限中国)、wgs84ll( GPS经纬度)
*/
private String coordinateType;
/**
* 纬度
*/
private Double lat;
/**
* 经度
*/
private Double lng;
}
2.4 测试代码
package com.wkf.workrecord.study;
import com.drew.imaging.ImageProcessingException;
import com.wkf.workrecord.tools.photo.address.utils.ImageAddressUtils;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.io.IOException;
/**
* 公共测试类
* @author wuKeFan
* @date 2023-02-20 09:53:14
*/
@Slf4j
@SpringBootTest
public class PublicTest {
@Resource
private ImageAddressUtils imageAddressUtils;
@Test
public void photoAddressTest() throws ImageProcessingException, IOException {
System.out.println(imageAddressUtils.getPhotoAddress("C:\\Users\\1\\Desktop\\2023_02_18_20_21_IMG_0057.JPG"));
}
}
运行结果如图: