前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java实现调用百度接口将大量数据库中保存的地址转换为经纬度

java实现调用百度接口将大量数据库中保存的地址转换为经纬度

作者头像
jiankang666
发布2022-05-12 21:06:23
1.3K0
发布2022-05-12 21:06:23
举报
文章被收录于专栏:java基础笔记java基础笔记

一、背景

最近,碰到了一个业务,是将数据库中所有的地址信息请求百度接口获取经纬度保存起来。有38万多个地址,想到的方案就是查出所有的地址字段加上主键字段,然后导出csv文件,读取这个文件,遍历请求百度api接口,获取经纬度信息,生成一个新的文件,作为一张表导入数据库,使用sql给地址刷一遍经纬度。

二、前期准备

1、生成需要转换的地址数据

(1)示例:查询sql需要筛选出经纬度字段为空的地址数据,之后的刷经纬度需要主键字段,所有也需要获取,然后导出一个文件。

代码语言:javascript
复制
1select external_id,address from customer where   longitude is null and latitude is null and address is not null 

(2)导出这条sql查出的记录,像下面这样,一个csv文件。

三、百度接口介绍

1、百度地址转经纬度接口支持返回json格式和xml格式

(1)get方式请求下面地址将返回json格式,key为自己在百度上申请的开发者密钥。

代码语言:javascript
复制
1 http://api.map.baidu.com/geocoder?address={address}&output=json&key=SkSf

(2)成功的返回格式如下:

代码语言:javascript
复制
 1{
 2    "status":"OK",
 3    "result":{
 4        "location":{
 5            "lng":123.473237,
 6            "lat":41.833995
 7        },
 8        "precise":1,
 9        "confidence":80,
10        "level":"\u95e8\u5740"
11    }
12}

(3)get方式请求下面地址将返回xml格式

代码语言:javascript
复制
1http://api.map.baidu.com/geocoder?address={address}&output=json&key=SkSf

(4)成功的格式如下:

代码语言:javascript
复制
 1<?xml version="1.0" encoding="utf-8" ?> 
 2<GeocoderSearchResponse> 
 3    <status>OK</status>
 4    <result>
 5                    <location>
 6                <lat>40.148852</lat>
 7                <lng>117.125265</lng>
 8            </location> 
 9            <precise>0</precise>
10            <confidence>75</confidence>
11            <level>购物</level>
12            </result>   
13</GeocoderSearchResponse>

(5)请求上面两个url,都可能返回失败内容,失败内容都是像下面这样,返回html页面。

代码语言:javascript
复制
 1<!DOCTYPE html>
 2<!--STATUS OK-->
 3<html>
 4<head>
 5    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
 6    <meta http-equiv="content-type" content="text/html;charset=utf-8">
 7    <meta content="always" name="referrer">
 8    <script src="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/nocache/imgdata/seErrorRec.js"></script>
 9    <title>页é¢ä¸å­å¨_ç¾åº¦æç´¢</title>
