以下内容来至https://cloud.tencent.com/developer/article/1008487,仅做备忘。
简介
现在几乎所有的O2O应用中都会存在“按范围搜素、离我最近、显示距离”等等基于位置的交互,那这样的功能是怎么实现的呢?本文提供的实现方式,适用于所有数据库。
实现
为了方便下面说明,先给出一个初始表结构,我使用的是MySQL:
CREATE TABLE `customer` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键', `name` VARCHAR(5) NOT NULL COMMENT '名称', `lon` DOUBLE(9,6) NOT NULL COMMENT '经度', `lat` DOUBLE(8,6) NOT NULL COMMENT '纬度', PRIMARY KEY (`id`) ) COMMENT='商户表' CHARSET=utf8mb4 ENGINE=InnoDB ;
实现过程主要分为四步: 1. 搜索 在数据库中搜索出接近指定范围内的商户,如:搜索出1公里范围内的。 2. 过滤 搜索出来的结果可能会存在超过1公里的,需要再次过滤。
如果对精度没有严格要求,可以跳过。 3. 排序 距离由近到远排序。如果不需要,可以跳过。 4. 分页 如果需要2、3步,才需要对分页特殊处理。如果不需要,可以在第1步直接SQL分页。
第1步数据库完成,后3步应用程序完成。
step1 搜索
搜索可以用下面两种方式来实现。
区间查找
customer表中使用两个字段存储了经度和纬度,如果提前计算出经纬度的范围,然后在这两个字段上加上索引,那搜索性能会很不错。 那怎么计算出经纬度的范围呢?
已知条件是移动设备所在的经纬度,还有满足业务要求的半径,这很像初中的一道平面几何题:给定圆心坐标和半径,求该圆外切正方形四个顶点的坐标。而我们面对的是一个球体,可以使用spatial4j来计算。
<dependency> <groupId>com.spatial4j</groupId> <artifactId>spatial4j</artifactId> <version>0.5</version> </dependency>
// 移动设备经纬度 double lon = 116.312528, lat = 39.983733; // 千米 int radius = 1; SpatialContext geo = SpatialContext.GEO; Rectangle rectangle = geo.getDistCalc().calcBoxByDistFromPt( geo.makePoint(lon, lat), radius * DistanceUtils.KM_TO_DEG, geo, null); System.out.println(rectangle.getMinX() + "-" + rectangle.getMaxX());// 经度范围 System.out.println(rectangle.getMinY() + "-" + rectangle.getMaxY());// 纬度范围
计算出经纬度范围之后,SQL是这样:
SELECT id, name FROM customer WHERE (lon BETWEEN ? AND ?) AND (lat BETWEEN ? AND ?);
需要给lon、lat两个字段建立联合索引:
INDEX `idx_lon_lat` (`lon`, `lat`)
geohash
geohash的原理不讲了,详细可以看这篇文章,讲的很详细。geohash算法能把二维的经纬度编码成一维的字符串,它的特点是越相近的经纬度编码后越相似,所以可以通过前缀like的方式
去匹配周围的商户。 customer表要增加一个字段,来存储每个商户的geohash编码,并且建立索引。
CREATE TABLE `customer` ( `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '自增主键', `name` VARCHAR(5) NOT NULL COMMENT '名称' COLLATE 'latin1_swedish_ci', `lon` DOUBLE(9,6) NOT NULL COMMENT '经度', `lat` DOUBLE(8,6) NOT NULL COMMENT '纬度', `geo_code` CHAR(12) NOT NULL COMMENT 'geohash编码', PRIMARY KEY (`id`), INDEX `idx_geo_code` (`geo_code`) ) COMMENT='商户表' CHARSET=utf8mb4 ENGINE=InnoDB ;
在新增或修改一个商户的时候,维护好geo_code,那geo_code怎么计算呢?spatial4j也提供了一个工具类GeohashUtils.encodeLatLon(lat, lon)
,默认精度是12位。这个存储做好后,就可以通
过geo_code去搜索了。拿到移动设备的经纬度,计算geo_code,这时可以指定精度计算,那指定多长呢?我们需要一个geo_code长度和距离的对照表:
geohash length |
width |
height |
---|---|---|
1 |
5,009.4km |
4,992.6km |
2 |
1,252.3km |
624.1km |
3 |
156.5km |
156km |
4 |
39.1km |
19.5km |
5 |
4.9km |
4.9km |
6 |
1.2km |
609.4m |
7 |
152.9m |
152.4m |
8 |
38.2m |
19m |
9 |
4.8m |
4.8m |
10 |
1.2m |
59.5cm |
11 |
14.9cm |
14.9cm |
12 |
3.7cm |
1.9cm |
假设我们的需求是1公里范围内的商户,geo_code的长度设置为5就可以了,GeohashUtils.encodeLatLon(lat, lon, 5)
。计算出移动设备经纬度的geo_code之后,SQL是这样:
SELECT id, name FROM customer WHERE geo_code LIKE CONCAT(?, '%');
这样会比区间查找快很多,并且得益于geo_code的相似性,可以对热点区域做缓存。但这样使用geohash还存在一个问题,geohash最终是在地图上铺上了一个网格,
每一个网格代表一个geohash值,当传入的坐标接近当前网格的边界时,用上面的搜索方式就会丢失它附近的数据。比如下图中,在绿点的位置搜索不到白家大院,绿点和白家大院在划分的时候就分到了两个格子中。
解决这个问题思路也比较简单,我们查询时,除了使用绿点的geohash编码进行匹配外,还使用周围8个网格的geohash编码,这样可以避免这个问题。
那怎么计算出周围8个网格的geohash呢,可以使用geohash-java来解决。
<dependency> <groupId>ch.hsr</groupId> <artifactId>geohash</artifactId> <version>1.3.0</version> </dependency>
// 移动设备经纬度 double lon = 116.312528, lat = 39.983733; GeoHash geoHash = GeoHash.withCharacterPrecision(lat, lon, 10); // 当前 System.out.println(geoHash.toBase32()); // N, NE, E, SE, S, SW, W, NW GeoHash[] adjacent = geoHash.getAdjacent(); for (GeoHash hash : adjacent) { System.out.println(hash.toBase32()); }
最终我们的sql变成了这样:
SELECT id, name FROM customer WHERE geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%') OR geo_code LIKE CONCAT(?, '%');
原来的1次查询变成了9次查询,性能肯定会下降,这里可以优化下。还用上面的需求场景,搜索1公里范围内的商户,从上面的表格知道,geo_code长度为5时,
网格宽高是4.9KM,用9个geo_code查询时,范围太大了,所以可以将geo_code长度设置为6,即缩小了查询范围,也满足了需求。还可以继续优化,在存储geo_code时,
只计算到6位,这样就可以将sql变成这样:
SELECT id, name FROM customer WHERE geo_code IN (?, ?, ?, ?, ?, ?, ?, ?, ?);
这样将前缀匹配换成了直接匹配,速度会提升很多。
step2 过滤
上面两种搜索方式,都不是精确搜索,只是尽量缩小搜索范围,提升响应速度。所以需要在应用程序中做过滤,把距离大于1公里的商户过滤掉。计算距离同样使用spatial4j。
// 移动设备经纬度 double lon1 = 116.3125333347639, lat1 = 39.98355521792821; // 商户经纬度 double lon2 = 116.312528, lat2 = 39.983733; SpatialContext geo = SpatialContext.GEO; double distance = geo.calcDistance(geo.makePoint(lon1, lat1), geo.makePoint(lon2, lat2)) * DistanceUtils.DEG_TO_KM; System.out.println(distance);// KM
过滤代码就不写了,遍历一遍搜索结果即可。
step3 排序
同样,排序也需要在应用程序中处理。排序基于上面的过滤结果做就可以了Collections.sort(list, comparator)
。
step4 分页
如果需要2、3步,只能在内存中分页,做法也很简单,可以参考这篇文章。
总结
全文的重点都在于搜索如何实现,更好的利用数据库的索引,两种搜索方式以百万数据量为分割线,第一种适用于百万以下,第二种适用于百万以上,未经过严格验证
。
可能有人会有疑问,过滤和排序都在应用层做,内存占用会不会很严重?这是个潜在问题,但大多数情况下不会。看我们大部分的应用场景,都是单一种类POI(Point Of Interest)的搜索,
如酒店、美食、KTV、电影院等等,这种数据密度是很小,1公里内的酒店,能有多少家,50家都算多的,所以最终要看具体业务数据密度。本文没有分析原理,只讲了具体实现,有关分析的文章可以看参考链接。
参考
http://www.infoq.com/cn/articles/depth-study-of-Symfony2
http://tech.meituan.com/lucene-distance.html
http://blog.csdn.net/liminlu0314/article/details/8553926
http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates
http://www.cnblogs.com/LBSer/p/3310455.html http://cevin.net/geohash/
#############################################################################################################################
<dependency> <groupId>ch.hsr</groupId> <artifactId>geohash</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>com.spatial4j</groupId> <artifactId>spatial4j</artifactId> <version>0.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>maven
代码实现:
package codes.LatAndLonOfCC; import ch.hsr.geohash.GeoHash; import com.spatial4j.core.context.SpatialContext; import com.spatial4j.core.distance.DistanceUtils; import com.spatial4j.core.io.GeohashUtils; import com.spatial4j.core.shape.Rectangle; import java.io.*; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * @Author:Xavier * @Data:2019-11-06 11:11 **/ public class AdjacentCityCaler { private static Connection connection = null; private static Statement stat = null; static { try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://yourhost/xavier?characterEncoding=utf-8", "name", "password"); stat = connection.createStatement(); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } } public void insertCityToSQL() { File file = new File("/home/xavier/Download/secondlevel_city.txt"); InputStream inputStream = null; InputStreamReader streamReader = null; Writer out = null; String str = ""; try { inputStream = new FileInputStream(file); streamReader = new InputStreamReader(inputStream, "UTF-8"); // out = new OutputStreamWriter( // new FileOutputStream("/home/xavier/Download/中国主要城市对应geohash_Base16.txt", true), "UTF-8"); BufferedReader bufferedReader = new BufferedReader(streamReader); String line = null; while ((line = bufferedReader.readLine()) != null) { line = line.trim(); if ("".equals(line)) continue; if (line.startsWith("#")) continue; String name = ""; String geoHashCode = ""; double lat = 0.000000000; double lng = 0.000000000; try { name = line.split("_")[0]; lng = Double.parseDouble(line.split("_")[1]); lat = Double.parseDouble(line.split("_")[2]); geoHashCode = GeohashUtils.encodeLatLon(lat, lng, 3); } catch (Exception e) { continue; } // String str = g.getGeoHashBase16(lat, lng); // try { // out.write(line.split("_")[0] + "_" + str + "\r\n"); // } catch (IOException e) { // e.printStackTrace(); // } str = "insert into second_level_city(city_name, lat, lon, geo_code) VALUES (\'" + name + "\'," + lat + "," + lng + ",\'" + geoHashCode + "\');"; // System.out.println(str); stat.execute(str); } // out.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public static void main(String[] args) { AdjacentCityCaler adjacentCityCaler = new AdjacentCityCaler(); // adjacentCityCaler.insertCityToSQL(); // 移动设备经纬度 //上海_121.4692688_31.23817635 double lon = 121.4692688, lat = 31.23817635; // 千米 int radius = 150; /*SpatialContext geo = SpatialContext.GEO; Rectangle rectangle = geo.getDistCalc().calcBoxByDistFromPt( geo.makePoint(lon, lat), radius * DistanceUtils.KM_TO_DEG, geo, null); System.out.println(rectangle.getMinX() + "-" + rectangle.getMaxX());// 经度范围 System.out.println(rectangle.getMinY() + "-" + rectangle.getMaxY());// 纬度范围*/ ch.hsr.geohash.GeoHash geoHash = ch.hsr.geohash.GeoHash.withCharacterPrecision(lat, lon, 3); // System.out.println(geoHash.toBase32()); ch.hsr.geohash.GeoHash[] adjacent = geoHash.getAdjacent(); String[] geoHashArray = new String[9]; int index = 0; // if(adjacent.length!=9) System.exit(0); System.out.println(adjacent.length); for (GeoHash hash : adjacent) { geoHashArray[index] = hash.toBase32(); // System.out.println("geoHashArray---------->"+index+" " +hash.toBase32()); System.out.println("geoHashArray----------> "+index+" " +geoHashArray[index]); index++; } geoHashArray[8]=geoHash.toBase32(); String str = "SELECT * " + "FROM second_level_city " + "WHERE geo_code IN (\'" + geoHashArray[0] + "\', \'" + geoHashArray[1] + "\', \'" + geoHashArray[2] + "\', \'" + geoHashArray[3] + "\', \'" + geoHashArray[4] + "\', \'" + geoHashArray[5] + "\', \'" + geoHashArray[6] + "\', \'" + geoHashArray[7] + "\', \'" + geoHashArray[8] + "\')"; System.out.println(str); List<String> valueList=new ArrayList<>(); List<String> resultList=new ArrayList<>(); try { ResultSet set = stat.executeQuery(str); while (set.next()) { String name = set.getString("city_name"); double lat_value = set.getDouble("lat"); double lon_value = set.getDouble("lon"); String geo_code = set.getString("geo_code"); valueList.add(name+"_"+lat_value+"_"+lon_value+"_"+geo_code); } for (String value : valueList) { SpatialContext geo = SpatialContext.GEO; double distance = geo.calcDistance(geo.makePoint(lon, lat), geo.makePoint(Double.parseDouble(value.split("_")[2]), Double.parseDouble(value.split("_")[1]))) * DistanceUtils.DEG_TO_KM; if(distance<=radius) resultList.add(value); } } catch (SQLException e) { e.printStackTrace(); } for (String s : resultList) { System.out.println(s.split("_")[0]); } /* String str1 = GeohashUtils.encodeLatLon(31.23, 120.57, 3); String str2 = GeohashUtils.encodeLatLon(30.46, 120.45, 3); String str3 = GeohashUtils.encodeLatLon(31.14, 121.29, 3); System.out.println("昆山市geohash----->" + str1); System.out.println("嘉兴市geohash----->" + str2); System.out.println("上海市geohash----->" + str3);*/ } }
全国二级城市经纬度(数据来自互联网)
##城市_经度_纬度 北京_116.3809433_39.9236145 天津_117.2034988_39.13111877 河北_114.4897766_38.04512787 石家庄_114.4897766_38.04512787 唐山_118.2017288_39.62533951 秦皇岛_119.5982971_39.92430878 邯郸_114.4729538_36.60151672 邢台_114.4950867_37.06558991 保定_115.5001831_38.85707092 张家口_114.8787766_40.81744003 承德_117.9223404_40.96760178 沧州_116.8607712_38.30884171 廊坊_116.6898575_39.51511002 衡水_115.7081909_37.72782135 山西_112.5693512_37.87111282 太原_112.5693512_37.87111282 大同_113.2963333_40.0971489 阳泉_113.5742569_37.86065674 长治_113.1055679_36.18191147 晋城_112.84272_35.50651169 朔州_112.4232712_39.31313324 晋中_112.7453613_37.67613983 运城_110.9911499_35.01391602 忻州_112.7315521_38.39920807 临汾_111.5141678_36.08282471 吕梁_111.1348114_37.512043 内蒙古_111.6632996_40.82094193 呼和浩特_111.6632996_40.82094193 包头_109.8517075_40.6664238 乌海_106.8148727_39.67420197 赤峰_118.9498215_42.26798248 通辽_122.2603302_43.61156082 鄂尔多斯_109.7808671_39.60844559 呼伦贝尔_119.7305603_49.21152878 巴彦淖尔_107.3945694_40.76234055 乌兰察布_113.0985184_41.03116608 兴安盟_122.0381598_46.08207144 锡林郭勒盟_116.0477155_43.9331762 阿拉善盟_105.7289837_38.8515317 辽宁_123.4116821_41.7966156 沈阳_123.4116821_41.7966156 大连_121.6008377_38.91780472 鞍山_122.9843826_41.11525726 抚顺_123.9295578_41.84786606 本溪_123.7645035_41.28758621 丹东_124.3814621_40.13518143 锦州_121.1333695_41.11112595 营口_122.2241516_40.66835022 阜新_121.6488037_42.00795364 辽阳_123.1617432_41.26513672 盘锦_122.0476303_41.18847656 铁岭_123.844429_42.29558182 朝阳_120.4514694_41.57785797 葫芦岛_120.8474808_40.75334168 吉林_125.3154297_43.89256287 长春_125.3154297_43.89256287 吉林_126.5668182_43.88667679 四平_124.377449_43.16560745 辽源_125.1372833_42.90859222 通化_125.9231262_41.7232933 白山_126.421608_41.93033218 *_124.82204_45.172604 白城_122.8395767_45.61641693 延边朝鲜族自治州_129.5091262_42.89120266 黑龙江_126.6433411_45.74149323 哈尔滨_126.6433411_45.74149323 齐齐哈尔_123.9592667_47.34136963 鸡西_130.9477539_45.2970047 鹤岗_130.2761993_47.33728409 双鸭山_131.1521607_46.6376915 大庆_125.0248566_46.59545136 伊春_128.9043121_47.72364426 佳木斯_130.36232_46.81366348 七台河_130.8753967_45.80927277 牡丹江_129.5984955_44.58392334 黑河_127.4869385_50.24448776 绥化_126.98349_46.63701248 大兴安岭地区_124.5921351_51.9239847 上海_121.4692688_31.23817635 江苏_118.7727814_32.04761505 南京_118.7727814_32.04761505 无锡_120.2991333_31.57723045 徐州_117.1856079_34.26752853 常州_119.9502869_31.78393364 苏州_120.6187286_31.31645203 南通_120.8555679_32.01506805 连云港_119.1668015_34.60517883 淮安_119.14111_33.502789 盐城_120.1351776_33.38982773 扬州_119.4368362_32.39188767 镇江_119.4442978_32.20589829 泰州_119.91124_32.495872 宿迁_118.29706_33.958302 浙江_120.1592484_30.26599503 杭州_120.1592484_30.26599503 宁波_121.5412827_29.87066841 温州_120.6502914_28.01647568 嘉兴_120.7536316_30.77111435 湖州_120.0971298_30.86603928 绍兴_120.5739288_30.01093102 金华_119.6522064_29.11081696 衢州_118.8691788_28.9584446 舟山_122.1016083_30.02004242 台州_121.4205629_028.6561185037 临海_121.1184464_28.84889221 丽水_119.9165573_28.44883728 安徽_117.2757034_31.86325455 合肥_117.2757034_31.86325455 芜湖_118.3598328_31.33449554 蚌埠_117.3613815_32.93924332 淮南_117.0207291_32.6166954 马鞍山_118.4807129_31.72492409 淮北_116.7874985_33.9704895 铜陵_117.813179_30.92524719 安庆_117.0344315_30.51264572 黄山_118.3090668_29.72084427 滁州_118.3011627_32.31653214 阜阳_115.8097305_32.90220642 宿州_116.9701538_33.6401329 六安_116.4927902_31.75352287 亳州_115.7709_33.879292 池州_117.4773331_30.65686607 宣城_118.7586551_30.94078918 福建_119.2978134_26.07859039 福州_119.2978134_26.07859039 厦门_118.0875168_24.45743561 莆田_119.0103226_25.43813705 三明_117.6012268_26.22301292 泉州_118.5896378_24.91591835 漳州_117.6530914_24.51816368 南平_118.1691208_26.64484215 龙岩_117.0303879_25.10970306 宁德_119.5183182_26.6664772 江西_115.8999176_28.67599106 南昌_115.8999176_28.67599106 景德镇_117.1179428_29.19516754 萍乡_113.841423_27.63298988 九江_115.984581_29.72321129 新余_114.9293823_27.80654717 鹰潭_117.0302811_28.2455864 赣州_114.9336777_25.85288239 吉安_114.9704285_27.1062088 宜春_114.3746109_27.79557419 抚州_116.3010483_27.93483162 上饶_117.9634018_28.45326614 山东_117.0056_36.6670723 济南_117.0056_36.6670723 青岛_120.3581696_36.13386154 淄博_118.0560532_36.7935791 枣庄_117.556282_34.87264633 东营_118.4959564_37.46191406 烟台_121.3799362_37.53561401 潍坊_119.1068497_36.7040863 济宁_116.576561_35.40924072 泰安_117.1241074_36.1871109 威海_122.1116867_37.50076294 日照_119.4515533_35.42756271 莱芜_117.66173_36.205116 临沂_118.3379593_35.06945038 德州_116.2878723_37.45369339 聊城_115.9884262_36.44943237 滨州_118.0217667_37.36781311 菏泽_115.4457626_35.24853897 河南_113.6500473_34.7570343 郑州_113.6500473_34.7570343 开封_114.3461685_34.7851944 洛阳_112.4247971_34.66804123 平顶山_113.3001938_33.74362946 安阳_114.3500519_36.09685135 鹤壁_114.1546707_35.94008255 新乡_113.8685532_35.30746841 焦作_113.2217865_35.24735642 濮阳_115.0149536_35.70189667 许昌_113.8215866_34.02685928 漯河_114.0410919_33.57250977 三门峡_111.1952591_34.78076935 南阳_112.5375137_32.99901962 商丘_115.6471863_34.44358444 信阳_114.0677185_32.13063049 周口_114.6372528_33.62804031 驻马店_114.0356903_32.97904205 济源_112.6027201_35.06706997 湖北_114.2919388_30.56751442 武汉_114.2919388_30.56751442 黄石_115.0749893_30.21379852 十堰_110.7827988_32.65213013 宜昌_111.2852707_30.70395279 襄阳_112.1411133_32.04539871 鄂州_114.8811874_30.40276718 荆门_112.2002106_31.03021622 孝感_113.9113312_30.92845535 荆州_112.2477875_30.31733513 黄冈_114.8649292_30.44901848 咸宁_114.2687378_29.89432716 随州_113.36982_31.715105 恩施土家族苗族自治州_109.4881804_30.27218711 仙桃_113.4545113_30.36252891 潜江_112.8993007_30.40148025 天门_113.1661477_30.66340805 神农架林区_110.6759643_31.74451435 湖南_112.9812698_28.20082474 长沙_112.9812698_28.20082474 株洲_113.1520615_27.85422325 湘潭_112.9150238_27.87335014 衡阳_112.5993576_26.90055466 邵阳_111.4773789_27.25023651 岳阳_113.0980682_29.37461853 常德_111.6876297_29.03820992 张家界_110.4814835_29.13187981 益阳_112.3340683_28.60197067 郴州_113.0286484_25.80229187 永州_111.6121979_26.2112999 怀化_109.9542313_27.54740715 娄底_111.9938965_27.74133492 湘西土家族苗族自治州_109.7389287_28.31173554 广东_113.2614288_23.11891174 广州_113.2614288_23.11891174 韶关_113.6053925_24.80877686 深圳_114.110672_22.55639648 珠海_113.5682602_22.27258873 汕头_116.6837997_23.36269188 佛山_113.1145172_23.03487778 江门_113.0847473_22.59119034 湛江_110.3992233_21.19499779 茂名_110.8888474_21.67071724 肇庆_112.4514084_23.05788231 惠州_114.3924027_23.08795738 梅州_116.1079407_24.31450081 汕尾_115.3640137_22.77868652 河源_114.6938171_23.73484039 阳江_111.9578934_21.84523392 清远_113.0212631_23.71959686 东莞_113.7487717_23.0485363 中山_113.3714523_22.52685356 潮州_116.63666_23.667706 揭阳_116.34977_23.542976 云浮_112.03999_22.933193 广西_108.3117676_22.80654335 南宁_108.3117676_22.80654335 柳州_109.4028091_24.31040573 桂林_110.2866821_25.28188324 梧州_111.3059464_23.48661995 北海_109.1191711_21.47979736 防城港_108.35658_21.768936 钦州_108.6147003_21.94986916 贵港_109.60844_23.099092 玉林_110.1414719_22.63189697 百色_106.6121063_23.90158272 贺州_111.53455_24.417259 河池_108.0516281_24.69689179 来宾_109.23294_23.73144 崇左_107.35506_22.420197 海南_110.3465118_20.03179359 海口_110.3465118_20.03179359 三亚_109.5078201_18.23404312 三沙_112.33356_16.83272 儋州_109.5806849_19.52092966 五指山_109.5169672_18.77516377 琼海_110.4746526_19.25839642 文昌_110.797742_19.54330274 万宁_110.3897474_18.79532292 东方_108.6536594_19.09613826 定安县_110.3590789_19.68133946 屯昌县_110.1034735_19.35182579 澄迈县_110.0048522_19.73847934 临高县_109.690764_19.91242821 白沙黎族自治县_109.4516753_19.22543478 昌江黎族自治县_109.0555816_19.29827945 乐东黎族自治县_109.1736131_18.7498679 陵水黎族自治县_110.0371871_18.50595483 保亭黎族苗族自治县_109.7025825_18.63904542 琼中黎族苗族自治县_109.8382244_19.03322394 重庆_106.5103378_29.55817604 四川_104.0817566_30.66105652 成都_104.0817566_30.66105652 自贡_104.7763519_29.36772156 攀枝花_101.6984177_26.55479813 泸州_105.4378433_28.88199425 德阳_104.3915482_31.13044548 绵阳_104.7485504_31.45634842 广元_105.8317032_32.44396973 遂宁_105.5697098_30.50339317 内江_105.0534363_29.57756805 乐山_103.7514038_29.56822395 南充_106.0816269_30.79582214 眉山_103.83146_30.050497 宜宾_104.6168671_28.77025604 广安_106.63175_30.474428 达州_107.5003433_31.22469711 雅安_102.9826965_29.98229408 巴中_106.75476_31.849014 资阳_104.65019_30.122671 阿坝藏族羌族自治州_102.2247375_31.89937935 甘孜藏族自治州_101.962514_30.04930605 凉山彝族自治州_102.2674383_27.88162244 贵州_106.7113724_26.57687378 贵阳_106.7113724_26.57687378 六盘水_104.8732529_26.5767746 遵义_106.9293976_27.69538689 安顺_105.9260712_26.24425888 毕节_105.2824173_27.3062954 铜仁_109.1926804_27.72216606 黔西南布依族苗族自治州_104.9043531_25.08987278 黔东南苗族侗族自治州_107.9841392_26.5836279 黔南布依族苗族自治州_107.5222592_26.25427309 云南_102.704567_25.04384422 昆明_102.704567_25.04384422 曲靖_103.7947006_25.49616623 玉溪_102.5332336_24.35497284 保山_99.16872406_25.11680222 昭通_103.7149277_27.34227943 丽江_100.2342529_26.87666512 普洱_100.9752121_22.79548073 临沧_100.0878067_23.8799305 楚雄彝族自治州_101.5276607_25.04494301 红河哈尼族彝族自治州_103.3755878_23.36421652 文山壮族苗族自治州_104.2150486_23.39868766 西双版纳傣族自治州_100.7973892_22.0074942 大理白族自治州_100.2676255_25.60646837 德宏傣族景颇族自治州_98.58484387_24.4323115 怒江傈僳族自治州_98.85671501_25.81753271 迪庆藏族自治州_99.70302652_27.81906659 *_91.11445308_29.64411352 拉萨_91.11445308_29.64411352 日喀则_88.88110958_29.26701395 昌都_97.17220509_31.14069782 林芝_94.36153102_29.64893975 山南_91.77308713_29.23701982 那曲地区_92.0513207_31.47611103 阿里地区_81.14540521_30.40052298 陕西_108.949028_34.26168442 西安_108.949028_34.26168442 铜川_109.0572815_35.07545853 宝鸡_107.1383591_34.38228607 咸阳_108.7101288_34.33721542 渭南_109.5008392_34.50152588 延安_109.471283_36.59387207 汉中_107.0343933_33.07814789 榆林_109.7574463_38.29727554 安康_109.0257874_32.68986511 商洛_109.9403909_33.87035105 甘肃_103.7500534_36.06803894 兰州_103.7500534_36.06803894 嘉峪关_98.27471161_39.80265427 金昌_102.1657486_38.49519348 白银_104.1837769_36.53941727 天水_105.7152405_34.58426666 武威_102.633461_37.9269104 张掖_100.4502869_38.93505859 平凉_106.6830673_35.53551865 酒泉_98.51111603_39.74496841 庆阳_107.6362305_35.73855972 定西_104.6185684_35.57523727 陇南_104.92928_33.39484 临夏回族自治州_103.2108906_35.60121067 甘南藏族自治州_102.9109863_34.98324974 青海_101.7778162_36.61728828 西宁_101.7778162_36.61728828 海东_102.4017109_36.48207227 海北藏族自治州_100.9009262_36.95451784 黄南藏族自治州_102.0150337_35.51988388 海南藏族自治州_100.6203395_36.28660837 果洛藏族自治州_100.2447092_34.47138225 玉树藏族自治州_97.00645511_33.00525219 海西蒙古族藏族自治州_97.37119774_37.37707972 宁夏_106.2719421_38.46800995 银川_106.2719421_38.46800995 石嘴山_106.3820572_39.02428055 吴忠_106.1991119_37.98549652 固原_106.2785873_36.01325989 中卫_105.18661_37.513252 *_89.1895474_42.95130195 乌鲁木齐_87.60611725_43.79093933 克拉玛依_84.86360931_45.59651184 吐鲁番_89.1895474_42.95130195 哈密_93.51536928_42.81854115 昌吉回族自治州_87.30817902_44.01114114 博尔塔拉蒙古自治州_82.06674632_44.90603596 巴音郭楞蒙古自治州_86.14517515_41.76404026 阿克苏地区_80.2600596_41.1684029 克孜勒苏柯尔克孜自治州_76.16660835_39.71529724 喀什地区_75.98973068_39.47039628 和田地区_79.92243983_37.11429217 伊犁哈萨克自治州_81.32412996_43.91686827 塔城地区_82.98043953_46.74531234 阿勒泰地区_88.14031038_47.8456187 石河子_86.078911_44.30652036 阿拉尔_81.28064163_40.54795812 图木舒克_79.06901071_39.8649427 五家渠_87.54014426_44.16797119 北屯_87.80014797_47.36328619 铁门关_85.67585289_41.86869689 双河_82.35500262_44.84417562 可克达拉_81.04474154_43.94797595 昆玉_79.29125037_37.20942446 *_120.960515_23.69781 香港_114.109497_22.396428 澳门_113.5440083_22.20167546secondevel_city.txt