老板想集成地图又不想花钱,于是让我...

前言

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

天地图简介

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

具体实现代码

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

1. 逆地理编码

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

java 复制代码
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;
}

2. 周边搜索

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

java 复制代码
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;
}

3. 文本搜索

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

java 复制代码
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;
}

4. 坐标系转换

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

java 复制代码
/**
 * 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;
    }
}

结论

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

注意事项

  • 确保在使用天地图API时遵守其服务条款,尤其是在商业用途中。
  • 由于网络或其他原因,天地图API可能存在访问延迟或不稳定的情况,建议在生产环境中做好异常处理和备用方案。
  • 坐标系转换是一个复杂的过程,确保使用可靠的算法和工具进行转换,以保证定位的准确性。

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

相关推荐
搬码后生仔1 小时前
asp.net core webapi项目中 在生产环境中 进不去swagger
chrome·后端·asp.net
凡人的AI工具箱1 小时前
每天40分玩转Django:Django国际化
数据库·人工智能·后端·python·django·sqlite
Lx3522 小时前
Pandas数据重命名:列名与索引为标题
后端·python·pandas
小池先生2 小时前
springboot启动不了 因一个spring-boot-starter-web底下的tomcat-embed-core依赖丢失
java·spring boot·后端
小蜗牛慢慢爬行3 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
wm10434 小时前
java web springboot
java·spring boot·后端
龙少95435 小时前
【深入理解@EnableCaching】
java·后端·spring
溟洵7 小时前
Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)
linux·运维·数据库·后端·sql·mysql
SomeB1oody10 小时前
【Rust自学】6.1. 定义枚举
开发语言·后端·rust
SomeB1oody10 小时前
【Rust自学】5.3. struct的方法(Method)
开发语言·后端·rust