文章目录
- 背景
- 目标
- 方案设计:
- 表结构设计:
- 方案实现
- 1.高德API获取行政区边界点
- 2.外包矩形中心作为中心点
- 3.坐标点经纬度转换为geohash
- 测试
- 建表语句
- 测试造数
- 测试用例
- 测试结果
- 总结
- 总结
背景
最近遇到一个需求,需要查询给定的经纬度坐标点,是否在指定的城市内。 分析需求,需要获取城市区域的边界,再判断目标坐标点是否在边界内。单个坐标点的查询可以通过mysql空间函数来实现,要是遇到一批坐标点都要转城市,那就要重新考虑方案了。咨询了隔壁组的数据开发大佬,提出了可以开发一张经纬度转行政区的维表,后续查询直接关联表就好了,于是有了这篇文章。
目标
用户传入一批经纬度,可以快速匹配出对应的行政区,包括省份、城市、区域。
方案设计:
-
调用高德API 获取所有行政区以及对应的边界点
-
坐标点经纬度 -> geohash,将每个边界点转换为6位精度的geohash
-
取这些Geohash中的中心点的Geohash,作为区域代表
-
整合数据,得到行政区和区域中心点Geohash的映射表
-
使用经纬度转行政区表,进行查询
表结构设计:
全国行政区表
以下是根据您提供的内容整理的表格结构,包括字段名、字段类型、字段描述和说明:
字段名 | 字段类型 | 字段描述 | 说明 |
---|---|---|---|
province_code | STRING | 省编号 | 用于标识省份的唯一编号 |
province_name | STRING | 省名称 | 省份的全称 |
city_code | STRING | 市编号 | 用于标识城市的唯一编号 |
city_name | STRING | 市名称若为直辖市则是显示市辖区 | 城市的全称,对于直辖市则显示为市辖区 |
dist_code | STRING | 区编号 | 用于标识区/县/市辖区的唯一编号,也是主键 |
dist_name | STRING | 区名称 | 区/县/市辖区的全称 |
full_name | STRING | 行政单位全名 | 由省名称、市名称(或市辖区)和区名称组合而成的全名 |
is_direct_city | BIGINT | 是否直辖市或特别行政区 | 1表示是直辖市或特别行政区,0表示否 |
经纬度转行政区维表
字段名 | 字段类型 | 字段描述 | 说明 |
---|---|---|---|
geohash | STRING | geohash(长度为6位) | 将经纬度转换为geohash与此字段关联,用于地理位置的近似表示 |
full_name | STRING | 解析结果全名(省-市-区格式) | 表示行政单位的全名,格式为省份-城市-区域 |
province_code | STRING | 省编码 | 用于标识省份的唯一编码 |
province_name | STRING | 省名称 | 省份的全称 |
city_code | STRING | 市编码 | 用于标识城市的唯一编码 |
city_name | STRING | 市名称 | 城市的全称 |
district_code | STRING | 区编码 | 用于标识区/县/市辖区的唯一编码 |
district_name | STRING | 区名称 | 区/县/市辖区的全称 |
precision | STRING | geohash长度 | 5位精确到2400米,6位精确到610米 |
方案实现
1.高德API获取行政区边界点
详细接口可以参考高德官网链接:https://lbs.amap.com/api/webservice/guide/api/district
2.外包矩形中心作为中心点
import java.util.ArrayList;
import java.util.List;
class Point {
double lng; // 经度
double lat; // 纬度
Point(double lng, double lat) {
this.lng = lng;
this.lat = lat;
}
}
public class BoundingBoxCenterCalculator {
public static Point calculateBoundingBoxCenter(List<Point> boundaryPoints) {
double minLng = Double.MAX_VALUE;
double minLat = Double.MAX_VALUE;
double maxLng = Double.MIN_VALUE;
double maxLat = Double.MIN_VALUE;
// 找到最小和最大经纬度值
for (Point point : boundaryPoints) {
if (point.lng < minLng) minLng = point.lng;
if (point.lng > maxLng) maxLng = point.lng;
if (point.lat < minLat) minLat = point.lat;
if (point.lat > maxLat) maxLat = point.lat;
}
// 计算外接矩形的中心点
double centerLng = (minLng + maxLng) / 2;
double centerLat = (minLat + maxLat) / 2;
return new Point(centerLng, centerLat);
}
public static void main(String[] args) {
// 假设你已经从高德API或其他服务中获取了行政区的边界点
List<Point> boundaryPoints = new ArrayList<>();
// 示例点(需要替换为实际获取的边界点)
boundaryPoints.add(new Point(116.397128, 39.916527)); // 示例点1
boundaryPoints.add(new Point(116.406921, 39.904989)); // 示例点2
// ... 添加更多边界点
// 计算中心点
Point centerPoint = calculateBoundingBoxCenter(boundaryPoints);
System.out.println("中心点坐标: 经度=" + centerPoint.lng + ", 纬度=" + centerPoint.lat);
}
}
3.坐标点经纬度转换为geohash
Geohash 是一种用于地理编码的算法,可以将经纬度坐标编码成一个较短的字符串,便于存储、传输和搜索。下面是一个在 Java 中将经纬度坐标转换成 Geohash 的实现。这个实现使用了一个流行的 Java 库 geohash
。
-
项目中添加
geohash
库的依赖。对于 Maven 项目,可以在pom.xml
文件中添加以下依赖:<dependency> <groupId>ch.hsr</groupId> <artifactId>geohash</artifactIda> <version>1.3.0</version> </dependency>
-
使用示例:使用
GeoHash.withCharacterPrecision(centerPoint.lat, centerPoint.lng, precision);
方法将经纬度编码成 Geohash。
import ch.hsr.geohash.GeoHash;
import ch.hsr.geohash.GeoHashUtil;
public class GeoHashExample {
public static void main(String[] args) {
double latitude = 48.858844; // 示例纬度
double longitude = 2.294351; // 示例经度
// 设置Geohash的精度,范围从1到12,精度越高字符串越长
int precision = 12;
// 计算Geohash
GeoHash geoHash = GeoHash.withCharacterPrecision(latitude, longitude,precision));
String geohashString = geoHash.toString();
System.out.println("Geohash: " + geohashString);
}
}
测试
建表语句
CREATE TABLE t_administrative_units (
province_code VARCHAR(10) NOT NULL COMMENT '省编号,用于标识省份的唯一编号',
province_name VARCHAR(50) NOT NULL COMMENT '省名称,省份的全称',
city_code VARCHAR(10) NOT NULL COMMENT '市编号,用于标识城市的唯一编号',
city_name VARCHAR(50) NOT NULL COMMENT '市名称,若为直辖市则是显示市辖区,城市的全称',
dist_code VARCHAR(10) NOT NULL COMMENT '区编号,用于标识区/县/市辖区的唯一编号,也是主键',
dist_name VARCHAR(50) NOT NULL COMMENT '区名称,区/县/市辖区的全称',
full_name VARCHAR(150) GENERATED ALWAYS AS (CONCAT(province_name, city_name, dist_name)) STORED COMMENT '行政单位全名,由省名称、市名称(或市辖区)和区名称组合而成的全名',
is_direct_city BIGINT(1) NOT NULL COMMENT '是否直辖市或特别行政区,1表示是,0表示否',
PRIMARY KEY (dist_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='行政单位信息表';
CREATE TABLE t_geohash_to_address (
geohash varchar(6) NOT NULL COMMENT 'geohash(长度为6位),将经纬度转换为geohash与此字段关联,用于地理位置的近似表示',
full_name VARCHAR(150) NOT NULL COMMENT '解析结果全名(省-市-区格式),表示行政单位的全名',
province_code VARCHAR(10) NOT NULL COMMENT '省编码,用于标识省份的唯一编码',
province_name VARCHAR(50) NOT NULL COMMENT '省名称,省份的全称',
city_code VARCHAR(10) NOT NULL COMMENT '市编码,用于标识城市的唯一编码',
city_name VARCHAR(50) NOT NULL COMMENT '市名称,城市的全称',
district_code VARCHAR(10) NOT NULL COMMENT '区编码,用于标识区/县/市辖区的唯一编码',
district_name VARCHAR(50) NOT NULL COMMENT '区名称,区/县/市辖区的全称',
`precision` int(2) NOT NULL COMMENT 'geohash长度,5位精确到2400米,6位精确到610米)',
PRIMARY KEY (geohash)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='geohash-行政区映射维表';
测试造数
行政区数据表
INSERT INTO t_administrative_units (province_code, province_name, city_code, city_name, dist_code, dist_name, is_direct_city) VALUES
('110000', '北京市', '110000', '北京市', '110101', '东城区', 1),
('120000', '天津市', '120000', '天津市', '120101', '和平区', 1),
('320000', '江苏省', '320100', '南京市', '320106', '鼓楼区', 0),
('320000', '江苏省', '320500', '苏州市', '320506', '吴中区', 0),
('330000', '浙江省', '330100', '杭州市', '330102', '上城区', 0),
('330000', '浙江省', '330200', '宁波市', '330203', '海曙区', 0),
('440000', '广东省', '440100', '广州市', '440106', '天河区', 0),
('440000', '广东省', '440300', '深圳市', '440304', '福田区', 0),
('500000', '重庆市', '500000', '重庆市', '500101', '万州区', 1),
('650000', '新疆维吾尔自治区', '650100', '乌鲁木齐市', '650102', '天山区', 0);
经纬度geohash-行政区维表
INSERT INTO t_geohash_to_address (geohash, full_name, province_code, province_name, city_code, city_name, district_code, district_name, `precision`) VALUES
('wx4g0c', '北京市-北京市-东城区', '110000', '北京市', '110000', '北京市', '110101', '东城区', 6),
('wtwy3r', '上海市-上海市-黄浦区', '310000', '上海市', '310000', '上海市', '310101', '黄浦区', 6),
('u4uru3', '广东省-广州市-天河区', '440000', '广东省', '440100', '广州市', '440106', '天河区', 6),
('u4uuxq', '广东省-深圳市-福田区', '440000', '广东省', '440300', '深圳市', '440304', '福田区', 6),
('wm3wxr', '浙江省-杭州市-西湖区', '330000', '浙江省', '330100', '杭州市', '330106', '西湖区', 6);
测试用例
package cn.suwg;
import ch.hsr.geohash.GeoHash;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: suwg
* @Date: 2024/11/21
*/
public class GeoHashToAddress {
public static Point calculateBoundingBoxCenter(List<Point> boundaryPoints) {
double minLng = Double.MAX_VALUE;
double minLat = Double.MAX_VALUE;
double maxLng = Double.MIN_VALUE;
double maxLat = Double.MIN_VALUE;
// 找到最小和最大经纬度值
for (Point point : boundaryPoints) {
if (point.lng < minLng) minLng = point.lng;
if (point.lng > maxLng) maxLng = point.lng;
if (point.lat < minLat) minLat = point.lat;
if (point.lat > maxLat) maxLat = point.lat;
}
// 计算外接矩形的中心点
double centerLng = (minLng + maxLng) / 2;
double centerLat = (minLat + maxLat) / 2;
return new Point(centerLng, centerLat);
}
public static void main(String[] args) {
// 假设你已经从高德API或其他服务中获取了行政区的边界点
List<Point> boundaryPoints = new ArrayList<>();
// 示例点(需要替换为实际获取的边界点)
boundaryPoints.add(new Point(116.397128, 39.916527)); // 示例点1
boundaryPoints.add(new Point(116.406921, 39.904989)); // 示例点2
// ... 添加更多边界点
// 计算中心点
Point centerPoint = calculateBoundingBoxCenter(boundaryPoints);
System.out.println("中心点坐标: 经度=" + centerPoint.lng + ", 纬度=" + centerPoint.lat);
// 计算geoHash
int precision = 6;
// 计算Geohash
GeoHash geoHash = GeoHash.withCharacterPrecision(centerPoint.lat, centerPoint.lng, precision);
String geohashStr = geoHash.toBase32();
System.out.println("GeoHash: " + geoHash.toBase32());
}
}
class Point {
double lng; // 经度
double lat; // 纬度
Point(double lng, double lat) {
this.lng = lng;
this.lat = lat;
}
}
测试结果
查询数据库结果
总结
总结
本文介绍了一个将经纬度坐标快速匹配到对应行政区的实现方案。该方案主要包括以下步骤:通过高德API获取所有行政区信息和边界点,将行政区数据存储到t_administrative_units
(行政单位信息表),计算行政区的中心点,转成geohash编码,并记录geohash和行政区的映射关系到t_geohash_to_address
,本方案适用于热力图、行车轨迹灯不要求精确位置
的场景。
该方案的优势在于
- 高效性:通过GeoHash编码,将经纬度坐标转换为较短的字符串,便于存储和搜索,提高了匹配效率。
- 准确性:使用外接矩形的中心点作为行政区的代表点,能快速计算出中心点
- 可扩展性:方案中的表结构和代码设计都考虑到了扩展性,可以方便地添加更多的行政区或调整精度。
该方案的不足在于
-
粗粒度:针对高精度的经纬度定位行政区的需求,本方案精度不够
-
近似统计:使用外接矩形中心点的方式,可能因行政区的形状不规则而导致中心点偏差较大。