按距离搜索邻近城市的一种实现

Posted xavier-xd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了按距离搜索邻近城市的一种实现相关的知识,希望对你有一定的参考价值。

以下内容来至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

https://en.wikipedia.org/wiki/Geohash#Cell_Dimensions

假设我们的需求是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 + "
");
//                } 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.20167546
secondevel_city.txt

 

 

以上是关于按距离搜索邻近城市的一种实现的主要内容,如果未能解决你的问题,请参考以下文章

缓存用户特定的邻近搜索

机器学习KNN算法实践:预测城市空气质量

如何根据地理位置搜索城市?

使用 mySQL 的 OpenStreetMap 邻近搜索

通过KNN算法,确定球星的风格(很水)

具有 MySQL 未来证明的邻近搜索