地址是否在某区域范围内--基于百度地图

功能描述

项目需要判断某个地点是否在某个区域范围内,区域范围需要支持用户编辑,所以不能直接对地址进行解析。本文采用的技术方案是利用百度地图将地址进行编码成经纬度,然后判断经纬度是否在用户绘制的区域范围内即可。

前期准备

申请百度地图账号,获取对应的key,此流程用户自行百度,操作非常简便,就不对此进行详细说明了。

技术实现

地理编码

将地址信息进行地理编码,由实际地理位置转化为经纬度信息,百度地图每天有5000额度的免费转化,如果用户量大需要用户申请额度。

@Value("${map.baidu.key}")
private String key;

@Value("${map.baidu.coordinateUrl:https://api.map.baidu.com/geocoding/v3/?address=%s&output=json&ak=%s}")
private String coordinateUrl;

/**
 * 输入地址信息, 返回该地址的经纬度信息
 * 地址编码
 *
 * @param address 地址参数
 * @return 经纬度参数
 */
public CoordinateDTO getCoordinate(String address) {

    String requestUrl = String.format(coordinateUrl, address, key);
    String response = restTemplate.getForObject(requestUrl, String.class);
    if (response == null) {
        log.error("百度地图地址解析错误,解析地址是:{}, 请求结果为空", address);
        throw new BizException("百度地图地址解析错误,请求地址是" + address, GlobalErrorInfo.THIRD_ERROR);
    }
    JSONObject jsonObject = JSONObject.parseObject(response);
    if (jsonObject.getInteger("status") != 0) {
        log.error("百度地图地址解析错误,解析地址是:{}, 请求结果是:{}", address, jsonObject.toJSONString());
        throw new BizException("百度地图地址解析错误,请求地址是" + address, GlobalErrorInfo.THIRD_ERROR);
    }

    // 百度地图有精确度字段,但同一地址多次请求返回值一致,所以精确度导致的问题需要人工处理
    JSONObject result = jsonObject.getJSONObject("result");
    return result.getObject("location", CoordinateDTO.class);
}

坐标位置转化

如果地址信息是其他地图的经纬度,可以将其经纬度信息转化为百度地图的经纬度信息,然后再进行判断点是否在区块范围内。

/**
 * 坐标装换, 将坐标进行转换
 *
 * @param coordinate 需要转换的坐标
 * @param from       需要转换坐标的类型
 * @param to         转换后的坐标类型
 * @return 转换后的坐标值
 */
public CoordinateDTO geoConv(CoordinateDTO coordinate, BaiduMapEnums from, BaiduMapEnums to) {
    String params = coordinate.getLng() + "," + coordinate.getLat();
    String requestUrl = String.format(geoConv, params, from.getValue(), to.getValue(), key);
    String response = restTemplate.getForObject(requestUrl, String.class);

    if (response == null) {
        log.error("百度地图地址转换错误, 请求经纬度为:{}, 请求结果为空", params);
        throw new BizException("百度地图地址转换错误, 请求经纬度为" + params, GlobalErrorInfo.THIRD_ERROR);
    }

    JSONObject jsonObject = JSONObject.parseObject(response);
    if (jsonObject.getInteger("status") != 0) {
        log.error("百度地图地址转换错误,请求经纬度为:{}, 请求结果是:{}", params, jsonObject.toJSONString());
        throw new BizException("百度地图地址转换错误,请求经纬度为" + params, GlobalErrorInfo.THIRD_ERROR);
    }

    JSONObject result = jsonObject.getJSONArray("result").getJSONObject(0);

    CoordinateDTO dto = new CoordinateDTO();
    dto.setLng(result.getString("x"));
    dto.setLat(result.getString("y"));
    return dto;
}
@Getter
public enum BaiduMapEnums {

    GPS(1, "GPS标准坐标"),
    SOGOU(2, "搜狗地图坐标"),
    SPARK(3, "火星坐标(gcj02),即高德地图、腾讯地图和MapABC等地图使用的坐标"),
    MERCATOR(4, "3中列举的地图坐标对应的墨卡托平面坐标"),
    BAIDU(5, "百度地图采用的经纬度坐标"),
    BAIDU_MERCATOR(6, "百度地图采用的墨卡托平面坐标"),
    TUBA(7, "图吧地图坐标"),
    M51(8, "51地图坐标");


