一、天地图
在官网-开发资源里可以注册申请相关的天地图服务许可(key)
天地图对于地理编码及逆地理编码的精准度在使用看来对于所属街道等信息解析的比较准确。
由于官网对于具体使用介绍不够具体,本文章主要讨论的是地理编码、逆地理编码WEB服务API的调用。
二、相关坐标系
天地图坐标系
天地图是中国国家地理信息公共服务平台,它提供了一系列的地图服务。在天地图中使用的坐标系主要是基于中国国家标准的CGCS2000(China Geodetic Coordinate System 2000)坐标系。CGCS2000是地心坐标系,于2008年7月1日正式启用,用于替代之前的BJ54和Xian80等坐标系统。该坐标系以地球质心为原点,Z轴指向国际地球参考框架(ITRF)定义的参考极方向,X轴指向格林尼治平均子午面与赤道的交点,Y轴完成右手直角坐标系。
WGS84坐标系
WGS84(World Geodetic System 1984)是一个全球适用的地心坐标系,广泛应用于GPS以及其他全球定位系统中。WGS84坐标系同样以地球质心作为原点,其坐标轴的方向也是依据国际时间局(BIH)1984.0定义的协议地球坐标系(CTRS)确定。WGS84坐标系通过不断地精化和完善,提供了高精度的位置信息描述方式,被全世界所采用作为标准地理坐标系统。
天地图坐标系与WGS84坐标系的关系
尽管天地图主要使用的是CGCS2000坐标系,但在实际应用中,考虑到全球化的需求以及与其他地理信息系统(GIS)数据的兼容性,天地图也支持WGS84坐标系。事实上,由于CGCS2000和WGS84都属于地心坐标系,两者之间的差异非常小(平面位置差异通常小于1米),因此在很多情况下可以互换使用。不过,在进行精确测量或需要高精度定位的应用场景下,还是需要注意二者之间的细微差别,并根据具体需求选择合适的坐标转换方法。
对于开发人员来说,当涉及到将数据从一种坐标系转换到另一种时,可能需要用到专业的GIS软件或库来实现坐标变换,确保数据的准确性和一致性。在中国境内使用天地图服务时,了解这些坐标系的基础知识及其相互关系是非常重要的。
三、地理编码
地理编码是由结构化地址数据(如:北京市海淀区莲花池西路28号)转换为对应坐标点(经纬度)功能,天地图的地址解析仅限于国内。
官网提供的地理编码API信息如下图:
根据对应信息可以使用以下工具类进行地理编码
@Component
@Slf4j
public class TianMapUtil {
// 申请到的天地图-服务端访问密钥
private static final String tk = "";
/**
* 天地图地理编码API基础URL
*/
private static final String url = "http://api.tianditu.gov.cn/geocoder?";
/**
* JSON解析器
*/
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 根据地址获取经纬度坐标
*
* @param address 需要查询的地址
* @return 包含经度(lng)和纬度(lat)的Map
* @throws Exception 处理过程中可能出现的异常
*/
public static Map<String, String> getLngLat(String address) throws Exception {
// 去除地址中的所有空格和制表符
address = address.replaceAll("\\s+", "");
// 构建查询参数并进行URL编码
String queryStr = "ds=" + URLEncoder.encode("{'keyWord':'" + address + "'}", StandardCharsets.UTF_8.toString());
// 添加服务端必要的参数
String queryUrl = url + queryStr + "&tk=" + tk;
Map<String, String> resultMap = new HashMap<>();
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(queryUrl);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
// 解析JSON响应
JsonNode jsonObject = objectMapper.readTree(result);
JsonNode locationNode = jsonObject.get("location");
if (locationNode != null && !locationNode.isNull()) {
String latitude = locationNode.get("lat").asText();
String longitude = locationNode.get("lon").asText();
// 保存经纬度信息
resultMap.put("lng", longitude);
resultMap.put("lat", latitude);
} else {
log.error("Location node is null or missing in the response for address: " + address);
}
} else {
String errorContent = EntityUtils.toString(response.getEntity(), "UTF-8");
log.error("请求失败,状态码: " + statusCode + ", 错误信息: " + errorContent);
}
}
} catch (Exception e) {
log.error("请求失败,地址: " + address + ", 错误: " + e.getMessage());
e.printStackTrace();
}
return resultMap;
}
}
本文仅取了经纬度度信息,若还需要其他信息可增加到resultMap中进行一起返回,可取的信息来自天地图返回值如下:
{
"msg": "ok",
"location": {
"score": 100,
"level": "门址",
"lon": "116.290158",
"lat": "39.894696",
"keyWord": "北京市海淀区莲花池西路28号"
},
"searchVersion": "6.4.9V",
"status": "0"
}
四、逆地理编码
天地图逆地理编码提供将坐标点(经纬度)转换为结构化的地址信息的功能。
官网提供的逆地理编码API信息如下图:
@Component
@Slf4j
public class TianMapUtil {
// 申请到的天地图-服务端访问密钥
private static final String tk = "";
/**
* 天地图地理编码API基础URL
*/
private static final String url = "http://api.tianditu.gov.cn/geocoder?";
/**
* JSON解析器
*/
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 根据经纬度获取街道名称和详细地址
*
* @param locationMap 包含经度(lng)和纬度(lat)的Map
* @return 包含详细地址(address)和街道名称(streetName)的Map
* @throws Exception 处理过程中可能出现的异常
*/
public static Map<String, String> getStreetName(Map<String, String> locationMap) throws Exception {
// 验证输入参数
if (locationMap == null || locationMap.isEmpty() || !locationMap.containsKey("lng") || !locationMap.containsKey("lat")) {
log.error("无效的位置信息");
return new HashMap<>();
}
String lng = locationMap.get("lng");
String lat = locationMap.get("lat");
// 构建查询参数并进行URL编码
String postStr = URLEncoder.encode("{'lon':" + lng + ",'lat':" + lat + ",'ver':1}", StandardCharsets.UTF_8.toString());
String queryUrl = url + "postStr=" + postStr + "&tk=" + tk;
Map<String, String> resultMap = new HashMap<>();
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(queryUrl);
try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String result = EntityUtils.toString(response.getEntity(), "UTF-8");
// 解析JSON响应
JsonNode rootNode = objectMapper.readTree(result);
JsonNode resultNode = rootNode.path("result");
JsonNode addressComponentNode = resultNode.path("addressComponent");
// 提取格式化地址
if (resultNode.has("formatted_address")) {
String addressName = resultNode.get("formatted_address").asText();
resultMap.put("address", addressName);
}
// 提取街道名称
if (addressComponentNode.has("town")) {
String townName = addressComponentNode.get("town").asText();
resultMap.put("streetName", townName);
}
} else {
String errorContent = EntityUtils.toString(response.getEntity(), "UTF-8");
log.error("请求失败,状态码: " + statusCode + ", 错误信息: " + errorContent);
}
} catch (IOException e) {
log.error("请求失败,错误: " + e.getMessage());
e.printStackTrace();
}
}
return resultMap;
}
}
本文仅需部分返回字段,若有其他需求可自行增加取值及返回。
{
"result": {
"formatted_address": "北京市西城区西什库大街31号院23东方开元信息科技公司",
"location": {
"lon": 116.37304,
"lat": 39.92594
},
"addressComponent": {
"address": "西什库大街31号院23",
"city": "北京市西城区",
"road": "大红罗厂街",
"poi_position": "东北",
"address_position": "东北",
"road_distance": 49,
"poi": "东方开元信息科技公司",
"poi_distance": "38",
"address_distance": 38
}
},
"msg": "ok",
"status": "0"
}