稀土掘金技术社区 2024年12月20日
老板想集成地图又不想花钱,于是让我...
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

公司为降低成本,选用天地图服务。文章介绍了天地图的功能、集成到系统的关键代码,包括逆地理编码、周边搜索、文本搜索及坐标系转换。

🎯天地图是中国领先在线地图服务,提供全面地理信息且免费

🗺️逆地理编码可将经纬度转换为可读地址,有详细实现代码

🔍周边搜索能根据地点经纬度搜索附近其他地点,代码示例给出

📝文本搜索允许按关键词搜索地点,实现过程在文中呈现

🌐介绍了天地图坐标系转换的工具类及相关代码

原创 JustinNeil 2024-12-20 08:30 重庆

点击关注公众号,“技术干货” 及时达!

前言

在数字化时代,地图服务已成为各类应用的标配,无论是导航、位置分享还是商业分析,地图都扮演着不可或缺的角色。然而,高质量的地图服务往往伴随着不菲的授权费用。公司原先使用的是国内某知名地图服务,但随着业务的扩展和成本的考量,老板决定寻找一种成本更低的解决方案。于是,我们的目光转向了免费的地图服务——天地图。

天地图简介

天地图(http://lbs.tianditu.gov.cn/server/guide.html)是中国领先的在线地图服务之一,提供全面的地理信息服务。它的API支持地理编码、逆地理编码、周边搜索等多种功能,且完全免费。这正是我们需要的。

具体实现代码

为了将天地图集成到我们的系统中,我们需要进行一系列的开发工作。以下是实现过程中的关键代码段。

逆地理编码

逆地理编码是将经纬度转换为可读的地址。在天地图中,这一功能可以通过以下代码实现:

public static MapLocation reverseGeocode(String longitude, String latitude) {    Request request = new Request();    LocateInfo locateInfo = GCJ02_WGS84Utils.gcj02_To_Wgs84(Double.valueOf(latitude), Double.valueOf(longitude));    longitude = String.valueOf(locateInfo.getLongitude());    latitude = String.valueOf(locateInfo.getLatitude());    String postStr = String.format(REVERSE_GEOCODE_POST_STR, longitude, latitude);    String encodedPostStr = null;    try {        encodedPostStr = URLEncoder.encode(postStr, "UTF-8");    } catch (UnsupportedEncodingException e) {        e.printStackTrace();    }    String url = REVERSE_GEOCODE_URL + "?tk=" + TK + "&type=" + GEOCODE + "&postStr=" + encodedPostStr;    request.setUrl(url);    Response<String> response = HttpClientHelper.getWithoutUserAgent(request, null);    if (response.getSuccess()) {        String body = response.getBody();        JSONObject jsonObject = JSON.parseObject(body);        String status = jsonObject.getString("status");        if (!"0".equals(status)) {            return null;        }        JSONObject resultObject = jsonObject.getJSONObject("result");        MapLocation mapLocation = new MapLocation();        String formattedAddress = resultObject.getString("formatted_address");        mapLocation.setAddress(formattedAddress);        String locationStr = resultObject.getString("location");        JSONObject location = JSON.parseObject(locationStr);        String lon = location.getString("lon");        String lat = location.getString("lat");        locateInfo = GCJ02_WGS84Utils.wgs84_To_Gcj02(Double.valueOf(lat), Double.valueOf(lon));        lon = String.valueOf(locateInfo.getLongitude());        lat = String.valueOf(locateInfo.getLatitude());        mapLocation.setLongitude(lon);        mapLocation.setLatitude(lat);        JSONObject addressComponent = resultObject.getJSONObject("addressComponent");        String address = addressComponent.getString("address");        mapLocation.setName(address);        mapLocation.setCity(addressComponent.getString("city"));        return mapLocation;    }    return null;}

周边搜索

周边搜索允许我们根据一个地点的经纬度搜索附近的其他地点。实现代码如下:

public static List<MapLocation> nearbySearch(String query, String longitude, String latitude, String radius) {    LocateInfo locateInfo = GCJ02_WGS84Utils.gcj02_To_Wgs84(Double.valueOf(latitude), Double.valueOf(longitude));    longitude = String.valueOf(locateInfo.getLongitude());    latitude = String.valueOf(locateInfo.getLatitude());    Request request = new Request();    String longLat = longitude + "," + latitude;    String postStr = String.format(NEARBY_SEARCH_POST_STR, query, Integer.valueOf(radius), longLat);    String encodedPostStr = null;    try {        encodedPostStr = URLEncoder.encode(postStr, "UTF-8");    } catch (UnsupportedEncodingException e) {        e.printStackTrace();    }    String url = SEARCH_URL + "?tk=" + TK + "&type=" + QUERY + "&postStr=" + encodedPostStr;    request.setUrl(url);    Response<String> response = HttpClientHelper.getWithoutUserAgent(request, null);    List<MapLocation> list = new ArrayList<>();    if (response.getSuccess()) {        String body = response.getBody();        JSONObject jsonObject = JSON.parseObject(body);        JSONObject statusObject = jsonObject.getJSONObject("status");        String infoCode = statusObject.getString("infocode");        if (!"1000".equals(infoCode)) {            return new ArrayList<>();        }        String resultType = jsonObject.getString("resultType");        String count = jsonObject.getString("count");        if (!"1".equals(resultType) || "0".equals(count)) {            return new ArrayList<>();        }        JSONArray poisArray = jsonObject.getJSONArray("pois");        for (int i = 0; i < poisArray.size(); i++) {            JSONObject poiObject = poisArray.getJSONObject(i);            MapLocation mapLocation = new MapLocation();            mapLocation.setName(poiObject.getString("name"));            mapLocation.setAddress(poiObject.getString("address"));            String lonlat = poiObject.getString("lonlat");            String[] lonlatArr = lonlat.split(",");            locateInfo = GCJ02_WGS84Utils.wgs84_To_Gcj02(Double.valueOf(lonlatArr[1]), Double.valueOf(lonlatArr[0]));            String lon = String.valueOf(locateInfo.getLongitude());            String lat = String.valueOf(locateInfo.getLatitude());            mapLocation.setLongitude(lon);            mapLocation.setLatitude(lat);            list.add(mapLocation);        }    }    return list;}

文本搜索

文本搜索功能允许用户根据关键词搜索地点。实现代码如下:

public static List<MapLocation> searchByText(String query, String mapBound) {    Request request = new Request();    String postStr = String.format(SEARCH_BY_TEXT_POST_STR, query, mapBound);    String encodedPostStr = null;    try {        encodedPostStr = URLEncoder.encode(postStr, "UTF-8");    } catch (UnsupportedEncodingException e) {        e.printStackTrace();    }    String url = SEARCH_URL + "?tk=" + TK + "&type=" + QUERY + "&postStr=" + encodedPostStr;    request.setUrl(url);    Response<String> response = HttpClientHelper.getWithoutUserAgent(request, null);    List<MapLocation> list = new ArrayList<>();    if (response.getSuccess()) {        String body = response.getBody();        JSONObject jsonObject = JSON.parseObject(body);        JSONObject statusObject = jsonObject.getJSONObject("status");        String infoCode = statusObject.getString("infocode");        if (!"1000".equals(infoCode)) {            return new ArrayList<>();        }        String resultType = jsonObject.getString("resultType");        String count = jsonObject.getString("count");
if (!"1".equals(resultType) || "0".equals(count)) { return new ArrayList<>(); } JSONArray poisArray = jsonObject.getJSONArray("pois"); for (int i = 0; i < poisArray.size(); i++) { JSONObject poiObject = poisArray.getJSONObject(i); MapLocation mapLocation = new MapLocation(); mapLocation.setName(poiObject.getString("name")); mapLocation.setAddress(poiObject.getString("address")); String lonlat = poiObject.getString("lonlat"); String[] lonlatArr = lonlat.split(","); LocateInfo locateInfo = GCJ02_WGS84Utils.wgs84_To_Gcj02(Double.valueOf(lonlatArr[1]), Double.valueOf(lonlatArr[0])); String lon = String.valueOf(locateInfo.getLongitude()); String lat = String.valueOf(locateInfo.getLatitude()); mapLocation.setLongitude(lon); mapLocation.setLatitude(lat); list.add(mapLocation); } } return list;}

坐标系转换

由于天地图使用的是WGS84坐标系,而国内常用的是GCJ-02坐标系,因此我们需要进行坐标转换。以下是坐标转换的工具类:


/** * WGS-84:是国际标准,GPS坐标(Google Earth使用、或者GPS模块) * GCJ-02:中国坐标偏移标准,Google Map、高德、腾讯使用 * BD-09:百度坐标偏移标准,Baidu Map使用(经由GCJ-02加密而来) * <p> * 这些坐标系是对真实坐标系统进行人为的加偏处理,按照特殊的算法,将真实的坐标加密成虚假的坐标, * 而这个加偏并不是线性的加偏,所以各地的偏移情况都会有所不同,具体的内部实现是没有对外开放的, * 但是坐标之间的转换算法是对外开放,在网上可以查到的,此算法的误差在0.1-0.4之间。 */public class GCJ02_WGS84Utils {
public static double pi = 3.1415926535897932384626;//圆周率 public static double a = 6378245.0;//克拉索夫斯基椭球参数长半轴a public static double ee = 0.00669342162296594323;//克拉索夫斯基椭球参数第一偏心率平方
/** * 从GPS转高德 * isOutOfChina 方法用于判断经纬度是否在中国范围内,如果不在中国范围内,则直接返回原始的WGS-84坐标。 * transformLat 和 transformLon 是辅助函数,用于进行经纬度的转换计算。 * 最终,wgs84ToGcj02 方法返回转换后的GCJ-02坐标系下的经纬度。 */ public static LocateInfo wgs84_To_Gcj02(double lat, double lon) { LocateInfo info = new LocateInfo(); if (isOutOfChina(lat, lon)) { info.setChina(false); info.setLatitude(lat); info.setLongitude(lon); } else { double dLat = transformLat(lon - 105.0, lat - 35.0); double dLon = transformLon(lon - 105.0, lat - 35.0); double radLat = lat / 180.0 * pi; double magic = Math.sin(radLat); magic = 1 - ee * magic * magic; double sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); double mgLat = lat + dLat; double mgLon = lon + dLon; info.setChina(true); info.setLatitude(mgLat); info.setLongitude(mgLon); } return info; }
//从高德转到GPS public static LocateInfo gcj02_To_Wgs84(double lat, double lon) { LocateInfo info = new LocateInfo(); LocateInfo gps = transform(lat, lon); double lontitude = lon * 2 - gps.getLongitude(); double latitude = lat * 2 - gps.getLatitude(); info.setChina(gps.isChina()); info.setLatitude(latitude); info.setLongitude(lontitude); return info; }
// 判断坐标是否在国外 private static boolean isOutOfChina(double lat, double lon) { if (lon < 72.004 || lon > 137.8347) return true; if (lat < 0.8293 || lat > 55.8271) return true; return false; }
//转换 private static LocateInfo transform(double lat, double lon) { LocateInfo info = new LocateInfo(); if (isOutOfChina(lat, lon)) { info.setChina(false); info.setLatitude(lat); info.setLongitude(lon); return info; } double dLat = transformLat(lon - 105.0, lat - 35.0); double dLon = transformLon(lon - 105.0, lat - 35.0); double radLat = lat / 180.0 * pi; double magic = Math.sin(radLat); magic = 1 - ee * magic * magic; double sqrtMagic = Math.sqrt(magic); dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi); dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi); double mgLat = lat + dLat; double mgLon = lon + dLon; info.setChina(true); info.setLatitude(mgLat); info.setLongitude(mgLon);
return info; }
//转换纬度所需 private static double transformLat(double x, double y) { double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * Math.sin(y * pi) + 40.0 * Math.sin(y / 3.0 * pi)) * 2.0 / 3.0; ret += (160.0 * Math.sin(y / 12.0 * pi) + 320 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0; return ret; }
//转换经度所需 private static double transformLon(double x, double y) { double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); ret += (20.0 * Math.sin(6.0 * x * pi) + 20.0 * Math.sin(2.0 * x * pi)) * 2.0 / 3.0; ret += (20.0 * Math.sin(x * pi) + 40.0 * Math.sin(x / 3.0 * pi)) * 2.0 / 3.0; ret += (150.0 * Math.sin(x / 12.0 * pi) + 300.0 * Math.sin(x / 30.0 * pi)) * 2.0 / 3.0; return ret; }}

结论

通过上述代码,我们成功地将天地图集成到了我们的系统中,不仅满足了功能需求,还大幅降低了成本。这一过程中,我们深入理解了地图服务的工作原理,也提升了团队的技术能力。

注意事项

通过这次集成,我们不仅为公司节省了成本,还提升了系统的稳定性和用户体验。在未来的开发中,我们将继续探索更多高效、低成本的技术解决方案。

点击关注公众号,“技术干货” 及时达!

阅读原文

跳转微信打开

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

天地图 地图服务 逆地理编码 周边搜索 文本搜索
相关文章