10    <style data-for="result">
11        body {color: #333; background: #fff; padding: 0; margin: 0; position: relative; min-width: 700px; font-family: arial; font-size: 12px }
12        p, form, ol, ul, li, dl, dt, dd, h3 {margin: 0; padding: 0; list-style: none }
13        input {padding-top: 0; padding-bottom: 0; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box } img {border: none; }
14        .logo {width: 117px; height: 38px; cursor: pointer }
15         ...
16

注意:无论想返回json格式还是xml格式,当请求返回这种html类型数据,就获取不到经纬度,需要收集下来重新请求。

四、功能实现

1、先来实现百度接口返回为xml格式并解析获取经纬度,最后附完整代码

(1)为了记录读取的csv文件的原始地址数据和请求百度接口获取经纬度数据,原始文件中有主键(external_id)和地址(address),请求接口返回我们需要的经度(longitude)维度(latitude),这四个字段都需要最终保存到生成的结果文件中,所以我们声明ResultBean类如下,来记录数据(省略setget方法)。

代码语言:javascript
复制
 1    static class ResultBean {
 2
 3        private String external_id;
 4        //百度经纬度
 5        private String longitude;
 6        private String latitude;
 7
 8        //address
 9        private String address;
10
11        public ResultBean(String external_id, String address, String longitude, String latitude) {
12            this.external_id = external_id;
13            this.longitude = longitude;
14            this.latitude = latitude;
15            this.address = address;
16        }
17
18    }

(2)读取导出的原始csv地址文件方法如下:通过CSVReader的write方法读取文件中的每条记录,保存到ResultBean,执行请求后面的经纬度方法。

代码语言:javascript
复制
 1    public static void readCSV(List<ResultBean> datas, String sourcePath) {
 2        List<ResultBean> failData = new ArrayList<>();
 3        try (CSVReader csvReader = new CSVReaderBuilder(new BufferedReader(new InputStreamReader(new FileInputStream(new File(sourcePath)), "utf-8"))).build()) {
 4            Iterator<String[]> iterator = csvReader.iterator();
 5            //导出文件有标题行,去掉标题行,没有就不需要
 6            iterator.next();
 7            while (iterator.hasNext()) {
 8                String[] next = iterator.next();
 9                String address = next[1].replaceAll("\\s*", "");
10                ResultBean resultBean = new ResultBean(next[0], address, null, null);
11
12                //百度接口地址转换经纬度方法
13                getLngLat(datas, failData, resultBean);
14            }
15
16            //失败数据再次请求百度接口,最多循环一千次,防止失败数据出现程序永不停止
17            int i = 1000;
18            while (failData.size() > 0 && i > 0) {
19                List<ResultBean> tempFailData = new ArrayList<>(failData);
20                failData.clear();
21                for (ResultBean resultBean : tempFailData) {
22                    getLngLat(datas, failData, resultBean);
23                }
24                i--;
25            }
26
27        } catch (Exception e) {
28            e.printStackTrace();
29        } finally {
30            System.out.println("fail record:" + failData.size());
31        }
32    }

(3)我们使用restTemplate的getForObject方法请求百度接口,得到响应的结果,从上面可以看出返回的正常数据都是String类型的,肯定有"GeocoderSearchResponse",会基于这个字符串判断是否返回了xml数据,防止返回上面所说的html类型的数据,导致xml转换为bean对象获取经纬度报错。请求百度接口方法如下:

代码语言:javascript
复制
 1 /**
 2     * 封装的获取经纬度方法
 3     * @param datas
 4     * @param failData
 5     * @param resultBean
 6     */
 7    private static void getLngLat(List<ResultBean> datas, List<ResultBean> failData, ResultBean resultBean) {
 8        Map<String, String> map = Maps.newHashMap();
 9        map.put("address", resultBean.getAddress());
10        String response = restTemplate.getForObject(URL, String.class, map);
11        if (response.contains("GeocoderSearchResponse")) {
12            GeocoderSearchResponse g = JAXB.unmarshal(new StringReader(response), GeocoderSearchResponse.class);
13            if (g.status.equals("OK")) {
14                resultBean.setLatitude(g.result.location.lat);
15                resultBean.setLongitude(g.result.location.lng);
16                datas.add(resultBean);
17            } else {
18                failData.add(resultBean);
19            }
20        } else {
21            failData.add(resultBean);
22        }
23    }

(4)对于偶尔返回html类型的错误数据,会收集相应的ResultBean到failData集合中,执行完csv文件中的所有数据后,遍历失败的集合再次请求百度接口,重复拿到失败数据集合请求百度,直到没有失败数据,或者已经重复了1000次,结束请求百度接口,将百度的所有转换成功的数据写入结果文件中。部分代码如下:

代码语言:javascript
复制
 1 //失败数据再次请求百度接口,最多循环一千次,防止失败数据出现程序永不停止
 2            int i = 1000;
 3            while (failData.size() > 0 && i > 0) {
 4                List<ResultBean> tempFailData = new ArrayList<>(failData);
 5                failData.clear();
 6                for (ResultBean resultBean : tempFailData) {
 7                    getLngLat(datas, failData, resultBean);
 8                }
 9                i--;
10            }

(5)当请求百度api返回正确xml数据以后, 需要将xml转换为bean,然后获取经纬度,很多博客说使用dom4j进行转换,但是我发现公司pom里没有dom4j这个依赖,加这个依赖需要向上申请,所以就使用了JAXB(Java Architecture for XML Binding) ,他是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。通过分析上面返回的xml,我们需要建立三个类,一个是GeocoderSearchResponse,Result,Location,他们都需要加上@XmlRootElement注解。类声明如下:

代码语言:javascript
复制
 1    @XmlRootElement(name = "GeocoderSearchResponse")
 2    static class GeocoderSearchResponse {
 3        private String status;
 4
 5        private Result result;
 6
 7
 8        public String getStatus() {
 9            return status;
10        }
11
12        public void setStatus(String status) {
13            this.status = status;
14        }
15
16        public Result getResult() {
17            return result;
18        }
19
20        public void setResult(Result result) {
21            this.result = result;
22        }
23    }
24
25    @XmlRootElement
26    static class Result {
27        private Location location;
28
29
30        public Location getLocation() {
31            return location;
32        }
33
34        public void setLocation(Location location) {
35            this.location = location;
36        }
37    }
38
39    @XmlRootElement
40    static class Location {
41        private String lat;
42        private String lng;
43
44        public String getLat() {
45            return lat;
46        }
47
48        public void setLat(String lat) {
49            this.lat = lat;
50        }
51
52        public String getLng() {
53            return lng;
54        }
55
56        public void setLng(String lng) {
57            this.lng = lng;
58        }
59    }

注意:

①类名的首字母会自动变为小写去对应xml中的字段,由于xml中GeocoderSearchResponse直接是大写的,所以需要在注解上加name属性,否则可能报错:

代码语言:javascript
复制
unexpected element (uri:"", local:"GeocoderSearchResponse"). Expected elements are <{}geocoderSearchResponse

②每个类的变量只能有一个获取方式,需要声明变量私有,通过getset方法获取,否则会报错:

代码语言:javascript
复制
Class has two properties of the same name "result"

(6)当获取所有已经转换成功的经纬度信息后,将数据写入结果csv文件中,通过CsvWriter的write方法如下:

代码语言:javascript
复制
1  public static void writeCSV(List<ResultBean> datas, String goalPath) {
2        CsvWriter csvWriter = new CsvWriter(new File(goalPath));
3        for (ResultBean data : datas) {
4            csvWriter.write(new String[]{data.getExternal_id(), data.getAddress(), data.getLongitude(), data.getLatitude()});
5        }
6        csvWriter.close();
7    }

(7)所有代码如下:

代码语言:javascript
复制
  1package com.forceclouds.crm.local;
  2
  3import cn.hutool.core.date.StopWatch;
  4import cn.hutool.core.text.csv.CsvWriter;
  5import com.google.common.collect.Maps;
  6import com.opencsv.CSVReader;
  7import com.opencsv.CSVReaderBuilder;
  8import org.springframework.web.client.RestTemplate;
  9
 10import javax.xml.bind.JAXB;
 11import javax.xml.bind.annotation.XmlRootElement;
 12import java.io.*;
 13import java.util.ArrayList;
 14import java.util.Iterator;
 15import java.util.List;
 16import java.util.Map;
 17
 18/*
 19 *@create by jiankang
 20 *@date 2020/6/3 time 14:59
 21 */
 22
 23public class GPSTest {
 24    private static String URL = "http://api.map.baidu.com/geocoder?address={address}&key=SkSf";
 25    private static RestTemplate restTemplate = new RestTemplate();
 26
 27    public static void main(String[] args) {
 28        List<ResultBean> datas = new ArrayList<>();
 29        ResultBean title = new ResultBean("external_id", "address", "longitude", "latitude");
 30        datas.add(title);
 31        String sourcePath = "C:\\Users\\ForceClouds\\Desktop\\aaa.csv";
 32        String goalPath = "C:\\Users\\ForceClouds\\Desktop\\jiangkang0905.csv";
 33        StopWatch stopWatch = new StopWatch();
 34        stopWatch.start("经纬度转换运行程序");
 35        readCSV(datas, sourcePath);
 36        stopWatch.stop();
 37        System.out.println(stopWatch.prettyPrint());
 38        writeCSV(datas, goalPath);
 39    }
 40
 41    public static void writeCSV(List<ResultBean> datas, String goalPath) {
 42        CsvWriter csvWriter = new CsvWriter(new File(goalPath));
 43        for (ResultBean data : datas) {
 44            csvWriter.write(new String[]{data.getExternal_id(), data.getAddress(), data.getLongitude(), data.getLatitude()});
 45        }
 46        csvWriter.close();
 47    }
 48
 49    public static void readCSV(List<ResultBean> datas, String sourcePath) {
 50        List<ResultBean> failData = new ArrayList<>();
 51        try (CSVReader csvReader = new CSVReaderBuilder(new BufferedReader(new InputStreamReader(new FileInputStream(new File(sourcePath)), "utf-8"))).build()) {
 52            Iterator<String[]> iterator = csvReader.iterator();
 53            //导出文件有标题行,去掉标题行,没有就不需要
 54            iterator.next();
 55            while (iterator.hasNext()) {
 56                String[] next = iterator.next();
 57                String address = next[1].replaceAll("\\s*", "");
 58                ResultBean resultBean = new ResultBean(next[0], address, null, null);
 59
 60                //百度接口地址转换经纬度方法
 61                getLngLat(datas, failData, resultBean);
 62            }
 63
 64            //失败数据再次请求百度接口,最多循环一千次,防止失败数据出现程序永不停止
 65            int i = 1000;
 66            while (failData.size() > 0 && i > 0) {
 67                List<ResultBean> tempFailData = new ArrayList<>(failData);
 68                failData.clear();
 69                for (ResultBean resultBean : tempFailData) {
 70                    getLngLat(datas, failData, resultBean);
 71                }
 72                i--;
 73            }
 74
 75        } catch (Exception e) {
 76            e.printStackTrace();
 77        } finally {
 78            System.out.println("fail record:" + failData.size());
 79        }
 80    }
 81
 82
 83    private static void getLngLat(List<ResultBean> datas, List<ResultBean> failData, ResultBean resultBean) {
 84        Map<String, String> map = Maps.newHashMap();
 85        map.put("address", resultBean.getAddress());
 86        String response = restTemplate.getForObject(URL, String.class, map);
 87        if (response.contains("GeocoderSearchResponse")) {
 88            GeocoderSearchResponse g = JAXB.unmarshal(new StringReader(response), GeocoderSearchResponse.class);
 89            if (g.status.equals("OK")) {
 90                resultBean.setLatitude(g.result.location.lat);
 91                resultBean.setLongitude(g.result.location.lng);
 92                datas.add(resultBean);
 93            } else {
 94                failData.add(resultBean);
 95            }
 96        } else {
 97            failData.add(resultBean);
 98        }
 99    }
100
101
102    @XmlRootElement(name = "GeocoderSearchResponse")
103    static class GeocoderSearchResponse {
104        private String status;
105
106        private Result result;
107
108
109        public String getStatus() {
110            return status;
111        }
112
113        public void setStatus(String status) {
114            this.status = status;
115        }
116
117        public Result getResult() {
118            return result;
119        }
120
121        public void setResult(Result result) {
122            this.result = result;
123        }
124    }
125
126    @XmlRootElement
127    static class Result {
128        private Location location;
129
130
131        public Location getLocation() {
132            return location;
133        }
134
135        public void setLocation(Location location) {
136            this.location = location;
137        }
138    }
139
140    @XmlRootElement
141    static class Location {
142        private String lat;
143        private String lng;
144
145        public String getLat() {
146            return lat;
147        }
148
149        public void setLat(String lat) {
150            this.lat = lat;
151        }
152
153        public String getLng() {
154            return lng;
155        }
156
157        public void setLng(String lng) {
158            this.lng = lng;
159        }
160    }
161
162
163    static class ResultBean {
164
165        private String external_id;
166        //百度经纬度
167        private String longitude;
168        private String latitude;
169
170        //address
171        private String address;
172
173        public ResultBean(String external_id, String address, String longitude, String latitude) {
174            this.external_id = external_id;
175            this.longitude = longitude;
176            this.latitude = latitude;
177            this.address = address;
178        }
179
180        public String getExternal_id() {
181            return external_id;
182        }
183
184        public String getAddress() {
185            return address;
186        }
187
188
189        public String getLongitude() {
190            return longitude;
191        }
192
193        public String getLatitude() {
194            return latitude;
195        }
196
197        public void setExternal_id(String external_id) {
198            this.external_id = external_id;
199        }
200
201        public void setLongitude(String longitude) {
202            this.longitude = longitude;
203        }
204
205        public void setLatitude(String latitude) {
206            this.latitude = latitude;
207        }
208
209        public void setAddress(String address) {
210            this.address = address;
211        }
212    }
213}

2、实现返回json格式并解析获取经纬度

Ⅰ、第一种比较简单,只需要把上面完整代码中url改一下,并且getLngLat方法换一下就可以了

(1)请求百度api加一个output=json的参数。

代码语言:javascript
复制
1 private static String URL = "http://api.map.baidu.com/geocoder?address={address}&output=json&key=SkSf";

(2)通过new JacksonJsonParser()对返回值进行解析,注意判断返回值是否为html形式的内容,是的话需要收集重新请求。代码如下。

代码语言:javascript
复制
 1    private static void getLngLat(List<ResultBean> datas, List<ResultBean> failData, ResultBean resultBean) {
 2        Map<String, String> map = Maps.newHashMap();
 3        map.put("address", resultBean.getAddress());
 4//        restTemplate.getMessageConverters().add(new MyMappingJackson2HttpMessageConverter());
 5        String response = restTemplate.getForObject(URL, String.class, map);
 6        if (response != null && response.contains("lng") && response.contains("lat")) {
 7            final Map<String, Object> geocoderResult = new JacksonJsonParser().parseMap(response);
 8            Map location = (Map) ((Map) geocoderResult.get("result")).get("location");
 9            Double lng = (Double) location.get("lng");
10            System.out.println(lng);
11            Double lat = (Double) location.get("lat");
12            System.out.println(lat);
13            ResultBean bean = new ResultBean(resultBean.getExternal_id(), resultBean.getAddress(), String.valueOf(lng), String.valueOf(lat));
14            datas.add(bean);
15        } else {
16            failData.add(resultBean);
17        }
18    }

Ⅱ、另一种解析json方式是我们根据返回值,封装一个bean对象使用RestTemplate的getForObject直接转换为bean类型

(1)封装的bean如下:

代码语言:javascript
复制
 1    static class GeocoderResult implements Serializable {
 2        String status;
 3        Map result;
 4
 5        public String getStatus() {
 6            return status;
 7        }
 8
 9        public void setStatus(String status) {
10            this.status = status;
11        }
12
13        public Map getResult() {
14            return result;
15        }
16
17        public void setResult(Map result) {
18            this.result = result;
19        }
20
21        @Override
22        public String toString() {
23            return super.toString();
24        }
25    }

(2)请求百度api接口如下:

代码语言:javascript
复制
 1    private static void getLngLat2(List<ResultBean> datas, List<ResultBean> failData, ResultBean resultBean) {
 2        Map<String, String> map = Maps.newHashMap();
 3        map.put("address", resultBean.getAddress());
 4        restTemplate.getMessageConverters().add(new MyMappingJackson2HttpMessageConverter());
 5        try {
 6            GeocoderResult  geocoderResult = restTemplate.getForObject(URL, GeocoderResult.class, map);
 7            if (geocoderResult.status.equals("OK")) {
 8                Map location = (Map) geocoderResult.getResult().get("location");
 9                Double lng = (Double) location.get("lng");
10                Double lat = (Double) location.get("lat");
11                ResultBean bean = new ResultBean(resultBean.getExternal_id(), resultBean.getAddress(), String.valueOf(lng), String.valueOf(lat));
12                datas.add(bean);
13            } else {
14                failData.add(resultBean);
15            }
16        } catch (RestClientException e) {
17            failData.add(resultBean);
18        }
19    }

(3)上面方法中我们对restTemplate加了一个自定义的MyMappingJackson2HttpMessageConverter实例,之所以自定义一个,是因为restTemplate不支持接口返回MediaType类型为text/javascript以及为text/html类型的返回值的转换,不加会报错信息:

UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class GPSTest2$GeocoderResult] and content type [text/javascript;charset=utf-8],

同时当返回html这种错误值时,会转换失败进入catch代码块中,我们需要收集起来,下次继续请求。MyMappingJackson2HttpMessageConverter类代码如下

代码语言:javascript
复制
1    static class MyMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
2
3        public MyMappingJackson2HttpMessageConverter() {
4            List<MediaType> mediaTypes = new ArrayList<>();
5            mediaTypes.add(new MediaType("text", "javascript"));
6            mediaTypes.add(new MediaType("text", "html"));
7            setSupportedMediaTypes(mediaTypes);
8        }
9    }

五、成果展示及总结

不论是请求百度接口返回xml类型获取经纬度还是返回json类型获取经纬度,都会得到同样的结果,程序正确执行完成。

1、控制台输出

2、同时生成一个csv结果文件,使用excel打开部分结果如下

将拿到的结果文件导入数据库的新表中,写一个sql语句通过主键条件更新源表的经纬度字段就顺利完成任务。以上就是对地址转换经纬度的一点总结和分享

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java基础笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档