上一篇文章讲述其原理。该文章用java实现:
谷歌街景下载(一)
创建一个正常springBoot,数据库用SQLite。
其中每一个SQLite,可以根据自己规律来划分,我这里的规律为经纬度来进行划分大小。例如:
我要下载一个经纬度大小的一块区域:
{“minLon”:120.179443359375,“maxLat”:23.7744140625,“minLat”:23.763427734375,“maxLon”:120.1904296875,“sqlname”:“11231”}
根据经纬度划分的一个矩形区域,其下载方式分两种:
方案一:暴力破解,给定的区域可以用双循环的方式,每个点平移经纬度小数点后六位的方式进行暴力破解,谷歌地图相邻点大概6米间距。暴力破解耗时高,并且会有遗漏。方案二不做赘述。
方案二:拿到了该矩形的区域后,首先对改区域的边界进行暴力破解:
List<String> stringListAll = new ArrayList<>();
//4条边分别使用
stringListAll.addAll(linePoint(north, south, east, 0));
stringListAll.addAll(linePoint(north, south, west, 0));
stringListAll.addAll(linePoint(east, west, north, 1));
stringListAll.addAll(linePoint(east, west, south, 1));
List<String> stringList1 = stringListAll.stream().distinct().collect(Collectors.toList());
4条边调用4次,用一个list来接收后,需要进行去重。
public List<String> linePoint(BigDecimal north, BigDecimal south, BigDecimal sitePoint, int lineFlag) throws IOException {
//经度,表示每一个点位平移的距离
//BigDecimal up = new BigDecimal("0.0001");
BigDecimal up = new BigDecimal("0.00005");
List<String> panoidList = new ArrayList<>();
for (BigDecimal i = south; i.compareTo(north) < 1; i = i.add(up)) {
double weidu = i.setScale(8, ROUND_HALF_UP).doubleValue();
double jingdu = sitePoint.setScale(8, ROUND_HALF_UP).doubleValue();
String street;
//http://cbk0.google.com/cbk?output=json&oe=utf-8&dm=1&pm=1&ll=纬度%2C经度&hl=zh-CN
if (lineFlag == 0) {
street = "http://cbk0.google.com/cbk?output=json&oe=utf-8&dm=1&pm=1&ll=" + weidu + "%2C" + jingdu + "&hl=zh-CN";
} else {
street = "http://cbk0.google.com/cbk?output=json&oe=utf-8&dm=1&pm=1&ll=" + jingdu + "%2C" + weidu + "&hl=zh-CN";
}
log.info(street);
byte[] urlData = ProtoBfUtils.getUrl3D(street);
String res = new String(urlData);
if (!res.equals("{}")) {
JSONObject json;
json = JSONObject.parseObject(res);
String panoId = json.getJSONObject("Location").getString("panoId");
panoidList.add(panoId);
}
}
return panoidList;
}
public static byte[] getUrl3D(String fileUrl) throws IOException {
HttpGet httpGet = new HttpGet(fileUrl);
httpGet.addHeader("User-Agent", USER_AGENT);
httpGet.addHeader("Referer", "https://earth.google.com/");
CloseableHttpClient httpClient = HttpClients.createDefault();
@Cleanup CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
int statusCode = httpResponse.getStatusLine().getStatusCode();
log.info("url:" + httpGet.getURI().toString() + ",响应码:" + statusCode);
if (statusCode == HttpStatus.SC_OK) {
HttpEntity entity = httpResponse.getEntity();
byte[] bytes = EntityUtils.toByteArray(entity);
httpClient.close();
return bytes;
}else if (statusCode == HttpStatus.SC_BAD_REQUEST) {
return null;
}else if (statusCode == HttpStatus.SC_NOT_FOUND) {
return null;
}else if (statusCode == HttpStatus.SC_FORBIDDEN) {
return null;
}else if (statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR) {
return null;
}else {
EntityUtils.consume(httpResponse.getEntity());
httpClient.close();
//throw new CustomException(CustomException.STOP_TASK);
throw new CustomException(CustomException.GOOGLE_SERVER_EXCEPTION + statusCode + "。url:" + httpGet.getURI().toString());
}
}
当前方法内只去获取json里面的panoid这个唯一标识。
通过以上方法能拿到该区域的所有边界上的点位,点位与点位之间在json中可以使用"links"这个标识来识别所有的相邻点,那么该区域就可以使用递归的方法获取到整个有效的点位。
接下来介绍递归获取内部点位:
首先两个实体类:
@Data
public class StreetView {
private String panoid;
private double lat;
private double lng;
private String json;
}
public class Bbox {
//最小经度,West
private double West;
//最小纬度 South
private double South;
//最大经度 East
private double East;
//最大纬度North
private double North;
}
开始递归:
public Set<StreetView> downloadStreet2(List<String> stringListAll, Bbox bbox) throws IOException {
//获取相邻点位
//List<String> panoidListAll = new ArrayList<>();
Set<StreetView> streetViewSet = new HashSet<>();
Map<String,StreetView> streetViewMap=new HashMap<>();
//创建一个对象 用来存需要的点位包含经纬度,StreetView
for (int i = 0; i < stringListAll.size(); i++) {
Map<String, StreetView> streetViewMap1 = checkPoint(stringListAll.get(i), bbox, streetViewMap);
for (Map.Entry<String, StreetView> entry:streetViewMap1.entrySet()
) {
streetViewSet.add(entry.getValue());
//log.info("当前存入点位的panoid:"+entry.getKey());
}
}
return streetViewSet;
}
public Map<String,StreetView> checkPoint(String panoid, Bbox bbox, Map<String,StreetView> stringStreetViewMap) throws IOException {
String url = "http://cbk0.google.com/cbk?output=json&oe=utf-8&dm=1&pm=1&panoid=" + panoid + "&hl=zh-CN";
byte[] urlData = ProtoBfUtils.getUrl3D(url);
String res = new String(urlData);
if (!res.equals("{}")) {
JSONObject json;
json = JSONObject.parseObject(res);
JSONArray links = json.getJSONArray("Links");
if( !CollectionUtils.isEmpty(links)){
for (int i = 0; i < links.size(); i++) {
JSONObject panoidJson = (JSONObject) JSONObject.toJSON(links.get(i));
String panoIdLink = (String) panoidJson.get("panoId");
String urllink = "http://cbk0.google.com/cbk?output=json&oe=utf-8&dm=1&pm=1&panoid=" + panoIdLink + "&hl=zh-CN";
byte[] urlDataLink = ProtoBfUtils.getUrl3D(urllink);
String resLink = new String(urlDataLink);
JSONObject jsonLink = JSONObject.parseObject(resLink);
BigDecimal original_lng = new BigDecimal(jsonLink.getJSONObject("Location").getString("lng"));
//纬度
BigDecimal original_lat = new BigDecimal(jsonLink.getJSONObject("Location").getString("lat"));
if (checkArea(original_lng, original_lat, bbox)) {
StreetView streetView = new StreetView(panoIdLink, original_lat.doubleValue(), original_lng.doubleValue(), resLink);
//map<panoid,streetView>
if(stringStreetViewMap.get(panoIdLink)==null){
// log.info("当前:"+panoIdLink);
stringStreetViewMap.put(panoIdLink,streetView);
checkPoint(panoIdLink, bbox, stringStreetViewMap);
}
}
}
}
}
return stringStreetViewMap;
}
//该方法判断该点位是否在矩形范围内
public boolean checkArea(BigDecimal original_lng, BigDecimal original_lat, Bbox bbox) {
BigDecimal north = new BigDecimal(bbox.getNorth());
BigDecimal south = new BigDecimal(bbox.getSouth());
BigDecimal east = new BigDecimal(bbox.getEast());
BigDecimal west = new BigDecimal(bbox.getWest());
//坐标点与bbox范围对比
if ((original_lat.compareTo(north) == -1) && (original_lat.compareTo(south) == 1) &&
(original_lng.compareTo(east) == -1) && (original_lng.compareTo(west) == 1)) {
return true;
}
return false;
}
最后的这个list
Set<StreetView> streetViewSet = downloadStreet2(stringList1, bbox);
是装下了改区域的所有点位。
点位的数量多少与区域的大小和街道的密集程度相关。这里进行存入数据库有存在以下问题:点位数量太多会导致内存溢出,解决方式可以把给定的区域进行分割后,在进行下载。
把一块区域分4快的方法:
public List<Bbox> spirtBbox(Bbox bbox){
BigDecimal east =new BigDecimal(bbox.getEast());
BigDecimal west =new BigDecimal(bbox.getWest());
BigDecimal north =new BigDecimal(bbox.getNorth());
BigDecimal south =new BigDecimal(bbox.getSouth());
//大区域分割为4块区域。
BigDecimal add = east.add(west);
BigDecimal add2 = north.add(south);
BigDecimal b2 = new BigDecimal("2");
BigDecimal divide = add.divide(b2, 6, BigDecimal.ROUND_DOWN);
BigDecimal divide2 = add2.divide(b2, 6, BigDecimal.ROUND_DOWN);
double st = divide.doubleValue();
double orh = divide2.doubleValue();
//4位数重新封装:
// Bbox bbox = new Bbox(latLonBox.getWest(), latLonBox.getSouth(), latLonBox.getEast(), latLonBox.getNorth());
Bbox bbox1=new Bbox(bbox.getWest(),orh,st,bbox.getNorth());
Bbox bbox2=new Bbox(st,orh,bbox.getEast(),bbox.getNorth());
Bbox bbox3=new Bbox(bbox.getWest(),bbox.getSouth(),st,orh);
Bbox bbox4=new Bbox(st,bbox.getSouth(),bbox.getEast(),orh);
List<Bbox> bboxes=new ArrayList<>();
bboxes.add(bbox1);
bboxes.add(bbox2);
bboxes.add(bbox3);
bboxes.add(bbox4);
return bboxes;
}
区域点位街道路网给复杂的情况下:checkPoint这个方法递归层数太多也会报错,所有最好的方式就是划小区域。
付一张数据库存储样例:
以上是所有点位的下载。
步骤二下载图片:
public void streetViewChangeListForID(String panoid,SqlSessionFactory sqlSessionFactory) throws IOException {
log.info("当前panoid为: "+panoid);
List<PanoidPojo> panoidPojoList=new ArrayList<>();
for (int x = 0; x <=7 ; x++) {
for (int y = 0; y <=3 ; y++) {
String picUrlLe3=
"http://cbk0.google.com/cbk?output=tile&panoid="+panoid+"&zoom=3&x="+x+"&y="+y;
byte[] urlDataLe6 = ProtoBfUtils.getUrl3D(picUrlLe3);
if (urlDataLe6!=null){
PanoidPojo panoidPojo=new PanoidPojo(panoid,x,y,urlDataLe6);
panoidPojoList.add(panoidPojo);
}
}
}
//这里做批量新增
DBUtils.insetTileDb(sqlSessionFactory,panoidPojoList);
}
通过上一张表拿到所有panoid 然后进行下载图片。
在此所有下载完毕