Java中对上传的图片获取位置信息并添加位置水印,整合SpringBoot+Idea+腾讯地图Api,详细篇.

    科技2024-05-22  86

    文章目录

    文章前言技术使用搭建演示DEMO处理图片如何改变文本的样式第三方地图API(腾讯地图)结尾

    文章前言

    最近工作中有个需求是获取上传的图片的位置信息,并添加位置和时间水印。因为之前也没做过,所以找了很多网上的各种文章,杂七杂八的都有,比较乱,国庆假期正好有时间,写了这篇整理文章,可能写的不好请谅解.

    技术使用

    本文使用Springboot来做演示项目,因为比较方便和快速,下面我把需要使用到的pom信息贴到代码块中:

    <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> <version>2.7.2</version> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> thymeleaf :用来展示页面和上传图片fastjson:解析Json数据格式metadata-extractor :主要用来获取图片中的EXIF信息commons-fileupload:可忽略,用来把file转换成MultipartFile的依赖

    搭建演示DEMO

    我们需要在templeates中创建2个页面进行显示和上传图片: upload.html

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Upload a file for</h1> <form method="POST" action="/upload" enctype="multipart/form-data"> <input type="file" name="file" /><br/><br/> <input type="submit" value="Submit" /> </form> </body> </html>

    展示结果页面: result.html:

    <!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Extracted Content:</h1> <h2>><span th:text="${text}"></span></h2> <p>From the image:</p> <img th:src="'/' + ${file.getName()}"/> </body> </html>

    application.properties:

    spring.servlet.multipart.max-file-size=10MB #设置文件上传大小 spring.servlet.multipart.max-request-size=100MB

    接着创建控制器进行显示页面:

    处理图片

    在开始处理图片之前,我们得准备一张手机拍摄的原图,记住一定得是原图,否则使用drew是获取不到图片的EXIF信息的,如图则为原图(我是直接用手机插PC直接拖的图片): EXIF工具类,用来获取图片信息:

    public class ExifUitl { /** * 得到图片的exif属性 */ public static String[] readExif(File file) throws ImageProcessingException, IOException { String[] array = new String[3]; ImageMetadataReader.readMetadata(file) .getDirectories().forEach(v -> v.getTags().forEach(t -> { System.out.println(t.getTagName() + " : " + t.getDescription()); switch (t.getTagName()) { // 伟度 case "GPS Latitude": array[0] = pointToLatlong(t.getDescription()); break; // 经度 case "GPS Longitude": array[1] = pointToLatlong(t.getDescription()); break; // 拍摄时间 case "Date/Time Original": array[2] = t.getDescription(); default: break; } }) ); return array; } /** * 经纬度格式 转换 */ 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 duStr.toString(); } }

    编写控制层:

    @RequestMapping(value = "/upload", method = RequestMethod.POST) public RedirectView singleFileUpload(MultipartRequest file, RedirectAttributes redirectAttributes) throws IOException, ImageProcessingException { MultipartFile mfile = file.getFile("file"); File convFile = convert(mfile); // 获取图片中得exif信息 String[] test = ExifUitl.readExif(convFile); for (String one:test) { System.out.println(one); } return new RedirectView("result"); } @RequestMapping("/result") public String result() { return "result"; } // 转换成file格式 public static File convert(MultipartFile file) throws IOException { File convFile = new File(file.getOriginalFilename()); convFile.createNewFile(); FileOutputStream fos = new FileOutputStream(convFile); fos.write(file.getBytes()); fos.close(); return convFile; }

    如何改变文本的样式

    然后我们上传图片,页面500是因为没指定File文件,上传完后看看控制台已经打印了图片的EXIF信息,我们主要获取3个key就行了,如果这个地方显示NullPointException则是你的图片问题 3个Key信息,经纬度已经通过工具类转换,上面已经提供好了. 下面我们就把获取到的三个key通过水印的方式添加到图片中,现在需要用到一个水印的工具类,如代码块:

    public class ImageFileUtil { /** * 图片添加水印 * * @param srcImgPath * 需要添加水印的图片的路径 * @param outImgPath * 添加水印后图片输出路径 * @param markContentColor * 水印文字的颜色 * @param waterMarkContent * 水印的文字 */ public File mark(File file, String srcImgPath, String outImgPath, Color markContentColor, String date, String waterMarkContent) { Graphics2D g = null; try { Color color = new Color(133,123,85); // 读取原图片信息 //File srcImgFile = new File(srcImgPath); Image srcImg = ImageIO.read(file); int srcImgWidth = srcImg.getWidth(null); int srcImgHeight = srcImg.getHeight(null); // 加水印 BufferedImage bufImg = new BufferedImage(srcImgWidth, srcImgHeight, BufferedImage.TYPE_INT_RGB); g = bufImg.createGraphics(); g.drawImage(srcImg, 0, 0, srcImgWidth, srcImgHeight, null); // 设置水印的颜色 g.setColor(markContentColor); Font font = new Font("微软雅黑", Font.PLAIN, 77); g.setFont(font); //设置地址水印的位置坐标 int x = srcImgWidth - getWatermarkLength(waterMarkContent, g) - 12; int y = srcImgHeight - 12; g.drawString(waterMarkContent, x, y); //设置时间水印的位置坐标 int xx = srcImgWidth -1* getWatermarkLength(date, g); int yy = srcImgHeight - 99; g.drawString(date, xx, yy);//画出水印 // 输出图片到本地 FileOutputStream outImgStream = new FileOutputStream(outImgPath); ImageIO.write(bufImg, "jpg", outImgStream); outImgStream.flush(); outImgStream.close(); // 输出图片到file中 File outputfile = new File(file.getName()); ImageIO.write(bufImg, "png", outputfile); return outputfile; } catch (Exception e) { e.printStackTrace(); }finally { if(g!=null){ // 释放图形资源 g.dispose(); } } return null; } /** * 获取水印文字总长度 * * @param waterMarkContent * 水印的文字 * @param g * @return 水印文字总长度 */ public int getWatermarkLength(String waterMarkContent, Graphics2D g) { return g.getFontMetrics(g.getFont()).charsWidth(waterMarkContent.toCharArray(), 0, waterMarkContent.length()); } }

    通过这个工具类我们就能生成水印图片,还有控制层需要添加的测试代码,为之前/upload接口

    File file1 = new ImageFileUtil().mark(convFile,null,"D:/work/codestory-master/exitdemo/src/main/resources/static/"+mfile.getOriginalFilename(),color,test[2],"测试--后续替换成地址"); System.out.print("file1:"); System.out.println(file1); redirectAttributes.addFlashAttribute("text","测试"); redirectAttributes.addFlashAttribute("file", file1);

    下面我们来上传图片实测:

    效果基本上实现,水印现在我们已经添加成功了,现在还需要做的就是根据GPS的经纬度得到位置信息文本,然后添加到水印中

    第三方地图API(腾讯地图)

    我这里采用的是腾讯地图的API,因为免费调用次数相对其它的API比较多,嘿嘿你也可以用其它的API进行替代,我这里就只讲解腾讯的API和简单使用和提供2个等会需要使用的接口。

    前面我们已经获取到了图片中的EXIF的经纬度信息,但是现在这个经纬度数值为GPS,我们现在需要把这个经纬度转换成腾讯地图的经纬度信息,专业一点就是坐标转换,在官网的开发文档中我们找到这个接口https://lbs.qq.com/service/webService/webServiceGuide/webServiceTranslate,我们需要关注这个接口的type值,毫无疑问我们现在从图中拿到得是GPS坐标,所以之后我们如果需要去调用这个接口时,附加的参数type为1 还有一个需要用到的接口就是逆地址解析(坐标位置描述),简单来说就是给他经纬度,从而返回根据经纬度得到的位置信息(JSON格式形式的数据格式)

    这种东西也没什么好介绍的,自己去看看开发文档就知道了,下面直接贴代码:

    /** * 腾讯API 逆地址解析 根据经纬度得到地址信息 * @param lat * @param lng * @param key * @return */ public static String getAddress(String lat,String lng,String key) { //lat 小 log 大 //纬度前 经度后 "https://apis.map.qq.com/ws/geocoder/v1/?location="+lat+","+lng+"&key="+key+"&get_poi=1"; String urlString = "https://apis.map.qq.com/ws/geocoder/v1/?location="+lat+","+lng+"&key="+key+"&get_poi=1"; String res = ""; try { URL url = new URL(urlString); java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("GET"); java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = in.readLine()) != null) { res += line + "\n"; } in.close(); } catch (Exception e) { System.out.println("error in wapaction,and e is " + e.getMessage()); } //System.out.println(res); return res; } /** * 腾讯API 坐标转换 * @param lat * @param lng * @param key * @return */ public static String changeAddress(String lat,String lng,String key) { //lat 小 log 大 String urlString = "https://apis.map.qq.com/ws/coord/v1/translate?locations="+lat+","+lng+"&type=1&key="+key; String res = ""; try { URL url = new URL(urlString); java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("GET"); java.io.BufferedReader in = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream(), "UTF-8")); String line; while ((line = in.readLine()) != null) { res += line + "\n"; } in.close(); } catch (Exception e) { System.out.println("error in wapaction,and e is " + e.getMessage()); } //System.out.println(res); return res; }

    这里就是简单的写了2个http接口,进行调用,需要注意的是需要使用自己的密钥(Key),下面在之前的接口中进行调用整合,下面贴调用代码,简单粗暴:

    String as = ExifUitl.changeAddress(test[0],test[1],key); JSONObject jsonObject = JSON.parseObject(as); JSONArray jsonArray = jsonObject.getJSONArray("locations"); JSONObject jso = jsonArray.getJSONObject(0); String ass = ExifUitl.getAddress(jso.getString("lat"),jso.getString("lng"),key); JSONObject jsonObjects = JSON.parseObject(ass); JSONObject jsonArray1 = jsonObjects.getJSONObject("result"); JSONObject jsonObject1 = jsonArray1.getJSONObject("formatted_addresses"); JSONObject jsonObject2 = jsonArray1.getJSONObject("address_component"); String address = ""+jsonObject2.getString("province")+""+jsonObject2.getString("city")+""+jsonObject2.getString("district")+""+ jsonObject1.getString("rough");

    把之前写死的字符串,替换成"address"就行,下面我们启动项目来直接测试效果: 完美实现效果,基本上需要的代码我都提供了,代码中的注释相信我也写的比较详细,后续会把这个demo上传到Git仓库中。

    结尾

    本文的演示代码仅供参考,实际项目中使用变动应该也不大,也算是我在几十篇文章之中吸取的精华,如果对你有丁点儿的帮助的话,点个赞可好,有问题欢迎留言、私信!!

    Processed: 0.013, SQL: 9