    private Integer value;

    private String description;

    BaiduMapEnums(Integer value, String description) {
        this.value = value;
        this.description = description;
    }
}

判断点是否在多边形内

判断给出点是否在区域范围内,采用的算法是做点的垂直方向的延长线,看看点与多边形有多少个交点,如果是奇数个,则在区域范围内,如果是偶数个,则在区域范围外。

/**
 * 返回一个点是否在一个多边形区域内
 *
 * @param mPoints 多边形坐标点列表
 * @param point   待判断点
 * @return true 多边形包含这个点,false 多边形未包含这个点。
 */
public static boolean isPolygonContainsPoint(List<LatLng> mPoints, LatLng point) {
    int nCross = 0;
    for (int i = 0; i < mPoints.size(); i++) {
        LatLng p1 = mPoints.get(i);
        LatLng p2 = mPoints.get((i + 1) % mPoints.size());
        // 取多边形任意一个边,做点point的垂直延长线,求解与当前边的交点个数
        // p1p2是平行线段,要么没有交点,要么有无限个交点
        if (p1.getLongitude().equals(p2.getLongitude()))
            continue;
        // point 在p1p2 底部 --> 无交点
        if (point.getLongitude() < Math.min(p1.getLongitude(), p2.getLongitude()))
            continue;
        // point 在p1p2 顶部 --> 无交点
        if (point.getLongitude() >= Math.max(p1.getLongitude(), p2.getLongitude()))
            continue;
        // 求解 point点水平线与当前p1p2边的交点的 X 坐标
        double x = (point.getLongitude() - p1.getLongitude()) * (p2.getLatitude() - p1.getLatitude()) / (p2.getLongitude() - p1.getLongitude()) + p1.getLatitude();
        if (x > point.getLatitude()) // 当x=point.x时,说明point在p1p2线段上
            nCross++; // 只统计单边交点
    }
    // 单边交点为偶数,点在多边形之外 ---
    return (nCross % 2 == 1);
}

/**
 * 返回一个点是否在一个多边形边界上
 *
 * @param mPoints 多边形坐标点列表
 * @param point   待判断点
 * @return true 点在多边形边上,false 点不在多边形边上。
 */
public static boolean isPointInPolygonBoundary(List<LatLng> mPoints, LatLng point) {
    for (int i = 0; i < mPoints.size(); i++) {
        LatLng p1 = mPoints.get(i);
        LatLng p2 = mPoints.get((i + 1) % mPoints.size());
        // 取多边形任意一个边,做点point的垂直延长线,求解与当前边的交点个数
        // point 在p1p2 底部 --> 无交点
        if (point.getLongitude() < Math.min(p1.getLongitude(), p2.getLongitude()))
            continue;
        // point 在p1p2 顶部 --> 无交点
        if (point.getLongitude() > Math.max(p1.getLongitude(), p2.getLongitude()))
            continue;

        // p1p2是平行线段,要么没有交点,要么有无限个交点
        if (p1.getLongitude().equals(p2.getLongitude())) {
            double minX = Math.min(p1.getLatitude(), p2.getLatitude());
            double maxX = Math.max(p1.getLatitude(), p2.getLatitude());
            // point在水平线段p1p2上,直接return true
            if ((point.getLongitude().equals(p1.getLongitude())) && (point.getLatitude() >= minX && point.getLatitude() <= maxX)) {
                return true;
            }
        } else { // 求解交点
            double x = (point.getLongitude() - p1.getLongitude()) * (p2.getLatitude() - p1.getLatitude()) / (p2.getLongitude() - p1.getLongitude()) + p1.getLatitude();
            if (x == point.getLatitude()) // 当x=point.x时,说明point在p1p2线段上
                return true;
        }
    }
    return false;
}
@TableName(value = "sys_lat_lng")
@Data
public class LatLng implements Serializable {
    /**
     * 纬度
     */
    @TableField(value = "latitude")
    private Double latitude;

    /**
     * 精度
     */
    @TableField(value = "longitude")
    private Double longitude;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

参考博客:
https://lbsyun.baidu.com/faq/api?title=webapi/guide/webservice-geocoding
https://blog.csdn.net/shao941122/article/details/51504519

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值