1. 程式人生 > >查指定範圍內的街道(基於經緯度)

查指定範圍內的街道(基於經緯度)

一、表結構:

CREATE TABLE `district` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `p_id` bigint(20) NOT NULL COMMENT '父ID',
  `zipcode` varchar(4) DEFAULT NULL COMMENT '城市編碼',
  `adcode` varchar(6) NOT NULL DEFAULT '' COMMENT '區域編碼',
  `name` varchar(64) NOT NULL DEFAULT '' COMMENT '行政區名稱',
  `center` varchar(30) NOT NULL DEFAULT '' COMMENT '城市中心點',
  `lon` double(9,6) NOT NULL COMMENT '經度',
  `lat` double(8,6) NOT NULL COMMENT '維度',
  `geo_code` varchar(12) DEFAULT NULL COMMENT 'geohash編碼',
  `level` varchar(10) NOT NULL DEFAULT '' COMMENT '取值:province省份,city市,district區縣,street街道',
  `area` text COMMENT '街道覆蓋域',
  PRIMARY KEY (`id`),
  KEY `p_id` (`p_id`) USING BTREE,
  KEY `zipcode` (`zipcode`) USING BTREE,
  KEY `adcode` (`adcode`) USING BTREE,
  KEY `name` (`name`) USING BTREE,
  KEY `level` (`level`) USING BTREE,
  KEY `lon` (`lon`) USING BTREE,
  KEY `lat` (`lat`) USING BTREE,
  KEY `geo_code` (`geo_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=51264 DEFAULT CHARSET=utf8mb4 COMMENT='行政區域';


二、引入jar包

<dependency>
			<groupId>com.spatial4j</groupId>
			<artifactId>spatial4j</artifactId>
			<version>0.5</version>
		</dependency>
		<dependency>
			<groupId>ch.hsr</groupId>
			<artifactId>geohash</artifactId>
			<version>1.3.0</version>
		</dependency>
三、Java程式碼
    /**
     * 方法一,查詢指定範圍內的所有街道(區間查詢)
     * @return
     */
    public List<District> findNearbyDistrictByRadius(Double lon, Double lat, int radius){
        SpatialContext geo = SpatialContext.GEO;
        Rectangle rectangle = geo.getDistCalc().calcBoxByDistFromPt(geo.makePoint(lon, lat), radius * DistanceUtils.KM_TO_DEG, geo, null);
        double minX = rectangle.getMinX();
        double maxX = rectangle.getMaxX();
        double minY = rectangle.getMinY();
        double maxY = rectangle.getMaxY();
        LOGGER.info(minX + " - " + maxX);   // 經度範圍
        LOGGER.info(minY + " - " + maxY);   // 緯度範圍
        String sql = "select * from district where (lon BETWEEN ? AND ?) AND (lat BETWEEN ? AND ?) and level = 'street' ";
        List<District> districtList = dao.find(sql, minX, maxX, minY, maxY);
        LOGGER.info("districtList1={}", JSON.toJSONString(districtList));

        //按照距離排序
        Collections.sort(districtList, new DistrictComparator(lon, lat));

        return districtList;
    }

    /**
     * 方法二,查詢指定範圍內的所有街道(geohash查詢)
     * @param lon
     * @param lat
     * @return
     */
    public List<District> findNearbyDistrictByRadius2(Double lon, Double lat, int geohashLength){
        String geoCode = GeohashUtils.encodeLatLon(lat, lon, geohashLength);    //1公里
        LOGGER.info("geoCode={}", geoCode);

        StringBuilder sql = new StringBuilder("select * from district where ");
        GeoHash geoHash = GeoHash.withCharacterPrecision(lat, lon, geohashLength);
        // 當前
        sql.append(" geo_code LIKE CONCAT('" + geoHash.toBase32() + "', '%') ");
        // N, NE, E, SE, S, SW, W, NW
        GeoHash[] adjacent = geoHash.getAdjacent();
        for (GeoHash hash : adjacent) {
            sql.append("or geo_code LIKE CONCAT('" + hash.toBase32() +"', '%') ");
        }
        LOGGER.info("sql={}", sql.toString());
        List<District> districtList = dao.find(sql.toString());
        LOGGER.info("districtList2={}", JSON.toJSONString(districtList));

        //按照距離排序
        Collections.sort(districtList, new DistrictComparator(lon, lat));

        return districtList;
    }
    /**
     * 按照距離遠近排序
     */
    class DistrictComparator implements Comparator<District>{

        private SpatialContext geo = SpatialContext.GEO;
        private Double centerLon;   //中心點經度
        private Double centerLat;   //中心點緯度

        public DistrictComparator(Double centerLon, Double centerLat) {
            this.centerLon = centerLon;
            this.centerLat = centerLat;
        }

        @Override
        public int compare(District o1, District o2) {
            double o1Distance = geo.calcDistance(geo.makePoint(o1.getDouble("lon"), o1.getDouble("lat")), geo.makePoint(centerLon, centerLat)) * DistanceUtils.DEG_TO_KM;
            o1.put("distance", o1Distance);
            double o2Distance = geo.calcDistance(geo.makePoint(o2.getDouble("lon"), o2.getDouble("lat")), geo.makePoint(centerLon, centerLat)) * DistanceUtils.DEG_TO_KM;
            o2.put("distance", o2Distance);

            if(o1Distance < o2Distance){
                return -1;
            }else if(o1Distance > o2Distance){
                return 1;
            }else{
                return 0;
            }
        }
    }